acctfuncs.inc.php 42.6 KB
Newer Older
1
<?php
2
3
4
5
6
7
8
/**
 * Determine if an HTTP request variable is set
 *
 * @param string $name The request variable to test for
 *
 * @return string Return the value of the request variable, otherwise blank
 */
Dan McGee's avatar
Dan McGee committed
9
10
11
12
13
14
15
function in_request($name) {
	if (isset($_REQUEST[$name])) {
		return $_REQUEST[$name];
	}
	return "";
}

16
17
18
19
20
21
22
/**
 * Format the PGP key fingerprint
 *
 * @param string $fingerprint An unformatted PGP key fingerprint
 *
 * @return string PGP fingerprint with spaces every 4 characters
 */
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function html_format_pgp_fingerprint($fingerprint) {
	if (strlen($fingerprint) != 40 || !ctype_xdigit($fingerprint)) {
		return $fingerprint;
	}

	return htmlspecialchars(substr($fingerprint, 0, 4) . " " .
		substr($fingerprint, 4, 4) . " " .
		substr($fingerprint, 8, 4) . " " .
		substr($fingerprint, 12, 4) . " " .
		substr($fingerprint, 16, 4) . "  " .
		substr($fingerprint, 20, 4) . " " .
		substr($fingerprint, 24, 4) . " " .
		substr($fingerprint, 28, 4) . " " .
		substr($fingerprint, 32, 4) . " " .
		substr($fingerprint, 36, 4) . " ", ENT_QUOTES);
}

40
41
42
43
44
45
46
47
48
/**
 * Loads the account editing form, with any values that are already saved
 *
 * @global array $SUPPORTED_LANGS Languages that are supported by the AUR
 * @param string $A Form to use, either UpdateAccount or NewAccount
 * @param string $U The username to display
 * @param string $T The account type of the displayed user
 * @param string $S Whether the displayed user has a suspended account
 * @param string $E The e-mail address of the displayed user
49
 * @param string $H Whether the e-mail address of the displayed user is hidden
50
51
52
53
 * @param string $P The password value of the displayed user
 * @param string $C The confirmed password value of the displayed user
 * @param string $R The real name of the displayed user
 * @param string $L The language preference of the displayed user
Mark Weiman's avatar
Mark Weiman committed
54
 * @param string $TZ The timezone preference of the displayed user
55
 * @param string $HP The homepage of the displayed user
56
57
 * @param string $I The IRC nickname of the displayed user
 * @param string $K The PGP key fingerprint of the displayed user
58
 * @param string $PK The list of SSH public keys
59
 * @param string $J The inactivity status of the displayed user
60
 * @param string $CN Whether to notify of new comments
61
 * @param string $UN Whether to notify of package updates
62
 * @param string $ON Whether to notify of ownership changes
63
 * @param string $UID The user ID of the displayed user
64
 * @param string $N The username as present in the database
65
66
 * @param string $captcha_salt The salt used for the CAPTCHA.
 * @param string $captcha The CAPTCHA answer.
67
68
69
 *
 * @return void
 */
70
function display_account_form($A,$U="",$T="",$S="",$E="",$H="",$P="",$C="",$R="",
71
		$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="",$captcha_salt="",$captcha="") {
72
73
	global $SUPPORTED_LANGS;

Mark Weiman's avatar
Mark Weiman committed
74
75
76
77
	if ($TZ == "") {
		$TZ = config_get("options", "default_timezone");
	}

78
79
	if (!in_array($captcha_salt, get_captcha_salts())) {
		$captcha_salt = get_captcha_salts()[0];
80
81
82
83
		$captcha = "";
	}
	$captcha_challenge = get_captcha_challenge($captcha_salt);

84
	include("account_edit_form.php");
85
	return;
86
}
87

88
89
90
91
92
93
94
95
96
97
/**
 * Process information given to new/edit account form
 *
 * @global array $SUPPORTED_LANGS Languages that are supported by the AUR
 * @param string $TYPE Either "edit" for editing or "new" for registering an account
 * @param string $A Form to use, either UpdateAccount or NewAccount
 * @param string $U The username for the account
 * @param string $T The account type for the user
 * @param string $S Whether or not the account is suspended
 * @param string $E The e-mail address for the user
98
 * @param string $H Whether or not the e-mail address should be hidden
99
 * @param string $PO The old password of the user
100
101
102
103
 * @param string $P The password for the user
 * @param string $C The confirmed password for the user
 * @param string $R The real name of the user
 * @param string $L The language preference of the user
Mark Weiman's avatar
Mark Weiman committed
104
 * @param string $TZ The timezone preference of the user
105
 * @param string $HP The homepage of the displayed user
106
107
 * @param string $I The IRC nickname of the user
 * @param string $K The PGP fingerprint of the user
108
 * @param string $PK The list of public SSH keys
109
 * @param string $J The inactivity status of the user
110
 * @param string $CN Whether to notify of new comments
111
 * @param string $UN Whether to notify of package updates
112
 * @param string $ON Whether to notify of ownership changes
113
 * @param string $UID The user ID of the modified account
114
 * @param string $N The username as present in the database
115
116
 * @param string $captcha_salt The salt used for the CAPTCHA.
 * @param string $captcha The CAPTCHA answer.
117
 *
118
 * @return array Boolean indicating success and message to be printed
119
 */
