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
		$editor_user = uid_from_sid($_COOKIE['AURSID']);
138
139
	}
	else {
140
		$editor_user = null;
141
	}
142
143

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

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

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

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

163
164
165
166
167
168
169
	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) {
170
171
		$error = __("Password fields do not match.");
	}
172
173
174
	if (!$error && $P && check_passwd($UID, $PO) != 1) {
		$error = __("The old password is invalid.");
	}
175
176
177
178
179
	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);
	}
180

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

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

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

193
	if (!$error && !empty($PK)) {
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
		$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;
213
		}
214
215
216
217
218
219

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

222
223
224
225
226
	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
227
	}
228

229
230
231
	if (!$error && !array_key_exists($L, $SUPPORTED_LANGS)) {
		$error = __("Language is not currently supported.");
	}
Mark Weiman's avatar
Mark Weiman committed
232
233
234
	if (!$error && !array_key_exists($TZ, generate_timezone_list())) {
		$error = __("Timezone is not currently supported.");
	}
235
	if (!$error) {
236
237
238
239
		/*
		 * Check whether the user name is available.
		 * TODO: Fix race condition.
		 */
240
		$q = "SELECT COUNT(*) AS CNT FROM Users ";
canyonknight's avatar
canyonknight committed
241
		$q.= "WHERE Username = " . $dbh->quote($U);
eric's avatar
eric committed
242
243
244
		if ($TYPE == "edit") {
			$q.= " AND ID != ".intval($UID);
		}
canyonknight's avatar
canyonknight committed
245
246
247
248
249
		$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
250
				"<strong>", htmlspecialchars($U,ENT_QUOTES), "</strong>");
251
252
253
		}
	}
	if (!$error) {
254
255
256
257
		/*
		 * Check whether the e-mail address is available.
		 * TODO: Fix race condition.
		 */
258
		$q = "SELECT COUNT(*) AS CNT FROM Users ";
canyonknight's avatar
canyonknight committed
259
		$q.= "WHERE Email = " . $dbh->quote($E);
eric's avatar
eric committed
260
261
262
		if ($TYPE == "edit") {
			$q.= " AND ID != ".intval($UID);
		}
canyonknight's avatar
canyonknight committed
263
264
265
266
267
		$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
268
					"<strong>", htmlspecialchars($E,ENT_QUOTES), "</strong>");
269
270
		}
	}
