File roundcubemail-1.1-csrf.patch of Package roundcubemail (Revision 47d10155de802c878f9b76f2a90be803)

Currently displaying revision 47d10155de802c878f9b76f2a90be803 , Show latest

786
 
1
diff -ur roundcubemail-1.1.orig/config/defaults.inc.php roundcubemail-1.1/config/defaults.inc.php
2
--- roundcubemail-1.1.orig/config/defaults.inc.php  2014-09-04 13:26:10.000000000 +0200
3
+++ roundcubemail-1.1/config/defaults.inc.php   2014-09-06 14:30:33.910366429 +0200
4
@@ -532,6 +532,23 @@
5
 // Note: useful when SMTP server stores sent mail in user mailbox
6
 $config['no_save_sent_messages'] = false;
7
 
8
+// Improve system security by using special URL with security token.
9
+// This can be set to a number defining token length. Default: 16.
10
+// Warning: This requires http server configuration. Sample:
11
+//    RewriteRule ^/roundcubemail/[a-f0-9]{16}/(.*) /roundcubemail/$1 [PT]
12
+//    Alias /roundcubemail /var/www/roundcubemail/
13
+// Note: Use assets_path to not prevent the browser from caching assets
14
+$config['use_secure_urls'] = false;
15
+
16
+// Allows to define separate server/path for image/js/css files
17
+// Warning: If the domain is different cross-domain access to some
18
+// resources need to be allowed
19
+// Sample:
20
+//    <FilesMatch ".(eot|ttf|woff)">
21
+//    Header set Access-Control-Allow-Origin "*"
22
+//    </FilesMatch>
23
+$config['assets_path'] = '';
24
+
25
 // ----------------------------------
26
 // PLUGINS
27
 // ----------------------------------
28
diff -ur roundcubemail-1.1.orig/.htaccess roundcubemail-1.1/.htaccess
29
--- roundcubemail-1.1.orig/.htaccess    2014-09-04 13:26:10.000000000 +0200
30
+++ roundcubemail-1.1/.htaccess 2014-09-06 14:30:33.908366428 +0200
31
@@ -31,7 +31,7 @@
32
 # security rules:
33
 # - deny access to files not containing a dot or starting with a dot
34
 #   in all locations except installer directory
35
-RewriteRule ^(?!installer)(\.?[^\.]+)$ - [F]
36
+RewriteRule ^(?!installer|[a-f0-9]{16})(\.?[^\.]+)$ - [F]
37
 # - deny access to some locations
38
 RewriteRule ^/?(\.git|\.tx|SQL|bin|config|logs|temp|tests|program\/(include|lib|localization|steps)) - [F]
39
 # - deny access to some documentation files
40
diff -ur roundcubemail-1.1.orig/index.php roundcubemail-1.1/index.php
41
--- roundcubemail-1.1.orig/index.php    2014-09-04 13:26:10.000000000 +0200
42
+++ roundcubemail-1.1/index.php 2014-09-06 14:30:33.911366430 +0200
43
@@ -90,9 +90,9 @@
44
 
45
 // try to log in
46
 if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
47
-    $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login');
48
+    $request_valid = $_SESSION['temp'] && $RCMAIL->check_request();
49
 
50
-    // purge the session in case of new login when a session already exists 
51
+    // purge the session in case of new login when a session already exists
52
     $RCMAIL->kill_session();
53
 
