Projects
Kolab:3.4
roundcubemail
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
roundcubemail-1.1-csrf-old.patch
Deleted
@@ -1,615 +0,0 @@ -diff -ur roundcubemail-1.1.orig/config/defaults.inc.php roundcubemail-1.1/config/defaults.inc.php ---- roundcubemail-1.1.orig/config/defaults.inc.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/config/defaults.inc.php 2014-08-03 11:30:31.369886557 +0200 -@@ -521,6 +521,23 @@ - // Note: useful when SMTP server stores sent mail in user mailbox - $config['no_save_sent_messages'] = false; - -+// Improve system security by using special URL with security token. -+// This can be set to a number defining token length. Default: 16. -+// Warning: This requires http server configuration. Sample: -+// RewriteRule ^/roundcubemail/[a-f0-9]{16}/(.*) /roundcubemail/$1 [PT] -+// Alias /roundcubemail /var/www/roundcubemail/ -+// Note: Use assets_path to not prevent the browser from caching assets -+$config['use_secure_urls'] = true; -+ -+// Allows to define separate server/path for image/js/css files -+// Warning: If the domain is different cross-domain access to some -+// resources need to be allowed -+// Sample: -+// <FilesMatch ".(eot|ttf|woff)"> -+// Header set Access-Control-Allow-Origin "*" -+// </FilesMatch> -+$config['assets_path'] = ''; -+ - // ---------------------------------- - // PLUGINS - // ---------------------------------- -diff -ur roundcubemail-1.1.orig/.htaccess roundcubemail-1.1/.htaccess ---- roundcubemail-1.1.orig/.htaccess 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/.htaccess 2014-08-03 11:30:31.370886557 +0200 -@@ -31,7 +31,7 @@ - # security rules: - # - deny access to files not containing a dot or starting with a dot - # in all locations except installer directory --RewriteRule ^(?!installer)(\.?[^\.]+)$ - [F] -+RewriteRule ^(?!installer|[a-f0-9]{16})(\.?[^\.]+)$ - [F] - # - deny access to some locations - RewriteRule ^/?(\.git|\.tx|SQL|bin|config|logs|temp|tests|program\/(include|lib|localization|steps)) - [F] - # - deny access to some documentation files -diff -ur roundcubemail-1.1.orig/index.php roundcubemail-1.1/index.php ---- roundcubemail-1.1.orig/index.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/index.php 2014-08-03 11:30:31.371886557 +0200 -@@ -90,9 +90,9 @@ - - // try to log in - if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { -- $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login'); -+ $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(); - -- // purge the session in case of new login when a session already exists -+ // purge the session in case of new login when a session already exists - $RCMAIL->kill_session(); - - $auth = $RCMAIL->plugins->exec_hook('authenticate', array( -@@ -140,7 +140,7 @@ - unset($redir['abort'], $redir['_err']); - - // send redirect -- $OUTPUT->redirect($redir); -+ $OUTPUT->redirect($redir, 0, true); - } - else { - if (!$auth['valid']) { -@@ -171,10 +171,10 @@ - } - } - --// end session (after optional referer check) --else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) -- && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer()) --) { -+// end session -+else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])) { -+ $RCMAIL->request_security_check($mode = rcube_utils::INPUT_GET); -+ - $userdata = array( - 'user' => $_SESSION['username'], - 'host' => $_SESSION['storage_host'], -@@ -236,32 +236,10 @@ - } - // CSRF prevention - else { -- // don't check for valid request tokens in these actions -- $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1); -- -- if (!$request_check_whitelist[$RCMAIL->action]) { -- // check client X-header to verify request origin -- if ($OUTPUT->ajax_call) { -- if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) { -- header('HTTP/1.1 403 Forbidden'); -- die("Invalid Request"); -- } -- } -- // check request token in POST form submissions -- else if (!empty($_POST) && !$RCMAIL->check_request()) { -- $OUTPUT->show_message('invalidrequest', 'error'); -- $OUTPUT->send($RCMAIL->task); -- } -- -- // check referer if configured -- if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) { -- raise_error(array( -- 'code' => 403, 'type' => 'php', -- 'message' => "Referer check failed"), true, true); -- } -- } -+ $RCMAIL->request_security_check(); - } - -+ - // we're ready, user is authenticated and the request is safe - $plugin = $RCMAIL->plugins->exec_hook('ready', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action)); - $RCMAIL->set_task($plugin['task']); -diff -ur roundcubemail-1.1.orig/program/include/rcmail_output_html.php roundcubemail-1.1/program/include/rcmail_output_html.php ---- roundcubemail-1.1.orig/program/include/rcmail_output_html.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/include/rcmail_output_html.php 2014-08-03 11:30:31.373886557 +0200 -@@ -45,6 +45,7 @@ - protected $footer = ''; - protected $body = ''; - protected $base_path = ''; -+ protected $assets_path; - protected $devel_mode = false; - - // deprecated names of templates used before 0.5 -@@ -80,6 +81,8 @@ - $this->set_skin($skin); - $this->set_env('skin', $skin); - -+ $this->set_assets_path($this->config->get('assets_path')); -+ - if (!empty($_REQUEST['_extwin'])) - $this->set_env('extwin', 1); - if ($this->framed || $framed) -@@ -145,6 +148,39 @@ - } - - /** -+ * Parse and set assets path -+ * -+ * @param string Assets path (relative or absolute URL) -+ */ -+ public function set_assets_path($path) -+ { -+ $path = trim($path, '/') . '/'; -+ -+ if (empty($path)) { -+ return; -+ } -+ -+ // convert to absolute URL -+ if (!preg_match('|^https?://|', $path)) { -+ $base = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']); -+ $base = rtrim($base, '/'); -+ -+ // remove url token if exists -+ if ($token = $this->app->get_secure_url_token()) { -+ if (strpos($base, $token)) { -+ $base = substr($base, 0, -(strlen($token)+1)); -+ } -+ } -+ -+ $path = (rcube_utils::https_check() ? 'https' : 'http') . '://' -+ . $_SERVER['SERVER_NAME'] . $base . '/' . $path; -+ } -+ -+ $this->assets_path = $path; -+ $this->set_env('assets_path', $path); -+ } -+ -+ /** - * Getter for the current page title - * - * @return string The page title -@@ -367,14 +403,15 @@ - /** - * Redirect to a certain url - * -- * @param mixed $p Either a string with the action or url parameters as key-value pairs -- * @param int $delay Delay in seconds -+ * @param mixed $p Either a string with the action or url parameters as key-value pairs -+ * @param int $delay Delay in seconds -+ * @param bool $secure Redirect to secure location (see rcmail::url()) - */ -- public function redirect($p = array(), $delay = 1) -+ public function redirect($p = array(), $delay = 1, $secure = false) - { - if ($this->env['extwin']) - $p['extwin'] = 1; -- $location = $this->app->url($p); -+ $location = $this->app->url($p, $secure); - header('Location: ' . $location); - exit; - } -@@ -659,6 +696,24 @@ - exit; - } - -+ /** -+ * Modify path by adding URL prefix if configured -+ */ -+ public function asset_url($path) -+ { -+ // iframe content can't be in a different domain -+ // @TODO: check if assests are on a different domain -+ if (!$this->assets_path || $path == $this->env['blankpage']) { -+ return $path; -+ } -+ -+ if ($path[0] == '.' && $path[1] == '/') { -+ $path = substr($path, 2); -+ } -+ -+ return $this->assets_path . $path; -+ } -+ - - /***** Template parsing methods *****/ - -@@ -715,6 +770,8 @@ - $file = $this->file_mod($file); - } - -+ $file = $this->asset_url($file); -+ - return $matches[1] . '=' . $matches[2] . $file . $matches[4]; - } - -@@ -1305,6 +1362,7 @@ - { - if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { - $file = $this->file_mod($this->scripts_path . $file); -+ $file = $this->asset_url($file); - } - - if (!is_array($this->script_files[$position])) { -@@ -1517,7 +1575,7 @@ - } - - $attrib['name'] = $attrib['id']; -- $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif'; -+ $attrib['src'] = $this->asset_url($attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif'); - - // register as 'contentframe' object - if ($is_contentframe || $attrib['contentframe']) { -@@ -1734,9 +1792,11 @@ - { - $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY); - $images = array_map(array($this, 'abs_url'), $images); -+ $images = array_map(array($this, 'asset_url'), $images); - -- if (empty($images) || $this->app->task == 'logout') -+ if (empty($images) || $_REQUEST['_task'] == 'logout') { - return; -+ } - - $this->add_script('var images = ' . self::json_serialize($images) .'; - for (var i=0; i<images.length; i++) { -diff -ur roundcubemail-1.1.orig/program/include/rcmail_output_json.php roundcubemail-1.1/program/include/rcmail_output_json.php ---- roundcubemail-1.1.orig/program/include/rcmail_output_json.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/include/rcmail_output_json.php 2014-08-03 11:30:31.374886556 +0200 -@@ -181,6 +181,11 @@ - */ - public function raise_error($code, $message) - { -+ if ($code == 403) { -+ header('HTTP/1.1 403 Forbidden'); -+ die("Invalid Request"); -+ } -+ - $this->show_message("Application Error ($code): $message", 'error'); - $this->remote_response(); - exit; -diff -ur roundcubemail-1.1.orig/program/include/rcmail.php roundcubemail-1.1/program/include/rcmail.php ---- roundcubemail-1.1.orig/program/include/rcmail.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/include/rcmail.php 2014-08-03 11:30:31.376886556 +0200 -@@ -753,47 +753,14 @@ - } - - /** -- * Generate a unique token to be used in a form request -- * -- * @return string The request token -- */ -- public function get_request_token() -- { -- $sess_id = $_COOKIE[ini_get('session.name')]; -- -- if (!$sess_id) { -- $sess_id = session_id(); -- } -- -- $plugin = $this->plugins->exec_hook('request_token', array( -- 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); -- -- return $plugin['value']; -- } -- -- /** -- * Check if the current request contains a valid token -- * -- * @param int Request method -- * -- * @return boolean True if request token is valid false if not -- */ -- public function check_request($mode = rcube_utils::INPUT_POST) -- { -- $token = rcube_utils::get_input_value('_token', $mode); -- $sess_id = $_COOKIE[ini_get('session.name')]; -- -- return !empty($sess_id) && $token == $this->get_request_token(); -- } -- -- /** - * Build a valid URL to this instance of Roundcube - * - * @param mixed Either a string with the action or url parameters as key-value pairs -+ * @param bool Return absolute URL to secure location - * - * @return string Valid application URL - */ -- public function url($p) -+ public function url($p, $secure = false) - { - if (!is_array($p)) { - if (strpos($p, 'http') === 0) { -@@ -807,8 +774,9 @@ - $p['_task'] = $task; - unset($p['task']); - -- $url = './' . $this->filename; -- $delm = '?'; -+ $prefix = './'; -+ $url = $this->filename; -+ $delm = '?'; - - foreach (array_reverse($p) as $key => $val) { - if ($val !== '' && $val !== null) { -@@ -818,7 +786,22 @@ - } - } - -- return $url; -+ // generate absolute URL if URL token exists -+ if ($secure && ($token = $this->get_secure_url_token(true))) { -+ $uri = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']); -+ if (strpos($uri, '/' . $token) === false) { -+ $uri = rtrim($uri, '/'); -+ // remove old token -+ $uri = preg_replace('/\/[a-f0-9]{' . strlen($token) . '}$/', '', $uri); -+ // add new token -+ $uri .= '/' . $token . '/'; -+ -+ $prefix = (rcube_utils::https_check() ? 'https' : 'http') . '://' -+ . $_SERVER['SERVER_NAME'] . $uri; -+ } -+ } -+ -+ return $prefix . $url; - } - - /** -@@ -852,6 +835,36 @@ - } - } - -+ /** -+ * CSRF attack prevention code -+ * -+ * @param int Request mode -+ */ -+ public function request_security_check($mode = rcube_utils::INPUT_POST) -+ { -+ // don't check for valid request tokens in these actions -+ // @TODO: get rid of this -+ $request_check_whitelist = array('spell'=>1, 'spell_html'=>1); -+ -+ if ($request_check_whitelist[$this->action]) { -+ return; -+ } -+ -+ // check request token -+ if (!$this->check_request($mode)) { -+ self::raise_error(array( -+ 'code' => 403, 'type' => 'php', -+ 'message' => "Request security check failed"), false, true); -+ } -+ -+ // check referer if configured -+ if ($this->config->get('referer_check') && !rcube_utils::check_referer()) { -+ self::raise_error(array( -+ 'code' => 403, 'type' => 'php', -+ 'message' => "Referer check failed"), true, true); -+ } -+ } -+ - /** - * Registers action aliases for current task - * -diff -ur roundcubemail-1.1.orig/program/js/app.js roundcubemail-1.1/program/js/app.js ---- roundcubemail-1.1.orig/program/js/app.js 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/js/app.js 2014-08-03 11:30:31.380886556 +0200 -@@ -1404,8 +1404,10 @@ - - if (task == 'mail') - url += '&_mbox=INBOX'; -- else if (task == 'logout' && !this.env.server_error) -+ else if (task == 'logout' && !this.env.server_error) { -+ url += '&_token=' + this.env.request_token; - this.clear_compose_data(); -+ } - - this.redirect(url); - }; -@@ -1415,7 +1417,10 @@ - if (!url) - url = this.env.comm_path; - -- return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task); -+ if (url.match(/[?&]_task=[a-zA-Z0-9_-]+/)) -+ return url.replace(/_task=[a-zA-Z0-9_-]+/, '_task=' + task); -+ else -+ return url.replace(/\?.*$/, '') + '?_task=' + task; - }; - - this.reload = function(delay) -diff -ur roundcubemail-1.1.orig/program/js/editor.js roundcubemail-1.1/program/js/editor.js ---- roundcubemail-1.1.orig/program/js/editor.js 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/js/editor.js 2014-08-03 11:30:31.382886556 +0200 -@@ -36,6 +36,7 @@ - function rcube_text_editor(config, id) - { - var ref = this, -+ abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''), - conf = { - selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), - theme: 'modern', -@@ -81,7 +82,7 @@ - toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify' - + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect' - + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo', -- spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1', -+ spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1', - spellchecker_language: rcmail.env.spell_lang, - accessibility_focus: false, - file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); }, -diff -ur roundcubemail-1.1.orig/program/lib/Roundcube/rcube.php roundcubemail-1.1/program/lib/Roundcube/rcube.php ---- roundcubemail-1.1.orig/program/lib/Roundcube/rcube.php 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/lib/Roundcube/rcube.php 2014-08-03 11:30:31.383886555 +0200 -@@ -28,9 +28,15 @@ - */ - class rcube - { -- const INIT_WITH_DB = 1; -+ // Init options -+ const INIT_WITH_DB = 1; - const INIT_WITH_PLUGINS = 2; - -+ // Request status -+ const REQUEST_VALID = 0; -+ const REQUEST_ERROR_URL = 1; -+ const REQUEST_ERROR_TOKEN = 2; -+ - /** - * Singleton instace of rcube - * -@@ -107,6 +113,12 @@ - protected $caches = array(); - protected $shutdown_functions = array(); - -+ /** -+ * Request status -+ * -+ * @var int -+ */ -+ public $request_status = 0; - - /** - * This implements the 'singleton' design pattern -@@ -962,6 +974,104 @@ - - - /** -+ * Returns session token for secure URLs -+ * -+ * @param bool $generate Generate token if not exists in session yet -+ * -+ * @return string|bool Token string, False when disabled -+ */ -+ public function get_secure_url_token($generate = false) -+ { -+ if ($len = $this->config->get('use_secure_urls')) { -+ if (empty($_SESSION['secure_token']) && $generate) { -+ // generate x characters long token -+ $length = $len > 1 ? $len : 16; -+ $token = openssl_random_pseudo_bytes($length / 2); -+ $token = bin2hex($token); -+ -+ $plugin = $this->plugins->exec_hook('secure_token', -+ array('value' => $token, 'length' => $length)); -+ -+ $_SESSION['secure_token'] = $plugin['value']; -+ } -+ -+ return $_SESSION['secure_token']; -+ } -+ -+ return false; -+ } -+ -+ -+ /** -+ * Generate a unique token to be used in a form request -+ * -+ * @return string The request token -+ */ -+ public function get_request_token() -+ { -+ $sess_id = $_COOKIE[ini_get('session.name')]; -+ if (!$sess_id) { -+ $sess_id = session_id(); -+ } -+ -+ $plugin = $this->plugins->exec_hook('request_token', array( -+ 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); -+ -+ return $plugin['value']; -+ } -+ -+ -+ /** -+ * Check if the current request contains a valid token. -+ * Empty requests aren't checked until use_secure_urls is set. -+ * -+ * @param int Request method -+ * -+ * @return boolean True if request token is valid false if not -+ */ -+ public function check_request($mode = rcube_utils::INPUT_POST) -+ { -+ // check secure token in URL if enabled -+ if ($token = $this->get_secure_url_token()) { -+ foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) { -+ if ($tok == $token) { -+ return true; -+ } -+ } -+ -+ $this->request_status = self::REQUEST_ERROR_URL; -+ -+ return false; -+ } -+ -+ $sess_tok = $this->get_request_token(); -+ -+ // ajax requests -+ if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) { -+ return true; -+ } -+ -+ // skip empty requests -+ if (($mode == rcube_utils::INPUT_POST && empty($_POST)) -+ || ($mode == rcube_utils::INPUT_GET && empty($_GET)) -+ ) { -+ return true; -+ } -+ -+ // default method of securing requests -+ $token = rcube_utils::get_input_value('_token', $mode); -+ $sess_id = $_COOKIE[ini_get('session.name')]; -+ -+ if (empty($sess_id) || $token != $sess_tok) { -+ $this->request_status = self::REQUEST_ERROR_TOKEN; -+ return false; -+ } -+ -+ return true; -+ } -+ -+ -+ /** - * Build a valid URL to this instance of Roundcube - * - * @param mixed Either a string with the action or url parameters as key-value pairs -@@ -1245,7 +1355,7 @@ - - $cli = php_sapi_name() == 'cli'; - -- if (($log || $terminate) && !$cli && $arg['message']) { -+ if ($log && !$cli && $arg['message']) { - $arg['fatal'] = $terminate; - self::log_bug($arg); - } -diff -ur roundcubemail-1.1.orig/program/steps/utils/error.inc roundcubemail-1.1/program/steps/utils/error.inc ---- roundcubemail-1.1.orig/program/steps/utils/error.inc 2014-08-02 09:03:29.000000000 +0200 -+++ roundcubemail-1.1/program/steps/utils/error.inc 2014-08-03 11:30:31.384886555 +0200 -@@ -50,9 +50,17 @@ - - // forbidden due to request check - else if ($ERROR_CODE == 403) { -+ if ($_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->request_status == rcube::REQUEST_ERROR_URL) { -+ parse_str($_SERVER['QUERY_STRING'], $url); -+ $url = $rcmail->url($url, true); -+ $add = "<br /><a href=\"$url\">Click here to try again.<a/>"; -+ } -+ else { -+ $add = "Please contact your server-administrator."; -+ } -+ - $__error_title = "REQUEST CHECK FAILED"; -- $__error_text = "Access to this service was denied due to failing security checks!<br />\n" -- . "Please contact your server-administrator."; -+ $__error_text = "Access to this service was denied due to failing security checks!<br />\n$add"; - } - - // failed request (wrong step in URL)
View file
roundcubemail.conf.r67
Deleted
@@ -1,59 +0,0 @@ -# Those aliases do not work properly with several hosts on your apache server -# Uncomment them to use it or adapt them to your configuration -# Alias /roundcube/program/js/tiny_mce/ /usr/share/tinymce/www/ -# Alias /roundcube /var/lib/roundcube - -<ifModule mod_rewrite.c> - RewriteEngine On - RewriteRule ^/roundcubemail/[a-f0-9]{16}/(.*) /roundcubemail/$1 [PT,L] - RewriteRule ^/webmail/[a-f0-9]{16}/(.*) /webmail/$1 [PT,L] - - # Be compatible with older packages and installed plugins. - RewriteCond %{REQUEST_URI} ^/roundcubemail/assets/ - RewriteCond %{REQuEST_URI} !-f - RewriteCond %{REQuEST_URI} !-d - RewriteRule .*/roundcubemail/assets/(.*)$ /roundcubemail/$1 [PT,L] - -</ifModule> - -Alias /roundcubemail /usr/share/roundcubemail/ -Alias /webmail /usr/share/roundcubemail/ - -# Access to tinymce files -#<Directory "/usr/share/tinymce/www/"> -# Options Indexes MultiViews FollowSymLinks -# AllowOverride None -# Order allow,deny -# Allow from all -#</Directory> - -<Directory /usr/share/roundcubemail/> - Options +FollowSymLinks - AllowOverride All - <ifModule mod_authz_core.c> - Require all granted - </ifModule> - <ifModule !mod_authz_core.c> - Order Allow,Deny - Allow from All - </ifModule> -</Directory> - -# Protecting basic directories: -<Directory /usr/share/roundcubemail/config> - Options -FollowSymLinks - AllowOverride None -</Directory> - -<Directory /usr/share/roundcubemail/logs> - Options -FollowSymLinks - AllowOverride None - <ifModule mod_authz_core.c> - Require all denied - </ifModule> - <ifModule !mod_authz_core.c> - Order Deny,Allow - Deny from All - </ifModule> -</Directory> -
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.