271
	if (!$error && isset($ssh_keys) && count($ssh_keys) > 0) {
272
		/*
273
		 * Check whether any of the SSH public keys is already in use.
274
275
		 * TODO: Fix race condition.
		 */
276
277
278
279
		$q = "SELECT Fingerprint FROM SSHPubKeys ";
		$q.= "WHERE Fingerprint IN (";
		$q.= implode(',', array_map(array($dbh, 'quote'), $ssh_fingerprints));
		$q.= ")";
280
		if ($TYPE == "edit") {
281
			$q.= " AND UserID != " . intval($UID);
282
283
284
285
		}
		$result = $dbh->query($q);
		$row = $result->fetch(PDO::FETCH_NUM);

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

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

296
	if (!$error && $TYPE == "new" && !in_array($captcha_salt, get_captcha_salts())) {
297
298
299
300
301
302
303
		$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.");
	}

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

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

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

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

348
349
		if ($send_resetkey) {
			send_resetkey($email, true);
350
351
			$message .= __("A password reset key has been sent to your e-mail address.");
			$message .= "</p>\n";
352
		} else {
353
354
			$message .= __("Click on the Login link above to use your account.");
			$message .= "</p>\n";
355
		}
356
	} else {
357
358
359
360
361
362
363
364
365
366
367
368
		/* 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;
		}
369

370
371
372
373
374
375
376
377
378
		$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";
379
		} else {
380
381
382
			$q.= ", Suspended = 0";
		}
		$q.= ", Email = " . $dbh->quote($E);
383
384
385
386
387
		if ($H) {
			$q.= ", HideEmail = 1";
		} else {
			$q.= ", HideEmail = 0";
		}
388
		if ($P) {
Lukas Fleischer's avatar
Lukas Fleischer committed
389
390
			$hash = password_hash($P, PASSWORD_DEFAULT);
			$q .= ", Passwd = " . $dbh->quote($hash);
391
392
393
		}
		$q.= ", RealName = " . $dbh->quote($R);
		$q.= ", LangPreference = " . $dbh->quote($L);
Mark Weiman's avatar
Mark Weiman committed
394
		$q.= ", Timezone = " . $dbh->quote($TZ);
395
		$q.= ", Homepage = " . $dbh->quote($HP);
396
397
398
		$q.= ", IRCNick = " . $dbh->quote($I);
		$q.= ", PGPKey = " . $dbh->quote(str_replace(" ", "", $K));
		$q.= ", InactivityTS = " . $inactivity_ts;
399
		$q.= ", CommentNotify = " . ($CN ? "1" : "0");
400
		$q.= ", UpdateNotify = " . ($UN ? "1" : "0");
401
		$q.= ", OwnershipNotify = " . ($ON ? "1" : "0");
402
403
		$q.= " WHERE ID = ".intval($UID);
		$result = $dbh->exec($q);
404

405
406
407
408
409
		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;
		}
410

Mark Weiman's avatar
Mark Weiman committed
411
412
413
414
415
416
417
		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, "/");
		}

418
419
420
421
422
423
424
		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, "/");
		}

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

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

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/**
 * 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
452
function search_results_page($O=0,$SB="",$U="",$T="",
453
		$S="",$E="",$R="",$I="",$K="") {
454
455
456
457
458
459
460
461
462
463
464
465

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

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

468
469
470
471
472
473
474
475
476
477
478
479
	$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";
480
481
482
	} elseif ($T == "td") {
		$q.= "AND AccountTypes.ID = 4 ";
		$search_vars[] = "T";
483
484
485
486
487
488
	}
	if ($S) {
		$q.= "AND Users.Suspended = 1 ";
		$search_vars[] = "S";
	}
	if ($U) {
canyonknight's avatar
canyonknight committed
489
490
		$U = "%" . addcslashes($U, '%_') . "%";
		$q.= "AND Username LIKE " . $dbh->quote($U) . " ";
491
492
493
		$search_vars[] = "U";
	}
	if ($E) {
canyonknight's avatar
canyonknight committed
494
495
		$E = "%" . addcslashes($E, '%_') . "%";
		$q.= "AND Email LIKE " . $dbh->quote($E) . " ";
496
497
498
		$search_vars[] = "E";
	}
	if ($R) {
canyonknight's avatar
canyonknight committed
499
500
		$R = "%" . addcslashes($R, '%_') . "%";
		$q.= "AND RealName LIKE " . $dbh->quote($R) . " ";
501
502
503
		$search_vars[] = "R";
	}
	if ($I) {
canyonknight's avatar
canyonknight committed
504
505
		$I = "%" . addcslashes($I, '%_') . "%";
		$q.= "AND IRCNick LIKE " . $dbh->quote($I) . " ";
506
507
		$search_vars[] = "I";
	}
508
	if ($K) {
canyonknight's avatar
canyonknight committed
509
510
		$K = "%" . addcslashes(str_replace(" ", "", $K), '%_') . "%";
		$q.= "AND PGPKey LIKE " . $dbh->quote($K) . " ";
511
512
		$search_vars[] = "K";
	}
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
	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";
528
	$q.= "LIMIT " . $HITS_PER_PAGE . " OFFSET " . $OFFSET;
529

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

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

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

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

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

555
556
557
558
559
560
561
562
563
564
565
	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);
	}
566

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

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

	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;
591
592
593
594
595
596
597
	}

	$logged_in = 0;
	$num_tries = 0;

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

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

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

		$num_tries++;
	}

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

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

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

		/* 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);
654
655
656
657
658
659

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

663
664
665
666
667
668
669
670
/**
 * 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();

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

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

677
678
679
/**
 * Validate a username against a collection of rules
 *
680
681
682
683
 * 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.
684
685
686
 *
 * @param string $user Username to validate
 *
687
 * @return bool True if username meets criteria, otherwise false
688
 */
689
function valid_username($user) {
690
691
692
693
	$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) {
694
		return false;
695
	} else if (!preg_match("/^[a-z0-9]+[.\-_]?[a-z0-9]+$/Di", $user)) {
696
		return false;
697
	}
698

699
	return true;
700
701
}