54
     $auth = $RCMAIL->plugins->exec_hook('authenticate', array(
55
@@ -140,7 +140,7 @@
56
         unset($redir['abort'], $redir['_err']);
57
 
58
         // send redirect
59
-        $OUTPUT->redirect($redir);
60
+        $OUTPUT->redirect($redir, 0, true);
61
     }
62
     else {
63
         if (!$auth['valid']) {
64
@@ -171,10 +171,10 @@
65
     }
66
 }
67
 
68
-// end session (after optional referer check)
69
-else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])
70
-    && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer())
71
-) {
72
+// end session
73
+else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])) {
74
+    $RCMAIL->request_security_check($mode = rcube_utils::INPUT_GET);
75
+
76
     $userdata = array(
77
         'user' => $_SESSION['username'],
78
         'host' => $_SESSION['storage_host'],
79
@@ -234,32 +234,9 @@
80
 
81
     $OUTPUT->send($plugin['task']);
82
 }
83
-// CSRF prevention
84
 else {
85
-    // don't check for valid request tokens in these actions
86
-    $request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1);
87
-
88
-    if (!$request_check_whitelist[$RCMAIL->action]) {
89
-        // check client X-header to verify request origin
90
-        if ($OUTPUT->ajax_call) {
91
-            if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) {
92
-                header('HTTP/1.1 403 Forbidden');
93
-                die("Invalid Request");
94
-            }
95
-        }
96
-        // check request token in POST form submissions
97
-        else if (!empty($_POST) && !$RCMAIL->check_request()) {
98
-            $OUTPUT->show_message('invalidrequest', 'error');
99
-            $OUTPUT->send($RCMAIL->task);
100
-        }
101
-
102
-        // check referer if configured
103
-        if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) {
104
-            raise_error(array(
105
-                'code' => 403, 'type' => 'php',
106
-                'message' => "Referer check failed"), true, true);
107
-        }
108
-    }
109
+    // CSRF prevention
110
+    $RCMAIL->request_security_check();
111
 
112
     // check access to disabled actions
113
     $disabled_actions = (array) $RCMAIL->config->get('disabled_actions');
114
diff -ur roundcubemail-1.1.orig/plugins/acl/acl.js roundcubemail-1.1/plugins/acl/acl.js
115
--- roundcubemail-1.1.orig/plugins/acl/acl.js   2014-09-04 13:26:10.000000000 +0200
116
+++ roundcubemail-1.1/plugins/acl/acl.js    2014-09-06 14:30:33.911366430 +0200
117
@@ -58,8 +58,11 @@
118
     var users = this.acl_get_usernames();
119
 
120
     if (users && users.length && confirm(this.get_label('acl.deleteconfirm'))) {
121
-        this.http_request('settings/plugin.acl', '_act=delete&_user='+urlencode(users.join(','))
122
-            + '&_mbox='+urlencode(this.env.mailbox),
123
+        this.http_post('settings/plugin.acl', {
124
+                _act: 'delete',
125
+                _user: users.join(','),
126
+                _mbox: this.env.mailbox
127
+            },
128
             this.set_busy(true, 'acl.deleting'));
129
     }
130
 }
131
@@ -67,7 +70,7 @@
132
 // Save ACL data
133
 rcube_webmail.prototype.acl_save = function()
134
 {
135
-    var user = $('#acluser', this.acl_form).val(), rights = '', type;
136
+    var data, type, rights = '', user = $('#acluser', this.acl_form).val();
137
 
138
     $((this.env.acl_advanced ? '#advancedrights :checkbox' : '#simplerights :checkbox'), this.acl_form).map(function() {
139
         if (this.checked)
140
@@ -88,12 +91,18 @@
141
         return;
142
     }
143
 
144
-    this.http_request('settings/plugin.acl', '_act=save'
145
-        + '&_user='+urlencode(user)
146
-        + '&_acl=' +rights
147
-        + '&_mbox='+urlencode(this.env.mailbox)
148
-        + (this.acl_id ? '&_old='+this.acl_id : ''),
149
-        this.set_busy(true, 'acl.saving'));
150
+    data = {
151
+        _act: 'save',
152
+        _user: user,
153
+        _acl: rights,
154
+        _mbox: this.env.mailbox
155
+    }
156
+
157
+    if (this.acl_id) {
158
+        data._old = this.acl_id;
159
+    }
160
+
161
+    this.http_post('settings/plugin.acl', data, this.set_busy(true, 'acl.saving'));
162
 }
163
 
164
 // Cancel/Hide form
165
diff -ur roundcubemail-1.1.orig/plugins/acl/acl.php roundcubemail-1.1/plugins/acl/acl.php
166
--- roundcubemail-1.1.orig/plugins/acl/acl.php  2014-09-04 13:26:10.000000000 +0200
167
+++ roundcubemail-1.1/plugins/acl/acl.php   2014-09-06 14:30:33.912366431 +0200
168
@@ -452,10 +452,10 @@
169
      */
170
     private function action_save()
171
     {
172
-        $mbox  = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP
173
-        $user  = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC));
174
-        $acl   = trim(rcube_utils::get_input_value('_acl', rcube_utils::INPUT_GPC));
175
-        $oldid = trim(rcube_utils::get_input_value('_old', rcube_utils::INPUT_GPC));
176
+        $mbox  = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); // UTF7-IMAP
177
+        $user  = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST));
178
+        $acl   = trim(rcube_utils::get_input_value('_acl', rcube_utils::INPUT_POST));
179
+        $oldid = trim(rcube_utils::get_input_value('_old', rcube_utils::INPUT_POST));
180
 
