1<?php // vim: et ts=2 sw=2 2 3#TODO: 4# acls 5# handle flipping of the sort views 6 7require '../include/login.inc'; 8require '../include/email-validation.inc'; 9require '../include/email-templates.inc'; 10 11function csrf_generate(&$mydata, $name) { 12 $mydata["CSRF"][$name] = $csrf = hash("sha512", (string)mt_rand(0,mt_getrandmax())); 13 return "$name:$csrf"; 14} 15function csrf_validate(&$mydata, $name) { 16 $val = filter_input(INPUT_POST, "csrf", FILTER_UNSAFE_RAW); 17 list($which, $hash) = explode(":", $val, 2); 18 19 if ($which != $name) { 20 warn("Failed CSRF Check"); 21 foot(); 22 exit; 23 } 24 25 if ($mydata["CSRF"][$name] != $hash) { 26 warn("Failed CSRF Check"); 27 foot(); 28 exit; 29 } 30 31 csrf_generate($mydata, $name); 32 return true; 33} 34 35$indesc = [ 36 "id" => FILTER_VALIDATE_INT, 37 "rawpasswd" => FILTER_UNSAFE_RAW, 38 "rawpasswd2" => FILTER_UNSAFE_RAW, 39 "svnpasswd" => FILTER_SANITIZE_STRIPPED, 40 "cvsaccess" => ["filter" => FILTER_CALLBACK, "options" => function($v) { if ($v == "on") { return true; } return false; }], 41 "enable" => ["filter" => FILTER_CALLBACK, "options" => function($v) { if ($v == "on") { return true; } return false; }], 42 "spamprotect" => ["filter" => FILTER_CALLBACK, "options" => function($v) { if ($v == "on") { return true; } return false; }], 43 "greylist" => ["filter" => FILTER_CALLBACK, "options" => function($v) { if ($v == "on") { return true; } return false; }], 44 "verified" => FILTER_VALIDATE_INT, 45 "use_sa" => FILTER_VALIDATE_INT, 46 "email" => FILTER_SANITIZE_EMAIL, 47 "name" => FILTER_SANITIZE_SPECIAL_CHARS, 48 "sshkey" => FILTER_SANITIZE_SPECIAL_CHARS, 49 "purpose" => FILTER_SANITIZE_SPECIAL_CHARS, 50 "profile_markdown" => FILTER_UNSAFE_RAW, 51]; 52 53$rawin = filter_input_array(INPUT_POST) ?: []; 54$in = isset($rawin["in"]) ? filter_var_array($rawin["in"], $indesc, false) : []; 55$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT) ?: 0; 56$username = filter_input(INPUT_GET, "username", FILTER_SANITIZE_STRIPPED) ?: 0; 57 58head("user administration"); 59 60db_connect(); 61 62# ?username=whatever will look up 'whatever' by email or username 63if ($username) { 64 $query = "SELECT userid FROM users" 65 . " WHERE username=? OR email=?"; 66 $res = db_query_safe($query, [$username, $username]); 67 68 if (!($id = @mysql_result($res, 0))) { 69 warn("wasn't able to find user matching '$username'"); 70 } 71} 72if ($id) { 73 $query = "SELECT * FROM users WHERE users.userid=?"; 74 $res = db_query_safe($query, [$id]); 75 $userdata = mysql_fetch_array($res); 76 if (!$userdata) { 77 warn("Can't find user#$id"); 78 } 79} 80 81$action = filter_input(INPUT_POST, "action", FILTER_CALLBACK, ["options" => "validateAction"]); 82if ($id && $action) { 83 csrf_validate($_SESSION, $action); 84 if (!is_admin($_SESSION["username"])) { 85 warn("you're not allowed to take actions on users."); 86 exit; 87 } 88 89 switch ($action) { 90 case 'approve': 91 user_approve((int)$id); 92 break; 93 94 case 'remove': 95 user_remove((int)$id); 96 break; 97 98 default: 99 warn("that action ('$action') is not understood."); 100 } 101} 102 103if ($in) { 104 csrf_validate($_SESSION, "useredit"); 105 if (!can_modify($_SESSION["username"],$id)) { 106 warn("you're not allowed to modify this user."); 107 } 108 else { 109 if ($error = invalid_input($in)) { 110 warn($error); 111 } 112 else { 113 if (!empty($in['rawpasswd'])) { 114 $userinfo = fetch_user($id); 115 $in['svnpasswd'] = gen_svn_pass($userinfo["username"], $in['rawpasswd']); 116 } 117 118 $cvsaccess = empty($in['cvsaccess']) ? 0 : 1; 119 $enable = empty($in['enable']) ? 0 : 1; 120 $spamprotect = empty($in['spamprotect']) ? 0 : 1; 121 $use_sa = empty($in['use_sa']) ? 0 : (int)$in['use_sa']; 122 $greylist = empty($in['greylist']) ? 0 : 1; 123 124 if ($id) { 125 # update main table data 126 if (!empty($in['email']) && !empty($in['name'])) { 127 $query = new Query("UPDATE users SET name=?,email=?", [$in['name'], $in['email']]); 128 if (!empty($in['svnpasswd'])) { 129 $query->add(',svnpasswd=?', [$in['svnpasswd']]); 130 } 131 if (!empty($in['sshkey'])) { 132 $query->add(',ssh_keys=?', [html_entity_decode($in['sshkey'],ENT_QUOTES)]); 133 } 134 if (is_admin($_SESSION["username"]) && !empty($in['username'])) { 135 $query->add(',username=?', [$in['username']]); 136 } 137 if (is_admin($_SESSION["username"])) { 138 $query->add(',cvsaccess=?', [$cvsaccess]); 139 } 140 $query->add( 141 ',spamprotect=?, enable=?, use_sa=?, greylist=?', 142 [$spamprotect, $enable, $use_sa, $greylist]); 143 if (!empty($in['rawpasswd'])) { 144 $query->add(',pchanged=?', [$ts]); 145 } 146 $query->add(' WHERE userid=?', [$id]); 147 if (!empty($in['passwd'])) { 148 // Kill the session data after updates :) 149 $_SERVER["credentials"] = []; 150 } 151 db_query($query); 152 153 if(!empty($in['purpose'])) { 154 $purpose = hsc($in['purpose']); 155 $query = "INSERT INTO users_note (userid, note, entered) VALUES (?, ?, NOW())"; 156 db_query_safe($query, [$id, $purpose]); 157 } 158 159 if(!empty($in['profile_markdown'])) { 160 $profile_markdown = $in['profile_markdown']; 161 $profile_html = Markdown($profile_markdown); 162 $query = "INSERT INTO users_profile (userid, markdown, html) VALUES (?, ?, ?) 163 ON DUPLICATE KEY UPDATE markdown=?, html=?"; 164 db_query_safe($query, [$id, $profile_markdown, $profile_html, $profile_markdown, $profile_html]); 165 } 166 } 167 168 warn("record $id updated"); 169 $id = false; 170 } 171 } 172 } 173} 174 175if ($id) { 176?> 177<form method="post" action="users.php?id=<?php echo $userdata["userid"]?>"> 178 <input type="hidden" name="csrf" value="<?php echo csrf_generate($_SESSION, "useredit") ?>" /> 179<table class="useredit"> 180<tbody> 181<tr> 182 <th>Name:</th> 183 <td><input type="text" name="in[name]" value="<?php echo $userdata['name'];?>" size="40" maxlength="255" /></td> 184</tr> 185<tr> 186 <th>Email:</th> 187 <td><input type="text" name="in[email]" value="<?php echo $userdata['email'];?>" size="40" maxlength="255" /><br/> 188 <input type="checkbox" name="in[enable]"<?php echo $userdata['enable'] ? " checked" : "";?> /> Enable email for my account. 189 </td> 190</tr> 191<tr> 192 <th>VCS username:</th> 193<?php if (is_admin($_SESSION["username"])): ?> 194 <td><input type="text" name="in[username]" value="<?php echo hsc($userdata['username']);?>" size="16" maxlength="16" /></td> 195<?php else: ?> 196 <td><?php echo hsc($userdata['username']);?></td> 197<?php endif ?> 198</tr> 199<tr> 200 <td colspan="2">Leave password fields blank to leave password unchanged.</td> 201</tr> 202<tr> 203 <th>Password:</th> 204 <td><input type="password" name="in[rawpasswd]" value="" size="20" maxlength="120" /></td> 205</tr> 206<tr> 207 <th>Password (again):</th> 208 <td><input type="password" name="in[rawpasswd2]" value="" size="20" maxlength="120" /></td> 209</tr> 210<?php if (is_admin($_SESSION["username"])) {?> 211<tr> 212 <th>VCS access?</th> 213 <td><input type="checkbox" name="in[cvsaccess]"<?php echo $userdata['cvsaccess'] ? " checked" : "";?> /></td> 214</tr> 215<?php } else { ?> 216<tr> 217 <th>Has VCS access?</th> 218 <td><?php echo $userdata['cvsaccess'] ? "Yes" : "No";?></td> 219</tr> 220<?php } ?> 221<tr> 222 <th>Use Challenge/Response spam protection?</th> 223 <td><input type="checkbox" name="in[spamprotect]"<?php echo $userdata['spamprotect'] ? " checked" : "";?> /> 224 <?php if ($userdata['username'] == $_SESSION["username"]) { ?> 225 <br/> 226 <a href="challenge-response.php">Show people on my quarantine list</a> 227 <?php } ?> 228 </td> 229</tr> 230<tr> 231 <th>SpamAssassin threshold</th> 232 <td>Block mail scoring <input type="text" name="in[use_sa]" value="<?php echo $userdata['use_sa'] ?>" size="4" maxlength="4"/> or higher in SpamAssassin tests. Set to 0 to disable.</td> 233</tr> 234<tr> 235 <th>Greylist</th> 236 <td>Delay reception of your incoming mail by a minimum of one hour using a 451 response.<br/> 237 Legitimate senders will continue to try to deliver the mail, whereas 238 spammers will typically give up and move on to spamming someone else.<br/> 239 See <a href="http://projects.puremagic.com/greylisting/whitepaper.html">this whitepaper</a> for more information on greylisting.<br/> 240 <input type="checkbox" name="in[greylist]"<?php echo $userdata['greylist'] ? " checked" : "";?> /> Enable greylisting on my account</td> 241</tr> 242<tr> 243 <th>Verified?</th> 244 <td><input type="checkbox" name="in[verified]"<?php echo $userdata['verified'] ? " checked" : "";?> /> Note: Do not worry about this value. It's sometimes used to check if old-timers are still around.</td> 245</tr> 246</tbody> 247<tfoot> 248<tr> 249 <th>SSH Key</th> 250 <td><textarea name="in[sshkey]" placeholder="Paste in the contents of your id_rsa.pub"><?php echo hsc(html_entity_decode($userdata['ssh_keys'], ENT_QUOTES)); ?></textarea> 251 <p>Adding/editing the SSH key takes a few minutes to propagate to the server.<br> 252 Multiple keys are allowed, separated using a newline.</p></td> 253</tr> 254<?php 255 if ($id) { 256 $res = db_query_safe("SELECT markdown FROM users_profile WHERE userid=?", [$id]); 257 $userdata['profile_markdown'] = ''; 258 if ($profile_row = mysql_fetch_assoc($res)) { 259 $userdata['profile_markdown'] = $profile_row['markdown']; 260 } 261?> 262<tr> 263 <th>People Profile<br>(<a href="http://people.php.net/user.php?username=<?php echo urlencode($userdata['username']);?>"><?php echo hsc($userdata['username']);?>'s page</a>)</th> 264 <td> 265 <p>Use <a href="http://michelf.ca/projects/php-markdown/dingus/" title="PHP Markdown: Dingus">Markdown</a>. Type as much as you like.</p> 266 <div><textarea name="in[profile_markdown]" placeholder="My PHP People page content"><?php echo hsc($userdata['profile_markdown']); ?></textarea></div> 267 </td> 268</tr> 269<?php 270 } 271?> 272<tr> 273 <th>Add Note: </th> 274 <td><textarea name="in[purpose]" placeholder="Administrative notes"></textarea></td> 275</tr> 276<tr> 277 <td colspan="2"><input type="submit" value="Update" /> 278</tr> 279</tfoot> 280</table> 281</form> 282<?php 283if (is_admin($_SESSION["username"]) && !$userdata['cvsaccess']) { 284?> 285<table> 286<tr> 287<td> 288 <form method="post" action="users.php?id=<?php echo $id?>"> 289 <input type="hidden" name="csrf" value="<?php echo csrf_generate($_SESSION, "remove") ?>" /> 290 <input type="hidden" name="action" value="remove" /> 291 <input type="submit" value="Reject" /> 292 </form> 293</td> 294<td> 295<?php 296 $hash = gen_svn_pass($_SESSION["credentials"][0], $_SESSION["credentials"][1]); 297 $csrf = "approve:$hash:"; 298?> 299 <form method="post" action="users.php?id=<?php echo $id?>"> 300 <input type="hidden" name="csrf" value="<?php echo csrf_generate($_SESSION, "approve") ?>" /> 301 <input type="hidden" name="action" value="approve" /> 302 <input type="submit" value="Approve" /> 303 </form> 304</td> 305</tr> 306</table> 307<?php 308} 309?> 310<h2 id="notes">Notes:</h2> 311<?php 312 $res = db_query_safe("SELECT note, UNIX_TIMESTAMP(entered) AS ts FROM users_note WHERE userid=?", [$id]); 313 while ($res && $userdata = mysql_fetch_assoc($res)) { 314 echo "<div class='note'>", date("r",$userdata['ts']), "<br />".$userdata['note']."</div>"; 315 } 316 foot(); 317 exit; 318} 319?> 320<?php 321 322$unapproved = filter_input(INPUT_GET, "unapproved", FILTER_VALIDATE_INT) ?: 0; 323$begin = filter_input(INPUT_GET, "begin", FILTER_VALIDATE_INT) ?: 0; 324$max = filter_input(INPUT_GET, "max", FILTER_VALIDATE_INT) ?: 20; 325$forward = filter_input(INPUT_GET, "forward", FILTER_VALIDATE_INT) ?: 0; 326$search = filter_input(INPUT_GET, "search", FILTER_UNSAFE_RAW) ?: ""; 327$order = filter_input(INPUT_GET, "order", FILTER_UNSAFE_RAW) ?: ""; 328 329$query = new Query("SELECT DISTINCT SQL_CALC_FOUND_ROWS users.userid,cvsaccess,username,name,email,GROUP_CONCAT(note) note FROM users "); 330$query->add(" LEFT JOIN users_note ON users_note.userid = users.userid "); 331 332if ($search) { 333 $query->add("WHERE (MATCH(name,email,username) AGAINST (?) OR username = ?) ", [$search, $search]); 334 335} else { 336 $query->add(' WHERE 1=1 '); 337} 338 339if ($unapproved) { 340 $query->add(' AND NOT cvsaccess '); 341} 342 343$query->add(" GROUP BY users.userid "); 344 345if ($order) { 346 if (!in_array($order, ["username", "name", "email", "note"], true)) { 347 die("Invalid order!"); 348 } 349 if ($forward) { 350 $ext = "ASC"; 351 } else { 352 $ext = "DESC"; 353 } 354 // Safe because we checked that $order is part of a fixed set. 355 $query->add(" ORDER BY $order $ext"); 356} 357$query->add(" LIMIT ?int, ?int ", [$begin, $max]); 358$res = db_query($query); 359 360$res2 = db_query_safe("SELECT FOUND_ROWS()"); 361$total = (int)mysql_result($res2,0); 362 363 364$extra = [ 365 "search" => $search, 366 "order" => $order, 367 "forward" => $forward, 368 "begin" => $begin, 369 "max" => $max, 370 "unapproved" => $unapproved, 371]; 372 373?> 374<h1 class="browse">Browse users<ul> 375 <li><a href="?unapproved=0">See all users</a></li> 376 <li><a href="?unapproved=1">See outstanding requests</a></li> 377 </ul></h1> 378<table id="users"> 379<thead> 380<?php show_prev_next($begin,mysql_num_rows($res),$max,$total,$extra, false); ?> 381</thead> 382<tbody> 383<tr> 384 <th><a href="?<?php echo array_to_url($extra,["unapproved"=>!$unapproved]);?>"><?php echo $unapproved ? "&otimes" : "&oplus"; ?>;</a></th> 385 <th><a href="?<?php echo array_to_url($extra,["order"=>"username"]);?>">username</a></th> 386 <th><a href="?<?php echo array_to_url($extra,["order"=>"name"]);?>">name</a></th> 387<?php if (!$unapproved) { ?> 388 <th colspan="2"><a href="?<?php echo array_to_url($extra,["order"=>"email"]);?>">email</a></th> 389<?php } else { ?> 390 <th><a href="?<?php echo array_to_url($extra,["order"=>"email"]);?>">email</a></th> 391 <th><a href="?<?php echo array_to_url($extra,["order"=>"note"]);?>">note</a></th> 392<?php } ?> 393 <th> </th> 394</tr> 395<?php 396while ($userdata = mysql_fetch_array($res)) { 397?> 398 <tr class="<?php if (!$userdata["cvsaccess"]) { echo "noaccess"; }?>"> 399 <td><a href="?username=<?php echo $userdata["username"];?>">edit</a></td> 400 <td><a href="https://people.php.net/?username=<?php echo hsc($userdata['username']) ?>"><?php echo hsc($userdata['username']) ?></a></td> 401 <td><?php echo hsc($userdata['name']);?></td> 402<?php if (!$unapproved) { ?> 403 <td colspan="2"><?php echo hsc($userdata['email']);?></td> 404<?php } else { ?> 405 <td><?php echo hsc($userdata['email']);?></td> 406 <td><?php echo hsc($userdata['note']) ?></td> 407<?php } ?> 408 <td> </td> 409 </tr> 410<?php 411} 412?> 413</tbody> 414<tfoot> 415<?php show_prev_next($begin,mysql_num_rows($res),$max,$total,$extra, false); ?> 416</tfoot> 417</table> 418<?php 419foot(); 420 421