Add cookie SameSite attribute; uses default if "Strict" but can be overridden by...
[squirrelmail.git] / functions / global.php
CommitLineData
61d9ec71 1<?php
2
3/**
0c701a88 4 * global.php
61d9ec71 5 *
62f7daa5 6 * This includes code to update < 4.1.0 globals to the newer format
242342d0 7 * It also has some session register functions that work across various
62f7daa5 8 * php versions.
61d9ec71 9 *
c997cbe6 10 * @copyright 1999-2021 The SquirrelMail Project Team
4b4abf93 11 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
31841a9e 12 * @version $Id$
d6c32258 13 * @package squirrelmail
61d9ec71 14 */
15
051f6245 16/**
bce96055 17 * These constants are used in the function sqgetGlobalVar(). See
18 * sqgetGlobalVar() for a description of what they mean.
19 *
20 * @since 1.4.0
2ca4c65a 21 */
7f62aaef 22define('SQ_INORDER',0);
23define('SQ_GET',1);
24define('SQ_POST',2);
25define('SQ_SESSION',3);
26define('SQ_COOKIE',4);
27define('SQ_SERVER',5);
28define('SQ_FORM',6);
a32985a5 29
202bcbcc 30
62f7daa5 31/**
32 * returns true if current php version is at mimimum a.b.c
33 *
97bdc607 34 * Called: check_php_version(4,1)
8b096f0a 35 * @param int a major version number
36 * @param int b minor version number
37 * @param int c release number
38 * @return bool
97bdc607 39 */
62f7daa5 40function check_php_version ($a = '0', $b = '0', $c = '0')
9697c5ab 41{
5673cabe 42 return version_compare ( PHP_VERSION, "$a.$b.$c", 'ge' );
9697c5ab 43}
44
97bdc607 45/**
62f7daa5 46 * returns true if the current internal SM version is at minimum a.b.c
47 * These are plain integer comparisons, as our internal version is
97bdc607 48 * constructed by us, as an array of 3 ints.
49 *
50 * Called: check_sm_version(1,3,3)
8b096f0a 51 * @param int a major version number
52 * @param int b minor version number
53 * @param int c release number
54 * @return bool
97bdc607 55 */
56function check_sm_version($a = 0, $b = 0, $c = 0)
57{
58 global $SQM_INTERNAL_VERSION;
59 if ( !isset($SQM_INTERNAL_VERSION) ||
60 $SQM_INTERNAL_VERSION[0] < $a ||
150c28d6 61 ( $SQM_INTERNAL_VERSION[0] == $a &&
62 $SQM_INTERNAL_VERSION[1] < $b) ||
63 ( $SQM_INTERNAL_VERSION[0] == $a &&
64 $SQM_INTERNAL_VERSION[1] == $b &&
97bdc607 65 $SQM_INTERNAL_VERSION[2] < $c ) ) {
66 return FALSE;
62f7daa5 67 }
68 return TRUE;
97bdc607 69}
70
71
8b096f0a 72/**
73 * Recursively strip slashes from the values of an array.
74 * @param array array the array to strip, passed by reference
75 * @return void
76 */
a32985a5 77function sqstripslashes(&$array) {
3aa17cf9 78 if(count($array) > 0) {
79 foreach ($array as $index=>$value) {
80 if (is_array($array[$index])) {
81 sqstripslashes($array[$index]);
82 }
83 else {
84 $array[$index] = stripslashes($value);
85 }
a32985a5 86 }
87 }
88}
89
8442ecb9 90/**
91 * Squelch error output to screen (only) for the given function.
1888b1bf 92 * If the SquirrelMail debug mode SM_DEBUG_MODE_ADVANCED is not
93 * enabled, error output will not go to the log, either.
8442ecb9 94 *
95 * This provides an alternative to the @ error-suppression
96 * operator where errors will not be shown in the interface
97 * but will show up in the server log file (assuming the
98 * administrator has configured PHP logging).
99 *
100 * @since 1.4.12 and 1.5.2
101 *
102 * @param string $function The function to be executed
103 * @param array $args The arguments to be passed to the function
104 * (OPTIONAL; default no arguments)
105 * NOTE: The caller must take extra action if
106 * the function being called is supposed
107 * to use any of the parameters by
108 * reference. In the following example,
109 * $x is passed by reference and $y is
110 * passed by value to the "my_func"
111 * function.
112 * sq_call_function_suppress_errors('my_func', array(&$x, $y));
113 *
114 * @return mixed The return value, if any, of the function being
115 * executed will be returned.
116 *
117 */
d0e7f324 118function sq_call_function_suppress_errors($function, $args=array()) {
1888b1bf 119 global $sm_debug_mode;
120
8442ecb9 121 $display_errors = ini_get('display_errors');
122 ini_set('display_errors', '0');
1888b1bf 123
124 // if advanced debug mode isn't enabled, don't log the error, either
125 //
126 if (!($sm_debug_mode & SM_DEBUG_MODE_ADVANCED))
127 $error_reporting = error_reporting(0);
128
8442ecb9 129 $ret = call_user_func_array($function, $args);
1888b1bf 130
131 if (!($sm_debug_mode & SM_DEBUG_MODE_ADVANCED))
132 error_reporting($error_reporting);
133
8442ecb9 134 ini_set('display_errors', $display_errors);
135 return $ret;
136}
137
8b096f0a 138/**
139 * Add a variable to the session.
140 * @param mixed $var the variable to register
141 * @param string $name the name to refer to this variable
142 * @return void
143 */
61d9ec71 144function sqsession_register ($var, $name) {
281c3d5b 145
146 sqsession_is_active();
147
04ce2477 148 $_SESSION[$name] = $var;
61d9ec71 149}
3aa17cf9 150
8b096f0a 151/**
152 * Delete a variable from the session.
153 * @param string $name the name of the var to delete
154 * @return void
155 */
61d9ec71 156function sqsession_unregister ($name) {
281c3d5b 157
158 sqsession_is_active();
159
abd74f7d 160 unset($_SESSION[$name]);
62f7daa5 161
f48a90ca 162 // starts throwing warnings in PHP 5.3.0 and is
163 // removed in PHP 6 and is redundant anyway
164 //session_unregister("$name");
61d9ec71 165}
3aa17cf9 166
8b096f0a 167/**
168 * Checks to see if a variable has already been registered
169 * in the session.
170 * @param string $name the name of the var to check
171 * @return bool whether the var has been registered
172 */
d7c82551 173function sqsession_is_registered ($name) {
174 $test_name = &$name;
175 $result = false;
62f7daa5 176
abd74f7d 177 if (isset($_SESSION[$test_name])) {
178 $result = true;
d7c82551 179 }
62f7daa5 180
d7c82551 181 return $result;
182}
183
54ce41dd 184
185/**
186 * Retrieves a form variable, from a set of possible similarly named
187 * form variables, based on finding a different, single field. This
68a7e1d6 188 * is intended to allow more than one same-named inputs in a single
189 * <form>, where the submit button that is clicked tells us which
54ce41dd 190 * input we should retrieve. An example is if we have:
191 * <select name="startMessage_1">
192 * <select name="startMessage_2">
b116fd78 193 * <input type="submit" name="form_submit_1" />
194 * <input type="submit" name="form_submit_2" />
68a7e1d6 195 * and we want to know which one of the select inputs should be
54ce41dd 196 * returned as $startMessage (without the suffix!), this function
197 * decides by looking for either "form_submit_1" or "form_submit_2"
198 * (both should not appear). In this example, $name should be
199 * "startMessage" and $indicator_field should be "form_submit".
200 *
201 * NOTE that form widgets must be named with the suffix "_1", "_2", "_3"
202 * and so on, or this function will not work.
203 *
204 * If more than one of the indicator fields is found, the first one
205 * (numerically) will win.
206 *
68a7e1d6 207 * If an indicator field is found without a matching input ($name)
139a4b99 208 * field, FALSE is returned.
209 *
68a7e1d6 210 * If no indicator fields are found, a field of $name *without* any
211 * suffix is searched for (but only if $fallback_no_suffix is TRUE),
139a4b99 212 * and if not found, FALSE is ultimately returned.
54ce41dd 213 *
214 * It should also be possible to use the same string for both
215 * $name and $indicator_field to look for the first possible
216 * widget with a suffix that can be found (and possibly fallback
217 * to a widget without a suffix).
218 *
219 * @param string name the name of the var to search
220 * @param mixed value the variable to return
221 * @param string indicator_field the name of the field upon which to base
222 * our decision upon (see above)
223 * @param int search constant defining where to look
224 * @param bool fallback_no_suffix whether or not to look for $name with
225 * no suffix when nothing else is found
226 * @param mixed default the value to assign to $value when nothing is found
227 * @param int typecast force variable to be cast to given type (please
228 * use SQ_TYPE_XXX constants or set to FALSE (default)
229 * to leave variable type unmolested)
230 *
231 * @return bool whether variable is found.
232 */
68a7e1d6 233function sqGetGlobalVarMultiple($name, &$value, $indicator_field,
234 $search = SQ_INORDER,
235 $fallback_no_suffix=TRUE, $default=NULL,
54ce41dd 236 $typecast=FALSE) {
237
1793f985 238 // Set arbitrary max limit -- should be much lower except on the
239 // search results page, if there are many (50 or more?) mailboxes
240 // shown, this may not be high enough. Is there some way we should
241 // automate this value?
242 //
243 $max_form_search = 100;
54ce41dd 244
245 for ($i = 1; $i <= $max_form_search; $i++) {
246 if (sqGetGlobalVar($indicator_field . '_' . $i, $temp, $search)) {
247 return sqGetGlobalVar($name . '_' . $i, $value, $search, $default, $typecast);
248 }
249 }
250
251
252 // no indicator field found; just try without suffix if allowed
253 //
254 if ($fallback_no_suffix) {
255 return sqGetGlobalVar($name, $value, $search, $default, $typecast);
256 }
257
258
259 // no dice, set default and return FALSE
260 //
261 if (!is_null($default)) {
262 $value = $default;
263 }
264 return FALSE;
265
266}
267
268
4cd8ae7d 269/**
bce96055 270 * Search for the variable $name in one or more of the global variables
271 * $_SESSION, $_POST, $_GET, $_COOKIE, and $_SERVER, and set the value of it in
272 * the variable $vaule.
d1975c5b 273 *
bce96055 274 * $search must be one of the defined constants below. The default is
275 * SQ_INORDER. Both SQ_INORDER and SQ_FORM stops on the first match.
d1975c5b 276 *
bce96055 277 * SQ_INORDER searches $_SESSION, then $_POST, and then $_GET.
278 * SQ_FORM searches $_POST and then $_GET.
279 * SQ_COOKIE searches $_COOKIE only.
280 * SQ_GET searches $_GET only.
281 * SQ_POST searches $_POST only.
282 * SQ_SERVER searches $_SERVER only.
283 * SQ_SESSION searches $_SESSION only.
d1975c5b 284 *
2d055f0a 285 * Example:
bce96055 286 * sqgetGlobalVar('username', $username, SQ_SESSION);
287 * // No quotes around the last parameter, it's a constant - not a string!
d1975c5b 288 *
8b096f0a 289 * @param string name the name of the var to search
290 * @param mixed value the variable to return
291 * @param int search constant defining where to look
54ce41dd 292 * @param mixed default the value to assign to $value when nothing is found
c2b585c5 293 * @param int typecast force variable to be cast to given type (please
294 * use SQ_TYPE_XXX constants or set to FALSE (default)
295 * to leave variable type unmolested)
54ce41dd 296 *
8b096f0a 297 * @return bool whether variable is found.
4cd8ae7d 298 */
bce96055 299function sqgetGlobalVar($name, &$value, $search = SQ_INORDER, $default = NULL, $typecast = FALSE) {
300 // The return value defaults to FALSE, i.e. the variable wasn't found.
301 $result = FALSE;
f79c19a4 302
bce96055 303 // Search the global variables to find a match.
4cd8ae7d 304 switch ($search) {
bce96055 305 default:
306 // The default needs to be first here so SQ_INORDER will be used if
307 // $search isn't a valid constant.
308 case SQ_INORDER:
309 // Search $_SESSION, then $_POST, and then $_GET. Stop on the first
310 // match.
311 case SQ_SESSION:
312 if (isset($_SESSION[$name])) {
313 // If a match is found, set the specified variable to the found
314 // value, indicate a match, and stop the search.
315 $value = $_SESSION[$name];
316 $result = TRUE;
317 break;
318 } elseif ($search == SQ_SESSION) {
319 // Only stop the search if SQ_SESSION is set. SQ_INORDER will
320 // continue with the next clause.
321 break;
322 }
323 case SQ_FORM:
324 // Search $_POST and then $_GET. Stop on the first match.
325 case SQ_POST:
326 if (isset($_POST[$name])) {
327 // If a match is found, set the specified variable to the found
328 // value, indicate a match, and stop the search.
329 $value = $_POST[$name];
330 $result = TRUE;
331 break;
332 } elseif ($search == SQ_POST) {
333 // Only stop the search if SQ_POST is set. SQ_INORDER and
334 // SQ_FORM will continue with the next clause.
335 break;
336 }
337 case SQ_GET:
338 if (isset($_GET[$name])) {
339 // If a match is found, set the specified variable to the found
340 // value, indicate a match, and stop the search.
341 $value = $_GET[$name];
342 $result = TRUE;
343 break;
344 }
345 // Stop the search regardless of if SQ_INORDER, SQ_FORM, or SQ_GET
346 // is set. All three of them ends here.
202bcbcc 347 break;
bce96055 348 case SQ_COOKIE:
349 if (isset($_COOKIE[$name])) {
350 // If a match is found, set the specified variable to the found
351 // value, indicate a match, and stop the search.
352 $value = $_COOKIE[$name];
353 $result = TRUE;
354 break;
355 }
356 // Stop the search.
202bcbcc 357 break;
bce96055 358 case SQ_SERVER:
359 if (isset($_SERVER[$name])) {
360 // If a match is found, set the specified variable to the found
361 // value, indicate a match, and stop the search.
362 $value = $_SERVER[$name];
363 $result = TRUE;
364 break;
365 }
366 // Stop the search.
202bcbcc 367 break;
4cd8ae7d 368 }
bce96055 369
202bcbcc 370 if ($result && $typecast) {
bce96055 371 // Only typecast if it's requested and a match is found. The default is
372 // not to typecast, which will happen if a valid constant isn't
373 // specified.
202bcbcc 374 switch ($typecast) {
bce96055 375 case SQ_TYPE_INT:
376 // Typecast the value and stop.
377 $value = (int) $value;
378 break;
379 case SQ_TYPE_STRING:
380 // Typecast the value and stop.
381 $value = (string) $value;
382 break;
383 case SQ_TYPE_BOOL:
384 // Typecast the value and stop.
385 $value = (bool) $value;
386 break;
1c2963ab 387 case SQ_TYPE_BIGINT:
bce96055 388 // Typecast the value and stop.
1c2963ab 389 $value = (preg_match('/^[0-9]+$/', $value) ? $value : '0');
390 break;
bce96055 391 default:
392 // The default is to do nothing.
393 break;
202bcbcc 394 }
ced8272a 395 } else if (!$result && !is_null($default)) {
bce96055 396 // If no match is found and a default value is specified, set it.
202bcbcc 397 $value = $default;
398 }
bce96055 399
400 // Return if a match was found or not.
202bcbcc 401 return $result;
4cd8ae7d 402}
403
061108dc 404/**
405 * Get an immutable copy of a configuration variable if SquirrelMail
406 * is in "secured configuration" mode. This guarantees the caller
407 * gets a copy of the requested value as it is set in the main
408 * application configuration (including config_local overrides), and
409 * not what it might be after possibly having been modified by some
410 * other code (usually a plugin overriding configuration values for
411 * one reason or another).
412 *
413 * WARNING: Please use this function as little as possible, because
414 * every time it is called, it forcibly reloads the main configuration
415 * file(s).
416 *
417 * Caller beware that this function will do nothing if SquirrelMail
418 * is not in "secured configuration" mode per the $secured_config
419 * setting.
420 *
421 * @param string $var_name The name of the desired variable
422 *
423 * @return mixed The desired value
424 *
425 * @since 1.5.2
426 *
427 */
428function get_secured_config_value($var_name) {
429
430 static $return_values = array();
431
432 // if we can avoid it, return values that have
433 // already been retrieved (so we don't have to
434 // include the config file yet again)
435 //
436 if (isset($return_values[$var_name])) {
437 return $return_values[$var_name];
438 }
439
440
441 // load site configuration
442 //
443 require(SM_PATH . 'config/config.php');
444
445 // load local configuration overrides
446 //
447 if (file_exists(SM_PATH . 'config/config_local.php')) {
448 require(SM_PATH . 'config/config_local.php');
449 }
450
451 // if SM isn't in "secured configuration" mode,
452 // just return the desired value from the global scope
453 //
454 if (!$secured_config) {
455 global $$var_name;
456 $return_values[$var_name] = $$var_name;
457 return $$var_name;
458 }
459
460 // else we return what we got from the config file
461 //
462 $return_values[$var_name] = $$var_name;
463 return $$var_name;
464
465}
466
8b096f0a 467/**
468 * Deletes an existing session, more advanced than the standard PHP
469 * session_destroy(), it explicitly deletes the cookies and global vars.
66c7cd3f 470 *
471 * WARNING: Older PHP versions have some issues with session management.
68a7e1d6 472 * See http://bugs.php.net/11643 (warning, spammed bug tracker) and
66c7cd3f 473 * http://bugs.php.net/13834. SID constant is not destroyed in PHP 4.1.2,
68a7e1d6 474 * 4.2.3 and maybe other versions. If you restart session after session
475 * is destroyed, affected PHP versions produce PHP notice. Bug should
66c7cd3f 476 * be fixed only in 4.3.0
8b096f0a 477 */
513db22c 478function sqsession_destroy() {
242342d0 479
281c3d5b 480 /*
481 * php.net says we can kill the cookie by setting just the name:
482 * http://www.php.net/manual/en/function.setcookie.php
483 * maybe this will help fix the session merging again.
484 *
485 * Changed the theory on this to kill the cookies first starting
486 * a new session will provide a new session for all instances of
487 * the browser, we don't want that, as that is what is causing the
488 * merging of sessions.
489 */
242342d0 490
716cc530 491 global $base_uri, $_COOKIE, $_SESSION;
f31687f6 492
1f80d9f5 493 if (isset($_COOKIE[session_name()]) && session_name()) {
494 sqsetcookie(session_name(), $_COOKIE[session_name()], 1, $base_uri);
495
496 /*
497 * Make sure to kill /src and /src/ cookies, just in case there are
498 * some left-over or malicious ones set in user's browser.
499 * NB: Note that an attacker could try to plant a cookie for one
500 * of the /plugins/* directories. Such cookies can block
501 * access to certain plugin pages, but they do not influence
502 * or fixate the $base_uri cookie, so we don't worry about
503 * trying to delete all of them here.
504 */
505 sqsetcookie(session_name(), $_COOKIE[session_name()], 1, $base_uri . 'src');
506 sqsetcookie(session_name(), $_COOKIE[session_name()], 1, $base_uri . 'src/');
507 }
508
f6cd95a1 509 if (isset($_COOKIE['key']) && $_COOKIE['key']) sqsetcookie('key','SQMTRASH',1,$base_uri);
281c3d5b 510
1f80d9f5 511 /* Make sure new session id is generated on subsequent session_start() */
512 unset($_COOKIE[session_name()]);
513 unset($_GET[session_name()]);
514 unset($_POST[session_name()]);
515
281c3d5b 516 $sessid = session_id();
517 if (!empty( $sessid )) {
abd74f7d 518 $_SESSION = array();
21e18f59 519 @session_destroy();
242342d0 520 }
281c3d5b 521}
242342d0 522
8b096f0a 523/**
281c3d5b 524 * Function to verify a session has been started. If it hasn't
525 * start a session up. php.net doesn't tell you that $_SESSION
526 * (even though autoglobal), is not created unless a session is
527 * started, unlike $_POST, $_GET and such
253ca97e 528 * Update: (see #1685031) the session ID is left over after the
529 * session is closed in some PHP setups; this function just becomes
530 * a passthru to sqsession_start(), but leaving old code in for
531 * edification.
281c3d5b 532 */
281c3d5b 533function sqsession_is_active() {
253ca97e 534 //$sessid = session_id();
535 //if ( empty( $sessid ) ) {
3a1de9f1 536 sqsession_start();
253ca97e 537 //}
513db22c 538}
539
3a1de9f1 540/**
541 * Function to start the session and store the cookie with the session_id as
542 * HttpOnly cookie which means that the cookie isn't accessible by javascript
543 * (IE6 only)
253ca97e 544 * Note that as sqsession_is_active() no longer discriminates as to when
545 * it calls this function, session_start() has to have E_NOTICE suppression
546 * (thus the @ sign).
3a1de9f1 547 */
548function sqsession_start() {
202bcbcc 549 global $base_uri;
7f62aaef 550
8442ecb9 551 sq_call_function_suppress_errors('session_start');
552 // was: @session_start();
202bcbcc 553 $session_id = session_id();
554
79a7fdf5 555 // session_starts sets the sessionid cookie but without the httponly var
3a1de9f1 556 // setting the cookie again sets the httponly cookie attribute
79a7fdf5 557 //
558 // need to check if headers have been sent, since sqsession_is_active()
559 // has become just a passthru to this function, so the sqsetcookie()
560 // below is called every time, even after headers have already been sent
561 //
562 if (!headers_sent())
563 sqsetcookie(session_name(),$session_id,false,$base_uri);
3a1de9f1 564}
565
566
79a7fdf5 567
3a1de9f1 568/**
569 * Set a cookie
945c733e 570 *
3a1de9f1 571 * @param string $sName The name of the cookie.
572 * @param string $sValue The value of the cookie.
945c733e 573 * @param int $iExpire The time the cookie expires. This is a Unix
574 * timestamp so is in number of seconds since
575 * the epoch.
576 * @param string $sPath The path on the server in which the cookie
577 * will be available on.
3a1de9f1 578 * @param string $sDomain The domain that the cookie is available.
945c733e 579 * @param boolean $bSecure Indicates that the cookie should only be
580 * transmitted over a secure HTTPS connection.
3a1de9f1 581 * @param boolean $bHttpOnly Disallow JS to access the cookie (IE6 only)
945c733e 582 * @param boolean $bReplace Replace previous cookies with same name?
f0d2cd40 583 * @param string $sSameSite Optional override of the default SameSite
584 * cookie policy detemined from the global
585 * configuration item $same_site_cookies
586 * (which can be set in config/config_local.php)
587 * (should be NULL to accept the configured global
588 * default or one of "Lax" "Strict" or "None"
589 * but "None" will not work if $bSecure is FALSE.
590 * Can also be set set to an empty string in order
591 * to NOT specify the SameSite cookie attribute at
592 * all and accept whatever the browser default is)
945c733e 593 *
3a1de9f1 594 * @return void
945c733e 595 *
b80887b4 596 * @since 1.4.16 and 1.5.1
597 *
3a1de9f1 598 */
945c733e 599function sqsetcookie($sName, $sValue='deleted', $iExpire=0, $sPath="", $sDomain="",
f0d2cd40 600 $bSecure=false, $bHttpOnly=true, $bReplace=false, $sSameSite=NULL) {
945c733e 601
c3d4275e 602 // some environments can get overwhelmed by an excessive
603 // setting of the same cookie over and over (e.g., many
604 // calls to this function via sqsession_is_active() result
605 // in repeated setting of the session cookie when $bReplace
606 // is FALSE, but something odd happens (during login only)
607 // if we change that to default TRUE) ... so we keep our own
608 // naive per-request name/value cache and only set the cookie
609 // if its value is changing (or never seen before)
610 static $cookies = array();
611 if (isset($cookies[$sName]) && $cookies[$sName] === $sValue)
612 return;
613 else
614 $cookies[$sName] = $sValue;
615
616
68a7e1d6 617 // if we have a secure connection then limit the cookies to https only.
8f557b94 618 global $is_secure_connection;
619 if ($sName && $is_secure_connection)
68a7e1d6 620 $bSecure = true;
9c0f1780 621
622 // admin config can override the restriction of secure-only cookies
623 global $only_secure_cookies;
624 if (!$only_secure_cookies)
625 $bSecure = false;
626
f0d2cd40 627 // use global SameSite setting, but allow override
628 // The global $same_site_cookies (for which an override value
629 // can be specified in config/config_local.php) defaults to
630 // "Strict" when it is NULL (when not given in the config file),
631 // or can be manually set to "Lax" "Strict" or "None" if desired
632 // or can be set to an empty string in order to not specify
633 // SameSite at all and use the browser default
634 if (is_null($sSameSite)) {
635 global $same_site_cookies;
636 if (is_null($same_site_cookies))
637 $sSameSite = 'Strict';
638 else
639 $sSameSite = $same_site_cookies;
640 }
641
68a7e1d6 642 if (false && check_php_version(5,2)) {
643 // php 5 supports the httponly attribute in setcookie, but because setcookie seems a bit
644 // broken we use the header function for php 5.2 as well. We might change that later.
645 //setcookie($sName,$sValue,(int) $iExpire,$sPath,$sDomain,$bSecure,$bHttpOnly);
646 } else {
a14da8d6 647 if (!empty($sDomain)) {
68a7e1d6 648 // Fix the domain to accept domains with and without 'www.'.
a14da8d6 649 if (strtolower(substr($sDomain, 0, 4)) == 'www.') $sDomain = substr($sDomain, 4);
650 $sDomain = '.' . $sDomain;
68a7e1d6 651
652 // Remove port information.
a14da8d6 653 $Port = strpos($sDomain, ':');
654 if ($Port !== false) $sDomain = substr($sDomain, 0, $Port);
68a7e1d6 655 }
716cc530 656 if (!$sValue) $sValue = 'deleted';
68a7e1d6 657 header('Set-Cookie: ' . rawurlencode($sName) . '=' . rawurlencode($sValue)
a14da8d6 658 . (empty($iExpire) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $iExpire) . ' GMT')
68a7e1d6 659 . (empty($sPath) ? '' : '; path=' . $sPath)
660 . (empty($sDomain) ? '' : '; domain=' . $sDomain)
661 . (!$bSecure ? '' : '; secure')
f0d2cd40 662 . (!$bHttpOnly ? '' : '; HttpOnly')
663 . (empty($sSameSite) ? '' : '; SameSite=' . $sSameSite), $bReplace);
68a7e1d6 664 }
202bcbcc 665}
666
945c733e 667
202bcbcc 668/**
669 * session_regenerate_id replacement for PHP < 4.3.2
670 *
671 * This code is borrowed from Gallery, session.php version 1.53.2.1
10f4b195 672FIXME: I saw this code on php.net (in the manual); that's where it comes from originally, but I don't think we need it - it's just redundant to all the hard work we already did seeding the random number generator IMO. I think we can just call to GenerateRandomString() and dump the rest.
202bcbcc 673 */
674if (!function_exists('session_regenerate_id')) {
202bcbcc 675
676 function php_combined_lcg() {
202bcbcc 677 $tv = gettimeofday();
678 $lcg['s1'] = $tv['sec'] ^ (~$tv['usec']);
679 $lcg['s2'] = mt_rand();
680 $q = (int) ($lcg['s1'] / 53668);
681 $lcg['s1'] = (int) (40014 * ($lcg['s1'] - 53668 * $q) - 12211 * $q);
682 if ($lcg['s1'] < 0) {
683 $lcg['s1'] += 2147483563;
684 }
685 $q = (int) ($lcg['s2'] / 52774);
686 $lcg['s2'] = (int) (40692 * ($lcg['s2'] - 52774 * $q) - 3791 * $q);
687 if ($lcg['s2'] < 0) {
688 $lcg['s2'] += 2147483399;
689 }
690 $z = (int) ($lcg['s1'] - $lcg['s2']);
691 if ($z < 1) {
692 $z += 2147483562;
693 }
694 return $z * 4.656613e-10;
695 }
3a1de9f1 696
202bcbcc 697 function session_regenerate_id() {
698 global $base_uri;
699 $tv = gettimeofday();
700 sqgetGlobalVar('REMOTE_ADDR',$remote_addr,SQ_SERVER);
701 $buf = sprintf("%.15s%ld%ld%0.8f", $remote_addr, $tv['sec'], $tv['usec'], php_combined_lcg() * 10);
702 session_id(md5($buf));
703 if (ini_get('session.use_cookies')) {
73ee0267 704 sqsetcookie(session_name(), session_id(), 0, $base_uri);
202bcbcc 705 }
706 return TRUE;
707 }
3a1de9f1 708}
7f62aaef 709
202bcbcc 710
7f62aaef 711/**
712 * php_self
713 *
d930613a 714 * Attempts to determine the path and filename and any arguments
715 * for the currently executing script. This is usually found in
716 * $_SERVER['REQUEST_URI'], but some environments may differ, so
717 * this function tries to standardize this value.
718 *
719 * Note that before SquirrelMail version 1.5.1, this function was
720 * stored in function/strings.php.
7f62aaef 721 *
7f62aaef 722 * @since 1.2.3
d930613a 723 * @return string The path, filename and any arguments for the
724 * current script
7f62aaef 725 */
adc3ea74 726function php_self($with_query_string=TRUE) {
d930613a 727
adc3ea74 728 static $request_uri = '';
729 if (!empty($request_uri))
730 return ($with_query_string ? $request_uri : (strpos($request_uri, '?') !== FALSE ? substr($request_uri, 0, strpos($request_uri, '?')) : $request_uri));
7f62aaef 731
d930613a 732 // first try $_SERVER['PHP_SELF'], which seems most reliable
733 // (albeit it usually won't include the query string)
734 //
735 $request_uri = '';
736 if (!sqgetGlobalVar('PHP_SELF', $request_uri, SQ_SERVER)
737 || empty($request_uri)) {
39790db7 738
d930613a 739 // well, then let's try $_SERVER['REQUEST_URI']
39790db7 740 //
d930613a 741 $request_uri = '';
742 if (!sqgetGlobalVar('REQUEST_URI', $request_uri, SQ_SERVER)
743 || empty($request_uri)) {
744
745 // TODO: anyone have any other ideas? maybe $_SERVER['SCRIPT_NAME']???
746 //
747 return '';
39790db7 748 }
7f62aaef 749
39790db7 750 }
7f62aaef 751
d930613a 752 // we may or may not have any query arguments, depending on
753 // which environment variable was used above, and the PHP
754 // version, etc., so let's check for it now
755 //
756 $query_string = '';
757 if (strpos($request_uri, '?') === FALSE
758 && sqgetGlobalVar('QUERY_STRING', $query_string, SQ_SERVER)
759 && !empty($query_string)) {
39790db7 760
d930613a 761 $request_uri .= '?' . $query_string;
762 }
39790db7 763
adc3ea74 764 global $php_self_pattern, $php_self_replacement;
765 if (!empty($php_self_pattern))
766 $request_uri = preg_replace($php_self_pattern, $php_self_replacement, $request_uri);
767 return ($with_query_string ? $request_uri : (strpos($request_uri, '?') !== FALSE ? substr($request_uri, 0, strpos($request_uri, '?')) : $request_uri));
7f62aaef 768
7f62aaef 769}
aa201211 770
771
aa201211 772/**
773 * Print variable
774 *
775 * sm_print_r($some_variable, [$some_other_variable [, ...]]);
776 *
777 * Debugging function - does the same as print_r, but makes sure special
778 * characters are converted to htmlentities first. This will allow
779 * values like <some@email.address> to be displayed.
780 * The output is wrapped in <<pre>> and <</pre>> tags.
781 * Since 1.4.2 accepts unlimited number of arguments.
782 * @since 1.4.1
783 * @return void
784 */
785function sm_print_r() {
786 ob_start(); // Buffer output
787 foreach(func_get_args() as $var) {
788 print_r($var);
789 echo "\n";
790 // php has get_class_methods function that can print class methods
791 if (is_object($var)) {
792 // get class methods if $var is object
793 $aMethods=get_class_methods(get_class($var));
794 // make sure that $aMethods is array and array is not empty
795 if (is_array($aMethods) && $aMethods!=array()) {
796 echo "Object methods:\n";
797 foreach($aMethods as $method) {
798 echo '* ' . $method . "\n";
799 }
800 }
801 echo "\n";
802 }
803 }
804 $buffer = ob_get_contents(); // Grab the print_r output
805 ob_end_clean(); // Silently discard the output & stop buffering
806 print '<div align="left"><pre>';
807 print htmlentities($buffer);
808 print '</pre></div>';
809}
45ca6962 810
811
253ca97e 812/**
3047e291 813 * Sanitize a value using sm_encode_html_special_chars() or similar, but also
814 * recursively run sm_encode_html_special_chars() (or similar) on array keys
253ca97e 815 * and values.
816 *
817 * If $value is not a string or an array with strings in it,
818 * the value is returned as is.
819 *
820 * @param mixed $value The value to be sanitized.
821 * @param mixed $quote_style Either boolean or an integer. If it
822 * is an integer, it must be the PHP
823 * constant indicating if/how to escape
824 * quotes: ENT_QUOTES, ENT_COMPAT, or
825 * ENT_NOQUOTES. If it is a boolean value,
826 * it must be TRUE and thus indicates
827 * that the only sanitizing to be done
828 * herein is to replace single and double
829 * quotes with &#039; and &quot;, no other
830 * changes are made to $value. If it is
831 * boolean and FALSE, behavior reverts
832 * to same as if the value was ENT_QUOTES
833 * (OPTIONAL; default is ENT_QUOTES).
834 *
835 * @return mixed The sanitized value.
836 *
837 * @since 1.5.2
838 *
839 **/
840function sq_htmlspecialchars($value, $quote_style=ENT_QUOTES) {
841
842 if ($quote_style === FALSE) $quote_style = ENT_QUOTES;
843
844 // array? go recursive...
845 //
846 if (is_array($value)) {
847 $return_array = array();
848 foreach ($value as $key => $val) {
849 $return_array[sq_htmlspecialchars($key, $quote_style)]
850 = sq_htmlspecialchars($val, $quote_style);
851 }
852 return $return_array;
853
854 // sanitize strings only
855 //
856 } else if (is_string($value)) {
857 if ($quote_style === TRUE)
858 return str_replace(array('\'', '"'), array('&#039;', '&quot;'), $value);
859 else
3047e291 860 return sm_encode_html_special_chars($value, $quote_style);
253ca97e 861 }
862
863 // anything else gets returned with no changes
864 //
865 return $value;
866
867}
67c826ce 868
869
8f557b94 870/**
871 * Detect whether or not we have a SSL secured (HTTPS) connection
872 * connection to the browser
873 *
874 * It is thought to be so if you have 'SSLOptions +StdEnvVars'
875 * in your Apache configuration,
876 * OR if you have HTTPS set to a non-empty value (except "off")
877 * in your HTTP_SERVER_VARS,
878 * OR if you have HTTP_X_FORWARDED_PROTO=https in your HTTP_SERVER_VARS,
879 * OR if you are on port 443.
880 *
881 * Note: HTTP_X_FORWARDED_PROTO could be sent from the client and
882 * therefore possibly spoofed/hackable. Thus, SquirrelMail
883 * ignores such headers by default. The administrator
884 * can tell SM to use such header values by setting
885 * $sq_ignore_http_x_forwarded_headers to boolean FALSE
886 * in config/config.php or by using config/conf.pl.
887 *
888 * Note: It is possible to run SSL on a port other than 443, and
889 * if that is the case, the administrator should set
890 * $sq_https_port in config/config.php or by using config/conf.pl.
891 *
892 * @return boolean TRUE if the current connection is SSL-encrypted;
893 * FALSE otherwise.
894 *
895 * @since 1.4.17 and 1.5.2
896 *
897 */
898function is_ssl_secured_connection()
899{
900 global $sq_ignore_http_x_forwarded_headers, $sq_https_port;
901 $https_env_var = getenv('HTTPS');
902 if ($sq_ignore_http_x_forwarded_headers
903 || !sqgetGlobalVar('HTTP_X_FORWARDED_PROTO', $forwarded_proto, SQ_SERVER))
904 $forwarded_proto = '';
905 if (empty($sq_https_port)) // won't work with port 0 (zero)
906 $sq_https_port = 443;
907 if ((isset($https_env_var) && strcasecmp($https_env_var, 'on') === 0)
908 || (sqgetGlobalVar('HTTPS', $https, SQ_SERVER) && !empty($https)
909 && strcasecmp($https, 'off') !== 0)
910 || (strcasecmp($forwarded_proto, 'https') === 0)
911 || (sqgetGlobalVar('SERVER_PORT', $server_port, SQ_SERVER)
912 && $server_port == $sq_https_port))
913 return TRUE;
914 return FALSE;
915}
916
917
e45a534b 918/**
919 * Endeavor to detect what user and group PHP is currently
920 * running as. Probably only works in non-Windows environments.
921 *
922 * @return mixed Boolean FALSE is returned if something went wrong,
923 * otherwise an array is returned with the following
924 * elements:
925 * uid The current process' UID (integer)
926 * euid The current process' effective UID (integer)
927 * gid The current process' GID (integer)
928 * egid The current process' effective GID (integer)
929 * name The current process' name/handle (string)
930 * ename The current process' effective name/handle (string)
931 * group The current process' group name (string)
932 * egroup The current process' effective group name (string)
933 * Note that some of these elements may have empty
934 * values, especially if they could not be determined.
935 *
936 * @since 1.5.2
937 *
938 */
939function get_process_owner_info()
940{
941 if (!function_exists('posix_getuid'))
942 return FALSE;
943
944 $process_info['uid'] = posix_getuid();
945 $process_info['euid'] = posix_geteuid();
946 $process_info['gid'] = posix_getgid();
947 $process_info['egid'] = posix_getegid();
948
949 $user_info = posix_getpwuid($process_info['uid']);
950 $euser_info = posix_getpwuid($process_info['euid']);
951 $group_info = posix_getgrgid($process_info['gid']);
952 $egroup_info = posix_getgrgid($process_info['egid']);
953
954 $process_info['name'] = $user_info['name'];
955 $process_info['ename'] = $euser_info['name'];
956 $process_info['group'] = $user_info['name'];
957 $process_info['egroup'] = $euser_info['name'];
958
959 return $process_info;
960}
961
962