181
         $acl    = array_intersect(str_split($acl), $this->rights_supported());
182
         $users  = $oldid ? array($user) : explode(',', $user);
183
@@ -508,8 +508,8 @@
184
      */
185
     private function action_delete()
186
     {
187
-        $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true)); //UTF7-IMAP
188
-        $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC));
189
+        $mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); //UTF7-IMAP
190
+        $user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST));
191
 
192
         $user = explode(',', $user);
193
 
194
diff -ur roundcubemail-1.1.orig/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php roundcubemail-1.1/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
195
--- roundcubemail-1.1.orig/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php 2014-09-04 13:26:10.000000000 +0200
196
+++ roundcubemail-1.1/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php  2014-09-06 14:30:33.914366432 +0200
197
@@ -349,7 +349,7 @@
198
                 }
199
             }
200
             else if ($action == 'setact' && !$error) {
201
-                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true);
202
+                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
203
                 $result = $this->activate_script($script_name);
204
                 $kep14  = $this->rc->config->get('managesieve_kolab_master');
205
 
206
@@ -363,7 +363,7 @@
207
                 }
208
             }
209
             else if ($action == 'deact' && !$error) {
210
-                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true);
211
+                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
212
                 $result = $this->deactivate_script($script_name);
213
 
214
                 if ($result === true) {
215
@@ -376,7 +376,7 @@
216
                 }
217
             }
218
             else if ($action == 'setdel' && !$error) {
219
-                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true);
220
+                $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
221
                 $result = $this->remove_script($script_name);
222
 
223
                 if ($result === true) {
224
@@ -419,14 +419,14 @@
225
                 $this->rc->output->command('managesieve_updatelist', 'list', array('list' => $result));
226
             }
227
             else if ($action == 'ruleadd') {
228
-                $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_GPC);
229
+                $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_POST);
230
                 $id = $this->genid();
231
                 $content = $this->rule_div($fid, $id, false);
232
 
233
                 $this->rc->output->command('managesieve_rulefill', $content, $id, $rid);
234
             }
235
             else if ($action == 'actionadd') {
236
-                $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_GPC);
237
+                $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_POST);
238
                 $id = $this->genid();
239
                 $content = $this->action_div($fid, $id, false);
240
 
241
Only in roundcubemail-1.1/plugins/managesieve/lib/Roundcube: rcube_sieve_engine.php.orig
242
diff -ur roundcubemail-1.1.orig/program/include/rcmail_output_html.php roundcubemail-1.1/program/include/rcmail_output_html.php
243
--- roundcubemail-1.1.orig/program/include/rcmail_output_html.php   2014-09-04 13:26:10.000000000 +0200
244
+++ roundcubemail-1.1/program/include/rcmail_output_html.php    2014-09-06 14:30:33.917366435 +0200
245
@@ -45,6 +45,7 @@
246
     protected $footer = '';
247
     protected $body = '';
248
     protected $base_path = '';
249
+    protected $assets_path;
250
     protected $devel_mode = false;
251
 
252
     // deprecated names of templates used before 0.5
253
@@ -80,6 +81,8 @@
254
         $this->set_skin($skin);
255
         $this->set_env('skin', $skin);
256
 
257
+        $this->set_assets_path($this->config->get('assets_path'));
258
+
259
         if (!empty($_REQUEST['_extwin']))
260
             $this->set_env('extwin', 1);