120
function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$H="",$PO="",$P="",$C="",
121
		$R="",$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="",$captcha_salt="",$captcha="") {
122
	global $SUPPORTED_LANGS;
123

124
	$error = '';
125
	$message = '';
126
127
128
129
130
131
132
133

	if (is_ipbanned()) {
		$error = __('Account registration has been disabled ' .
					'for your IP address, probably due ' .
					'to sustained spam attacks. Sorry for the ' .
					'inconvenience.');
	}

134
	$dbh = DB::connect();
135

136
	if(isset($_COOKIE['AURSID'])) {
137
138
139
		$uid_session = uid_from_sid($_COOKIE['AURSID']);
	} else {
		$uid_session = null;
140
	}
141
142

	if (empty($E) || empty($U)) {
143
144
		$error = __("Missing a required field.");
	}
145

146
147
	if ($TYPE != "new" && !$UID) {
		$error = __("Missing User ID");
148
	}
149

150
	if (!$error && !valid_username($U)) {
151
152
153
		$length_min = config_get_int('options', 'username_min_len');
		$length_max = config_get_int('options', 'username_max_len');

154
		$error = __("The username is invalid.") . "<ul>\n"
155
			. "<li>" . __("It must be between %s and %s characters long", $length_min, $length_max)
156
			. "</li>"
157
			. "<li>" . __("Start and end with a letter or number") . "</li>"
158
			. "<li>" . __("Can contain only one period, underscore or hyphen.")
159
			. "</li>\n</ul>";
160
	}
161

162
163
164
165
166
167
168
	if (!$error && $P && !$C) {
		$error = __("Please confirm your new password.");
	}
	if (!$error && $P && !$PO) {
		$error = __("Please enter your old password in order to set a new one.");
	}
	if (!$error && $P && $P != $C) {
169
170
		$error = __("Password fields do not match.");
	}
171
	if (!$error && $P && check_passwd($uid_session, $PO) != 1) {
172
173
		$error = __("The old password is invalid.");
	}
174
175
176
177
178
	if (!$error && $P != '' && !good_passwd($P)) {
		$length_min = config_get_int('options', 'passwd_min_len');
		$error = __("Your password must be at least %s characters.",
			$length_min);
	}
179

180
181
182
	if (!$error && !valid_email($E)) {
		$error = __("The email address is invalid.");
	}
183

184
	if (!$error && !empty($HP) && !valid_homepage($HP)) {
185
186
187
		$error = __("The home page is invalid, please specify the full HTTP(s) URL.");
	}

188
189
190
191
	if (!$error && $K != '' && !valid_pgp_fingerprint($K)) {
		$error = __("The PGP key fingerprint is invalid.");
	}

192
	if (!$error && !empty($PK)) {
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
		$ssh_keys = array_filter(array_map('trim', explode("\n", $PK)));
		$ssh_fingerprints = array();

		foreach ($ssh_keys as &$ssh_key) {
			if (!valid_ssh_pubkey($ssh_key)) {
				$error = __("The SSH public key is invalid.");
				break;
			}

			$ssh_fingerprint = ssh_key_fingerprint($ssh_key);
			if (!$ssh_fingerprint) {
				$error = __("The SSH public key is invalid.");
				break;
			}

			$tokens = explode(" ", $ssh_key);
			$ssh_key = $tokens[0] . " " . $tokens[1];

			$ssh_fingerprints[] = $ssh_fingerprint;
212
		}
213
214
215
216
217
218

		/*
		 * Destroy last reference to prevent accidentally overwriting
		 * an array element.
		 */
		unset($ssh_key);
219
220
	}

221
222
223
224
225
	if (isset($_COOKIE['AURSID'])) {
		$atype = account_from_sid($_COOKIE['AURSID']);
		if (($atype == "User" && $T > 1) || ($atype == "Trusted User" && $T > 2)) {
			$error = __("Cannot increase account permissions.");
		}
eric's avatar
eric committed
226
	}
227

228
229
230
	if (!$error && !array_key_exists($L, $SUPPORTED_LANGS)) {
		$error = __("Language is not currently supported.");
	}
Mark Weiman's avatar
Mark Weiman committed
231
232
233
	if (!$error && !array_key_exists($TZ, generate_timezone_list())) {
		$error = __("Timezone is not currently supported.");
	}
234
	if (!$error) {
235
236
237
238
		/*
		 * Check whether the user name is available.
		 * TODO: Fix race condition.
		 */
239
		$q = "SELECT COUNT(*) AS CNT FROM Users ";
canyonknight's avatar
canyonknight committed
240
		$q.= "WHERE Username = " . $dbh->quote($U);
eric's avatar
eric committed
241
242
243
		if ($TYPE == "edit") {
			$q.= " AND ID != ".intval($UID);
		}
canyonknight's avatar
canyonknight committed
244
245
246
247
248
		$result = $dbh->query($q);
		$row = $result->fetch(PDO::FETCH_NUM);

		if ($row[0]) {
			$error = __("The username, %s%s%s, is already in use.",
Lukas Fleischer's avatar
Lukas Fleischer committed
249
				"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
250
251
252
		}
	}
	if (!$error) {
253
254
255
256
		/*
		 * Check whether the e-mail address is available.
		 * TODO: Fix race condition.
		 */
257
		$q = "SELECT COUNT(*) AS CNT FROM Users ";
canyonknight's avatar
canyonknight committed
258
		$q.= "WHERE Email = " . $dbh->quote($E);
eric's avatar
eric committed
259
260
261
		if ($TYPE == "edit") {
			$q.= " AND ID != ".intval($UID);
		}
canyonknight's avatar
canyonknight committed
262
263
264
265
266
		$result = $dbh->query($q);
		$row = $result->fetch(PDO::FETCH_NUM);

		if ($row[0]) {
			$error = __("The address, %s%s%s, is already in use.",
Lukas Fleischer's avatar
Lukas Fleischer committed
267
					"<strong>", htmlspecialchars($E,ENT_QUOTES), "</strong>");
268
269
		}
	}
270
	if (!$error && isset($ssh_keys) && count($ssh_keys) > 0) {
271
		/*
272
		 * Check whether any of the SSH public keys is already in use.
273
274
		 * TODO: Fix race condition.
		 */
275
276
277
278
		$q = "SELECT Fingerprint FROM SSHPubKeys ";
		$q.= "WHERE Fingerprint IN (";
		$q.= implode(',', array_map(array($dbh, 'quote'), $ssh_fingerprints));
		$q.= ")";
279
		if ($TYPE == "edit") {
280
			$q.= " AND UserID != " . intval($UID);
281
282
283
284
		}
		$result = $dbh->query($q);
		$row = $result->fetch(PDO::FETCH_NUM);

285
		if ($row) {
286
			$error = __("The SSH public key, %s%s%s, is already in use.",
287
					"<strong>", htmlspecialchars($row[0], ENT_QUOTES), "</strong>");
288
289
		}
	}
290

291
292
293
294
	if (!$error && $TYPE == "new" && empty($captcha)) {
		$error = __("The CAPTCHA is missing.");
	}

295
	if (!$error && $TYPE == "new" && !in_array($captcha_salt, get_captcha_salts())) {
296
297
298
299
300
301
302
		$error = __("This CAPTCHA has expired. Please try again.");
	}

	if (!$error && $TYPE == "new" && $captcha != get_captcha_answer($captcha_salt)) {
		$error = __("The entered CAPTCHA answer is invalid.");
	}

303
	if ($error) {
304
305
		$message = "<ul class='errorlist'><li>".$error."</li></ul>\n";
		return array(false, $message);
306
307
308
309
310
311
312
313
314
	}

	if ($TYPE == "new") {
		/* Create an unprivileged user. */
		if (empty($P)) {
			$send_resetkey = true;
			$email = $E;
		} else {
			$send_resetkey = false;
Lukas Fleischer's avatar
Lukas Fleischer committed
315
			$P = password_hash($P, PASSWORD_DEFAULT);
316
317
318
319
320
321
		}
		$U = $dbh->quote($U);
		$E = $dbh->quote($E);
		$P = $dbh->quote($P);
		$R = $dbh->quote($R);
		$L = $dbh->quote($L);
Mark Weiman's avatar
Mark Weiman committed
322
		$TZ = $dbh->quote($TZ);
323
		$HP = $dbh->quote($HP);
324
325
326
		$I = $dbh->quote($I);
		$K = $dbh->quote(str_replace(" ", "", $K));
		$q = "INSERT INTO Users (AccountTypeID, Suspended, ";
Lukas Fleischer's avatar
Lukas Fleischer committed
327
		$q.= "InactivityTS, Username, Email, Passwd , ";
Mark Weiman's avatar
Mark Weiman committed
328
		$q.= "RealName, LangPreference, Timezone, Homepage, IRCNick, PGPKey) ";
329
		$q.= "VALUES (1, 0, 0, $U, $E, $P, $R, $L, $TZ, ";
330
		$q.= "$HP, $I, $K)";
331
332
		$result = $dbh->exec($q);
		if (!$result) {
333
			$message = __("Error trying to create account, %s%s%s.",
334
					"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
335
			return array(false, $message);
336
337
		}

338
		$uid = $dbh->lastInsertId();
339
340
341
		if (isset($ssh_keys) && count($ssh_keys) > 0) {
			account_set_ssh_keys($uid, $ssh_keys, $ssh_fingerprints);
		}
342

343
		$message = __("The account, %s%s%s, has been successfully created.",
344
				"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
345
		$message .= "<p>\n";
346

347
348
		if ($send_resetkey) {
			send_resetkey($email, true);
349
350
			$message .= __("A password reset key has been sent to your e-mail address.");
			$message .= "</p>\n";
351
		} else {
352
353
			$message .= __("Click on the Login link above to use your account.");
			$message .= "</p>\n";
354
		}
355
	} else {
356
357
358
359
360
361
362
363
364
365
366
367
		/* Modify an existing account. */
		$q = "SELECT InactivityTS FROM Users WHERE ";
		$q.= "ID = " . intval($UID);
		$result = $dbh->query($q);
		$row = $result->fetch(PDO::FETCH_NUM);
		if ($row[0] && $J) {
			$inactivity_ts = $row[0];
		} elseif ($J) {
			$inactivity_ts = time();
		} else {
			$inactivity_ts = 0;
		}
368

369
370
371
372
373
374
375
376
377
		$q = "UPDATE Users SET ";
		$q.= "Username = " . $dbh->quote($U);
		if ($T) {
			$q.= ", AccountTypeID = ".intval($T);
		}
		if ($S) {
			/* Ensure suspended users can't keep an active session */
			delete_user_sessions($UID);
			$q.= ", Suspended = 1";
378
		} else {
379
380
381
			$q.= ", Suspended = 0";
		}
		$q.= ", Email = " . $dbh->quote($E);
382
383
384
385
386
		if ($H) {
			$q.= ", HideEmail = 1";
		} else {
			$q.= ", HideEmail = 0";
		}
387
		if ($P) {
Lukas Fleischer's avatar
Lukas Fleischer committed
388
389
			$hash = password_hash($P, PASSWORD_DEFAULT);
			$q .= ", Passwd = " . $dbh->quote($hash);
390
391
392
		}
		$q.= ", RealName = " . $dbh->quote($R);
		$q.= ", LangPreference = " . $dbh->quote($L);
Mark Weiman's avatar
Mark Weiman committed
393
		$q.= ", Timezone = " . $dbh->quote($TZ);
394
		$q.= ", Homepage = " . $dbh->quote($HP);
395
396
397
		$q.= ", IRCNick = " . $dbh->quote($I);
		$q.= ", PGPKey = " . $dbh->quote(str_replace(" ", "", $K));
		$q.= ", InactivityTS = " . $inactivity_ts;
398
		$q.= ", CommentNotify = " . ($CN ? "1" : "0");
399
		$q.= ", UpdateNotify = " . ($UN ? "1" : "0");
400
		$q.= ", OwnershipNotify = " . ($ON ? "1" : "0");
401
402
		$q.= " WHERE ID = ".intval($UID);
		$result = $dbh->exec($q);
403

404
405
406
407
408
		if (isset($ssh_keys) && count($ssh_keys) > 0) {
			$ssh_key_result = account_set_ssh_keys($UID, $ssh_keys, $ssh_fingerprints);
		} else {
			$ssh_key_result = true;
		}
409

Mark Weiman's avatar
Mark Weiman committed
410
411
412
413
414
415
416
		if (isset($_COOKIE["AURTZ"]) && ($_COOKIE["AURTZ"] != $TZ)) {
			/* set new cookie for timezone */
			$timeout = intval(config_get("options", "persistent_cookie_timeout"));
			$cookie_time = time() + $timeout;
			setcookie("AURTZ", $TZ, $cookie_time, "/");
		}

417
418
419
420
421
422
423
		if (isset($_COOKIE["AURLANG"]) && ($_COOKIE["AURLANG"] != $L)) {
			/* set new cookie for language */
			$timeout = intval(config_get("options", "persistent_cookie_timeout"));
			$cookie_time = time() + $timeout;
			setcookie("AURLANG", $L, $cookie_time, "/");
		}

424
		if ($result === false || $ssh_key_result === false) {
425
			$message = __("No changes were made to the account, %s%s%s.",
426
427
					"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
		} else {
428
			$message = __("The account, %s%s%s, has been successfully modified.",
429
					"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
430
431
		}
	}
432
433

	return array(true, $message);
434
435
}

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/**
 * Display the search results page
 *
 * @param string $O The offset for the results page
 * @param string $SB The column to sort the results page by
 * @param string $U The username search criteria
 * @param string $T The account type search criteria
 * @param string $S Whether the account is suspended search criteria
 * @param string $E The e-mail address search criteria
 * @param string $R The real name search criteria
 * @param string $I The IRC nickname search criteria
 * @param string $K The PGP key fingerprint search criteria
 *
 * @return void
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
451
function search_results_page($O=0,$SB="",$U="",$T="",
452
		$S="",$E="",$R="",$I="",$K="") {
453
454
455
456
457
458
459
460
461
462
463
464

	$HITS_PER_PAGE = 50;
	if ($O) {
		$OFFSET = intval($O);
	} else {
		$OFFSET = 0;
	}
	if ($OFFSET < 0) {
		$OFFSET = 0;
	}
	$search_vars = array();

465
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
466

467
468
469
470
471
472
473
474
475
476
477
478
	$q = "SELECT Users.*, AccountTypes.AccountType ";
	$q.= "FROM Users, AccountTypes ";
	$q.= "WHERE AccountTypes.ID = Users.AccountTypeID ";
	if ($T == "u") {
		$q.= "AND AccountTypes.ID = 1 ";
		$search_vars[] = "T";
	} elseif ($T == "t") {
		$q.= "AND AccountTypes.ID = 2 ";
		$search_vars[] = "T";
	} elseif ($T == "d") {
		$q.= "AND AccountTypes.ID = 3 ";
		$search_vars[] = "T";
479
480
481
	} elseif ($T == "td") {
		$q.= "AND AccountTypes.ID = 4 ";
		$search_vars[] = "T";
482
483
484
485
486
487
	}
	if ($S) {
		$q.= "AND Users.Suspended = 1 ";
		$search_vars[] = "S";
	}
	if ($U) {
canyonknight's avatar
canyonknight committed
488
489
		$U = "%" . addcslashes($U, '%_') . "%";
		$q.= "AND Username LIKE " . $dbh->quote($U) . " ";
490
491
492
		$search_vars[] = "U";
	}
	if ($E) {
canyonknight's avatar
canyonknight committed
493
494
		$E = "%" . addcslashes($E, '%_') . "%";
		$q.= "AND Email LIKE " . $dbh->quote($E) . " ";
495
496
497
		$search_vars[] = "E";
	}
	if ($R) {
canyonknight's avatar
canyonknight committed
498
499
		$R = "%" . addcslashes($R, '%_') . "%";
		$q.= "AND RealName LIKE " . $dbh->quote($R) . " ";
500
501
502
		$search_vars[] = "R";
	}
	if ($I) {
canyonknight's avatar
canyonknight committed
503
504
		$I = "%" . addcslashes($I, '%_') . "%";
		$q.= "AND IRCNick LIKE " . $dbh->quote($I) . " ";
505
506
		$search_vars[] = "I";
	}
507
	if ($K) {
canyonknight's avatar
canyonknight committed
508
509
		$K = "%" . addcslashes(str_replace(" ", "", $K), '%_') . "%";
		$q.= "AND PGPKey LIKE " . $dbh->quote($K) . " ";
510
511
		$search_vars[] = "K";
	}
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
	switch ($SB) {
		case 't':
			$q.= "ORDER BY AccountTypeID, Username ";
			break;
		case 'r':
			$q.= "ORDER BY RealName, AccountTypeID ";
			break;
		case 'i':
			$q.= "ORDER BY IRCNick, AccountTypeID ";
			break;
		default:
			$q.= "ORDER BY Username, AccountTypeID ";
			break;
	}
	$search_vars[] = "SB";
527
	$q.= "LIMIT " . $HITS_PER_PAGE . " OFFSET " . $OFFSET;
528

529
	$dbh = DB::connect();
530

canyonknight's avatar
canyonknight committed
531
	$result = $dbh->query($q);
532

533
534
535
536
537
	$userinfo = array();
	if ($result) {
		while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
			$userinfo[] = $row;
		}
538
539
	}

540
	include("account_search_results.php");
541
542
543
	return;
}

544
545
546
547
/**
 * Attempt to login and generate a session
 *
 * @return array Session ID for user, error message if applicable
548
 */
549
function try_login() {
Loui Chang's avatar
Loui Chang committed
550
	$login_error = "";
551
552
553
	$new_sid = "";
	$userID = null;

554
555
556
557
558
559
560
561
562
563
564
	if (!isset($_REQUEST['user']) && !isset($_REQUEST['passwd'])) {
		return array('SID' => '', 'error' => null);
	}

	if (is_ipbanned()) {
		$login_error = __('The login form is currently disabled ' .
						'for your IP address, probably due ' .
						'to sustained spam attacks. Sorry for the ' .
						'inconvenience.');
		return array('SID' => '', 'error' => $login_error);
	}
565

566
	$dbh = DB::connect();
567
	$userID = uid_from_loginname($_REQUEST['user']);
568
569
570
571

	if (user_suspended($userID)) {
		$login_error = __('Account suspended');
		return array('SID' => '', 'error' => $login_error);
Lukas Fleischer's avatar
Lukas Fleischer committed
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
	}

	switch (check_passwd($userID, $_REQUEST['passwd'])) {
		case -1:
			$login_error = __('Your password has been reset. ' .
				'If you just created a new account, please ' .
				'use the link from the confirmation email ' .
				'to set an initial password. Otherwise, ' .
				'please request a reset key on the %s' .
				'Password Reset%s page.', '<a href="' .
				htmlspecialchars(get_uri('/passreset')) . '">',
				'</a>');
			return array('SID' => '', 'error' => $login_error);
		case 0:
			$login_error = __("Bad username or password.");
			return array('SID' => '', 'error' => $login_error);
		case 1:
			break;
590
591
592
593
594
595
596
	}

	$logged_in = 0;
	$num_tries = 0;

	/* Generate a session ID and store it. */
	while (!$logged_in && $num_tries < 5) {
597
598
		$session_limit = config_get_int('options', 'max_sessions_per_user');
		if ($session_limit) {
599
600
			/*
			 * Delete all user sessions except the
601
			 * last ($session_limit - 1).
602
603
604
605
606
			 */
			$q = "DELETE s.* FROM Sessions s ";
			$q.= "LEFT JOIN (SELECT SessionID FROM Sessions ";
			$q.= "WHERE UsersId = " . $userID . " ";
			$q.= "ORDER BY LastUpdateTS DESC ";
607
			$q.= "LIMIT " . ($session_limit - 1) . ") q ";
608
609
610
611
			$q.= "ON s.SessionID = q.SessionID ";
			$q.= "WHERE s.UsersId = " . $userID . " ";
			$q.= "AND q.SessionID IS NULL;";
			$dbh->query($q);
612
		}
613
614
615

		$new_sid = new_sid();
		$q = "INSERT INTO Sessions (UsersID, SessionID, LastUpdateTS)"
616
		  ." VALUES (" . $userID . ", '" . $new_sid . "', " . strval(time()) . ")";
617
618
619
620
621
622
		$result = $dbh->exec($q);

		/* Query will fail if $new_sid is not unique. */
		if ($result) {
			$logged_in = 1;
			break;
623
		}
624
625
626
627
628
629
630

		$num_tries++;
	}

	if (!$logged_in) {
		$login_error = __('An error occurred trying to generate a user session.');
		return array('SID' => $new_sid, 'error' => $login_error);
631
	}
632

633
	$q = "UPDATE Users SET LastLogin = " . strval(time()) . ", ";
634
635
	$q.= "LastLoginIPAddress = " . $dbh->quote($_SERVER['REMOTE_ADDR']) . " ";
	$q.= "WHERE ID = $userID";
636
637
638
639
640
	$dbh->exec($q);

	/* Set the SID cookie. */
	if (isset($_POST['remember_me']) && $_POST['remember_me'] == "on") {
		/* Set cookies for 30 days. */
641
642
		$timeout = config_get_int('options', 'persistent_cookie_timeout');
		$cookie_time = time() + $timeout;
643
644
645
646
647
648
649
650
651
652

		/* Set session for 30 days. */
		$q = "UPDATE Sessions SET LastUpdateTS = $cookie_time ";
		$q.= "WHERE SessionID = '$new_sid'";
		$dbh->exec($q);
	} else {
		$cookie_time = 0;
	}

	setcookie("AURSID", $new_sid, $cookie_time, "/", null, !empty($_SERVER['HTTPS']), true);
653
654
655
656
657
658

	$referer = in_request('referer');
	if (strpos($referer, aur_location()) !== 0) {
		$referer = '/';
	}
	header("Location: " . get_uri($referer));
659
	$login_error = "";
660
661
}

662
663
664
665
666
667
668
669
/**
 * Determine if the user is using a banned IP address
 *
 * @return bool True if IP address is banned, otherwise false
 */
function is_ipbanned() {
	$dbh = DB::connect();

670
	$q = "SELECT * FROM Bans WHERE IPAddress = " . $dbh->quote($_SERVER['REMOTE_ADDR']);
671
672
	$result = $dbh->query($q);

673
	return ($result->fetchColumn() ? true : false);
674
675
}

676
677
678
/**
 * Validate a username against a collection of rules
 *
679
680
681
682
 * The username must be longer or equal to the configured minimum length. It
 * must be shorter or equal to the configured maximum length. It must start and
 * end with either a letter or a number. It can contain one period, hypen, or
 * underscore. Returns boolean of whether name is valid.
683
684
685
 *
 * @param string $user Username to validate
 *
686
 * @return bool True if username meets criteria, otherwise false
687
 */
688
function valid_username($user) {
689
690
691
692
	$length_min = config_get_int('options', 'username_min_len');
	$length_max = config_get_int('options', 'username_max_len');

	if (strlen($user) < $length_min || strlen($user) > $length_max) {
693
		return false;
694
	} else if (!preg_match("/^[a-z0-9]+[.\-_]?[a-z0-9]+$/Di", $user)) {
695
		return false;
696
	}
697

698
	return true;
699
700
}

701
702
703
704
705
706
707
/**
 * Determine if a user already has a proposal open about themselves
 *
 * @param string $user Username to checkout for open proposal
 *
 * @return bool True if there is an open proposal about the user, otherwise false
 */
708
function open_user_proposals($user) {
709
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
710
	$q = "SELECT * FROM TU_VoteInfo WHERE User = " . $dbh->quote($user) . " ";
711
	$q.= "AND End > " . strval(time());
canyonknight's avatar
canyonknight committed
712
	$result = $dbh->query($q);
713
714

	return ($result->fetchColumn() ? true : false);
canyonknight's avatar
canyonknight committed
715
716
}

717
718
719
720
721
722
723
724
725
726
/**
 * Add a new Trusted User proposal to the database
 *
 * @param string $agenda The agenda of the vote
 * @param string $user The use the vote is about
 * @param int $votelength The length of time for the vote to last
 * @param string $submitteruid The user ID of the individual who submitted the proposal
 *
 * @return void
 */
727
function add_tu_proposal($agenda, $user, $votelength, $quorum, $submitteruid) {
728
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
729

730
	$q = "SELECT COUNT(*) FROM Users WHERE (AccountTypeID = 2 OR AccountTypeID = 4)";
731
732
733
734
	$result = $dbh->query($q);
	$row = $result->fetch(PDO::FETCH_NUM);
	$active_tus = $row[0];

735
	$q = "INSERT INTO TU_VoteInfo (Agenda, User, Submitted, End, Quorum, ";
736
	$q.= "SubmitterID, ActiveTUs) VALUES ";
canyonknight's avatar
canyonknight committed
737
	$q.= "(" . $dbh->quote($agenda) . ", " . $dbh->quote($user) . ", ";
738
	$q.= strval(time()) . ", " . strval(time()) . " + " . $dbh->quote($votelength);
739
740
	$q.= ", " . $dbh->quote($quorum) . ", " . $submitteruid . ", ";
	$q.= $active_tus . ")";
canyonknight's avatar
canyonknight committed
741
	$result = $dbh->exec($q);
canyonknight's avatar
canyonknight committed
742
743
}

744
745
746
747
748
749
750
751
/**
 * Add a reset key to the database for a specified user
 *
 * @param string $resetkey A password reset key to be stored in database
 * @param string $uid The user ID to store the reset key for
 *
 * @return void
 */
752
function create_resetkey($resetkey, $uid) {
753
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
754
755
756
	$q = "UPDATE Users ";
	$q.= "SET ResetKey = '" . $resetkey . "' ";
	$q.= "WHERE ID = " . $uid;
canyonknight's avatar
canyonknight committed
757
	$dbh->exec($q);
canyonknight's avatar
canyonknight committed
758
759
}

760
761
762
763
/**
 * Send a reset key to a specific e-mail address
 *
 * @param string $email E-mail address of the user resetting their password
764
 * @param bool $welcome Whether to use the welcome message
765
766
767
 *
 * @return void
 */
768
function send_resetkey($email, $welcome=false) {
769
	$uid = uid_from_email($email);
770
771
772
773
774
775
776
777
778
	if ($uid == null) {
		return;
	}

	/* We (ab)use new_sid() to get a random 32 characters long string. */
	$resetkey = new_sid();
	create_resetkey($resetkey, $uid);

	/* Send e-mail with confirmation link. */
779
	notify(array($welcome ? 'welcome' : 'send-resetkey', $uid));
780
781
}

782
783
784
/**
 * Change a user's password in the database if reset key and e-mail are correct
 *
Lukas Fleischer's avatar
Lukas Fleischer committed
785
 * @param string $password The new password
786
787
788
789
790
 * @param string $resetkey Code e-mailed to a user to reset a password
 * @param string $email E-mail address of the user resetting their password
 *
 * @return string|void Redirect page if successful, otherwise return error message
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
791
792
793
function password_reset($password, $resetkey, $email) {
	$hash = password_hash($password, PASSWORD_DEFAULT);

794
	$dbh = DB::connect();
Lukas Fleischer's avatar
Lukas Fleischer committed
795
796
	$q = "UPDATE Users SET ";
	$q.= "Passwd = " . $dbh->quote($hash) . ", ";
canyonknight's avatar
canyonknight committed
797
798
	$q.= "ResetKey = '' ";
	$q.= "WHERE ResetKey != '' ";
canyonknight's avatar
canyonknight committed
799
800
801
	$q.= "AND ResetKey = " . $dbh->quote($resetkey) . " ";
	$q.= "AND Email = " . $dbh->quote($email);
	$result = $dbh->exec($q);
canyonknight's avatar
canyonknight committed
802

canyonknight's avatar
canyonknight committed
803
	if (!$result) {
canyonknight's avatar
canyonknight committed
804
805
806
		$error = __('Invalid e-mail and reset key combination.');
		return $error;
	} else {
807
		header('Location: ' . get_uri('/passreset/') . '?step=complete');
canyonknight's avatar
canyonknight committed
808
809
810
811
		exit();
	}
}

812
813
814
815
816
817
818
/**
 * Determine if the password is longer than the minimum length
 *
 * @param string $passwd The password to check
 *
 * @return bool True if longer than minimum length, otherwise false
 */
819
function good_passwd($passwd) {
820
821
	$length_min = config_get_int('options', 'passwd_min_len');
	return (strlen($passwd) >= $length_min);
822
823
}

824
825
826
/**
 * Determine if the password is correct and salt it if it hasn't been already
 *
Lukas Fleischer's avatar
Lukas Fleischer committed
827
 * @param int $user_id The user ID to check the password against
828
829
 * @param string $passwd The password the visitor sent
 *
Lukas Fleischer's avatar
Lukas Fleischer committed
830
 * @return int Positive if password is correct, negative if password is unset
831
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
832
function check_passwd($user_id, $passwd) {
833
	$dbh = DB::connect();
834

Lukas Fleischer's avatar
Lukas Fleischer committed
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
	/* Get password hash and salt. */
	$q = "SELECT Passwd, Salt FROM Users WHERE ID = " . intval($user_id);
	$result = $dbh->query($q);
	if (!$result) {
		return 0;
	}
	$row = $result->fetch(PDO::FETCH_ASSOC);
	if (!$row) {
		return 0;
	}
	$hash = $row['Passwd'];
	$salt = $row['Salt'];
	if (!$hash) {
		return -1;
	}
850

Lukas Fleischer's avatar
Lukas Fleischer committed
851
852
853
854
855
	/* Verify the password hash. */
	if (!password_verify($passwd, $hash)) {
		/* Invalid password, fall back to MD5. */
		if (md5($salt . $passwd) != $hash) {
			return 0;
856
		}
857
858
	}

Lukas Fleischer's avatar
Lukas Fleischer committed
859
860
861
	/* Password correct, migrate the hash if necessary. */
	if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
		$hash = password_hash($passwd, PASSWORD_DEFAULT);
862

Lukas Fleischer's avatar
Lukas Fleischer committed
863
864
865
		$q = "UPDATE Users SET Passwd = " . $dbh->quote($hash) . " ";
		$q.= "WHERE ID = " . intval($user_id);
		$dbh->query($q);
866
	}
Lukas Fleischer's avatar
Lukas Fleischer committed
867
868

	return 1;
869
870
}

871
872
873
874
875
876
/**
 * Determine if the PGP key fingerprint is valid (must be 40 hexadecimal digits)
 *
 * @param string $fingerprint PGP fingerprint to check if valid
 *
 * @return bool True if the fingerprint is 40 hexadecimal digits, otherwise false
877
 */
878
function valid_pgp_fingerprint($fingerprint) {
879
880
	$fingerprint = str_replace(" ", "", $fingerprint);
	return (strlen($fingerprint) == 40 && ctype_xdigit($fingerprint));
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
}

/**
 * Determine if the SSH public key is valid
 *
 * @param string $pubkey SSH public key to check
 *
 * @return bool True if the SSH public key is valid, otherwise false
 */
function valid_ssh_pubkey($pubkey) {
	$valid_prefixes = array(
		"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256",
		"ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "ssh-ed25519"
	);

	$has_valid_prefix = false;
	foreach ($valid_prefixes as $prefix) {
		if (strpos($pubkey, $prefix . " ") === 0) {
			$has_valid_prefix = true;
			break;
		}
	}
	if (!$has_valid_prefix) {
		return false;
	}

	$tokens = explode(" ", $pubkey);
	if (empty($tokens[1])) {
		return false;
	}

	return (base64_encode(base64_decode($tokens[1], true)) == $tokens[1]);
913
914
}

915
916
917
918
919
920
/**
 * Determine if the user account has been suspended
 *
 * @param string $id The ID of user to check if suspended
 *
 * @return bool True if the user is suspended, otherwise false
921
 */
922
function user_suspended($id) {
923
	$dbh = DB::connect();
elij's avatar
elij committed
924
925
926
	if (!$id) {
		return false;
	}
927
	$q = "SELECT Suspended FROM Users WHERE ID = " . $id;
canyonknight's avatar
canyonknight committed
928
	$result = $dbh->query($q);
929
	if ($result) {
canyonknight's avatar
canyonknight committed
930
		$row = $result->fetch(PDO::FETCH_NUM);
931
		if ($row[0]) {
932
933
			return true;
		}
934
935
936
937
	}
	return false;
}

938
939
940
941
942
943
/**
 * Delete a specified user account from the database
 *
 * @param int $id The user ID of the account to be deleted
 *
 * @return void
944
 */
945
function user_delete($id) {
946
	$dbh = DB::connect();
947
948
949
950
951
952
953
954
955
	$id = intval($id);

	/*
	 * These are normally already taken care of by propagation constraints
	 * but it is better to be explicit here.
	 */
	$fields_delete = array(
		array("Sessions", "UsersID"),
		array("PackageVotes", "UsersID"),
956
		array("PackageNotifications", "UsersID")
957
958
959
960
961
	);

	$fields_set_null = array(
		array("PackageBases", "SubmitterUID"),
		array("PackageBases", "MaintainerUID"),
962
		array("PackageBases", "PackagerUID"),
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
		array("PackageComments", "UsersID"),
		array("PackageComments", "DelUsersID"),
		array("PackageRequests", "UsersID"),
		array("TU_VoteInfo", "SubmitterID"),
		array("TU_Votes", "UserID")
	);

	foreach($fields_delete as list($table, $field)) {
		$q = "DELETE FROM " . $table . " ";
		$q.= "WHERE " . $field . " = " . $id;
		$dbh->query($q);
	}

	foreach($fields_set_null as list($table, $field)) {
		$q = "UPDATE " . $table . " SET " . $field . " = NULL ";
		$q.= "WHERE " . $field . " = " . $id;
		$dbh->query($q);
	}

982
	$q = "DELETE FROM Users WHERE ID = " . $id;
canyonknight's avatar
canyonknight committed
983
	$dbh->query($q);
984
985
986
	return;
}

987
988
989
990
991
992
993
/**
 * Remove the session from the database on logout
 *
 * @param string $sid User's session ID
 *
 * @return void
 */
994
function delete_session_id($sid) {
995
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
996

canyonknight's avatar
canyonknight committed
997
998
	$q = "DELETE FROM Sessions WHERE SessionID = " . $dbh->quote($sid);
	$dbh->query($q);
canyonknight's avatar
canyonknight committed
999
1000
}

For faster browsing, not all history is shown. View entire blame