yppasswdd_server.c revision 15687
1/* 2 * Copyright (c) 1995, 1996 3 * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Bill Paul. 16 * 4. Neither the name of the author nor the names of any co-contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $Id: yppasswdd_server.c,v 1.2 1996/02/24 22:10:42 wpaul Exp $ 33 */ 34 35#include <stdio.h> 36#include <string.h> 37#include <ctype.h> 38#include <stdlib.h> 39#include <unistd.h> 40#include <dirent.h> 41#include <sys/stat.h> 42#include <sys/socket.h> 43#include <netinet/in.h> 44#include <arpa/inet.h> 45#include <limits.h> 46#include <db.h> 47#include <pwd.h> 48#include <errno.h> 49#include <signal.h> 50#include <rpc/rpc.h> 51#include <rpcsvc/yp.h> 52#include <sys/types.h> 53#include <sys/wait.h> 54#include <sys/param.h> 55struct dom_binding {}; 56#include <rpcsvc/ypclnt.h> 57#include "yppasswdd_extern.h" 58#include "yppasswd.h" 59#include "yppasswd_private.h" 60#include "yppasswd_comm.h" 61 62#ifndef lint 63static const char rcsid[] = "$Id: yppasswdd_server.c,v 1.2 1996/02/24 22:10:42 wpaul Exp $"; 64#endif /* not lint */ 65 66char *tempname; 67 68void reaper(sig) 69 int sig; 70{ 71 extern pid_t pid; 72 extern int pstat; 73 int st; 74 75 if (sig > 0) { 76 if (sig == SIGCHLD) 77 while(wait3(&st, WNOHANG, NULL) > 0) ; 78 } else { 79 pid = waitpid(pid, &pstat, 0); 80 } 81 return; 82} 83 84void install_reaper(on) 85 int on; 86{ 87 if (on) { 88 signal(SIGCHLD, reaper); 89 } else { 90 signal(SIGCHLD, SIG_DFL); 91 } 92 return; 93} 94 95static struct passwd yp_password; 96 97static void copy_yp_pass(p, x, m) 98char *p; 99int x, m; 100{ 101 register char *t, *s = p; 102 static char *buf; 103 104 yp_password.pw_fields = 0; 105 106 buf = (char *)realloc(buf, m + 10); 107 bzero(buf, m + 10); 108 109 /* Turn all colons into NULLs */ 110 while (strchr(s, ':')) { 111 s = (strchr(s, ':') + 1); 112 *(s - 1)= '\0'; 113 } 114 115 t = buf; 116#define EXPAND(e) e = t; while ((*t++ = *p++)); 117 EXPAND(yp_password.pw_name); 118 yp_password.pw_fields |= _PWF_NAME; 119 EXPAND(yp_password.pw_passwd); 120 yp_password.pw_fields |= _PWF_PASSWD; 121 yp_password.pw_uid = atoi(p); 122 p += (strlen(p) + 1); 123 yp_password.pw_fields |= _PWF_UID; 124 yp_password.pw_gid = atoi(p); 125 p += (strlen(p) + 1); 126 yp_password.pw_fields |= _PWF_GID; 127 if (x) { 128 EXPAND(yp_password.pw_class); 129 yp_password.pw_fields |= _PWF_CLASS; 130 yp_password.pw_change = atol(p); 131 p += (strlen(p) + 1); 132 yp_password.pw_fields |= _PWF_CHANGE; 133 yp_password.pw_expire = atol(p); 134 p += (strlen(p) + 1); 135 yp_password.pw_fields |= _PWF_EXPIRE; 136 } 137 EXPAND(yp_password.pw_gecos); 138 yp_password.pw_fields |= _PWF_GECOS; 139 EXPAND(yp_password.pw_dir); 140 yp_password.pw_fields |= _PWF_DIR; 141 EXPAND(yp_password.pw_shell); 142 yp_password.pw_fields |= _PWF_SHELL; 143 144 return; 145} 146 147static int validchars(arg) 148 char *arg; 149{ 150 int i; 151 152 for (i = 0; i < strlen(arg); i++) { 153 if (iscntrl(arg[i])) { 154 yp_error("string contains a control character"); 155 return(1); 156 } 157 if (arg[i] == ':') { 158 yp_error("string contains a colon"); 159 return(1); 160 } 161 /* Be evil: truncate strings with \n in them silently. */ 162 if (arg[i] == '\n') { 163 arg[i] = '\0'; 164 return(0); 165 } 166 } 167 return(0); 168} 169 170static int validate_master(opw, npw) 171 struct passwd *opw; 172 struct x_master_passwd *npw; 173{ 174 175 if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') { 176 yp_error("client tried to modify an NIS entry"); 177 return(1); 178 } 179 180 if (validchars(npw->pw_shell)) { 181 yp_error("specified shell contains invalid characters"); 182 return(1); 183 } 184 185 if (validchars(npw->pw_gecos)) { 186 yp_error("specified gecos field contains invalid characters"); 187 return(1); 188 } 189 190 if (validchars(npw->pw_passwd)) { 191 yp_error("specified password contains invalid characters"); 192 return(1); 193 } 194 return(0); 195} 196 197static int validate(opw, npw) 198 struct passwd *opw; 199 struct x_passwd *npw; 200{ 201 202 if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') { 203 yp_error("client tried to modify an NIS entry"); 204 return(1); 205 } 206 207 if (npw->pw_uid != opw->pw_uid) { 208 yp_error("UID mismatch: client says user %s has UID %d", 209 npw->pw_name, npw->pw_uid); 210 yp_error("database says user %s has UID %d", opw->pw_name, 211 opw->pw_uid); 212 return(1); 213 } 214 215 if (npw->pw_gid != opw->pw_gid) { 216 yp_error("GID mismatch: client says user %s has GID %d", 217 npw->pw_name, npw->pw_gid); 218 yp_error("database says user %s has GID %d", opw->pw_name, 219 opw->pw_gid); 220 return(1); 221 } 222 223 /* 224 * Don't allow the user to shoot himself in the foot, 225 * even on purpose. 226 */ 227 if (!ok_shell(npw->pw_shell)) { 228 yp_error("%s is not a valid shell", npw->pw_shell); 229 return(1); 230 } 231 232 if (validchars(npw->pw_shell)) { 233 yp_error("specified shell contains invalid characters"); 234 return(1); 235 } 236 237 if (validchars(npw->pw_gecos)) { 238 yp_error("specified gecos field contains invalid characters"); 239 return(1); 240 } 241 242 if (validchars(npw->pw_passwd)) { 243 yp_error("specified password contains invalid characters"); 244 return(1); 245 } 246 return(0); 247} 248 249/* 250 * Kludge alert: 251 * In order to have one rpc.yppasswdd support multiple domains, 252 * we have to cheat: we search each directory under /var/yp 253 * and try to match the user in each master.passwd.byname 254 * map that we find. If the user matches (username, uid and gid 255 * all agree), then we use that domain. If we match the user in 256 * more than one database, we must abort. 257 */ 258static char *find_domain(pw) 259 struct x_passwd *pw; 260{ 261 struct stat statbuf; 262 struct dirent *dirp; 263 DIR *dird; 264 char yp_mapdir[MAXPATHLEN + 2]; 265 static char domain[YPMAXDOMAIN]; 266 char *tmp = NULL; 267 DBT key, data; 268 int hit = 0; 269 270 yp_error("performing multidomain lookup"); 271 272 if ((dird = opendir(yp_dir)) == NULL) { 273 yp_error("opendir(%s) failed: %s", yp_dir, strerror(errno)); 274 return(NULL); 275 } 276 277 while ((dirp = readdir(dird)) != NULL) { 278 snprintf(yp_mapdir, sizeof(yp_mapdir), "%s/%s", 279 yp_dir, dirp->d_name); 280 if (stat(yp_mapdir, &statbuf) < 0) { 281 yp_error("stat(%s) failed: %s", yp_mapdir, 282 strerror(errno)); 283 closedir(dird); 284 return(NULL); 285 } 286 if (S_ISDIR(statbuf.st_mode)) { 287 tmp = (char *)dirp->d_name; 288 key.data = pw->pw_name; 289 key.size = strlen(pw->pw_name); 290 291 if (yp_get_record(tmp,"master.passwd.byname", 292 &key, &data, 0) != YP_TRUE) { 293 continue; 294 } 295 *(char *)(data.data + data.size) = '\0'; 296 copy_yp_pass(data.data, 1, data.size); 297 if (yp_password.pw_uid == pw->pw_uid && 298 yp_password.pw_gid == pw->pw_gid) { 299 hit++; 300 snprintf(domain, YPMAXDOMAIN, "%s", tmp); 301 } 302 } 303 } 304 305 closedir(dird); 306 if (hit > 1) { 307 yp_error("found same user in two different domains"); 308 return(NULL); 309 } else 310 return(&domain); 311} 312 313int * 314yppasswdproc_update_1_svc(yppasswd *argp, struct svc_req *rqstp) 315{ 316 static int result; 317 struct sockaddr_in *rqhost; 318 DBT key, data; 319 int rval = 0; 320 int pfd, tfd; 321 int pid; 322 int passwd_changed = 0; 323 int shell_changed = 0; 324 int gecos_changed = 0; 325 char *oldshell = NULL; 326 char *oldgecos = NULL; 327 char *passfile_hold; 328 char passfile_buf[MAXPATHLEN + 2]; 329 char template[] = "/etc/yppwtmp.XXXXX"; 330 char *domain = yppasswd_domain; 331 332 /* 333 * Normal user updates always use the 'default' master.passwd file. 334 */ 335 336 passfile = passfile_default; 337 result = 1; 338 339 rqhost = svc_getcaller(rqstp->rq_xprt); 340 341 if (yp_access(resvport ? "master.passwd.byname" : NULL, rqstp)) { 342 yp_error("rejected update request from unauthorized host"); 343 svcerr_auth(rqstp->rq_xprt, AUTH_BADCRED); 344 return(&result); 345 } 346 347 /* 348 * Step one: find the user. (It's kinda pointless to 349 * proceed if the user doesn't exist.) We look for the 350 * user in the master.passwd.byname database, _NOT_ by 351 * using getpwent() and friends! We can't use getpwent() 352 * since the NIS master server is not guaranteed to be 353 * configured as an NIS client. 354 */ 355 356 if (multidomain) { 357 if ((domain = find_domain(&argp->newpw)) == NULL) { 358 yp_error("multidomain lookup failed - aborting update"); 359 return(&result); 360 } else 361 yp_error("updating user %s in domain %s", 362 argp->newpw.pw_name, domain); 363 } 364 365 key.data = argp->newpw.pw_name; 366 key.size = strlen(argp->newpw.pw_name); 367 368 if ((rval=yp_get_record(domain,"master.passwd.byname", 369 &key, &data, 0)) != YP_TRUE) { 370 if (rval == YP_NOKEY) { 371 yp_error("user %s not found in passwd database", 372 argp->newpw.pw_name); 373 } else { 374 yp_error("database access error: %s", 375 yperr_string(rval)); 376 } 377 return(&result); 378 } 379 380 /* Nul terminate, please. */ 381 *(char *)(data.data + data.size) = '\0'; 382 383 copy_yp_pass(data.data, 1, data.size); 384 385 /* Step 2: check that the supplied oldpass is valid. */ 386 387 if (strcmp(crypt(argp->oldpass, yp_password.pw_passwd), 388 yp_password.pw_passwd)) { 389 yp_error("rejected change attempt -- bad password"); 390 yp_error("client address: %s username: %s", 391 inet_ntoa(rqhost->sin_addr), 392 argp->newpw.pw_name); 393 return(&result); 394 } 395 396 /* Step 3: validate the arguments passed to us by the client. */ 397 398 if (validate(&yp_password, &argp->newpw)) { 399 yp_error("rejecting change attempt: bad arguments"); 400 yp_error("client address: %s username: %s", 401 inet_ntoa(rqhost->sin_addr), 402 argp->newpw.pw_name); 403 svcerr_decode(rqstp->rq_xprt); 404 return(&result); 405 } 406 407 /* Step 4: update the user's passwd structure. */ 408 409 if (!no_chsh && strcmp(argp->newpw.pw_shell, yp_password.pw_shell)) { 410 oldshell = yp_password.pw_shell; 411 yp_password.pw_shell = argp->newpw.pw_shell; 412 shell_changed++; 413 } 414 415 416 if (!no_chfn && strcmp(argp->newpw.pw_gecos, yp_password.pw_gecos)) { 417 oldgecos = yp_password.pw_gecos; 418 yp_password.pw_gecos = argp->newpw.pw_gecos; 419 gecos_changed++; 420 } 421 422 if (strcmp(argp->newpw.pw_passwd, yp_password.pw_passwd)) { 423 yp_password.pw_passwd = argp->newpw.pw_passwd; 424 passwd_changed++; 425 } 426 427 /* 428 * If the caller specified a domain other than our 'default' 429 * domain, change the path to master.passwd accordingly. 430 */ 431 432 if (strcmp(domain, yppasswd_domain)) { 433 snprintf(passfile_buf, sizeof(passfile_buf), 434 "/var/yp/%s/master.passwd", domain); 435 passfile = (char *)&passfile_buf; 436 } 437 438 /* Step 5: make a new password file with the updated info. */ 439 440 if ((pfd = pw_lock()) < 0) { 441 return (&result); 442 } 443 if ((tfd = pw_tmp()) < 0) { 444 return (&result); 445 } 446 447 if (pw_copy(pfd, tfd, &yp_password)) { 448 yp_error("failed to created updated password file -- \ 449cleaning up and bailing out"); 450 unlink(tempname); 451 return(&result); 452 } 453 454 passfile_hold = mktemp((char *)&template); 455 rename(passfile, passfile_hold); 456 if (strcmp(passfile, _PATH_MASTERPASSWD)) { 457 rename(tempname, passfile); 458 } else { 459 if (pw_mkdb() < 0) { 460 yp_error("pwd_mkdb failed"); 461 return(&result); 462 } 463 } 464 465 switch((pid = fork())) { 466 case 0: 467 /* unlink(passfile_hold); */ 468 execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile, 469 yppasswd_domain, NULL); 470 yp_error("couldn't exec map update process: %s", 471 strerror(errno)); 472 unlink(passfile); 473 rename(passfile_hold, passfile); 474 exit(1); 475 break; 476 case -1: 477 yp_error("fork() failed: %s", strerror(errno)); 478 return(&result); 479 unlink(passfile); 480 rename(passfile_hold, passfile); 481 break; 482 default: 483 break; 484 } 485 486 if (verbose) { 487 yp_error("update completed for user %s (uid %d):", 488 argp->newpw.pw_name, 489 argp->newpw.pw_uid); 490 491 if (passwd_changed) 492 yp_error("password changed"); 493 494 if (gecos_changed) 495 yp_error("gecos changed ('%s' -> '%s')", 496 oldgecos, argp->newpw.pw_gecos); 497 498 if (shell_changed) 499 yp_error("shell changed ('%s' -> '%s')", 500 oldshell, argp->newpw.pw_shell); 501 } 502 503 result = 0; 504 return (&result); 505 506} 507 508/* 509 * Note that this function performs a little less sanity checking 510 * than the last one. Since only the superuser is allowed to use it, 511 * it is assumed that the caller knows what he's doing. 512 */ 513static int update_master(master_yppasswd *argp) 514{ 515 int result; 516 int pfd, tfd; 517 int pid; 518 int rval = 0; 519 DBT key, data; 520 char *passfile_hold; 521 char passfile_buf[MAXPATHLEN + 2]; 522 char template[] = "/etc/yppwtmp.XXXXX"; 523 524 result = 1; 525 passfile = passfile_default; 526 527 key.data = argp->newpw.pw_name; 528 key.size = strlen(argp->newpw.pw_name); 529 530 /* 531 * The superuser may add entries to the passwd maps if 532 * rpc.yppasswdd is started with the -a flag. Paranoia 533 * prevents me from allowing additions by default. 534 */ 535 if ((rval = yp_get_record(argp->domain, "master.passwd.byname", 536 &key, &data, 0)) != YP_TRUE) { 537 if (rval == YP_NOKEY) { 538 yp_error("user %s not found in passwd database", 539 argp->newpw.pw_name); 540 if (allow_additions) 541 yp_error("notice: adding user %s to \ 542master.passwd database for domain %s", argp->newpw.pw_name, argp->domain); 543 else 544 yp_error("restart %s with the -a flag to \ 545allow additions to be made to the password database", progname); 546 } else { 547 yp_error("database access error: %s", 548 yperr_string(rval)); 549 } 550 if (!allow_additions) 551 return(result); 552 } else { 553 554 /* Nul terminate, please. */ 555 *(char *)(data.data + data.size) = '\0'; 556 557 copy_yp_pass(data.data, 1, data.size); 558 } 559 560 /* 561 * Perform a small bit of sanity checking. 562 */ 563 if (validate_master(rval == YP_TRUE ? &yp_password:NULL,&argp->newpw)){ 564 yp_error("rejecting update attempt for %s: bad arguments", 565 argp->newpw.pw_name); 566 return(result); 567 } 568 569 /* 570 * If the caller specified a domain other than our 'default' 571 * domain, change the path to master.passwd accordingly. 572 */ 573 574 if (strcmp(argp->domain, yppasswd_domain)) { 575 snprintf(passfile_buf, sizeof(passfile_buf), 576 "/var/yp/%s/master.passwd", argp->domain); 577 passfile = (char *)&passfile_buf; 578 } 579 580 if ((pfd = pw_lock()) < 0) { 581 return (result); 582 } 583 if ((tfd = pw_tmp()) < 0) { 584 return (result); 585 } 586 587 if (pw_copy(pfd, tfd, (struct passwd *)&argp->newpw)) { 588 yp_error("failed to created updated password file -- \ 589cleaning up and bailing out"); 590 unlink(tempname); 591 return(result); 592 } 593 594 passfile_hold = mktemp((char *)&template); 595 rename(passfile, passfile_hold); 596 if (strcmp(passfile, _PATH_MASTERPASSWD)) { 597 rename(tempname, passfile); 598 } else { 599 if (pw_mkdb() < 0) { 600 yp_error("pwd_mkdb failed"); 601 return(result); 602 } 603 } 604 605 switch((pid = fork())) { 606 case 0: 607 close(yp_sock); 608 execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile, 609 argp->domain, NULL); 610 yp_error("couldn't exec map update process: %s", 611 strerror(errno)); 612 unlink(passfile); 613 rename(passfile_hold, passfile); 614 exit(1); 615 break; 616 case -1: 617 yp_error("fork() failed: %s", strerror(errno)); 618 unlink(passfile); 619 rename(passfile_hold, passfile); 620 return(result); 621 break; 622 default: 623 break; 624 } 625 626 yp_error("performed update of user %s (uid %d) domain %s", 627 argp->newpw.pw_name, 628 argp->newpw.pw_uid, 629 argp->domain); 630 631 result = 0; 632 return(result); 633} 634 635/* 636 * Pseudo-dispatcher for private 'superuser-only' update handler. 637 */ 638void do_master() 639{ 640 struct master_yppasswd *pw; 641 642 if ((pw = getdat(yp_sock)) == NULL) { 643 return; 644 } 645 646 yp_error("received update request from superuser on localhost"); 647 sendresp(update_master(pw)); 648 649 /* Remember to free args. */ 650 xdr_free(xdr_master_yppasswd, (char *)pw); 651 652 return; 653} 654