261
         if ($this->framed || $framed)
262
@@ -145,6 +148,50 @@
263
     }
264
 
265
     /**
266
+     * Parse and set assets path
267
+     *
268
+     * @param string Assets path (relative or absolute URL)
269
+     */
270
+    public function set_assets_path($path)
271
+    {
272
+        $path = trim($path, '/') . '/';
273
+
274
+        if (empty($path)) {
275
+            return;
276
+        }
277
+
278
+        // convert to absolute URL
279
+        if (!preg_match('|^https?://|', $path)) {
280
+            $base = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']);
281
+            $base = rtrim($base, '/');
282
+
283
+            // remove url token if exists
284
+            if ($len = intval($this->config->get('use_secure_urls'))) {
285
+                $_base  = explode('/', $base);
286
+                $length = $len > 1 ? $len : 16; // as in rcube::get_secure_url_token()
287
+
288
+                // we can't use real token here because it
289
+                // does not exists in unauthenticated state,
290
+                // hope this will not produce false-positive matches
291
+                foreach ($_base as $idx => $token) {
292
+                    if (preg_match('/^[a-f0-9]{' . $length . '}$/', $token)) {
293
+                        unset($_base[$idx]);
294
+                        break;
295
+                    }
296
+                }
297
+
298
+                $base = implode('/', $_base);
299
+            }
300
+
301
+            $path = (rcube_utils::https_check() ? 'https' : 'http') . '://'
302
+                . $_SERVER['SERVER_NAME'] . $base . '/' . $path;
303
+        }
304
+
305
+        $this->assets_path = $path;
306
+        $this->set_env('assets_path', $path);
307
+    }
308
+
309
+    /**
310
      * Getter for the current page title
311
      *
312
      * @return string The page title
313
@@ -367,14 +414,15 @@
314
     /**
315
      * Redirect to a certain url
316
      *
317
-     * @param mixed $p     Either a string with the action or url parameters as key-value pairs
318
-     * @param int   $delay Delay in seconds
319
+     * @param mixed $p      Either a string with the action or url parameters as key-value pairs
320
+     * @param int   $delay  Delay in seconds
321
+     * @param bool  $secure Redirect to secure location (see rcmail::url())
322
      */
323
-    public function redirect($p = array(), $delay = 1)
324
+    public function redirect($p = array(), $delay = 1, $secure = false)
325
     {
326
         if ($this->env['extwin'])
327
             $p['extwin'] = 1;
328
-        $location = $this->app->url($p);
329
+        $location = $this->app->url($p, false, false, $secure);
330
         header('Location: ' . $location);
331
         exit;
332
     }
333
@@ -659,6 +707,24 @@
334
         exit;
335
     }
336
 
337
+    /**
338
+     * Modify path by adding URL prefix if configured
339
+     */
340
+    public function asset_url($path)
341
+    {
342
+        // iframe content can't be in a different domain
343
+        // @TODO: check if assests are on a different domain
344
+        if (!$this->assets_path || $path == $this->env['blankpage']) {
345
+            return $path;
346
+        }
347
+
348
+        if ($path[0] == '.' && $path[1] == '/') {
349
+            $path = substr($path, 2);
350
+        }
351
+
352
+        return $this->assets_path . $path;
353
+    }
354
+
355
 
356
     /*****  Template parsing methods  *****/
357
 
358
@@ -715,6 +781,8 @@
359
             $file = $this->file_mod($file);
360
         }
361
 
362
+        $file = $this->asset_url($file);
363
+
364
         return $matches[1] . '=' . $matches[2] . $file . $matches[4];
365
     }
366
 
367
@@ -1329,6 +1397,7 @@
368
     {
369
         if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
370
             $file = $this->file_mod($this->scripts_path . $file);
371
+            $file = $this->asset_url($file);
372
         }
373
 
374
         if (!is_array($this->script_files[$position])) {
375
@@ -1541,7 +1610,7 @@
376
         }
377
 
378
         $attrib['name'] = $attrib['id'];
