pw_user.c revision 272192
1238104Sdes/*- 2238104Sdes * Copyright (C) 1996 3238104Sdes * David L. Nugent. All rights reserved. 4238104Sdes * 5269257Sdes * Redistribution and use in source and binary forms, with or without 6238104Sdes * modification, are permitted provided that the following conditions 7238104Sdes * are met: 8238104Sdes * 1. Redistributions of source code must retain the above copyright 9238104Sdes * notice, this list of conditions and the following disclaimer. 10238104Sdes * 2. Redistributions in binary form must reproduce the above copyright 11238104Sdes * notice, this list of conditions and the following disclaimer in the 12238104Sdes * documentation and/or other materials provided with the distribution. 13238104Sdes * 14238104Sdes * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 15238104Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16269257Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17269257Sdes * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 18269257Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19269257Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20238104Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21238104Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22238104Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23238104Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24238104Sdes * SUCH DAMAGE. 25238104Sdes * 26238104Sdes */ 27238104Sdes 28246854Sdes#ifndef lint 29238104Sdesstatic const char rcsid[] = 30238104Sdes "$FreeBSD: stable/10/usr.sbin/pw/pw_user.c 272192 2014-09-26 23:01:27Z dteske $"; 31238104Sdes#endif /* not lint */ 32238104Sdes 33238104Sdes#include <ctype.h> 34238104Sdes#include <err.h> 35238104Sdes#include <fcntl.h> 36238104Sdes#include <sys/param.h> 37238104Sdes#include <dirent.h> 38238104Sdes#include <paths.h> 39238104Sdes#include <termios.h> 40238104Sdes#include <sys/types.h> 41238104Sdes#include <sys/time.h> 42238104Sdes#include <sys/resource.h> 43238104Sdes#include <unistd.h> 44238104Sdes#include <login_cap.h> 45238104Sdes#include <pwd.h> 46238104Sdes#include <grp.h> 47246854Sdes#include <libutil.h> 48238104Sdes#include "pw.h" 49238104Sdes#include "bitmap.h" 50238104Sdes 51238104Sdes#define LOGNAMESIZE (MAXLOGNAME-1) 52238104Sdes 53238104Sdesstatic char locked_str[] = "*LOCKED*"; 54238104Sdes 55238104Sdesstatic int print_user(struct passwd * pwd, int pretty, int v7); 56238104Sdesstatic uid_t pw_uidpolicy(struct userconf * cnf, struct cargs * args); 57238104Sdesstatic uid_t pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer); 58238104Sdesstatic time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args); 59238104Sdesstatic time_t pw_exppolicy(struct userconf * cnf, struct cargs * args); 60238104Sdesstatic char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user); 61238104Sdesstatic char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell); 62269257Sdesstatic char *pw_password(struct userconf * cnf, struct cargs * args, char const * user); 63269257Sdesstatic char *shell_path(char const * path, char *shells[], char *sh); 64238104Sdesstatic void rmat(uid_t uid); 65238104Sdesstatic void rmopie(char const * name); 66238104Sdes 67238104Sdes/*- 68238104Sdes * -C config configuration file 69269257Sdes * -q quiet operation 70238104Sdes * -n name login name 71238104Sdes * -u uid user id 72238104Sdes * -c comment user name/comment 73238104Sdes * -d directory home directory 74269257Sdes * -e date account expiry date 75238104Sdes * -p date password expiry date 76238104Sdes * -g grp primary group 77238104Sdes * -G grp1,grp2 additional groups 78238104Sdes * -m [ -k dir ] create and set up home 79238104Sdes * -s shell name of login shell 80238104Sdes * -o duplicate uid ok 81238104Sdes * -L class user class 82246854Sdes * -l name new login name 83238104Sdes * -h fd password filehandle 84246854Sdes * -H fd encrypted password filehandle 85246854Sdes * -F force print or add 86238104Sdes * Setting defaults: 87238104Sdes * -D set user defaults 88238104Sdes * -b dir default home root dir 89238104Sdes * -e period default expiry period 90246854Sdes * -p period default password change period 91246854Sdes * -g group default group 92238104Sdes * -G grp1,grp2.. default additional groups 93238104Sdes * -L class default login class 94238104Sdes * -k dir default home skeleton 95238104Sdes * -s shell default shell 96238104Sdes * -w method default password method 97238104Sdes */ 98238104Sdes 99269257Sdesint 100269257Sdespw_user(struct userconf * cnf, int mode, struct cargs * args) 101238104Sdes{ 102238104Sdes int rc, edited = 0; 103238104Sdes char *p = NULL; 104238104Sdes char *passtmp; 105238104Sdes struct carg *a_name; 106269257Sdes struct carg *a_uid; 107238104Sdes struct carg *arg; 108238104Sdes struct passwd *pwd = NULL; 109238104Sdes struct group *grp; 110238104Sdes struct stat st; 111238104Sdes char line[_PASSWORD_LEN+1]; 112238104Sdes FILE *fp; 113238104Sdes char *dmode_c; 114238104Sdes void *set = NULL; 115238104Sdes 116238104Sdes static struct passwd fakeuser = 117238104Sdes { 118238104Sdes NULL, 119238104Sdes "*", 120238104Sdes -1, 121238104Sdes -1, 122238104Sdes 0, 123238104Sdes "", 124238104Sdes "User &", 125238104Sdes "/nonexistent", 126238104Sdes "/bin/sh", 127238104Sdes 0 128238104Sdes#if defined(__FreeBSD__) 129238104Sdes ,0 130238104Sdes#endif 131238104Sdes }; 132238104Sdes 133238104Sdes 134238104Sdes /* 135238104Sdes * With M_NEXT, we only need to return the 136238104Sdes * next uid to stdout 137238104Sdes */ 138238104Sdes if (mode == M_NEXT) 139238104Sdes { 140238104Sdes uid_t next = pw_uidpolicy(cnf, args); 141238104Sdes if (getarg(args, 'q')) 142238104Sdes return next; 143238104Sdes printf("%ld:", (long)next); 144238104Sdes pw_group(cnf, mode, args); 145238104Sdes return EXIT_SUCCESS; 146238104Sdes } 147238104Sdes 148238104Sdes /* 149246854Sdes * We can do all of the common legwork here 150238104Sdes */ 151238104Sdes 152238104Sdes if ((arg = getarg(args, 'b')) != NULL) { 153238104Sdes cnf->home = arg->val; 154238104Sdes } 155238104Sdes 156238104Sdes if ((arg = getarg(args, 'M')) != NULL) { 157238104Sdes dmode_c = arg->val; 158238104Sdes if ((set = setmode(dmode_c)) == NULL) 159246854Sdes errx(EX_DATAERR, "invalid directory creation mode '%s'", 160238104Sdes dmode_c); 161238104Sdes cnf->homemode = getmode(set, _DEF_DIRMODE); 162238104Sdes free(set); 163238104Sdes } 164238104Sdes 165238104Sdes /* 166238104Sdes * If we'll need to use it or we're updating it, 167238104Sdes * then create the base home directory if necessary 168238104Sdes */ 169238104Sdes if (arg != NULL || getarg(args, 'm') != NULL) { 170238104Sdes int l = strlen(cnf->home); 171238104Sdes 172238104Sdes if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */ 173238104Sdes cnf->home[--l] = '\0'; 174238104Sdes 175238104Sdes if (l < 2 || *cnf->home != '/') /* Check for absolute path name */ 176238104Sdes errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home); 177238104Sdes 178246854Sdes if (stat(cnf->home, &st) == -1) { 179246854Sdes char dbuf[MAXPATHLEN]; 180246854Sdes 181246854Sdes /* 182238104Sdes * This is a kludge especially for Joerg :) 183238104Sdes * If the home directory would be created in the root partition, then 184238104Sdes * we really create it under /usr which is likely to have more space. 185246854Sdes * But we create a symlink from cnf->home -> "/usr" -> cnf->home 186238104Sdes */ 187238104Sdes if (strchr(cnf->home+1, '/') == NULL) { 188238104Sdes strcpy(dbuf, "/usr"); 189246854Sdes strncat(dbuf, cnf->home, MAXPATHLEN-5); 190238104Sdes if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) { 191246854Sdes chown(dbuf, 0, 0); 192246854Sdes /* 193246854Sdes * Skip first "/" and create symlink: 194246854Sdes * /home -> usr/home 195238104Sdes */ 196238104Sdes symlink(dbuf+1, cnf->home); 197238104Sdes } 198238104Sdes /* If this falls, fall back to old method */ 199238104Sdes } 200238104Sdes strlcpy(dbuf, cnf->home, sizeof(dbuf)); 201238104Sdes p = dbuf; 202238104Sdes if (stat(dbuf, &st) == -1) { 203238104Sdes while ((p = strchr(p + 1, '/')) != NULL) { 204238104Sdes *p = '\0'; 205238104Sdes if (stat(dbuf, &st) == -1) { 206246854Sdes if (mkdir(dbuf, _DEF_DIRMODE) == -1) 207238104Sdes goto direrr; 208238104Sdes chown(dbuf, 0, 0); 209238104Sdes } else if (!S_ISDIR(st.st_mode)) 210269257Sdes errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf); 211269257Sdes *p = '/'; 212238104Sdes } 213238104Sdes } 214238104Sdes if (stat(dbuf, &st) == -1) { 215238104Sdes if (mkdir(dbuf, _DEF_DIRMODE) == -1) { 216238104Sdes direrr: err(EX_OSFILE, "mkdir '%s'", dbuf); 217238104Sdes } 218246854Sdes chown(dbuf, 0, 0); 219238104Sdes } 220238104Sdes } else if (!S_ISDIR(st.st_mode)) 221238104Sdes errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home); 222238104Sdes } 223238104Sdes 224238104Sdes if ((arg = getarg(args, 'e')) != NULL) 225238104Sdes cnf->expire_days = atoi(arg->val); 226238104Sdes 227238104Sdes if ((arg = getarg(args, 'y')) != NULL) 228238104Sdes cnf->nispasswd = arg->val; 229238104Sdes 230238104Sdes if ((arg = getarg(args, 'p')) != NULL && arg->val) 231238104Sdes cnf->password_days = atoi(arg->val); 232238104Sdes 233238104Sdes if ((arg = getarg(args, 'g')) != NULL) { 234238104Sdes if (!*(p = arg->val)) /* Handle empty group list specially */ 235238104Sdes cnf->default_group = ""; 236238104Sdes else { 237238104Sdes if ((grp = GETGRNAM(p)) == NULL) { 238238104Sdes if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) 239238104Sdes errx(EX_NOUSER, "group `%s' does not exist", p); 240238104Sdes } 241238104Sdes cnf->default_group = newstr(grp->gr_name); 242238104Sdes } 243238104Sdes } 244238104Sdes if ((arg = getarg(args, 'L')) != NULL) 245238104Sdes cnf->default_class = pw_checkname((u_char *)arg->val, 0); 246238104Sdes 247238104Sdes if ((arg = getarg(args, 'G')) != NULL && arg->val) { 248246854Sdes int i = 0; 249238104Sdes 250238104Sdes for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) { 251246854Sdes if ((grp = GETGRNAM(p)) == NULL) { 252238104Sdes if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL) 253269257Sdes errx(EX_NOUSER, "group `%s' does not exist", p); 254269257Sdes } 255238104Sdes if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1) 256269257Sdes cnf->groups[i++] = newstr(grp->gr_name); 257269257Sdes } 258238104Sdes while (i < cnf->numgroups) 259269257Sdes cnf->groups[i++] = NULL; 260269257Sdes } 261269257Sdes 262269257Sdes if ((arg = getarg(args, 'k')) != NULL) { 263269257Sdes if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode)) 264269257Sdes errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir); 265269257Sdes } 266269257Sdes 267269257Sdes if ((arg = getarg(args, 's')) != NULL) 268269257Sdes cnf->shell_default = arg->val; 269269257Sdes 270269257Sdes if ((arg = getarg(args, 'w')) != NULL) 271269257Sdes cnf->default_password = boolean_val(arg->val, cnf->default_password); 272269257Sdes if (mode == M_ADD && getarg(args, 'D')) { 273269257Sdes if (getarg(args, 'n') != NULL) 274269257Sdes errx(EX_DATAERR, "can't combine `-D' with `-n name'"); 275269257Sdes if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { 276269257Sdes if ((cnf->min_uid = (uid_t) atoi(p)) == 0) 277238104Sdes cnf->min_uid = 1000; 278238104Sdes if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid) 279238104Sdes cnf->max_uid = 32000; 280238104Sdes } 281238104Sdes if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) { 282238104Sdes if ((cnf->min_gid = (gid_t) atoi(p)) == 0) 283238104Sdes cnf->min_gid = 1000; 284238104Sdes if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid) 285238104Sdes cnf->max_gid = 32000; 286238104Sdes } 287238104Sdes 288238104Sdes arg = getarg(args, 'C'); 289238104Sdes if (write_userconfig(arg ? arg->val : NULL)) 290238104Sdes return EXIT_SUCCESS; 291238104Sdes warn("config update"); 292238104Sdes return EX_IOERR; 293238104Sdes } 294238104Sdes 295238104Sdes if (mode == M_PRINT && getarg(args, 'a')) { 296238104Sdes int pretty = getarg(args, 'P') != NULL; 297238104Sdes int v7 = getarg(args, '7') != NULL; 298238104Sdes SETPWENT(); 299238104Sdes while ((pwd = GETPWENT()) != NULL) 300238104Sdes print_user(pwd, pretty, v7); 301238104Sdes ENDPWENT(); 302238104Sdes return EXIT_SUCCESS; 303238104Sdes } 304238104Sdes 305238104Sdes if ((a_name = getarg(args, 'n')) != NULL) 306238104Sdes pwd = GETPWNAM(pw_checkname((u_char *)a_name->val, 0)); 307238104Sdes a_uid = getarg(args, 'u'); 308238104Sdes 309238104Sdes if (a_uid == NULL) { 310238104Sdes if (a_name == NULL) 311238104Sdes errx(EX_DATAERR, "user name or id required"); 312238104Sdes 313238104Sdes /* 314238104Sdes * Determine whether 'n' switch is name or uid - we don't 315238104Sdes * really don't really care which we have, but we need to 316238104Sdes * know. 317238104Sdes */ 318238104Sdes if (mode != M_ADD && pwd == NULL 319238104Sdes && strspn(a_name->val, "0123456789") == strlen(a_name->val) 320238104Sdes && *a_name->val) { 321238104Sdes (a_uid = a_name)->ch = 'u'; 322238104Sdes a_name = NULL; 323238104Sdes } 324238104Sdes } 325238104Sdes 326238104Sdes /* 327238104Sdes * Update, delete & print require that the user exists 328238104Sdes */ 329238104Sdes if (mode == M_UPDATE || mode == M_DELETE || 330238104Sdes mode == M_PRINT || mode == M_LOCK || mode == M_UNLOCK) { 331238104Sdes 332238104Sdes if (a_name == NULL && pwd == NULL) /* Try harder */ 333238104Sdes pwd = GETPWUID(atoi(a_uid->val)); 334238104Sdes 335238104Sdes if (pwd == NULL) { 336238104Sdes if (mode == M_PRINT && getarg(args, 'F')) { 337238104Sdes fakeuser.pw_name = a_name ? a_name->val : "nouser"; 338238104Sdes fakeuser.pw_uid = a_uid ? (uid_t) atol(a_uid->val) : -1; 339238104Sdes return print_user(&fakeuser, 340238104Sdes getarg(args, 'P') != NULL, 341238104Sdes getarg(args, '7') != NULL); 342238104Sdes } 343238104Sdes if (a_name == NULL) 344238104Sdes errx(EX_NOUSER, "no such uid `%s'", a_uid->val); 345238104Sdes errx(EX_NOUSER, "no such user `%s'", a_name->val); 346238104Sdes } 347238104Sdes 348238104Sdes if (a_name == NULL) /* May be needed later */ 349238104Sdes a_name = addarg(args, 'n', newstr(pwd->pw_name)); 350238104Sdes 351238104Sdes /* 352238104Sdes * The M_LOCK and M_UNLOCK functions simply add or remove 353238104Sdes * a "*LOCKED*" prefix from in front of the password to 354238104Sdes * prevent it decoding correctly, and therefore prevents 355238104Sdes * access. Of course, this only prevents access via 356238104Sdes * password authentication (not ssh, kerberos or any 357269257Sdes * other method that does not use the UNIX password) but 358238104Sdes * that is a known limitation. 359238104Sdes */ 360238104Sdes 361238104Sdes if (mode == M_LOCK) { 362246854Sdes if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0) 363238104Sdes errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name); 364238104Sdes passtmp = malloc(strlen(pwd->pw_passwd) + sizeof(locked_str)); 365238104Sdes if (passtmp == NULL) /* disaster */ 366238104Sdes errx(EX_UNAVAILABLE, "out of memory"); 367238104Sdes strcpy(passtmp, locked_str); 368238104Sdes strcat(passtmp, pwd->pw_passwd); 369238104Sdes pwd->pw_passwd = passtmp; 370238104Sdes edited = 1; 371238104Sdes } else if (mode == M_UNLOCK) { 372238104Sdes if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0) 373238104Sdes errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name); 374238104Sdes pwd->pw_passwd += sizeof(locked_str)-1; 375238104Sdes edited = 1; 376238104Sdes } else if (mode == M_DELETE) { 377238104Sdes /* 378238104Sdes * Handle deletions now 379238104Sdes */ 380238104Sdes char file[MAXPATHLEN]; 381238104Sdes char home[MAXPATHLEN]; 382238104Sdes uid_t uid = pwd->pw_uid; 383238104Sdes struct group *gr; 384238104Sdes char grname[LOGNAMESIZE]; 385238104Sdes 386238104Sdes if (strcmp(pwd->pw_name, "root") == 0) 387238104Sdes errx(EX_DATAERR, "cannot remove user 'root'"); 388238104Sdes 389238104Sdes if (!PWALTDIR()) { 390238104Sdes /* 391238104Sdes * Remove opie record from /etc/opiekeys 392238104Sdes */ 393238104Sdes 394238104Sdes rmopie(pwd->pw_name); 395246854Sdes 396246854Sdes /* 397238104Sdes * Remove crontabs 398238104Sdes */ 399238104Sdes snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name); 400238104Sdes if (access(file, F_OK) == 0) { 401238104Sdes sprintf(file, "crontab -u %s -r", pwd->pw_name); 402238104Sdes system(file); 403238104Sdes } 404238104Sdes } 405238104Sdes /* 406238104Sdes * Save these for later, since contents of pwd may be 407238104Sdes * invalidated by deletion 408238104Sdes */ 409238104Sdes sprintf(file, "%s/%s", _PATH_MAILDIR, pwd->pw_name); 410238104Sdes strlcpy(home, pwd->pw_dir, sizeof(home)); 411238104Sdes gr = GETGRGID(pwd->pw_gid); 412238104Sdes if (gr != NULL) 413238104Sdes strlcpy(grname, gr->gr_name, LOGNAMESIZE); 414238104Sdes else 415238104Sdes grname[0] = '\0'; 416238104Sdes 417238104Sdes rc = delpwent(pwd); 418238104Sdes if (rc == -1) 419238104Sdes err(EX_IOERR, "user '%s' does not exist", pwd->pw_name); 420238104Sdes else if (rc != 0) { 421238104Sdes warn("passwd update"); 422269257Sdes return EX_IOERR; 423238104Sdes } 424238104Sdes 425238104Sdes if (cnf->nispasswd && *cnf->nispasswd=='/') { 426238104Sdes rc = delnispwent(cnf->nispasswd, a_name->val); 427238104Sdes if (rc == -1) 428238104Sdes warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name); 429238104Sdes else if (rc != 0) 430238104Sdes warn("WARNING: NIS passwd update"); 431238104Sdes /* non-fatal */ 432238104Sdes } 433238104Sdes 434238104Sdes grp = GETGRNAM(a_name->val); 435238104Sdes if (grp != NULL && 436238104Sdes (grp->gr_mem == NULL || *grp->gr_mem == NULL) && 437238104Sdes strcmp(a_name->val, grname) == 0) 438238104Sdes delgrent(GETGRNAM(a_name->val)); 439238104Sdes SETGRENT(); 440238104Sdes while ((grp = GETGRENT()) != NULL) { 441238104Sdes int i, j; 442238104Sdes char group[MAXLOGNAME]; 443238104Sdes if (grp->gr_mem != NULL) { 444238104Sdes for (i = 0; grp->gr_mem[i] != NULL; i++) { 445238104Sdes if (!strcmp(grp->gr_mem[i], a_name->val)) { 446238104Sdes for (j = i; grp->gr_mem[j] != NULL; j++) 447238104Sdes grp->gr_mem[j] = grp->gr_mem[j+1]; 448238104Sdes strlcpy(group, grp->gr_name, MAXLOGNAME); 449238104Sdes chggrent(group, grp); 450238104Sdes } 451238104Sdes } 452238104Sdes } 453238104Sdes } 454238104Sdes ENDGRENT(); 455238104Sdes 456238104Sdes pw_log(cnf, mode, W_USER, "%s(%ld) account removed", a_name->val, (long) uid); 457238104Sdes 458238104Sdes if (!PWALTDIR()) { 459238104Sdes /* 460238104Sdes * Remove mail file 461238104Sdes */ 462238104Sdes remove(file); 463238104Sdes 464238104Sdes /* 465238104Sdes * Remove at jobs 466238104Sdes */ 467238104Sdes if (getpwuid(uid) == NULL) 468238104Sdes rmat(uid); 469238104Sdes 470238104Sdes /* 471238104Sdes * Remove home directory and contents 472238104Sdes */ 473238104Sdes if (getarg(args, 'r') != NULL && *home == '/' && getpwuid(uid) == NULL) { 474238104Sdes if (stat(home, &st) != -1) { 475238104Sdes rm_r(home, uid); 476238104Sdes pw_log(cnf, mode, W_USER, "%s(%ld) home '%s' %sremoved", 477238104Sdes a_name->val, (long) uid, home, 478238104Sdes stat(home, &st) == -1 ? "" : "not completely "); 479238104Sdes } 480238104Sdes } 481238104Sdes } 482246854Sdes return EXIT_SUCCESS; 483238104Sdes } else if (mode == M_PRINT) 484238104Sdes return print_user(pwd, 485238104Sdes getarg(args, 'P') != NULL, 486238104Sdes getarg(args, '7') != NULL); 487238104Sdes 488238104Sdes /* 489238104Sdes * The rest is edit code 490238104Sdes */ 491238104Sdes if ((arg = getarg(args, 'l')) != NULL) { 492238104Sdes if (strcmp(pwd->pw_name, "root") == 0) 493238104Sdes errx(EX_DATAERR, "can't rename `root' account"); 494238104Sdes pwd->pw_name = pw_checkname((u_char *)arg->val, 0); 495238104Sdes edited = 1; 496238104Sdes } 497238104Sdes 498238104Sdes if ((arg = getarg(args, 'u')) != NULL && isdigit((unsigned char)*arg->val)) { 499238104Sdes pwd->pw_uid = (uid_t) atol(arg->val); 500238104Sdes edited = 1; 501246854Sdes if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0) 502246854Sdes errx(EX_DATAERR, "can't change uid of `root' account"); 503246854Sdes if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 504246854Sdes warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name); 505246854Sdes } 506246854Sdes 507269257Sdes if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */ 508269257Sdes gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid; 509246854Sdes if (newgid != pwd->pw_gid) { 510246854Sdes edited = 1; 511246854Sdes pwd->pw_gid = newgid; 512246854Sdes } 513246854Sdes } 514246854Sdes 515269257Sdes if ((arg = getarg(args, 'p')) != NULL) { 516269257Sdes if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { 517246854Sdes if (pwd->pw_change != 0) { 518246854Sdes pwd->pw_change = 0; 519246854Sdes edited = 1; 520246854Sdes } 521246854Sdes } 522246854Sdes else { 523269257Sdes time_t now = time(NULL); 524269257Sdes time_t expire = parse_date(now, arg->val); 525246854Sdes 526246854Sdes if (pwd->pw_change != expire) { 527246854Sdes pwd->pw_change = expire; 528246854Sdes edited = 1; 529246854Sdes } 530246854Sdes } 531269257Sdes } 532269257Sdes 533246854Sdes if ((arg = getarg(args, 'e')) != NULL) { 534246854Sdes if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) { 535246854Sdes if (pwd->pw_expire != 0) { 536246854Sdes pwd->pw_expire = 0; 537246854Sdes edited = 1; 538246854Sdes } 539269257Sdes } 540269257Sdes else { 541246854Sdes time_t now = time(NULL); 542246854Sdes time_t expire = parse_date(now, arg->val); 543246854Sdes 544246854Sdes if (pwd->pw_expire != expire) { 545246854Sdes pwd->pw_expire = expire; 546246854Sdes edited = 1; 547269257Sdes } 548269257Sdes } 549246854Sdes } 550246854Sdes 551246854Sdes if ((arg = getarg(args, 's')) != NULL) { 552246854Sdes char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val); 553246854Sdes if (shell == NULL) 554246854Sdes shell = ""; 555269257Sdes if (strcmp(shell, pwd->pw_shell) != 0) { 556269257Sdes pwd->pw_shell = shell; 557246854Sdes edited = 1; 558246854Sdes } 559246854Sdes } 560246854Sdes 561246854Sdes if (getarg(args, 'L')) { 562246854Sdes if (cnf->default_class == NULL) 563246854Sdes cnf->default_class = ""; 564269257Sdes if (strcmp(pwd->pw_class, cnf->default_class) != 0) { 565269257Sdes pwd->pw_class = cnf->default_class; 566246854Sdes edited = 1; 567246854Sdes } 568246854Sdes } 569246854Sdes 570246854Sdes if ((arg = getarg(args, 'd')) != NULL) { 571246854Sdes if (strcmp(pwd->pw_dir, arg->val)) 572269257Sdes edited = 1; 573269257Sdes if (stat(pwd->pw_dir = arg->val, &st) == -1) { 574246854Sdes if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0) 575246854Sdes warnx("WARNING: home `%s' does not exist", pwd->pw_dir); 576246854Sdes } else if (!S_ISDIR(st.st_mode)) 577246854Sdes warnx("WARNING: home `%s' is not a directory", pwd->pw_dir); 578246854Sdes } 579246854Sdes 580269257Sdes if ((arg = getarg(args, 'w')) != NULL && 581269257Sdes getarg(args, 'h') == NULL && getarg(args, 'H') == NULL) { 582246854Sdes login_cap_t *lc; 583246854Sdes 584246854Sdes lc = login_getpwclass(pwd); 585246854Sdes if (lc == NULL || 586246854Sdes login_setcryptfmt(lc, "sha512", NULL) == NULL) 587246854Sdes warn("setting crypt(3) format"); 588269257Sdes login_close(lc); 589269257Sdes pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); 590246854Sdes edited = 1; 591246854Sdes } 592246854Sdes 593246854Sdes } else { 594246854Sdes login_cap_t *lc; 595246854Sdes 596269257Sdes /* 597269257Sdes * Add code 598246854Sdes */ 599246854Sdes 600246854Sdes if (a_name == NULL) /* Required */ 601246854Sdes errx(EX_DATAERR, "login name required"); 602246854Sdes else if ((pwd = GETPWNAM(a_name->val)) != NULL) /* Exists */ 603246854Sdes errx(EX_DATAERR, "login name `%s' already exists", a_name->val); 604269257Sdes 605269257Sdes /* 606246854Sdes * Now, set up defaults for a new user 607246854Sdes */ 608246854Sdes pwd = &fakeuser; 609246854Sdes pwd->pw_name = a_name->val; 610246854Sdes pwd->pw_class = cnf->default_class ? cnf->default_class : ""; 611246854Sdes pwd->pw_uid = pw_uidpolicy(cnf, args); 612269257Sdes pwd->pw_gid = pw_gidpolicy(cnf, args, pwd->pw_name, (gid_t) pwd->pw_uid); 613269257Sdes pwd->pw_change = pw_pwdpolicy(cnf, args); 614246854Sdes pwd->pw_expire = pw_exppolicy(cnf, args); 615246854Sdes pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name); 616246854Sdes pwd->pw_shell = pw_shellpolicy(cnf, args, NULL); 617246854Sdes lc = login_getpwclass(pwd); 618246854Sdes if (lc == NULL || login_setcryptfmt(lc, "md5", NULL) == NULL) 619246854Sdes warn("setting crypt(3) format"); 620269257Sdes login_close(lc); 621269257Sdes pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name); 622246854Sdes edited = 1; 623246854Sdes 624246854Sdes if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) 625246854Sdes warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name); 626246854Sdes } 627246854Sdes 628269257Sdes /* 629269257Sdes * Shared add/edit code 630269257Sdes */ 631269257Sdes if ((arg = getarg(args, 'c')) != NULL) { 632246854Sdes char *gecos = pw_checkname((u_char *)arg->val, 1); 633246854Sdes if (strcmp(pwd->pw_gecos, gecos) != 0) { 634246854Sdes pwd->pw_gecos = gecos; 635246854Sdes edited = 1; 636246854Sdes } 637246854Sdes } 638246854Sdes 639269257Sdes if ((arg = getarg(args, 'h')) != NULL || 640269257Sdes (arg = getarg(args, 'H')) != NULL) { 641246854Sdes if (strcmp(arg->val, "-") == 0) { 642246854Sdes if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { 643246854Sdes pwd->pw_passwd = "*"; /* No access */ 644246854Sdes edited = 1; 645246854Sdes } 646246854Sdes } else { 647269257Sdes int fd = atoi(arg->val); 648269257Sdes int precrypt = (arg->ch == 'H'); 649246854Sdes int b; 650246854Sdes int istty = isatty(fd); 651246854Sdes struct termios t; 652246854Sdes login_cap_t *lc; 653246854Sdes 654246854Sdes if (istty) { 655269257Sdes if (tcgetattr(fd, &t) == -1) 656269257Sdes istty = 0; 657246854Sdes else { 658246854Sdes struct termios n = t; 659246854Sdes 660246854Sdes /* Disable echo */ 661246854Sdes n.c_lflag &= ~(ECHO); 662246854Sdes tcsetattr(fd, TCSANOW, &n); 663269257Sdes printf("%s%spassword for user %s:", 664269257Sdes (mode == M_UPDATE) ? "new " : "", 665246854Sdes precrypt ? "encrypted " : "", 666246854Sdes pwd->pw_name); 667246854Sdes fflush(stdout); 668246854Sdes } 669246854Sdes } 670246854Sdes b = read(fd, line, sizeof(line) - 1); 671269257Sdes if (istty) { /* Restore state */ 672269257Sdes tcsetattr(fd, TCSANOW, &t); 673246854Sdes fputc('\n', stdout); 674246854Sdes fflush(stdout); 675246854Sdes } 676246854Sdes if (b < 0) { 677246854Sdes warn("-%c file descriptor", precrypt ? 'H' : 678246854Sdes 'h'); 679246854Sdes return EX_IOERR; 680269257Sdes } 681269257Sdes line[b] = '\0'; 682246854Sdes if ((p = strpbrk(line, "\r\n")) != NULL) 683246854Sdes *p = '\0'; 684246854Sdes if (!*line) 685246854Sdes errx(EX_DATAERR, "empty password read on file descriptor %d", fd); 686246854Sdes if (precrypt) { 687246854Sdes if (strchr(line, ':') != NULL) 688269257Sdes return EX_DATAERR; 689269257Sdes pwd->pw_passwd = line; 690246854Sdes } else { 691246854Sdes lc = login_getpwclass(pwd); 692246854Sdes if (lc == NULL || 693246854Sdes login_setcryptfmt(lc, "md5", NULL) == NULL) 694246854Sdes warn("setting crypt(3) format"); 695246854Sdes login_close(lc); 696269257Sdes pwd->pw_passwd = pw_pwcrypt(line); 697269257Sdes } 698246854Sdes edited = 1; 699246854Sdes } 700246854Sdes } 701246854Sdes 702246854Sdes /* 703246854Sdes * Special case: -N only displays & exits 704246854Sdes */ 705246854Sdes if (getarg(args, 'N') != NULL) 706269257Sdes return print_user(pwd, 707269257Sdes getarg(args, 'P') != NULL, 708246854Sdes getarg(args, '7') != NULL); 709246854Sdes 710246854Sdes if (mode == M_ADD) { 711246854Sdes edited = 1; /* Always */ 712246854Sdes rc = addpwent(pwd); 713246854Sdes if (rc == -1) { 714269257Sdes warnx("user '%s' already exists", pwd->pw_name); 715269257Sdes return EX_IOERR; 716246854Sdes } else if (rc != 0) { 717246854Sdes warn("passwd file update"); 718246854Sdes return EX_IOERR; 719246854Sdes } 720246854Sdes if (cnf->nispasswd && *cnf->nispasswd=='/') { 721238104Sdes rc = addnispwent(cnf->nispasswd, pwd); 722246854Sdes if (rc == -1) 723246854Sdes warnx("User '%s' already exists in NIS passwd", pwd->pw_name); 724246854Sdes else 725246854Sdes warn("NIS passwd update"); 726246854Sdes /* NOTE: we treat NIS-only update errors as non-fatal */ 727246854Sdes } 728246854Sdes } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) { 729246854Sdes if (edited) { /* Only updated this if required */ 730246854Sdes rc = chgpwent(a_name->val, pwd); 731246854Sdes if (rc == -1) { 732246854Sdes warnx("user '%s' does not exist (NIS?)", pwd->pw_name); 733246854Sdes return EX_IOERR; 734246854Sdes } else if (rc != 0) { 735246854Sdes warn("passwd file update"); 736246854Sdes return EX_IOERR; 737246854Sdes } 738246854Sdes if ( cnf->nispasswd && *cnf->nispasswd=='/') { 739246854Sdes rc = chgnispwent(cnf->nispasswd, a_name->val, pwd); 740246854Sdes if (rc == -1) 741269257Sdes warn("User '%s' not found in NIS passwd", pwd->pw_name); 742269257Sdes else 743246854Sdes warn("NIS passwd update"); 744246854Sdes /* NOTE: NIS-only update errors are not fatal */ 745246854Sdes } 746238104Sdes } 747238104Sdes } 748246854Sdes 749246854Sdes /* 750269257Sdes * Ok, user is created or changed - now edit group file 751246854Sdes */ 752246854Sdes 753246854Sdes if (mode == M_ADD || getarg(args, 'G') != NULL) { 754246854Sdes int i; 755246854Sdes for (i = 0; cnf->groups[i] != NULL; i++) { 756246854Sdes grp = GETGRNAM(cnf->groups[i]); 757269257Sdes grp = gr_add(grp, pwd->pw_name); 758269257Sdes /* 759246854Sdes * grp can only be NULL in 2 cases: 760246854Sdes * - the new member is already a member 761246854Sdes * - a problem with memory occurs 762246854Sdes * in both cases we want to skip now. 763246854Sdes */ 764246854Sdes if (grp == NULL) 765269257Sdes continue; 766269257Sdes chggrent(cnf->groups[i], grp); 767246854Sdes free(grp); 768246854Sdes } 769246854Sdes } 770246854Sdes 771246854Sdes 772246854Sdes /* go get a current version of pwd */ 773269257Sdes pwd = GETPWNAM(a_name->val); 774269257Sdes if (pwd == NULL) { 775246854Sdes /* This will fail when we rename, so special case that */ 776246854Sdes if (mode == M_UPDATE && (arg = getarg(args, 'l')) != NULL) { 777246854Sdes a_name->val = arg->val; /* update new name */ 778238104Sdes pwd = GETPWNAM(a_name->val); /* refetch renamed rec */ 779238104Sdes } 780246854Sdes } 781246854Sdes if (pwd == NULL) /* can't go on without this */ 782269257Sdes errx(EX_NOUSER, "user '%s' disappeared during update", a_name->val); 783246854Sdes 784246854Sdes grp = GETGRGID(pwd->pw_gid); 785246854Sdes pw_log(cnf, mode, W_USER, "%s(%ld):%s(%ld):%s:%s:%s", 786246854Sdes pwd->pw_name, (long) pwd->pw_uid, 787246854Sdes grp ? grp->gr_name : "unknown", (long) (grp ? grp->gr_gid : -1), 788246854Sdes pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell); 789269257Sdes 790269257Sdes /* 791246854Sdes * If adding, let's touch and chown the user's mail file. This is not 792246854Sdes * strictly necessary under BSD with a 0755 maildir but it also 793246854Sdes * doesn't hurt anything to create the empty mailfile 794238104Sdes */ 795238104Sdes if (mode == M_ADD) { 796246854Sdes if (!PWALTDIR()) { 797246854Sdes sprintf(line, "%s/%s", _PATH_MAILDIR, pwd->pw_name); 798269257Sdes close(open(line, O_RDWR | O_CREAT, 0600)); /* Preserve contents & 799246854Sdes * mtime */ 800246854Sdes chown(line, pwd->pw_uid, pwd->pw_gid); 801246854Sdes } 802246854Sdes } 803246854Sdes 804246854Sdes /* 805269257Sdes * Let's create and populate the user's home directory. Note 806269257Sdes * that this also `works' for editing users if -m is used, but 807246854Sdes * existing files will *not* be overwritten. 808246854Sdes */ 809246854Sdes if (!PWALTDIR() && getarg(args, 'm') != NULL && pwd->pw_dir && *pwd->pw_dir == '/' && pwd->pw_dir[1]) { 810246854Sdes copymkdir(pwd->pw_dir, cnf->dotdir, cnf->homemode, pwd->pw_uid, pwd->pw_gid); 811246854Sdes pw_log(cnf, mode, W_USER, "%s(%ld) home %s made", 812246854Sdes pwd->pw_name, (long) pwd->pw_uid, pwd->pw_dir); 813269257Sdes } 814269257Sdes 815246854Sdes 816246854Sdes /* 817246854Sdes * Finally, send mail to the new user as well, if we are asked to 818246854Sdes */ 819246854Sdes if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) { 820246854Sdes FILE *pfp = popen(_PATH_SENDMAIL " -t", "w"); 821269257Sdes 822269257Sdes if (pfp == NULL) 823246854Sdes warn("sendmail"); 824246854Sdes else { 825246854Sdes fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name); 826238104Sdes while (fgets(line, sizeof(line), fp) != NULL) { 827238104Sdes /* Do substitutions? */ 828246854Sdes fputs(line, pfp); 829246854Sdes } 830269257Sdes pclose(pfp); 831246854Sdes pw_log(cnf, mode, W_USER, "%s(%ld) new user mail sent", 832246854Sdes pwd->pw_name, (long) pwd->pw_uid); 833246854Sdes } 834238104Sdes fclose(fp); 835238104Sdes } 836246854Sdes 837246854Sdes return EXIT_SUCCESS; 838269257Sdes} 839246854Sdes 840246854Sdes 841246854Sdesstatic uid_t 842238104Sdespw_uidpolicy(struct userconf * cnf, struct cargs * args) 843238104Sdes{ 844246854Sdes struct passwd *pwd; 845246854Sdes uid_t uid = (uid_t) - 1; 846269257Sdes struct carg *a_uid = getarg(args, 'u'); 847246854Sdes 848246854Sdes /* 849246854Sdes * Check the given uid, if any 850246854Sdes */ 851246854Sdes if (a_uid != NULL) { 852246854Sdes uid = (uid_t) atol(a_uid->val); 853269257Sdes 854269257Sdes if ((pwd = GETPWUID(uid)) != NULL && getarg(args, 'o') == NULL) 855246854Sdes errx(EX_DATAERR, "uid `%ld' has already been allocated", (long) pwd->pw_uid); 856246854Sdes } else { 857246854Sdes struct bitmap bm; 858246854Sdes 859246854Sdes /* 860246854Sdes * We need to allocate the next available uid under one of 861269257Sdes * two policies a) Grab the first unused uid b) Grab the 862269257Sdes * highest possible unused uid 863246854Sdes */ 864246854Sdes if (cnf->min_uid >= cnf->max_uid) { /* Sanity 865246854Sdes * claus^H^H^H^Hheck */ 866238104Sdes cnf->min_uid = 1000; 867238104Sdes cnf->max_uid = 32000; 868246854Sdes } 869246854Sdes bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1); 870269257Sdes 871246854Sdes /* 872246854Sdes * Now, let's fill the bitmap from the password file 873246854Sdes */ 874238104Sdes SETPWENT(); 875238104Sdes while ((pwd = GETPWENT()) != NULL) 876246854Sdes if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid) 877246854Sdes bm_setbit(&bm, pwd->pw_uid - cnf->min_uid); 878269257Sdes ENDPWENT(); 879246854Sdes 880246854Sdes /* 881246854Sdes * Then apply the policy, with fallback to reuse if necessary 882246854Sdes */ 883246854Sdes if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid) 884246854Sdes uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid); 885269257Sdes 886269257Sdes /* 887246854Sdes * Another sanity check 888246854Sdes */ 889246854Sdes if (uid < cnf->min_uid || uid > cnf->max_uid) 890238104Sdes errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used"); 891238104Sdes bm_dealloc(&bm); 892246854Sdes } 893246854Sdes return uid; 894269257Sdes} 895269257Sdes 896246854Sdes 897246854Sdesstatic uid_t 898246854Sdespw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer) 899246854Sdes{ 900246854Sdes struct group *grp; 901246854Sdes gid_t gid = (uid_t) - 1; 902269257Sdes struct carg *a_gid = getarg(args, 'g'); 903269257Sdes 904246854Sdes /* 905246854Sdes * If no arg given, see if default can help out 906246854Sdes */ 907238104Sdes if (a_gid == NULL && cnf->default_group && *cnf->default_group) 908238104Sdes a_gid = addarg(args, 'g', cnf->default_group); 909246854Sdes 910246854Sdes /* 911269257Sdes * Check the given gid, if any 912246854Sdes */ 913246854Sdes SETGRENT(); 914246854Sdes if (a_gid != NULL) { 915246854Sdes if ((grp = GETGRNAM(a_gid->val)) == NULL) { 916246854Sdes gid = (gid_t) atol(a_gid->val); 917246854Sdes if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL) 918269257Sdes errx(EX_NOUSER, "group `%s' is not defined", a_gid->val); 919269257Sdes } 920246854Sdes gid = grp->gr_gid; 921246854Sdes } else if ((grp = GETGRNAM(nam)) != NULL && 922246854Sdes (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) { 923246854Sdes gid = grp->gr_gid; /* Already created? Use it anyway... */ 924246854Sdes } else { 925246854Sdes struct cargs grpargs; 926269257Sdes char tmp[32]; 927269257Sdes 928246854Sdes LIST_INIT(&grpargs); 929246854Sdes addarg(&grpargs, 'n', nam); 930246854Sdes 931246854Sdes /* 932246854Sdes * We need to auto-create a group with the user's name. We 933246854Sdes * can send all the appropriate output to our sister routine 934269257Sdes * bit first see if we can create a group with gid==uid so we 935269257Sdes * can keep the user and group ids in sync. We purposely do 936246854Sdes * NOT check the gid range if we can force the sync. If the 937246854Sdes * user's name dups an existing group, then the group add 938246854Sdes * function will happily handle that case for us and exit. 939246854Sdes */ 940246854Sdes if (GETGRGID(prefer) == NULL) { 941246854Sdes sprintf(tmp, "%lu", (unsigned long) prefer); 942269257Sdes addarg(&grpargs, 'g', tmp); 943269257Sdes } 944246854Sdes if (getarg(args, 'N')) 945246854Sdes { 946246854Sdes addarg(&grpargs, 'N', NULL); 947246854Sdes addarg(&grpargs, 'q', NULL); 948246854Sdes gid = pw_group(cnf, M_NEXT, &grpargs); 949246854Sdes } 950246854Sdes else 951269257Sdes { 952269257Sdes pw_group(cnf, M_ADD, &grpargs); 953246854Sdes if ((grp = GETGRNAM(nam)) != NULL) 954246854Sdes gid = grp->gr_gid; 955246854Sdes } 956246854Sdes a_gid = LIST_FIRST(&grpargs); 957246854Sdes while (a_gid != NULL) { 958246854Sdes struct carg *t = LIST_NEXT(a_gid, list); 959246854Sdes LIST_REMOVE(a_gid, list); 960269257Sdes a_gid = t; 961246854Sdes } 962246854Sdes } 963246854Sdes ENDGRENT(); 964246854Sdes return gid; 965246854Sdes} 966246854Sdes 967246854Sdes 968269257Sdesstatic time_t 969246854Sdespw_pwdpolicy(struct userconf * cnf, struct cargs * args) 970246854Sdes{ 971246854Sdes time_t result = 0; 972246854Sdes time_t now = time(NULL); 973246854Sdes struct carg *arg = getarg(args, 'p'); 974246854Sdes 975246854Sdes if (arg != NULL) { 976269257Sdes if ((result = parse_date(now, arg->val)) == now) 977269257Sdes errx(EX_DATAERR, "invalid date/time `%s'", arg->val); 978246854Sdes } else if (cnf->password_days > 0) 979246854Sdes result = now + ((long) cnf->password_days * 86400L); 980246854Sdes return result; 981246854Sdes} 982246854Sdes 983246854Sdes 984246854Sdesstatic time_t 985269257Sdespw_exppolicy(struct userconf * cnf, struct cargs * args) 986246854Sdes{ 987246854Sdes time_t result = 0; 988246854Sdes time_t now = time(NULL); 989246854Sdes struct carg *arg = getarg(args, 'e'); 990246854Sdes 991246854Sdes if (arg != NULL) { 992246854Sdes if ((result = parse_date(now, arg->val)) == now) 993269257Sdes errx(EX_DATAERR, "invalid date/time `%s'", arg->val); 994246854Sdes } else if (cnf->expire_days > 0) 995246854Sdes result = now + ((long) cnf->expire_days * 86400L); 996246854Sdes return result; 997246854Sdes} 998246854Sdes 999246854Sdes 1000246854Sdesstatic char * 1001269257Sdespw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user) 1002269257Sdes{ 1003246854Sdes struct carg *arg = getarg(args, 'd'); 1004246854Sdes 1005246854Sdes if (arg) 1006246854Sdes return arg->val; 1007246854Sdes else { 1008246854Sdes static char home[128]; 1009246854Sdes 1010269257Sdes if (cnf->home == NULL || *cnf->home == '\0') 1011238104Sdes errx(EX_CONFIG, "no base home directory set"); 1012238104Sdes sprintf(home, "%s/%s", cnf->home, user); 1013238104Sdes return home; 1014238104Sdes } 1015238104Sdes} 1016238104Sdes 1017238104Sdesstatic char * 1018238104Sdesshell_path(char const * path, char *shells[], char *sh) 1019238104Sdes{ 1020238104Sdes if (sh != NULL && (*sh == '/' || *sh == '\0')) 1021238104Sdes return sh; /* specified full path or forced none */ 1022238104Sdes else { 1023238104Sdes char *p; 1024238104Sdes char paths[_UC_MAXLINE]; 1025238104Sdes 1026238104Sdes /* 1027238104Sdes * We need to search paths 1028238104Sdes */ 1029238104Sdes strlcpy(paths, path, sizeof(paths)); 1030246854Sdes for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) { 1031238104Sdes int i; 1032238104Sdes static char shellpath[256]; 1033238104Sdes 1034238104Sdes if (sh != NULL) { 1035238104Sdes sprintf(shellpath, "%s/%s", p, sh); 1036 if (access(shellpath, X_OK) == 0) 1037 return shellpath; 1038 } else 1039 for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) { 1040 sprintf(shellpath, "%s/%s", p, shells[i]); 1041 if (access(shellpath, X_OK) == 0) 1042 return shellpath; 1043 } 1044 } 1045 if (sh == NULL) 1046 errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh); 1047 errx(EX_CONFIG, "no default shell available or defined"); 1048 return NULL; 1049 } 1050} 1051 1052 1053static char * 1054pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell) 1055{ 1056 char *sh = newshell; 1057 struct carg *arg = getarg(args, 's'); 1058 1059 if (newshell == NULL && arg != NULL) 1060 sh = arg->val; 1061 return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default); 1062} 1063 1064#define SALTSIZE 32 1065 1066static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; 1067 1068char * 1069pw_pwcrypt(char *password) 1070{ 1071 int i; 1072 char salt[SALTSIZE + 1]; 1073 char *cryptpw; 1074 1075 static char buf[256]; 1076 1077 /* 1078 * Calculate a salt value 1079 */ 1080 for (i = 0; i < SALTSIZE; i++) 1081 salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; 1082 salt[SALTSIZE] = '\0'; 1083 1084 cryptpw = crypt(password, salt); 1085 if (cryptpw == NULL) 1086 errx(EX_CONFIG, "crypt(3) failure"); 1087 return strcpy(buf, cryptpw); 1088} 1089 1090 1091static char * 1092pw_password(struct userconf * cnf, struct cargs * args, char const * user) 1093{ 1094 int i, l; 1095 char pwbuf[32]; 1096 1097 switch (cnf->default_password) { 1098 case -1: /* Random password */ 1099 l = (arc4random() % 8 + 8); /* 8 - 16 chars */ 1100 for (i = 0; i < l; i++) 1101 pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)]; 1102 pwbuf[i] = '\0'; 1103 1104 /* 1105 * We give this information back to the user 1106 */ 1107 if (getarg(args, 'h') == NULL && getarg(args, 'H') == NULL && 1108 getarg(args, 'N') == NULL) { 1109 if (isatty(STDOUT_FILENO)) 1110 printf("Password for '%s' is: ", user); 1111 printf("%s\n", pwbuf); 1112 fflush(stdout); 1113 } 1114 break; 1115 1116 case -2: /* No password at all! */ 1117 return ""; 1118 1119 case 0: /* No login - default */ 1120 default: 1121 return "*"; 1122 1123 case 1: /* user's name */ 1124 strlcpy(pwbuf, user, sizeof(pwbuf)); 1125 break; 1126 } 1127 return pw_pwcrypt(pwbuf); 1128} 1129 1130 1131static int 1132print_user(struct passwd * pwd, int pretty, int v7) 1133{ 1134 if (!pretty) { 1135 char *buf; 1136 1137 if (!v7) 1138 pwd->pw_passwd = (pwd->pw_passwd == NULL) ? "" : "*"; 1139 1140 buf = v7 ? pw_make_v7(pwd) : pw_make(pwd); 1141 printf("%s\n", buf); 1142 free(buf); 1143 } else { 1144 int j; 1145 char *p; 1146 struct group *grp = GETGRGID(pwd->pw_gid); 1147 char uname[60] = "User &", office[60] = "[None]", 1148 wphone[60] = "[None]", hphone[60] = "[None]"; 1149 char acexpire[32] = "[None]", pwexpire[32] = "[None]"; 1150 struct tm * tptr; 1151 1152 if ((p = strtok(pwd->pw_gecos, ",")) != NULL) { 1153 strlcpy(uname, p, sizeof(uname)); 1154 if ((p = strtok(NULL, ",")) != NULL) { 1155 strlcpy(office, p, sizeof(office)); 1156 if ((p = strtok(NULL, ",")) != NULL) { 1157 strlcpy(wphone, p, sizeof(wphone)); 1158 if ((p = strtok(NULL, "")) != NULL) { 1159 strlcpy(hphone, p, 1160 sizeof(hphone)); 1161 } 1162 } 1163 } 1164 } 1165 /* 1166 * Handle '&' in gecos field 1167 */ 1168 if ((p = strchr(uname, '&')) != NULL) { 1169 int l = strlen(pwd->pw_name); 1170 int m = strlen(p); 1171 1172 memmove(p + l, p + 1, m); 1173 memmove(p, pwd->pw_name, l); 1174 *p = (char) toupper((unsigned char)*p); 1175 } 1176 if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL) 1177 strftime(acexpire, sizeof acexpire, "%c", tptr); 1178 if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL) 1179 strftime(pwexpire, sizeof pwexpire, "%c", tptr); 1180 printf("Login Name: %-15s #%-12ld Group: %-15s #%ld\n" 1181 " Full Name: %s\n" 1182 " Home: %-26.26s Class: %s\n" 1183 " Shell: %-26.26s Office: %s\n" 1184 "Work Phone: %-26.26s Home Phone: %s\n" 1185 "Acc Expire: %-26.26s Pwd Expire: %s\n", 1186 pwd->pw_name, (long) pwd->pw_uid, 1187 grp ? grp->gr_name : "(invalid)", (long) pwd->pw_gid, 1188 uname, pwd->pw_dir, pwd->pw_class, 1189 pwd->pw_shell, office, wphone, hphone, 1190 acexpire, pwexpire); 1191 SETGRENT(); 1192 j = 0; 1193 while ((grp=GETGRENT()) != NULL) 1194 { 1195 int i = 0; 1196 if (grp->gr_mem != NULL) { 1197 while (grp->gr_mem[i] != NULL) 1198 { 1199 if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) 1200 { 1201 printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name); 1202 break; 1203 } 1204 ++i; 1205 } 1206 } 1207 } 1208 ENDGRENT(); 1209 printf("%s", j ? "\n" : ""); 1210 } 1211 return EXIT_SUCCESS; 1212} 1213 1214char * 1215pw_checkname(u_char *name, int gecos) 1216{ 1217 char showch[8]; 1218 u_char const *badchars, *ch, *showtype; 1219 int reject; 1220 1221 ch = name; 1222 reject = 0; 1223 if (gecos) { 1224 /* See if the name is valid as a gecos (comment) field. */ 1225 badchars = ":!@"; 1226 showtype = "gecos field"; 1227 } else { 1228 /* See if the name is valid as a userid or group. */ 1229 badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\""; 1230 showtype = "userid/group name"; 1231 /* Userids and groups can not have a leading '-'. */ 1232 if (*ch == '-') 1233 reject = 1; 1234 } 1235 if (!reject) { 1236 while (*ch) { 1237 if (strchr(badchars, *ch) != NULL || *ch < ' ' || 1238 *ch == 127) { 1239 reject = 1; 1240 break; 1241 } 1242 /* 8-bit characters are only allowed in GECOS fields */ 1243 if (!gecos && (*ch & 0x80)) { 1244 reject = 1; 1245 break; 1246 } 1247 ch++; 1248 } 1249 } 1250 /* 1251 * A `$' is allowed as the final character for userids and groups, 1252 * mainly for the benefit of samba. 1253 */ 1254 if (reject && !gecos) { 1255 if (*ch == '$' && *(ch + 1) == '\0') { 1256 reject = 0; 1257 ch++; 1258 } 1259 } 1260 if (reject) { 1261 snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127) 1262 ? "`%c'" : "0x%02x", *ch); 1263 errx(EX_DATAERR, "invalid character %s at position %td in %s", 1264 showch, (ch - name), showtype); 1265 } 1266 if (!gecos && (ch - name) > LOGNAMESIZE) 1267 errx(EX_DATAERR, "name too long `%s' (max is %d)", name, 1268 LOGNAMESIZE); 1269 return (char *)name; 1270} 1271 1272 1273static void 1274rmat(uid_t uid) 1275{ 1276 DIR *d = opendir("/var/at/jobs"); 1277 1278 if (d != NULL) { 1279 struct dirent *e; 1280 1281 while ((e = readdir(d)) != NULL) { 1282 struct stat st; 1283 1284 if (strncmp(e->d_name, ".lock", 5) != 0 && 1285 stat(e->d_name, &st) == 0 && 1286 !S_ISDIR(st.st_mode) && 1287 st.st_uid == uid) { 1288 char tmp[MAXPATHLEN]; 1289 1290 sprintf(tmp, "/usr/bin/atrm %s", e->d_name); 1291 system(tmp); 1292 } 1293 } 1294 closedir(d); 1295 } 1296} 1297 1298static void 1299rmopie(char const * name) 1300{ 1301 static const char etcopie[] = "/etc/opiekeys"; 1302 FILE *fp = fopen(etcopie, "r+"); 1303 1304 if (fp != NULL) { 1305 char tmp[1024]; 1306 off_t atofs = 0; 1307 int length = strlen(name); 1308 1309 while (fgets(tmp, sizeof tmp, fp) != NULL) { 1310 if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') { 1311 if (fseek(fp, atofs, SEEK_SET) == 0) { 1312 fwrite("#", 1, 1, fp); /* Comment username out */ 1313 } 1314 break; 1315 } 1316 atofs = ftell(fp); 1317 } 1318 /* 1319 * If we got an error of any sort, don't update! 1320 */ 1321 fclose(fp); 1322 } 1323} 1324 1325