1 /* $NetBSD: user.c,v 1.38 2001/02/19 23:22:49 cgd Exp $ */ 2 3 /* 4 * Copyright (c) 1999 Alistair G. Crooks. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. All advertising materials mentioning features or use of this software 15 * must display the following acknowledgement: 16 * This product includes software developed by Alistair G. Crooks. 17 * 4. The name of the author may not be used to endorse or promote 18 * products derived from this software without specific prior written 19 * permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 22 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 27 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 #include <sys/cdefs.h> 34 35 #ifndef lint 36 __COPYRIGHT("@(#) Copyright (c) 1999 \ 37 The NetBSD Foundation, Inc. All rights reserved."); 38 __RCSID("$NetBSD: user.c,v 1.38 2001/02/19 23:22:49 cgd Exp $"); 39 #endif 40 41 #include <sys/types.h> 42 #include <sys/param.h> 43 #include <sys/stat.h> 44 45 #include <ctype.h> 46 #include <dirent.h> 47 #include <err.h> 48 #include <fcntl.h> 49 #include <grp.h> 50 #include <paths.h> 51 #include <pwd.h> 52 #include <stdarg.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <time.h> 57 #include <unistd.h> 58 #include <util.h> 59 60 #include "defs.h" 61 #include "usermgmt.h" 62 63 64 /* this struct describes a uid range */ 65 typedef struct range_t { 66 int r_from; /* low uid */ 67 int r_to; /* high uid */ 68 } range_t; 69 70 /* this struct encapsulates the user information */ 71 typedef struct user_t { 72 int u_flags; /* see below */ 73 int u_uid; /* uid of user */ 74 char *u_password; /* encrypted password */ 75 char *u_comment; /* comment field */ 76 char *u_home; /* home directory */ 77 char *u_primgrp; /* primary group */ 78 int u_groupc; /* # of secondary groups */ 79 char *u_groupv[NGROUPS_MAX]; /* secondary groups */ 80 char *u_shell; /* user's shell */ 81 char *u_basedir; /* base directory for home */ 82 char *u_expire; /* when password will expire */ 83 int u_inactive; /* inactive */ 84 char *u_skeldir; /* directory for startup files */ 85 unsigned u_rsize; /* size of range array */ 86 unsigned u_rc; /* # of ranges */ 87 range_t *u_rv; /* the ranges */ 88 unsigned u_defrc; /* # of ranges in defaults */ 89 int u_preserve; /* preserve uids on deletion */ 90 } user_t; 91 92 /* flags for which fields of the user_t replace the passwd entry */ 93 enum { 94 F_COMMENT = 0x0001, 95 F_DUPUID = 0x0002, 96 F_EXPIRE = 0x0004, 97 F_GROUP = 0x0008, 98 F_HOMEDIR = 0x0010, 99 F_MKDIR = 0x0020, 100 F_INACTIVE = 0x0040, 101 F_PASSWORD = 0x0080, 102 F_SECGROUP = 0x0100, 103 F_SHELL = 0x0200, 104 F_UID = 0x0400, 105 F_USERNAME = 0x0800 106 }; 107 108 #define CONFFILE "/etc/usermgmt.conf" 109 110 #ifndef DEF_GROUP 111 #define DEF_GROUP "users" 112 #endif 113 114 #ifndef DEF_BASEDIR 115 #define DEF_BASEDIR "/home" 116 #endif 117 118 #ifndef DEF_SKELDIR 119 #define DEF_SKELDIR "/etc/skel" 120 #endif 121 122 #ifndef DEF_SHELL 123 #define DEF_SHELL _PATH_CSHELL 124 #endif 125 126 #ifndef DEF_COMMENT 127 #define DEF_COMMENT "" 128 #endif 129 130 #ifndef DEF_LOWUID 131 #define DEF_LOWUID 1000 132 #endif 133 134 #ifndef DEF_HIGHUID 135 #define DEF_HIGHUID 60000 136 #endif 137 138 #ifndef DEF_INACTIVE 139 #define DEF_INACTIVE 0 140 #endif 141 142 #ifndef DEF_EXPIRE 143 #define DEF_EXPIRE NULL 144 #endif 145 146 #ifndef WAITSECS 147 #define WAITSECS 10 148 #endif 149 150 #ifndef NOBODY_UID 151 #define NOBODY_UID 32767 152 #endif 153 154 /* some useful constants */ 155 enum { 156 MaxShellNameLen = 256, 157 MaxFileNameLen = MAXPATHLEN, 158 MaxUserNameLen = 32, 159 MaxFieldNameLen = 32, 160 MaxCommandLen = 2048, 161 MaxEntryLen = 2048, 162 PasswordLength = 13, 163 164 LowGid = DEF_LOWUID, 165 HighGid = DEF_HIGHUID 166 }; 167 168 /* Full paths of programs used here */ 169 #define CHMOD "/bin/chmod" 170 #define CHOWN "/usr/sbin/chown" 171 #define MKDIR "/bin/mkdir" 172 #define MV "/bin/mv" 173 #define NOLOGIN "/sbin/nologin" 174 #define PAX "/bin/pax" 175 #define RM "/bin/rm" 176 177 #define UNSET_EXPIRY "Null (unset)" 178 179 static int asystem(const char *fmt, ...) 180 __attribute__((__format__(__printf__, 1, 2))); 181 182 static int verbose; 183 184 /* if *cpp is non-null, free it, then assign `n' chars of `s' to it */ 185 static void 186 memsave(char **cpp, char *s, size_t n) 187 { 188 if (*cpp != NULL) { 189 FREE(*cpp); 190 } 191 NEWARRAY(char, *cpp, n + 1, exit(1)); 192 (void) memcpy(*cpp, s, n); 193 (*cpp)[n] = '\0'; 194 } 195 196 /* a replacement for system(3) */ 197 static int 198 asystem(const char *fmt, ...) 199 { 200 va_list vp; 201 char buf[MaxCommandLen]; 202 int ret; 203 204 va_start(vp, fmt); 205 (void) vsnprintf(buf, sizeof(buf), fmt, vp); 206 va_end(vp); 207 if (verbose) { 208 (void) printf("Command: %s\n", buf); 209 } 210 if ((ret = system(buf)) != 0) { 211 warnx("[Warning] can't system `%s'", buf); 212 } 213 return ret; 214 } 215 216 /* remove a users home directory, returning 1 for success (ie, no problems encountered) */ 217 static int 218 removehomedir(const char *user, int uid, const char *dir) 219 { 220 struct stat st; 221 222 /* userid not root? */ 223 if (uid == 0) { 224 warnx("Not deleting home directory `%s'; userid is 0", dir); 225 return 0; 226 } 227 228 /* directory exists (and is a directory!) */ 229 if (stat(dir, &st) < 0) { 230 warnx("Home directory `%s' doesn't exist", dir); 231 return 0; 232 } 233 if (!S_ISDIR(st.st_mode)) { 234 warnx("Home directory `%s' is not a directory", dir); 235 return 0; 236 } 237 238 /* userid matches directory owner? */ 239 if (st.st_uid != uid) { 240 warnx("User `%s' doesn't own directory `%s', not removed\n", user, dir); 241 return 0; 242 } 243 244 (void) seteuid(uid); 245 /* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */ 246 (void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir); 247 (void) seteuid(0); 248 if (rmdir(dir) < 0) { 249 warnx("Unable to remove all files in `%s'\n", dir); 250 return 0; 251 } 252 return 1; 253 } 254 255 #define NetBSD_1_4_K 104110000 256 257 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_4_K) 258 /* bounds checking strncpy */ 259 static int 260 strlcpy(char *to, char *from, size_t tosize) 261 { 262 size_t n; 263 int fromsize; 264 265 fromsize = strlen(from); 266 n = MIN(tosize - 1, fromsize); 267 (void) memcpy(to, from, n); 268 to[n] = '\0'; 269 return fromsize; 270 } 271 #endif /* NetBSD < 1.4K */ 272 273 /* 274 * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com> 275 * All rights reserved. 276 * 277 * Redistribution and use in source and binary forms, with or without 278 * modification, are permitted provided that the following conditions 279 * are met: 280 * 1. Redistributions of source code must retain the above copyright 281 * notice, this list of conditions and the following disclaimer. 282 * 2. Redistributions in binary form must reproduce the above copyright 283 * notice, this list of conditions and the following disclaimer in the 284 * documentation and/or other materials provided with the distribution. 285 * 3. The name of the author may not be used to endorse or promote products 286 * derived from this software without specific prior written permission. 287 * 288 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 289 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 290 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 291 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 292 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 293 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 294 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 295 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 296 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 297 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 298 */ 299 300 /* 301 * research has shown that NetBSD 1.3H was the first version of -current 302 * with asprintf in libc. agc 303 */ 304 #define NetBSD_1_3_H 103080000 305 306 #if defined(__NetBSD_Version__) && (__NetBSD_Version__ < NetBSD_1_3_H) 307 308 int 309 asprintf(char **str, char const *fmt, ...) 310 { 311 int ret; 312 va_list ap; 313 FILE f; 314 unsigned char *_base; 315 316 va_start(ap, fmt); 317 f._flags = __SWR | __SSTR | __SALC; 318 f._bf._base = f._p = (unsigned char *)malloc(128); 319 if (f._bf._base == NULL) 320 goto err; 321 f._bf._size = f._w = 127; /* Leave room for the NUL */ 322 ret = vfprintf(&f, fmt, ap); 323 if (ret == -1) 324 goto err; 325 *f._p = '\0'; 326 va_end(ap); 327 _base = realloc(f._bf._base, (size_t)(ret + 1)); 328 if (_base == NULL) 329 goto err; 330 *str = (char *)_base; 331 return (ret); 332 333 err: 334 if (f._bf._base) 335 free(f._bf._base); 336 *str = NULL; 337 return (-1); 338 } 339 #endif /* NetBSD < 1.3H */ 340 341 /* return 1 if all of `s' is numeric */ 342 static int 343 is_number(char *s) 344 { 345 for ( ; *s ; s++) { 346 if (!isdigit(*s)) { 347 return 0; 348 } 349 } 350 return 1; 351 } 352 353 /* 354 * check that the effective uid is 0 - called from funcs which will 355 * modify data and config files. 356 */ 357 static void 358 checkeuid(void) 359 { 360 if (geteuid() != 0) { 361 errx(EXIT_FAILURE, "Program must be run as root"); 362 } 363 } 364 365 /* copy any dot files into the user's home directory */ 366 static int 367 copydotfiles(char *skeldir, int uid, int gid, char *dir) 368 { 369 struct dirent *dp; 370 DIR *dirp; 371 int n; 372 373 if ((dirp = opendir(skeldir)) == NULL) { 374 warn("can't open source . files dir `%s'", skeldir); 375 return 0; 376 } 377 for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) { 378 if (strcmp(dp->d_name, ".") == 0 || 379 strcmp(dp->d_name, "..") == 0) { 380 continue; 381 } 382 n = 1; 383 } 384 (void) closedir(dirp); 385 if (n == 0) { 386 warnx("No \"dot\" initialisation files found"); 387 } else { 388 (void) asystem("cd %s; %s -rw -pe %s . %s", 389 skeldir, PAX, (verbose) ? "-v" : "", dir); 390 } 391 (void) asystem("%s -R -h %d:%d %s", CHOWN, uid, gid, dir); 392 (void) asystem("%s -R u+w %s", CHMOD, dir); 393 return n; 394 } 395 396 /* create a group entry with gid `gid' */ 397 static int 398 creategid(char *group, int gid, char *name) 399 { 400 struct stat st; 401 FILE *from; 402 FILE *to; 403 char buf[MaxEntryLen]; 404 char f[MaxFileNameLen]; 405 int fd; 406 int cc; 407 408 if (getgrnam(group) != NULL) { 409 warnx("group `%s' already exists", group); 410 return 0; 411 } 412 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 413 warn("can't create gid for %s: can't open %s", name, _PATH_GROUP); 414 return 0; 415 } 416 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 417 warn("can't lock `%s'", _PATH_GROUP); 418 } 419 (void) fstat(fileno(from), &st); 420 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 421 if ((fd = mkstemp(f)) < 0) { 422 (void) fclose(from); 423 warn("can't create gid: mkstemp failed"); 424 return 0; 425 } 426 if ((to = fdopen(fd, "w")) == NULL) { 427 (void) fclose(from); 428 (void) close(fd); 429 (void) unlink(f); 430 warn("can't create gid: fdopen `%s' failed", f); 431 return 0; 432 } 433 while ((cc = fread(buf, sizeof(char), sizeof(buf), from)) > 0) { 434 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 435 (void) fclose(from); 436 (void) close(fd); 437 (void) unlink(f); 438 warn("can't create gid: short write to `%s'", f); 439 return 0; 440 } 441 } 442 (void) fprintf(to, "%s:*:%d:%s\n", group, gid, name); 443 (void) fclose(from); 444 (void) fclose(to); 445 if (rename(f, _PATH_GROUP) < 0) { 446 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 447 return 0; 448 } 449 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 450 return 1; 451 } 452 453 /* modify the group entry with name `group' to be newent */ 454 static int 455 modify_gid(char *group, char *newent) 456 { 457 struct stat st; 458 FILE *from; 459 FILE *to; 460 char buf[MaxEntryLen]; 461 char f[MaxFileNameLen]; 462 char *colon; 463 int groupc; 464 int entc; 465 int fd; 466 int cc; 467 468 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 469 warn("can't create gid for %s: can't open %s", group, _PATH_GROUP); 470 return 0; 471 } 472 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 473 warn("can't lock `%s'", _PATH_GROUP); 474 } 475 (void) fstat(fileno(from), &st); 476 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 477 if ((fd = mkstemp(f)) < 0) { 478 (void) fclose(from); 479 warn("can't create gid: mkstemp failed"); 480 return 0; 481 } 482 if ((to = fdopen(fd, "w")) == NULL) { 483 (void) fclose(from); 484 (void) close(fd); 485 (void) unlink(f); 486 warn("can't create gid: fdopen `%s' failed", f); 487 return 0; 488 } 489 groupc = strlen(group); 490 while (fgets(buf, sizeof(buf), from) != NULL) { 491 cc = strlen(buf); 492 if ((colon = strchr(buf, ':')) == NULL) { 493 warn("badly formed entry `%s'", buf); 494 continue; 495 } 496 entc = (int)(colon - buf); 497 if (entc == groupc && strncmp(group, buf, (unsigned) entc) == 0) { 498 if (newent == NULL) { 499 continue; 500 } else { 501 cc = strlen(newent); 502 (void) strlcpy(buf, newent, sizeof(buf)); 503 } 504 } 505 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 506 (void) fclose(from); 507 (void) close(fd); 508 (void) unlink(f); 509 warn("can't create gid: short write to `%s'", f); 510 return 0; 511 } 512 } 513 (void) fclose(from); 514 (void) fclose(to); 515 if (rename(f, _PATH_GROUP) < 0) { 516 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 517 return 0; 518 } 519 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 520 return 1; 521 } 522 523 /* modify the group entries for all `groups', by adding `user' */ 524 static int 525 append_group(char *user, int ngroups, char **groups) 526 { 527 struct group *grp; 528 struct stat st; 529 FILE *from; 530 FILE *to; 531 char buf[MaxEntryLen]; 532 char f[MaxFileNameLen]; 533 char *colon; 534 int groupc; 535 int entc; 536 int fd; 537 int nc; 538 int cc; 539 int i; 540 int j; 541 542 for (i = 0 ; i < ngroups ; i++) { 543 if ((grp = getgrnam(groups[i])) == NULL) { 544 warnx("can't append group `%s' for user `%s'", groups[i], user); 545 } else { 546 for (j = 0 ; grp->gr_mem[j] ; j++) { 547 if (strcmp(user, grp->gr_mem[j]) == 0) { 548 /* already in it */ 549 groups[i] = ""; 550 } 551 } 552 } 553 } 554 if ((from = fopen(_PATH_GROUP, "r")) == NULL) { 555 warn("can't append group for %s: can't open %s", user, _PATH_GROUP); 556 return 0; 557 } 558 if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) { 559 warn("can't lock `%s'", _PATH_GROUP); 560 } 561 (void) fstat(fileno(from), &st); 562 (void) snprintf(f, sizeof(f), "%s.XXXXXX", _PATH_GROUP); 563 if ((fd = mkstemp(f)) < 0) { 564 (void) fclose(from); 565 warn("can't create gid: mkstemp failed"); 566 return 0; 567 } 568 if ((to = fdopen(fd, "w")) == NULL) { 569 (void) fclose(from); 570 (void) close(fd); 571 (void) unlink(f); 572 warn("can't create gid: fdopen `%s' failed", f); 573 return 0; 574 } 575 while (fgets(buf, sizeof(buf), from) != NULL) { 576 cc = strlen(buf); 577 if ((colon = strchr(buf, ':')) == NULL) { 578 warn("badly formed entry `%s'", buf); 579 continue; 580 } 581 entc = (int)(colon - buf); 582 for (i = 0 ; i < ngroups ; i++) { 583 if ((groupc = strlen(groups[i])) == 0) { 584 continue; 585 } 586 if (entc == groupc && strncmp(groups[i], buf, (unsigned) entc) == 0) { 587 if ((nc = snprintf(&buf[cc - 1], 588 sizeof(buf) - cc + 1, 589 "%s%s\n", 590 (buf[cc - 2] == ':') ? "" : ",", 591 user)) < 0) { 592 warnx("Warning: group `%s' entry too long", groups[i]); 593 } 594 cc += nc - 1; 595 } 596 } 597 if (fwrite(buf, sizeof(char), (unsigned) cc, to) != cc) { 598 (void) fclose(from); 599 (void) close(fd); 600 (void) unlink(f); 601 warn("can't create gid: short write to `%s'", f); 602 return 0; 603 } 604 } 605 (void) fclose(from); 606 (void) fclose(to); 607 if (rename(f, _PATH_GROUP) < 0) { 608 warn("can't create gid: can't rename `%s' to `%s'", f, _PATH_GROUP); 609 return 0; 610 } 611 (void) chmod(_PATH_GROUP, st.st_mode & 07777); 612 return 1; 613 } 614 615 /* return 1 if `login' is a valid login name */ 616 static int 617 valid_login(char *login) 618 { 619 char *cp; 620 621 for (cp = login ; *cp ; cp++) { 622 if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-') { 623 return 0; 624 } 625 } 626 return 1; 627 } 628 629 /* return 1 if `group' is a valid group name */ 630 static int 631 valid_group(char *group) 632 { 633 char *cp; 634 635 for (cp = group ; *cp ; cp++) { 636 if (!isalnum(*cp)) { 637 return 0; 638 } 639 } 640 return 1; 641 } 642 643 /* find the next gid in the range lo .. hi */ 644 static int 645 getnextgid(int *gidp, int lo, int hi) 646 { 647 for (*gidp = lo ; *gidp < hi ; *gidp += 1) { 648 if (getgrgid((gid_t)*gidp) == NULL) { 649 return 1; 650 } 651 } 652 return 0; 653 } 654 655 #ifdef EXTENSIONS 656 /* save a range of uids */ 657 static int 658 save_range(user_t *up, char *cp) 659 { 660 int from; 661 int to; 662 int i; 663 664 if (up->u_rsize == 0) { 665 up->u_rsize = 32; 666 NEWARRAY(range_t, up->u_rv, up->u_rsize, return(0)); 667 } else if (up->u_rc == up->u_rsize) { 668 up->u_rsize *= 2; 669 RENEW(range_t, up->u_rv, up->u_rsize, return(0)); 670 } 671 if (up->u_rv && sscanf(cp, "%d..%d", &from, &to) == 2) { 672 for (i = 0 ; i < up->u_rc ; i++) { 673 if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) { 674 break; 675 } 676 } 677 if (i == up->u_rc) { 678 up->u_rv[up->u_rc].r_from = from; 679 up->u_rv[up->u_rc].r_to = to; 680 up->u_rc += 1; 681 } 682 } else { 683 warnx("Bad range `%s'", cp); 684 return 0; 685 } 686 return 1; 687 } 688 #endif 689 690 /* set the defaults in the defaults file */ 691 static int 692 setdefaults(user_t *up) 693 { 694 char template[MaxFileNameLen]; 695 FILE *fp; 696 int ret; 697 int fd; 698 int i; 699 700 (void) snprintf(template, sizeof(template), "%s.XXXXXX", CONFFILE); 701 if ((fd = mkstemp(template)) < 0) { 702 warnx("can't mkstemp `%s' for writing", CONFFILE); 703 return 0; 704 } 705 if ((fp = fdopen(fd, "w")) == NULL) { 706 warn("can't fdopen `%s' for writing", CONFFILE); 707 return 0; 708 } 709 ret = 1; 710 if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 || 711 fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 || 712 fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 || 713 fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 || 714 fprintf(fp, "inactive\t%d\n", up->u_inactive) <= 0 || 715 fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 || 716 fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) { 717 warn("can't write to `%s'", CONFFILE); 718 ret = 0; 719 } 720 #ifdef EXTENSIONS 721 for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) { 722 if (fprintf(fp, "range\t\t%d..%d\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) { 723 warn("can't write to `%s'", CONFFILE); 724 ret = 0; 725 } 726 } 727 #endif 728 (void) fclose(fp); 729 if (ret) { 730 ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0)); 731 } 732 return ret; 733 } 734 735 /* read the defaults file */ 736 static void 737 read_defaults(user_t *up) 738 { 739 struct stat st; 740 size_t lineno; 741 size_t len; 742 FILE *fp; 743 char *cp; 744 char *s; 745 746 memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP)); 747 memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR)); 748 memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR)); 749 memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL)); 750 memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT)); 751 up->u_rsize = 16; 752 NEWARRAY(range_t, up->u_rv, up->u_rsize, exit(1)); 753 up->u_inactive = DEF_INACTIVE; 754 up->u_expire = DEF_EXPIRE; 755 if ((fp = fopen(CONFFILE, "r")) == NULL) { 756 if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) { 757 warn("can't create `%s' defaults file", CONFFILE); 758 } 759 fp = fopen(CONFFILE, "r"); 760 } 761 if (fp != NULL) { 762 while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { 763 if (strncmp(s, "group", 5) == 0) { 764 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 765 } 766 memsave(&up->u_primgrp, cp, strlen(cp)); 767 } else if (strncmp(s, "base_dir", 8) == 0) { 768 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 769 } 770 memsave(&up->u_basedir, cp, strlen(cp)); 771 } else if (strncmp(s, "skel_dir", 8) == 0) { 772 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 773 } 774 memsave(&up->u_skeldir, cp, strlen(cp)); 775 } else if (strncmp(s, "shell", 5) == 0) { 776 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 777 } 778 memsave(&up->u_shell, cp, strlen(cp)); 779 } else if (strncmp(s, "inactive", 8) == 0) { 780 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 781 } 782 up->u_inactive = atoi(cp); 783 #ifdef EXTENSIONS 784 } else if (strncmp(s, "range", 5) == 0) { 785 for (cp = s + 5 ; *cp && isspace(*cp) ; cp++) { 786 } 787 (void) save_range(up, cp); 788 #endif 789 #ifdef EXTENSIONS 790 } else if (strncmp(s, "preserve", 8) == 0) { 791 for (cp = s + 8 ; *cp && isspace(*cp) ; cp++) { 792 } 793 up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 : 794 (strncmp(cp, "yes", 3) == 0) ? 1 : 795 atoi(cp); 796 #endif 797 } else if (strncmp(s, "expire", 6) == 0) { 798 for (cp = s + 6 ; *cp && isspace(*cp) ; cp++) { 799 } 800 if (strcmp(cp, UNSET_EXPIRY) == 0) { 801 if (up->u_expire) { 802 FREE(up->u_expire); 803 } 804 up->u_expire = NULL; 805 } else { 806 memsave(&up->u_expire, cp, strlen(cp)); 807 } 808 } 809 (void) free(s); 810 } 811 (void) fclose(fp); 812 } 813 if (up->u_rc == 0) { 814 up->u_rv[up->u_rc].r_from = DEF_LOWUID; 815 up->u_rv[up->u_rc].r_to = DEF_HIGHUID; 816 up->u_rc += 1; 817 } 818 up->u_defrc = up->u_rc; 819 } 820 821 /* return the next valid unused uid */ 822 static int 823 getnextuid(int sync_uid_gid, int *uid, int low_uid, int high_uid) 824 { 825 for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) { 826 if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) { 827 if (sync_uid_gid) { 828 if (getgrgid((gid_t)(*uid)) == NULL) { 829 return 1; 830 } 831 } else { 832 return 1; 833 } 834 } 835 } 836 return 0; 837 } 838 839 /* add a user */ 840 static int 841 adduser(char *login, user_t *up) 842 { 843 struct group *grp; 844 struct stat st; 845 struct tm tm; 846 time_t expire; 847 char password[PasswordLength + 1]; 848 char home[MaxFileNameLen]; 849 char buf[MaxFileNameLen]; 850 int sync_uid_gid; 851 int masterfd; 852 int ptmpfd; 853 int gid; 854 int cc; 855 int i; 856 857 if (!valid_login(login)) { 858 errx(EXIT_FAILURE, "`%s' is not a valid login name", login); 859 } 860 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 861 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD); 862 } 863 if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 864 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD); 865 } 866 pw_init(); 867 if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 868 (void) close(masterfd); 869 err(EXIT_FAILURE, "can't obtain pw_lock"); 870 } 871 while ((cc = read(masterfd, buf, sizeof(buf))) > 0) { 872 if (write(ptmpfd, buf, (size_t)(cc)) != cc) { 873 (void) close(masterfd); 874 (void) close(ptmpfd); 875 (void) pw_abort(); 876 err(EXIT_FAILURE, "short write to /etc/ptmp (not %d chars)", cc); 877 } 878 } 879 /* if no uid was specified, get next one in [low_uid..high_uid] range */ 880 sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0); 881 if (up->u_uid == -1) { 882 for (i = 0 ; i < up->u_rc ; i++) { 883 if (getnextuid(sync_uid_gid, &up->u_uid, up->u_rv[i].r_from, up->u_rv[i].r_to)) { 884 break; 885 } 886 } 887 if (i == up->u_rc) { 888 (void) close(ptmpfd); 889 (void) pw_abort(); 890 errx(EXIT_FAILURE, "can't get next uid for %d", up->u_uid); 891 } 892 } 893 /* check uid isn't already allocated */ 894 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 895 (void) close(ptmpfd); 896 (void) pw_abort(); 897 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 898 } 899 /* if -g=uid was specified, check gid is unused */ 900 if (sync_uid_gid) { 901 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 902 (void) close(ptmpfd); 903 (void) pw_abort(); 904 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 905 } 906 gid = up->u_uid; 907 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 908 gid = grp->gr_gid; 909 } else if (is_number(up->u_primgrp) && 910 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 911 gid = grp->gr_gid; 912 } else { 913 (void) close(ptmpfd); 914 (void) pw_abort(); 915 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 916 } 917 /* check name isn't already in use */ 918 if (!(up->u_flags & F_DUPUID) && getpwnam(login) != NULL) { 919 (void) close(ptmpfd); 920 (void) pw_abort(); 921 errx(EXIT_FAILURE, "already a `%s' user", login); 922 } 923 if (up->u_flags & F_HOMEDIR) { 924 (void) strlcpy(home, up->u_home, sizeof(home)); 925 } else { 926 /* if home directory hasn't been given, make it up */ 927 (void) snprintf(home, sizeof(home), "%s/%s", up->u_basedir, login); 928 } 929 expire = 0; 930 if (up->u_expire != NULL) { 931 (void) memset(&tm, 0, sizeof(tm)); 932 if (strptime(up->u_expire, "%c", &tm) == NULL) { 933 warnx("invalid time format `%s'", optarg); 934 } else { 935 expire = mktime(&tm); 936 } 937 } 938 if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR)) { 939 warnx("Warning: home directory `%s' doesn't exist, and -m was not specified", 940 home); 941 } 942 password[PasswordLength] = '\0'; 943 if (up->u_password != NULL && 944 strlen(up->u_password) == PasswordLength) { 945 (void) memcpy(password, up->u_password, PasswordLength); 946 } else { 947 (void) memset(password, '*', PasswordLength); 948 if (up->u_password != NULL) { 949 warnx("Password `%s' is invalid: setting it to `%s'", 950 up->u_password, password); 951 } 952 } 953 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:%d::%d:%ld:%s:%s:%s\n", 954 login, 955 password, 956 up->u_uid, 957 gid, 958 up->u_inactive, 959 (long) expire, 960 up->u_comment, 961 home, 962 up->u_shell); 963 if (write(ptmpfd, buf, (size_t) cc) != cc) { 964 (void) close(ptmpfd); 965 (void) pw_abort(); 966 err(EXIT_FAILURE, "can't add `%s'", buf); 967 } 968 if (up->u_flags & F_MKDIR) { 969 if (lstat(home, &st) == 0) { 970 (void) close(ptmpfd); 971 (void) pw_abort(); 972 errx(EXIT_FAILURE, "home directory `%s' already exists", home); 973 } else { 974 if (asystem("%s -p %s", MKDIR, home) != 0) { 975 (void) close(ptmpfd); 976 (void) pw_abort(); 977 err(EXIT_FAILURE, "can't mkdir `%s'", home); 978 } 979 (void) copydotfiles(up->u_skeldir, up->u_uid, gid, home); 980 } 981 } 982 if (strcmp(up->u_primgrp, "=uid") == 0 && 983 getgrnam(login) == NULL && 984 !creategid(login, gid, login)) { 985 (void) close(ptmpfd); 986 (void) pw_abort(); 987 err(EXIT_FAILURE, "can't create gid %d for login name %s", gid, login); 988 } 989 if (up->u_groupc > 0 && !append_group(login, up->u_groupc, up->u_groupv)) { 990 (void) close(ptmpfd); 991 (void) pw_abort(); 992 errx(EXIT_FAILURE, "can't append `%s' to new groups", login); 993 } 994 (void) close(ptmpfd); 995 if (pw_mkdb() < 0) { 996 err(EXIT_FAILURE, "pw_mkdb failed"); 997 } 998 return 1; 999 } 1000 1001 /* modify a user */ 1002 static int 1003 moduser(char *login, char *newlogin, user_t *up) 1004 { 1005 struct passwd *pwp; 1006 struct group *grp; 1007 struct tm tm; 1008 const char *homedir; 1009 char newdir[MaxFileNameLen]; 1010 size_t colonc, len, loginc; 1011 FILE *master; 1012 char *buf, *colon, *line; 1013 int masterfd; 1014 int ptmpfd; 1015 1016 if (!valid_login(newlogin)) { 1017 errx(EXIT_FAILURE, "`%s' is not a valid login name", login); 1018 } 1019 if ((pwp = getpwnam(login)) == NULL) { 1020 errx(EXIT_FAILURE, "No such user `%s'", login); 1021 } 1022 /* keep dir name in case we need it for '-m' */ 1023 homedir = pwp->pw_dir; 1024 1025 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) { 1026 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD); 1027 } 1028 if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) { 1029 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD); 1030 } 1031 pw_init(); 1032 if ((ptmpfd = pw_lock(WAITSECS)) < 0) { 1033 (void) close(masterfd); 1034 err(EXIT_FAILURE, "can't obtain pw_lock"); 1035 } 1036 if ((master = fdopen(masterfd, "r")) == NULL) { 1037 (void) close(masterfd); 1038 (void) close(ptmpfd); 1039 (void) pw_abort(); 1040 err(EXIT_FAILURE, "can't fdopen fd for %s", _PATH_MASTERPASSWD); 1041 } 1042 if (up != NULL) { 1043 if (up->u_flags & F_USERNAME) { 1044 /* if changing name, check new name isn't already in use */ 1045 if (strcmp(login, newlogin) != 0 && getpwnam(newlogin) != NULL) { 1046 (void) close(ptmpfd); 1047 (void) pw_abort(); 1048 errx(EXIT_FAILURE, "already a `%s' user", newlogin); 1049 } 1050 pwp->pw_name = newlogin; 1051 1052 /* 1053 * Provide a new directory name in case the 1054 * home directory is to be moved. 1055 */ 1056 if (up->u_flags & F_MKDIR) { 1057 snprintf(newdir, sizeof(newdir), "%s/%s", up->u_basedir, newlogin); 1058 pwp->pw_dir = newdir; 1059 } 1060 } 1061 if (up->u_flags & F_PASSWORD) { 1062 if (up->u_password != NULL && strlen(up->u_password) == PasswordLength) 1063 pwp->pw_passwd = up->u_password; 1064 } 1065 if (up->u_flags & F_UID) { 1066 /* check uid isn't already allocated */ 1067 if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) { 1068 (void) close(ptmpfd); 1069 (void) pw_abort(); 1070 errx(EXIT_FAILURE, "uid %d is already in use", up->u_uid); 1071 } 1072 pwp->pw_uid = up->u_uid; 1073 } 1074 if (up->u_flags & F_GROUP) { 1075 /* if -g=uid was specified, check gid is unused */ 1076 if (strcmp(up->u_primgrp, "=uid") == 0) { 1077 if (getgrgid((gid_t)(up->u_uid)) != NULL) { 1078 (void) close(ptmpfd); 1079 (void) pw_abort(); 1080 errx(EXIT_FAILURE, "gid %d is already in use", up->u_uid); 1081 } 1082 pwp->pw_gid = up->u_uid; 1083 } else if ((grp = getgrnam(up->u_primgrp)) != NULL) { 1084 pwp->pw_gid = grp->gr_gid; 1085 } else if (is_number(up->u_primgrp) && 1086 (grp = getgrgid((gid_t)atoi(up->u_primgrp))) != NULL) { 1087 pwp->pw_gid = grp->gr_gid; 1088 } else { 1089 (void) close(ptmpfd); 1090 (void) pw_abort(); 1091 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp); 1092 } 1093 } 1094 if (up->u_flags |= F_INACTIVE) 1095 pwp->pw_change = up->u_inactive; 1096 if (up->u_flags & F_EXPIRE) { 1097 (void) memset(&tm, 0, sizeof(tm)); 1098 if (strptime(up->u_expire, "%c", &tm) == NULL) 1099 warnx("invalid time format `%s'", optarg); 1100 else 1101 pwp->pw_expire = mktime(&tm); 1102 } 1103 if (up->u_flags & F_COMMENT) 1104 pwp->pw_gecos = up->u_comment; 1105 if (up->u_flags & F_HOMEDIR) 1106 pwp->pw_dir = up->u_home; 1107 if (up->u_flags & F_SHELL) 1108 pwp->pw_shell = up->u_shell; 1109 } 1110 loginc = strlen(login); 1111 while ((line = fgetln(master, &len)) != NULL) { 1112 if ((colon = strchr(line, ':')) == NULL) { 1113 warnx("Malformed entry `%s'. Skipping", line); 1114 continue; 1115 } 1116 colonc = (size_t)(colon - line); 1117 if (strncmp(login, line, loginc) == 0 && loginc == colonc) { 1118 if (up != NULL) { 1119 len = (int)asprintf(&buf, "%s:%s:%d:%d::%ld:%ld:%s:%s:%s\n", 1120 newlogin, 1121 pwp->pw_passwd, 1122 pwp->pw_uid, 1123 pwp->pw_gid, 1124 (long)pwp->pw_change, 1125 (long)pwp->pw_expire, 1126 pwp->pw_gecos, 1127 pwp->pw_dir, 1128 pwp->pw_shell); 1129 if (write(ptmpfd, buf, len) != len) { 1130 (void) close(ptmpfd); 1131 (void) pw_abort(); 1132 err(EXIT_FAILURE, "can't add `%s'", buf); 1133 } 1134 } 1135 /* } else if (write(ptmpfd, line, len) != len) { */ 1136 } else if ((colonc = write(ptmpfd, line, len)) != len) { 1137 (void) close(masterfd); 1138 (void) close(ptmpfd); 1139 (void) pw_abort(); 1140 err(EXIT_FAILURE, "short write to /etc/ptmp (%lld not %lld chars)", 1141 (long long)colonc, 1142 (long long)len); 1143 } 1144 } 1145 if (up != NULL) { 1146 if ((up->u_flags & F_MKDIR) && 1147 asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) { 1148 (void) close(ptmpfd); 1149 (void) pw_abort(); 1150 err(EXIT_FAILURE, "can't move `%s' to `%s'", 1151 homedir, pwp->pw_dir); 1152 } 1153 if (up->u_groupc > 0 && 1154 !append_group(newlogin, up->u_groupc, up->u_groupv)) { 1155 (void) close(ptmpfd); 1156 (void) pw_abort(); 1157 errx(EXIT_FAILURE, "can't append `%s' to new groups", 1158 newlogin); 1159 } 1160 } 1161 (void) close(ptmpfd); 1162 if (pw_mkdb() < 0) { 1163 err(EXIT_FAILURE, "pw_mkdb failed"); 1164 } 1165 return 1; 1166 } 1167 1168 1169 #ifdef EXTENSIONS 1170 /* see if we can find out the user struct */ 1171 static struct passwd * 1172 find_user_info(char *name) 1173 { 1174 struct passwd *pwp; 1175 1176 if ((pwp = getpwnam(name)) != NULL) { 1177 return pwp; 1178 } 1179 if (is_number(name) && (pwp = getpwuid((uid_t)atoi(name))) != NULL) { 1180 return pwp; 1181 } 1182 return NULL; 1183 } 1184 #endif 1185 1186 #ifdef EXTENSIONS 1187 /* see if we can find out the group struct */ 1188 static struct group * 1189 find_group_info(char *name) 1190 { 1191 struct group *grp; 1192 1193 if ((grp = getgrnam(name)) != NULL) { 1194 return grp; 1195 } 1196 if (is_number(name) && (grp = getgrgid((gid_t)atoi(name))) != NULL) { 1197 return grp; 1198 } 1199 return NULL; 1200 } 1201 #endif 1202 1203 /* print out usage message, and then exit */ 1204 void 1205 usermgmt_usage(const char *prog) 1206 { 1207 if (strcmp(prog, "useradd") == 0) { 1208 (void) fprintf(stderr, "Usage: %s -D [-b basedir] [-e expiry] " 1209 "[-f inactive] [-g group]\n\t[-r lowuid..highuid] " 1210 "[-s shell]\n", prog); 1211 (void) fprintf(stderr, "Usage: %s [-G group] [-b basedir] " 1212 "[-c comment] [-d homedir] [-e expiry]\n\t[-f inactive] " 1213 "[-g group] [-k skeletondir] [-m] [-o] [-p password]\n" 1214 "\t[-r lowuid..highuid] [-s shell]\n\t[-u uid] [-v] user\n", 1215 prog); 1216 } else if (strcmp(prog, "usermod") == 0) { 1217 (void) fprintf(stderr, "Usage: %s [-G group] [-c comment] " 1218 "[-d homedir] [-e expire] [-f inactive]\n\t[-g group] " 1219 "[-l newname] [-m] [-o] [-p password] [-s shell] [-u uid]\n" 1220 "\t[-v] user\n", prog); 1221 } else if (strcmp(prog, "userdel") == 0) { 1222 (void) fprintf(stderr, "Usage: %s -D [-p preserve]\n", prog); 1223 (void) fprintf(stderr, 1224 "Usage: %s [-p preserve] [-r] [-v] user\n", prog); 1225 #ifdef EXTENSIONS 1226 } else if (strcmp(prog, "userinfo") == 0) { 1227 (void) fprintf(stderr, "Usage: %s [-e] [-v] user\n", prog); 1228 #endif 1229 } else if (strcmp(prog, "groupadd") == 0) { 1230 (void) fprintf(stderr, "Usage: %s [-g gid] [-o] [-v] group\n", 1231 prog); 1232 } else if (strcmp(prog, "groupdel") == 0) { 1233 (void) fprintf(stderr, "Usage: %s [-v] group\n", prog); 1234 } else if (strcmp(prog, "groupmod") == 0) { 1235 (void) fprintf(stderr, 1236 "Usage: %s [-g gid] [-o] [-n newname] [-v] group\n", prog); 1237 } else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) { 1238 (void) fprintf(stderr, 1239 "Usage: %s ( add | del | mod | info ) ...\n", prog); 1240 #ifdef EXTENSIONS 1241 } else if (strcmp(prog, "groupinfo") == 0) { 1242 (void) fprintf(stderr, "Usage: %s [-e] [-v] group\n", prog); 1243 #endif 1244 } 1245 exit(EXIT_FAILURE); 1246 /* NOTREACHED */ 1247 } 1248 1249 #ifdef EXTENSIONS 1250 #define ADD_OPT_EXTENSIONS "p:r:v" 1251 #else 1252 #define ADD_OPT_EXTENSIONS 1253 #endif 1254 1255 int 1256 useradd(int argc, char **argv) 1257 { 1258 user_t u; 1259 int defaultfield; 1260 int bigD; 1261 int c; 1262 int i; 1263 1264 (void) memset(&u, 0, sizeof(u)); 1265 read_defaults(&u); 1266 u.u_uid = -1; 1267 defaultfield = bigD = 0; 1268 while ((c = getopt(argc, argv, "DG:b:c:d:e:f:g:k:mou:s:" ADD_OPT_EXTENSIONS)) != -1) { 1269 switch(c) { 1270 case 'D': 1271 bigD = 1; 1272 break; 1273 case 'G': 1274 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1275 u.u_groupc < NGROUPS_MAX) { 1276 if (u.u_groupv[u.u_groupc][0] != 0) { 1277 u.u_groupc++; 1278 } 1279 } 1280 if (optarg != NULL) { 1281 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1282 } 1283 break; 1284 case 'b': 1285 defaultfield = 1; 1286 memsave(&u.u_basedir, optarg, strlen(optarg)); 1287 break; 1288 case 'c': 1289 memsave(&u.u_comment, optarg, strlen(optarg)); 1290 break; 1291 case 'd': 1292 memsave(&u.u_home, optarg, strlen(optarg)); 1293 u.u_flags |= F_HOMEDIR; 1294 break; 1295 case 'e': 1296 defaultfield = 1; 1297 memsave(&u.u_expire, optarg, strlen(optarg)); 1298 break; 1299 case 'f': 1300 defaultfield = 1; 1301 u.u_inactive = atoi(optarg); 1302 break; 1303 case 'g': 1304 defaultfield = 1; 1305 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1306 break; 1307 case 'k': 1308 memsave(&u.u_skeldir, optarg, strlen(optarg)); 1309 break; 1310 case 'm': 1311 u.u_flags |= F_MKDIR; 1312 break; 1313 case 'o': 1314 u.u_flags |= F_DUPUID; 1315 break; 1316 #ifdef EXTENSIONS 1317 case 'p': 1318 memsave(&u.u_password, optarg, strlen(optarg)); 1319 break; 1320 #endif 1321 #ifdef EXTENSIONS 1322 case 'r': 1323 defaultfield = 1; 1324 (void) save_range(&u, optarg); 1325 break; 1326 #endif 1327 case 's': 1328 defaultfield = 1; 1329 memsave(&u.u_shell, optarg, strlen(optarg)); 1330 break; 1331 case 'u': 1332 if (!is_number(optarg)) { 1333 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1334 } 1335 u.u_uid = atoi(optarg); 1336 break; 1337 #ifdef EXTENSIONS 1338 case 'v': 1339 verbose = 1; 1340 break; 1341 #endif 1342 } 1343 } 1344 if (bigD) { 1345 if (defaultfield) { 1346 checkeuid(); 1347 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1348 } 1349 (void) printf("group\t\t%s\n", u.u_primgrp); 1350 (void) printf("base_dir\t%s\n", u.u_basedir); 1351 (void) printf("skel_dir\t%s\n", u.u_skeldir); 1352 (void) printf("shell\t\t%s\n", u.u_shell); 1353 (void) printf("inactive\t%d\n", u.u_inactive); 1354 (void) printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire); 1355 #ifdef EXTENSIONS 1356 for (i = 0 ; i < u.u_rc ; i++) { 1357 (void) printf("range\t\t%d..%d\n", u.u_rv[i].r_from, u.u_rv[i].r_to); 1358 } 1359 #endif 1360 return EXIT_SUCCESS; 1361 } 1362 if (argc == optind) { 1363 usermgmt_usage("useradd"); 1364 } 1365 checkeuid(); 1366 return adduser(argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1367 } 1368 1369 #ifdef EXTENSIONS 1370 #define MOD_OPT_EXTENSIONS "p:v" 1371 #else 1372 #define MOD_OPT_EXTENSIONS 1373 #endif 1374 1375 int 1376 usermod(int argc, char **argv) 1377 { 1378 user_t u; 1379 char newuser[MaxUserNameLen + 1]; 1380 int c, have_new_user; 1381 1382 (void) memset(&u, 0, sizeof(u)); 1383 (void) memset(newuser, 0, sizeof(newuser)); 1384 read_defaults(&u); 1385 have_new_user = 0; 1386 while ((c = getopt(argc, argv, "G:c:d:e:f:g:l:mos:u:" MOD_OPT_EXTENSIONS)) != -1) { 1387 switch(c) { 1388 case 'G': 1389 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL && 1390 u.u_groupc < NGROUPS_MAX) { 1391 if (u.u_groupv[u.u_groupc][0] != 0) { 1392 u.u_groupc++; 1393 } 1394 } 1395 if (optarg != NULL) { 1396 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX); 1397 } 1398 u.u_flags |= F_SECGROUP; 1399 break; 1400 case 'c': 1401 memsave(&u.u_comment, optarg, strlen(optarg)); 1402 u.u_flags |= F_COMMENT; 1403 break; 1404 case 'd': 1405 memsave(&u.u_home, optarg, strlen(optarg)); 1406 u.u_flags |= F_HOMEDIR; 1407 break; 1408 case 'e': 1409 memsave(&u.u_expire, optarg, strlen(optarg)); 1410 u.u_flags |= F_EXPIRE; 1411 break; 1412 case 'f': 1413 u.u_inactive = atoi(optarg); 1414 u.u_flags |= F_INACTIVE; 1415 break; 1416 case 'g': 1417 memsave(&u.u_primgrp, optarg, strlen(optarg)); 1418 u.u_flags |= F_GROUP; 1419 break; 1420 case 'l': 1421 (void) strlcpy(newuser, optarg, sizeof(newuser)); 1422 have_new_user = 1; 1423 u.u_flags |= F_USERNAME; 1424 break; 1425 case 'm': 1426 u.u_flags |= F_MKDIR; 1427 break; 1428 case 'o': 1429 u.u_flags |= F_DUPUID; 1430 break; 1431 #ifdef EXTENSIONS 1432 case 'p': 1433 memsave(&u.u_password, optarg, strlen(optarg)); 1434 u.u_flags |= F_PASSWORD; 1435 break; 1436 #endif 1437 case 's': 1438 memsave(&u.u_shell, optarg, strlen(optarg)); 1439 u.u_flags |= F_SHELL; 1440 break; 1441 case 'u': 1442 if (!is_number(optarg)) { 1443 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric"); 1444 } 1445 u.u_uid = atoi(optarg); 1446 u.u_flags |= F_UID; 1447 break; 1448 #ifdef EXTENSIONS 1449 case 'v': 1450 verbose = 1; 1451 break; 1452 #endif 1453 } 1454 } 1455 if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) && 1456 !(u.u_flags & F_USERNAME)) { 1457 warnx("option 'm' useless without 'd' or 'l' -- ignored"); 1458 u.u_flags &= !F_MKDIR; 1459 } 1460 1461 if (argc == optind) { 1462 usermgmt_usage("usermod"); 1463 } 1464 checkeuid(); 1465 return moduser(argv[optind], (have_new_user) ? newuser : argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1466 } 1467 1468 #ifdef EXTENSIONS 1469 #define DEL_OPT_EXTENSIONS "Dp:v" 1470 #else 1471 #define DEL_OPT_EXTENSIONS 1472 #endif 1473 1474 int 1475 userdel(int argc, char **argv) 1476 { 1477 struct passwd *pwp; 1478 user_t u; 1479 char password[PasswordLength + 1]; 1480 int defaultfield; 1481 int rmhome; 1482 int bigD; 1483 int c; 1484 1485 (void) memset(&u, 0, sizeof(u)); 1486 read_defaults(&u); 1487 defaultfield = bigD = rmhome = 0; 1488 while ((c = getopt(argc, argv, "r" DEL_OPT_EXTENSIONS)) != -1) { 1489 switch(c) { 1490 #ifdef EXTENSIONS 1491 case 'D': 1492 bigD = 1; 1493 break; 1494 #endif 1495 #ifdef EXTENSIONS 1496 case 'p': 1497 defaultfield = 1; 1498 u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 : 1499 (strcmp(optarg, "yes") == 0) ? 1 : 1500 atoi(optarg); 1501 break; 1502 #endif 1503 case 'r': 1504 rmhome = 1; 1505 break; 1506 #ifdef EXTENSIONS 1507 case 'v': 1508 verbose = 1; 1509 break; 1510 #endif 1511 } 1512 } 1513 #ifdef EXTENSIONS 1514 if (bigD) { 1515 if (defaultfield) { 1516 checkeuid(); 1517 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE; 1518 } 1519 (void) printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false"); 1520 return EXIT_SUCCESS; 1521 } 1522 #endif 1523 if (argc == optind) { 1524 usermgmt_usage("userdel"); 1525 } 1526 checkeuid(); 1527 if ((pwp = getpwnam(argv[optind])) == NULL) { 1528 warnx("No such user `%s'", argv[optind]); 1529 return EXIT_FAILURE; 1530 } 1531 if (rmhome) 1532 (void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir); 1533 if (u.u_preserve) { 1534 memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN)); 1535 (void) memset(password, '*', PasswordLength); 1536 password[PasswordLength] = '\0'; 1537 memsave(&u.u_password, password, PasswordLength); 1538 u.u_flags |= F_PASSWORD; 1539 return moduser(argv[optind], argv[optind], &u) ? EXIT_SUCCESS : EXIT_FAILURE; 1540 } 1541 return moduser(argv[optind], argv[optind], NULL) ? EXIT_SUCCESS : EXIT_FAILURE; 1542 } 1543 1544 #ifdef EXTENSIONS 1545 #define GROUP_ADD_OPT_EXTENSIONS "v" 1546 #else 1547 #define GROUP_ADD_OPT_EXTENSIONS 1548 #endif 1549 1550 /* add a group */ 1551 int 1552 groupadd(int argc, char **argv) 1553 { 1554 int dupgid; 1555 int gid; 1556 int c; 1557 1558 gid = -1; 1559 dupgid = 0; 1560 while ((c = getopt(argc, argv, "g:o" GROUP_ADD_OPT_EXTENSIONS)) != -1) { 1561 switch(c) { 1562 case 'g': 1563 if (!is_number(optarg)) { 1564 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1565 } 1566 gid = atoi(optarg); 1567 break; 1568 case 'o': 1569 dupgid = 1; 1570 break; 1571 #ifdef EXTENSIONS 1572 case 'v': 1573 verbose = 1; 1574 break; 1575 #endif 1576 } 1577 } 1578 if (argc == optind) { 1579 usermgmt_usage("groupadd"); 1580 } 1581 checkeuid(); 1582 if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) { 1583 err(EXIT_FAILURE, "can't add group: can't get next gid"); 1584 } 1585 if (!dupgid && getgrgid((gid_t) gid) != NULL) { 1586 errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid); 1587 } 1588 if (!valid_group(argv[optind])) { 1589 warnx("warning - invalid group name `%s'", argv[optind]); 1590 } 1591 if (!creategid(argv[optind], gid, "")) { 1592 err(EXIT_FAILURE, "can't add group: problems with %s file", _PATH_GROUP); 1593 } 1594 return EXIT_SUCCESS; 1595 } 1596 1597 #ifdef EXTENSIONS 1598 #define GROUP_DEL_OPT_EXTENSIONS "v" 1599 #else 1600 #define GROUP_DEL_OPT_EXTENSIONS 1601 #endif 1602 1603 /* remove a group */ 1604 int 1605 groupdel(int argc, char **argv) 1606 { 1607 int c; 1608 1609 while ((c = getopt(argc, argv, "" GROUP_DEL_OPT_EXTENSIONS)) != -1) { 1610 switch(c) { 1611 #ifdef EXTENSIONS 1612 case 'v': 1613 verbose = 1; 1614 break; 1615 #endif 1616 } 1617 } 1618 if (argc == optind) { 1619 usermgmt_usage("groupdel"); 1620 } 1621 checkeuid(); 1622 if (!modify_gid(argv[optind], NULL)) { 1623 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1624 } 1625 return EXIT_SUCCESS; 1626 } 1627 1628 #ifdef EXTENSIONS 1629 #define GROUP_MOD_OPT_EXTENSIONS "v" 1630 #else 1631 #define GROUP_MOD_OPT_EXTENSIONS 1632 #endif 1633 1634 /* modify a group */ 1635 int 1636 groupmod(int argc, char **argv) 1637 { 1638 struct group *grp; 1639 char buf[MaxEntryLen]; 1640 char *newname; 1641 char **cpp; 1642 int dupgid; 1643 int gid; 1644 int cc; 1645 int c; 1646 1647 gid = -1; 1648 dupgid = 0; 1649 newname = NULL; 1650 while ((c = getopt(argc, argv, "g:on:" GROUP_MOD_OPT_EXTENSIONS)) != -1) { 1651 switch(c) { 1652 case 'g': 1653 if (!is_number(optarg)) { 1654 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric"); 1655 } 1656 gid = atoi(optarg); 1657 break; 1658 case 'o': 1659 dupgid = 1; 1660 break; 1661 case 'n': 1662 memsave(&newname, optarg, strlen(optarg)); 1663 break; 1664 #ifdef EXTENSIONS 1665 case 'v': 1666 verbose = 1; 1667 break; 1668 #endif 1669 } 1670 } 1671 if (argc == optind) { 1672 usermgmt_usage("groupmod"); 1673 } 1674 checkeuid(); 1675 if (gid < 0 && newname == NULL) { 1676 err(EXIT_FAILURE, "Nothing to change"); 1677 } 1678 if (dupgid && gid < 0) { 1679 err(EXIT_FAILURE, "Duplicate which gid?"); 1680 } 1681 if ((grp = getgrnam(argv[optind])) == NULL) { 1682 err(EXIT_FAILURE, "can't find group `%s' to modify", argv[optind]); 1683 } 1684 if (newname != NULL && !valid_group(newname)) { 1685 warn("warning - invalid group name `%s'", newname); 1686 } 1687 cc = snprintf(buf, sizeof(buf), "%s:%s:%d:", 1688 (newname) ? newname : grp->gr_name, 1689 grp->gr_passwd, 1690 (gid < 0) ? grp->gr_gid : gid); 1691 for (cpp = grp->gr_mem ; *cpp && cc < sizeof(buf) ; cpp++) { 1692 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s%s", *cpp, 1693 (cpp[1] == NULL) ? "" : ","); 1694 } 1695 cc += snprintf(&buf[cc], sizeof(buf) - cc, "\n"); 1696 if (!modify_gid(argv[optind], buf)) { 1697 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP); 1698 } 1699 return EXIT_SUCCESS; 1700 } 1701 1702 #ifdef EXTENSIONS 1703 /* display user information */ 1704 int 1705 userinfo(int argc, char **argv) 1706 { 1707 struct passwd *pwp; 1708 struct group *grp; 1709 char buf[MaxEntryLen]; 1710 char **cpp; 1711 int exists; 1712 int cc; 1713 int i; 1714 1715 exists = 0; 1716 while ((i = getopt(argc, argv, "ev")) != -1) { 1717 switch(i) { 1718 case 'e': 1719 exists = 1; 1720 break; 1721 case 'v': 1722 verbose = 1; 1723 break; 1724 } 1725 } 1726 if (argc == optind) { 1727 usermgmt_usage("userinfo"); 1728 } 1729 pwp = find_user_info(argv[optind]); 1730 if (exists) { 1731 exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE); 1732 } 1733 if (pwp == NULL) { 1734 errx(EXIT_FAILURE, "can't find user `%s'", argv[optind]); 1735 } 1736 (void) printf("login\t%s\n", pwp->pw_name); 1737 (void) printf("passwd\t%s\n", pwp->pw_passwd); 1738 (void) printf("uid\t%d\n", pwp->pw_uid); 1739 for (cc = 0 ; (grp = getgrent()) != NULL ; ) { 1740 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1741 if (strcmp(*cpp, argv[optind]) == 0 && grp->gr_gid != pwp->pw_gid) { 1742 cc += snprintf(&buf[cc], sizeof(buf) - cc, "%s ", grp->gr_name); 1743 } 1744 } 1745 } 1746 if ((grp = getgrgid(pwp->pw_gid)) == NULL) { 1747 (void) printf("groups\t%d %s\n", pwp->pw_gid, buf); 1748 } else { 1749 (void) printf("groups\t%s %s\n", grp->gr_name, buf); 1750 } 1751 (void) printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n"); 1752 (void) printf("class\t%s\n", pwp->pw_class); 1753 (void) printf("gecos\t%s\n", pwp->pw_gecos); 1754 (void) printf("dir\t%s\n", pwp->pw_dir); 1755 (void) printf("shell\t%s\n", pwp->pw_shell); 1756 (void) printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n"); 1757 return EXIT_SUCCESS; 1758 } 1759 #endif 1760 1761 #ifdef EXTENSIONS 1762 /* display user information */ 1763 int 1764 groupinfo(int argc, char **argv) 1765 { 1766 struct group *grp; 1767 char **cpp; 1768 int exists; 1769 int i; 1770 1771 exists = 0; 1772 while ((i = getopt(argc, argv, "ev")) != -1) { 1773 switch(i) { 1774 case 'e': 1775 exists = 1; 1776 break; 1777 case 'v': 1778 verbose = 1; 1779 break; 1780 } 1781 } 1782 if (argc == optind) { 1783 usermgmt_usage("groupinfo"); 1784 } 1785 grp = find_group_info(argv[optind]); 1786 if (exists) { 1787 exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE); 1788 } 1789 if (grp == NULL) { 1790 errx(EXIT_FAILURE, "can't find group `%s'", argv[optind]); 1791 } 1792 (void) printf("name\t%s\n", grp->gr_name); 1793 (void) printf("passwd\t%s\n", grp->gr_passwd); 1794 (void) printf("gid\t%d\n", grp->gr_gid); 1795 (void) printf("members\t"); 1796 for (cpp = grp->gr_mem ; *cpp ; cpp++) { 1797 (void) printf("%s ", *cpp); 1798 } 1799 (void) fputc('\n', stdout); 1800 return EXIT_SUCCESS; 1801 } 1802 #endif 1803