702
703
704
705
706
707
708
/**
 * 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
 */
709
function open_user_proposals($user) {
710
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
711
	$q = "SELECT * FROM TU_VoteInfo WHERE User = " . $dbh->quote($user) . " ";
712
	$q.= "AND End > " . strval(time());
canyonknight's avatar
canyonknight committed
713
	$result = $dbh->query($q);
714
715

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

718
719
720
721
722
723
724
725
726
727
/**
 * 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
 */
728
function add_tu_proposal($agenda, $user, $votelength, $quorum, $submitteruid) {
729
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
730

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

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

745
746
747
748
749
750
751
752
/**
 * 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
 */
753
function create_resetkey($resetkey, $uid) {
754
	$dbh = DB::connect();
canyonknight's avatar
canyonknight committed
755
756
757
	$q = "UPDATE Users ";
	$q.= "SET ResetKey = '" . $resetkey . "' ";
	$q.= "WHERE ID = " . $uid;
canyonknight's avatar
canyonknight committed
758
	$dbh->exec($q);
canyonknight's avatar
canyonknight committed
759
760
}

761
762
763
764
/**
 * Send a reset key to a specific e-mail address
 *
 * @param string $email E-mail address of the user resetting their password
765
 * @param bool $welcome Whether to use the welcome message
766
767
768
 *
 * @return void
 */
769
function send_resetkey($email, $welcome=false) {
770
	$uid = uid_from_email($email);
771
772
773
774
775
776
777
778
779
	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. */
780
	notify(array($welcome ? 'welcome' : 'send-resetkey', $uid));
781
782
}

783
784
785
/**
 * Change a user's password in the database if reset key and e-mail are correct
 *
Lukas Fleischer's avatar
Lukas Fleischer committed
786
 * @param string $password The new password
787
788
789
790
791
 * @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
792
793
794
function password_reset($password, $resetkey, $email) {
	$hash = password_hash($password, PASSWORD_DEFAULT);

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

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

813
814
815
816
817
818
819
/**
 * 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
 */
820
function good_passwd($passwd) {
821
822
	$length_min = config_get_int('options', 'passwd_min_len');
	return (strlen($passwd) >= $length_min);
823
824
}

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

Lukas Fleischer's avatar
Lukas Fleischer committed
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
	/* 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;
	}
851

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

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

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

	return 1;
870
871
}

872
873
874
875
876
877
/**
 * 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
878
 */
879
function valid_pgp_fingerprint($fingerprint) {
880
881
	$fingerprint = str_replace(" ", "", $fingerprint);
	return (strlen($fingerprint) == 40 && ctype_xdigit($fingerprint));
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
913
}

/**
 * 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]);
914
915
}

916
917
918
919
920
921
/**
 * 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
922
 */
923
function user_suspended($id) {
924
	$dbh = DB::connect();
elij's avatar
elij committed
925
926
927
	if (!$id) {
		return false;
	}
928
	$q = "SELECT Suspended FROM Users WHERE ID = " . $id;
canyonknight's avatar
canyonknight committed
929
	$result = $dbh->query($q);
930
	if ($result) {
canyonknight's avatar
canyonknight committed
931
		$row = $result->fetch(PDO::FETCH_NUM);
932
		if ($row[0]) {
933
934
			return true;
		}
935
936
937
938
	}
	return false;
}

939
940
941
942
943
944
/**
 * Delete a specified user account from the database
 *
 * @param int $id The user ID of the account to be deleted
 *
 * @return void
945
 */
946
function user_delete($id) {
947
	$dbh = DB::connect();
948
949
950
951
952
953
954
955
956
	$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"),
957
		array("PackageNotifications", "UsersID")
958
959
960
961
962
	);

	$fields_set_null = array(
		array("PackageBases", "SubmitterUID"),
		array("PackageBases", "MaintainerUID"),
963
		array("PackageBases", "PackagerUID"),
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
		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);
	}

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

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

canyonknight's avatar
canyonknight committed
998
999
	$q = "DELETE FROM Sessions WHERE SessionID = " . $dbh->quote($sid);
	$dbh->query($q);
canyonknight's avatar
canyonknight committed
1000
}
For faster browsing, not all history is shown. View entire blame