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