379
-        $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif';
380
+        $attrib['src']  = $this->asset_url($attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif');
381
 
382
         // register as 'contentframe' object
383
         if ($is_contentframe || $attrib['contentframe']) {
384
@@ -1758,9 +1827,11 @@
385
     {
386
         $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
387
         $images = array_map(array($this, 'abs_url'), $images);
388
+        $images = array_map(array($this, 'asset_url'), $images);
389
 
390
-        if (empty($images) || $this->app->task == 'logout')
391
+        if (empty($images) || $_REQUEST['_task'] == 'logout') {
392
             return;
393
+        }
394
 
395
         $this->add_script('var images = ' . self::json_serialize($images) .';
396
             for (var i=0; i<images.length; i++) {
397
diff -ur roundcubemail-1.1.orig/program/include/rcmail_output_json.php roundcubemail-1.1/program/include/rcmail_output_json.php
398
--- roundcubemail-1.1.orig/program/include/rcmail_output_json.php   2014-09-04 13:26:10.000000000 +0200
399
+++ roundcubemail-1.1/program/include/rcmail_output_json.php    2014-09-06 14:30:33.917366435 +0200
400
@@ -181,6 +181,11 @@
401
      */
402
     public function raise_error($code, $message)
403
     {
404
+        if ($code == 403) {
405
+            header('HTTP/1.1 403 Forbidden');
406
+            die("Invalid Request");
407
+        }
408
+
409
         $this->show_message("Application Error ($code): $message", 'error');
410
         $this->remote_response();
411
         exit;
412
diff -ur roundcubemail-1.1.orig/program/include/rcmail.php roundcubemail-1.1/program/include/rcmail.php
413
--- roundcubemail-1.1.orig/program/include/rcmail.php   2014-09-04 13:26:10.000000000 +0200
414
+++ roundcubemail-1.1/program/include/rcmail.php    2014-09-06 14:30:33.916366434 +0200
415
@@ -758,49 +758,16 @@
416
     }
417
 
418
     /**
419
-     * Generate a unique token to be used in a form request
420
-     *
421
-     * @return string The request token
422
-     */
423
-    public function get_request_token()
424
-    {
425
-        $sess_id = $_COOKIE[ini_get('session.name')];
426
-
427
-        if (!$sess_id) {
428
-            $sess_id = session_id();
429
-        }
430
-
431
-        $plugin = $this->plugins->exec_hook('request_token', array(
432
-            'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
433
-
434
-        return $plugin['value'];
435
-    }
436
-
437
-    /**
438
-     * Check if the current request contains a valid token
439
-     *
440
-     * @param int Request method
441
-     *
442
-     * @return boolean True if request token is valid false if not
443
-     */
444
-    public function check_request($mode = rcube_utils::INPUT_POST)
445
-    {
446
-        $token   = rcube_utils::get_input_value('_token', $mode);
447
-        $sess_id = $_COOKIE[ini_get('session.name')];
448
-
449
-        return !empty($sess_id) && $token == $this->get_request_token();
450
-    }
451
-
452
-    /**
453
      * Build a valid URL to this instance of Roundcube
454
      *
455
      * @param mixed   Either a string with the action or url parameters as key-value pairs
456
      * @param boolean Build an URL absolute to document root
457
      * @param boolean Create fully qualified URL including http(s):// and hostname
458
+     * @param bool    Return absolute URL in secure location
459
      *
460
      * @return string Valid application URL
461
      */
462
-    public function url($p, $absolute = false, $full = false)
463
+    public function url($p, $absolute = false, $full = false, $secure = false)
464
     {
465
         if (!is_array($p)) {
466
             if (strpos($p, 'http') === 0) {
467
@@ -826,9 +793,21 @@
468
             }
469
         }
470
 
471
+        $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME']));
472
+
473
+        if ($secure && ($token = $this->get_secure_url_token(true))) {
474
+            // add token to the url
475
+            $url = $token . '/' . $url;
476
+
477
+            // remove old token from the path
478
+            $base_path = preg_replace('/\/[a-f0-9]{' . strlen($token) . '}$/', '', $base_path);
479
+
480
+            // this need to be full url to make redirects work
481
+            $absolute = true;
482
+        }
483
+
484
         if ($absolute || $full) {
485
             // add base path to this Roundcube installation
486
-            $base_path = preg_replace('![^/]+$!', '', strval($_SERVER['SCRIPT_NAME']));
487
             if ($base_path == '') $base_path = '/';
488
             $prefix = $base_path;
489
 
490
@@ -877,6 +856,36 @@
491
         }
492
     }
493
 
494
+    /**
495
+     * CSRF attack prevention code
496
+     *
497
+     * @param int Request mode
498
+     */
499
+    public function request_security_check($mode = rcube_utils::INPUT_POST)
500
+    {
501
+        // don't check for valid request tokens in these actions
502
+        // @TODO: get rid of this
503
+        $request_check_whitelist = array('spell'=>1, 'spell_html'=>1);
504
+
505
+        if ($request_check_whitelist[$this->action]) {
506
+            return;
507
+        }
508
+
509
+        // check request token
510
+        if (!$this->check_request($mode)) {
511
+            self::raise_error(array(
512
+                'code' => 403, 'type' => 'php',
513
+                'message' => "Request security check failed"), false, true);
514
+        }
515
+
516
+        // check referer if configured
517
+        if ($this->config->get('referer_check') && !rcube_utils::check_referer()) {
518
+            self::raise_error(array(
519
+                'code' => 403, 'type' => 'php',
520
+                'message' => "Referer check failed"), true, true);
521
+        }
522
+    }
523
+
524
     /**
525
      * Registers action aliases for current task
526
      *
527
diff -ur roundcubemail-1.1.orig/program/js/app.js roundcubemail-1.1/program/js/app.js
528
--- roundcubemail-1.1.orig/program/js/app.js    2014-09-04 13:26:10.000000000 +0200
529
+++ roundcubemail-1.1/program/js/app.js 2014-09-06 14:30:33.920366437 +0200
530
@@ -1405,8 +1405,10 @@
531
 
532
     if (task == 'mail')
533
       url += '&_mbox=INBOX';
534
-    else if (task == 'logout' && !this.env.server_error)
535
+    else if (task == 'logout' && !this.env.server_error) {
536
+      url += '&_token=' + this.env.request_token;
537
       this.clear_compose_data();
538
+    }
539
 
540
     this.redirect(url);
541
   };
542
@@ -1416,7 +1418,10 @@
543
     if (!url)
544
       url = this.env.comm_path;
545
 
546
-    return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
547
+    if (url.match(/[?&]_task=[a-zA-Z0-9_-]+/))
548
+        return url.replace(/_task=[a-zA-Z0-9_-]+/, '_task=' + task);
549
+    else
550
+        return url.replace(/\?.*$/, '') + '?_task=' + task;
551
   };
552
 
553
   this.reload = function(delay)
554
diff -ur roundcubemail-1.1.orig/program/js/editor.js roundcubemail-1.1/program/js/editor.js
555
--- roundcubemail-1.1.orig/program/js/editor.js 2014-09-04 13:26:10.000000000 +0200
556
+++ roundcubemail-1.1/program/js/editor.js  2014-09-06 14:30:33.921366438 +0200
557
@@ -36,11 +36,12 @@
558
 function rcube_text_editor(config, id)
559
 {
560
   var ref = this,
561
+    abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''),
562
     conf = {
563
       selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),
564
       theme: 'modern',
565
       language: config.lang,
566
-      content_css: 'program/js/tinymce/roundcube/content.css?v1',
567
+      content_css: (rcmail.env.assets_path || '') + 'program/js/tinymce/roundcube/content.css?v1',
568
       menubar: false,
569
       statusbar: false,
570
       toolbar_items_size: 'small',
571
@@ -82,7 +83,7 @@
572
       toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify'
573
         + ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect'
574
         + ' | link unlink table | emoticons charmap image media | code searchreplace undo redo',
575
-      spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1',
576
+      spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1',
577
       spellchecker_language: rcmail.env.spell_lang,
578
       accessibility_focus: false,
579
       file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); },
580
diff -ur roundcubemail-1.1.orig/program/lib/Roundcube/rcube.php roundcubemail-1.1/program/lib/Roundcube/rcube.php
581
--- roundcubemail-1.1.orig/program/lib/Roundcube/rcube.php  2014-09-04 13:26:10.000000000 +0200
582
+++ roundcubemail-1.1/program/lib/Roundcube/rcube.php   2014-09-06 14:30:33.921366438 +0200
583
@@ -28,9 +28,15 @@
584
  */
585
 class rcube
586
 {
587
-    const INIT_WITH_DB = 1;
588
+    // Init options
589
+    const INIT_WITH_DB      = 1;
590
     const INIT_WITH_PLUGINS = 2;
591
 
592
+    // Request status
593
+    const REQUEST_VALID       = 0;
594
+    const REQUEST_ERROR_URL   = 1;
595
+    const REQUEST_ERROR_TOKEN = 2;
596
+
597
     /**
598
      * Singleton instace of rcube
599
      *
600
@@ -101,6 +107,12 @@
601
      */
602
     public $user;
603
 
604
+    /**
605
+     * Request status
606
+     *
607
+     * @var int
608
+     */
609
+    public $request_status = 0;
610
 
611
     /* private/protected vars */
612
     protected $texts;
613
@@ -976,6 +988,104 @@
614
 
615
 
616
     /**
617
+     * Returns session token for secure URLs
618
+     *
619
+     * @param bool $generate Generate token if not exists in session yet
620
+     *
621
+     * @return string|bool Token string, False when disabled
622
+     */
623
+    public function get_secure_url_token($generate = false)
624
+    {
625
+        if ($len = $this->config->get('use_secure_urls')) {
626
+            if (empty($_SESSION['secure_token']) && $generate) {
627
+                // generate x characters long token
628
+                $length = $len > 1 ? $len : 16;
629
+                $token  = openssl_random_pseudo_bytes($length / 2);
630
+                $token  = bin2hex($token);
631
+
632
+                $plugin = $this->plugins->exec_hook('secure_token',
633
+                    array('value' => $token, 'length' => $length));
634
+
635
+                $_SESSION['secure_token'] = $plugin['value'];
636
+            }
637
+
638
+            return $_SESSION['secure_token'];
639
+        }
640
+
641
+        return false;
642
+    }
643
+
644
+
645
+    /**
646
+     * Generate a unique token to be used in a form request
647
+     *
648
+     * @return string The request token
649
+     */
650
+    public function get_request_token()
651
+    {
652
+        $sess_id = $_COOKIE[ini_get('session.name')];
653
+        if (!$sess_id) {
654
+            $sess_id = session_id();
655
+        }
656
+
657
+        $plugin = $this->plugins->exec_hook('request_token', array(
658
+            'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
659
+
660
+        return $plugin['value'];
661
+    }
662
+
663
+
664
+    /**
665
+     * Check if the current request contains a valid token.
666
+     * Empty requests aren't checked until use_secure_urls is set.
667
+     *
668
+     * @param int Request method
669
+     *
670
+     * @return boolean True if request token is valid false if not
671
+     */
672
+    public function check_request($mode = rcube_utils::INPUT_POST)
673
+    {
674
+        // check secure token in URL if enabled
675
+        if ($token = $this->get_secure_url_token()) {
676
+            foreach (explode('/', preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI'])) as $tok) {
677
+                if ($tok == $token) {
678
+                    return true;
679
+                }
680
+            }
681
+
682
+            $this->request_status = self::REQUEST_ERROR_URL;
683
+
684
+            return false;
685
+        }
686
+
687
+        $sess_tok = $this->get_request_token();
688
+
689
+        // ajax requests
690
+        if (rcube_utils::request_header('X-Roundcube-Request') == $sess_tok) {
691
+            return true;
692
+        }
693
+
694
+        // skip empty requests
695
+        if (($mode == rcube_utils::INPUT_POST && empty($_POST))
696
+            || ($mode == rcube_utils::INPUT_GET && empty($_GET))
697
+        ) {
698
+            return true;
699
+        }
700
+
701
+        // default method of securing requests
702
+        $token   = rcube_utils::get_input_value('_token', $mode);
703
+        $sess_id = $_COOKIE[ini_get('session.name')];
704
+
705
+        if (empty($sess_id) || $token != $sess_tok) {
706
+            $this->request_status = self::REQUEST_ERROR_TOKEN;
707
+            return false;
708
+        }
709
+
710
+        return true;
711
+    }
712
+
713
+
714
+    /**
715
      * Build a valid URL to this instance of Roundcube
716
      *
717
      * @param mixed Either a string with the action or url parameters as key-value pairs
718
@@ -1263,7 +1373,7 @@
719
 
720
         $cli = php_sapi_name() == 'cli';
721
 
722
-        if (($log || $terminate) && !$cli && $arg['message']) {
723
+        if ($log && !$cli && $arg['message']) {
724
             $arg['fatal'] = $terminate;
725
             self::log_bug($arg);
726
         }
727
diff -ur roundcubemail-1.1.orig/program/steps/addressbook/delete.inc roundcubemail-1.1/program/steps/addressbook/delete.inc
728
--- roundcubemail-1.1.orig/program/steps/addressbook/delete.inc 2014-09-04 13:26:10.000000000 +0200
729
+++ roundcubemail-1.1/program/steps/addressbook/delete.inc  2014-09-06 14:30:33.922366438 +0200
730
@@ -20,10 +20,11 @@
731
 */
732
 
733
 // process ajax requests only
734
-if (!$OUTPUT->ajax_call)
735
+if (!$OUTPUT->ajax_call) {
736
     return;
737
+}
738
 
739
-$cids   = rcmail_get_cids();
740
+$cids   = rcmail_get_cids(null, rcube_utils::INPUT_POST);
741
 $delcnt = 0;
742
 
743
 // remove previous deletes
744
diff -ur roundcubemail-1.1.orig/program/steps/addressbook/func.inc roundcubemail-1.1/program/steps/addressbook/func.inc
745
--- roundcubemail-1.1.orig/program/steps/addressbook/func.inc   2014-09-04 13:26:10.000000000 +0200
746
+++ roundcubemail-1.1/program/steps/addressbook/func.inc    2014-09-06 14:30:33.922366438 +0200
747
@@ -896,13 +896,13 @@
748
  *
749
  * @return array List of contact IDs per-source
750
  */
751
-function rcmail_get_cids($filter = null)
752
+function rcmail_get_cids($filter = null, $request_type = rcube_utils::INPUT_GPC)
753
 {
754
     // contact ID (or comma-separated list of IDs) is provided in two
755
     // forms. If _source is an empty string then the ID is a string
756
     // containing contact ID and source name in form: <ID>-<SOURCE>
757
 
758
-    $cid    = rcube_utils::get_input_value('_cid', rcube_utils::INPUT_GPC);
759
+    $cid    = rcube_utils::get_input_value('_cid', $request_type);
760
     $source = (string) rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC);
761
 
762
     if (is_array($cid)) {
763
diff -ur roundcubemail-1.1.orig/program/steps/utils/error.inc roundcubemail-1.1/program/steps/utils/error.inc
764
--- roundcubemail-1.1.orig/program/steps/utils/error.inc    2014-09-04 13:26:10.000000000 +0200
765
+++ roundcubemail-1.1/program/steps/utils/error.inc 2014-09-06 14:30:33.923366439 +0200
766
@@ -50,9 +50,17 @@
767
 
768
 // forbidden due to request check
769
 else if ($ERROR_CODE == 403) {
770
+    if ($_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->request_status == rcube::REQUEST_ERROR_URL) {
771
+        parse_str($_SERVER['QUERY_STRING'], $url);
772
+        $url = $rcmail->url($url, true, false, true);
773
+        $add = "<br /><a href=\"$url\">Click here to try again.<a/>";
774
+    }
775
+    else {
776
+        $add = "Please contact your server-administrator.";
777
+    }
778
+
779
     $__error_title = "REQUEST CHECK FAILED";
780
-    $__error_text  = "Access to this service was denied due to failing security checks!<br />\n"
781
-        . "Please contact your server-administrator.";
782
+    $__error_text  = "Access to this service was denied due to failing security checks!<br />\n$add";
783
 }
784
 
785
 // failed request (wrong step in URL)
786