1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2013 Gary Mills
23 *
24 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
25 * Use is subject to license terms.
26 */
27
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
30
31
32
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <ctype.h>
39 #include <limits.h>
40 #include <string.h>
41 #include <userdefs.h>
42 #include <user_attr.h>
43 #include <nss_dbdefs.h>
44 #include <errno.h>
45 #include <project.h>
46 #include "users.h"
47 #include "messages.h"
48 #include "funcs.h"
49
50 /*
51 * usermod [-u uid [-o] | -g group | -G group [[,group]...] | -d dir [-m]
52 * | -s shell | -c comment | -l new_logname]
53 * | -f inactive | -e expire ]
54 * [ -A authorization [, authorization ...]]
55 * [ -P profile [, profile ...]]
56 * [ -R role [, role ...]]
57 * [ -K key=value ]
58 * [ -p project [, project]] login
59 *
60 * This command adds new user logins to the system. Arguments are:
61 *
62 * uid - an integer less than MAXUID
63 * group - an existing group's integer ID or char string name
64 * dir - a directory
65 * shell - a program to be used as a shell
66 * comment - any text string
67 * skel_dir - a directory
68 * base_dir - a directory
69 * rid - an integer less than 2**16 (USHORT)
70 * login - a string of printable chars except colon (:)
71 * inactive - number of days a login maybe inactive before it is locked
72 * expire - date when a login is no longer valid
73 * authorization - One or more comma separated authorizations defined
74 * in auth_attr(4).
75 * profile - One or more comma separated execution profiles defined
76 * in prof_attr(4)
77 * role - One or more comma-separated role names defined in user_attr(4)
78 * key=value - One or more -K options each specifying a valid user_attr(4)
79 * attribute.
80 *
81 */
82
83 extern int **valid_lgroup(), isbusy();
84 extern int valid_uid(), check_perm(), create_home(), move_dir();
85 extern int valid_expire(), edit_group(), call_passmgmt();
86 extern projid_t **valid_lproject();
87
88 static uid_t uid; /* new uid */
89 static gid_t gid; /* gid of new login */
90 static char *new_logname = NULL; /* new login name with -l option */
91 static char *uidstr = NULL; /* uid from command line */
92 static char *group = NULL; /* group from command line */
93 static char *grps = NULL; /* multi groups from command line */
94 static char *dir = NULL; /* home dir from command line */
95 static char *shell = NULL; /* shell from command line */
96 static char *comment = NULL; /* comment from command line */
97 static char *logname = NULL; /* login name to add */
98 static char *inactstr = NULL; /* inactive from command line */
99 static char *expire = NULL; /* expiration date from command line */
100 static char *projects = NULL; /* project ids from command line */
101 static char *usertype;
102
103 char *cmdname;
104 static char gidstring[32], uidstring[32];
105 char inactstring[10];
106
107 char *
108 strcpmalloc(str)
109 char *str;
110 {
111 if (str == NULL)
112 return (NULL);
113
114 return (strdup(str));
115 }
116 struct passwd *
117 passwd_cpmalloc(opw)
118 struct passwd *opw;
119 {
120 struct passwd *npw;
121
122 if (opw == NULL)
123 return (NULL);
124
125
126 npw = malloc(sizeof (struct passwd));
127
128 npw->pw_name = strcpmalloc(opw->pw_name);
129 npw->pw_passwd = strcpmalloc(opw->pw_passwd);
130 npw->pw_uid = opw->pw_uid;
131 npw->pw_gid = opw->pw_gid;
132 npw->pw_age = strcpmalloc(opw->pw_age);
133 npw->pw_comment = strcpmalloc(opw->pw_comment);
134 npw->pw_gecos = strcpmalloc(opw->pw_gecos);
135 npw->pw_dir = strcpmalloc(opw->pw_dir);
136 npw->pw_shell = strcpmalloc(opw->pw_shell);
137
138 return (npw);
139 }
140
141 int
142 main(argc, argv)
143 int argc;
144 char **argv;
145 {
146 int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0;
147 int tries, mflag = 0, inact, **gidlist, flag = 0;
148 boolean_t fail_if_busy = B_FALSE;
149 char *ptr;
150 struct passwd *pstruct; /* password struct for login */
151 struct passwd *pw;
152 struct group *g_ptr; /* validated group from -g */
153 struct stat statbuf; /* status buffer for stat */
154 #ifndef att
155 FILE *pwf; /* fille ptr for opened passwd file */
156 #endif
157 int warning;
158 projid_t **projlist;
159 char **nargv; /* arguments for execvp of passmgmt */
160 int argindex; /* argument index into nargv */
161 userattr_t *ua;
162 char *val;
163 int isrole; /* current account is role */
164
165 cmdname = argv[0];
166
167 if (geteuid() != 0) {
168 errmsg(M_PERM_DENIED);
169 exit(EX_NO_PERM);
170 }
171
172 opterr = 0; /* no print errors from getopt */
173 /* get user type based on the program name */
174 usertype = getusertype(argv[0]);
175
176 while ((ch = getopt(argc, argv,
177 "c:d:e:f:G:g:l:mop:s:u:A:P:R:K:")) != EOF)
178 switch (ch) {
179 case 'c':
180 comment = optarg;
181 flag++;
182 break;
183 case 'd':
184 dir = optarg;
185 fail_if_busy = B_TRUE;
186 flag++;
187 break;
188 case 'e':
189 expire = optarg;
190 flag++;
191 break;
192 case 'f':
193 inactstr = optarg;
194 flag++;
195 break;
196 case 'G':
197 grps = optarg;
198 flag++;
199 break;
200 case 'g':
201 group = optarg;
202 fail_if_busy = B_TRUE;
203 flag++;
204 break;
205 case 'l':
206 new_logname = optarg;
207 fail_if_busy = B_TRUE;
208 flag++;
209 break;
210 case 'm':
211 mflag++;
212 flag++;
213 fail_if_busy = B_TRUE;
214 break;
215 case 'o':
216 oflag++;
217 flag++;
218 fail_if_busy = B_TRUE;
219 break;
220 case 'p':
221 projects = optarg;
222 flag++;
223 break;
224 case 's':
225 shell = optarg;
226 flag++;
227 break;
228 case 'u':
229 uidstr = optarg;
230 flag++;
231 fail_if_busy = B_TRUE;
232 break;
233 case 'A':
234 change_key(USERATTR_AUTHS_KW, optarg);
235 flag++;
236 break;
237 case 'P':
238 change_key(USERATTR_PROFILES_KW, optarg);
239 flag++;
240 break;
241 case 'R':
242 change_key(USERATTR_ROLES_KW, optarg);
243 flag++;
244 break;
245 case 'K':
246 change_key(NULL, optarg);
247 flag++;
248 break;
249 default:
250 case '?':
251 if (is_role(usertype))
252 errmsg(M_MRUSAGE);
253 else
254 errmsg(M_MUSAGE);
255 exit(EX_SYNTAX);
256 }
257
258 if (optind != argc - 1 || flag == 0) {
259 if (is_role(usertype))
260 errmsg(M_MRUSAGE);
261 else
262 errmsg(M_MUSAGE);
263 exit(EX_SYNTAX);
264 }
265
266 if ((!uidstr && oflag) || (mflag && !dir)) {
267 if (is_role(usertype))
268 errmsg(M_MRUSAGE);
269 else
270 errmsg(M_MUSAGE);
271 exit(EX_SYNTAX);
272 }
273
274 logname = argv[optind];
275
276 /* Determine whether the account is a role or not */
277 if ((ua = getusernam(logname)) == NULL ||
278 (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
279 strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
280 isrole = 0;
281 else
282 isrole = 1;
283
284 /* Verify that rolemod is used for roles and usermod for users */
285 if (isrole != is_role(usertype)) {
286 if (isrole)
287 errmsg(M_ISROLE);
288 else
289 errmsg(M_ISUSER);
290 exit(EX_SYNTAX);
291 }
292
293 /* Set the usertype key; defaults to the commandline */
294 usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
295
296 if (is_role(usertype)) {
297 /* Roles can't have roles */
298 if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
299 errmsg(M_MRUSAGE);
300 exit(EX_SYNTAX);
301 }
302 /* If it was an ordinary user, delete its roles */
303 if (!isrole)
304 change_key(USERATTR_ROLES_KW, "");
305 }
306
307 #ifdef att
308 pw = getpwnam(logname);
309 #else
310 /*
311 * Do this with fgetpwent to make sure we are only looking on local
312 * system (since passmgmt only works on local system).
313 */
314 if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
315 errmsg(M_OOPS, "open", "/etc/passwd");
316 exit(EX_FAILURE);
317 }
318 while ((pw = fgetpwent(pwf)) != NULL)
319 if (strcmp(pw->pw_name, logname) == 0)
320 break;
321
322 fclose(pwf);
323 #endif
324
325 if (pw == NULL) {
326 char pwdb[NSS_BUFLEN_PASSWD];
327 struct passwd pwd;
328
329 if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
330 /* This user does not exist. */
331 errmsg(M_EXIST, logname);
332 exit(EX_NAME_NOT_EXIST);
333 } else {
334 /* This user exists in non-local name service. */
335 errmsg(M_NONLOCAL, logname);
336 exit(EX_NOT_LOCAL);
337 }
338 }
339
340 pstruct = passwd_cpmalloc(pw);
341
342 /*
343 * We can't modify a logged in user if any of the following
344 * are being changed:
345 * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
346 * If none of those are specified it is okay to go ahead
347 * some types of changes only take effect on next login, some
348 * like authorisations and profiles take effect instantly.
349 * One might think that -K type=role should require that the
350 * user not be logged in, however this would make it very
351 * difficult to make the root account a role using this command.
352 */
353 if (isbusy(logname)) {
354 if (fail_if_busy) {
355 errmsg(M_BUSY, logname, "change");
356 exit(EX_BUSY);
357 }
358 warningmsg(WARN_LOGGED_IN, logname);
359 }
360
361 if (new_logname && strcmp(new_logname, logname)) {
362 switch (valid_login(new_logname, (struct passwd **)NULL,
363 &warning)) {
364 case INVALID:
365 errmsg(M_INVALID, new_logname, "login name");
366 exit(EX_BADARG);
367 /*NOTREACHED*/
368
369 case NOTUNIQUE:
370 errmsg(M_USED, new_logname);
371 exit(EX_NAME_EXISTS);
372 /*NOTREACHED*/
373
374 case LONGNAME:
375 errmsg(M_TOO_LONG, new_logname);
376 exit(EX_BADARG);
377 /*NOTREACHED*/
378
379 default:
380 call_pass = 1;
381 break;
382 }
383 if (warning)
384 warningmsg(warning, logname);
385 }
386
387 if (uidstr) {
388 /* convert uidstr to integer */
389 errno = 0;
390 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
391 if (*ptr || errno == ERANGE) {
392 errmsg(M_INVALID, uidstr, "user id");
393 exit(EX_BADARG);
394 }
395
396 if (uid != pstruct->pw_uid) {
397 switch (valid_uid(uid, NULL)) {
398 case NOTUNIQUE:
399 if (!oflag) {
400 /* override not specified */
401 errmsg(M_UID_USED, uid);
402 exit(EX_ID_EXISTS);
403 }
404 break;
405 case RESERVED:
406 errmsg(M_RESERVED, uid);
407 break;
408 case TOOBIG:
409 errmsg(M_TOOBIG, "uid", uid);
410 exit(EX_BADARG);
411 break;
412 }
413
414 call_pass = 1;
415
416 } else {
417 /* uid's the same, so don't change anything */
418 uidstr = NULL;
419 oflag = 0;
420 }
421
422 } else uid = pstruct->pw_uid;
423
424 if (group) {
425 switch (valid_group(group, &g_ptr, &warning)) {
426 case INVALID:
427 errmsg(M_INVALID, group, "group id");
428 exit(EX_BADARG);
429 /*NOTREACHED*/
430 case TOOBIG:
431 errmsg(M_TOOBIG, "gid", group);
432 exit(EX_BADARG);
433 /*NOTREACHED*/
434 case UNIQUE:
435 errmsg(M_GRP_NOTUSED, group);
436 exit(EX_NAME_NOT_EXIST);
437 /*NOTREACHED*/
438 case RESERVED:
439 gid = (gid_t)strtol(group, &ptr, (int)10);
440 errmsg(M_RESERVED_GID, gid);
441 break;
442 }
443 if (warning)
444 warningmsg(warning, group);
445
446 if (g_ptr != NULL)
447 gid = g_ptr->gr_gid;
448 else
449 gid = pstruct->pw_gid;
450
451 /* call passmgmt if gid is different, else ignore group */
452 if (gid != pstruct->pw_gid)
453 call_pass = 1;
454 else group = NULL;
455
456 } else gid = pstruct->pw_gid;
457
458 if (grps && *grps) {
459 if (!(gidlist = valid_lgroup(grps, gid)))
460 exit(EX_BADARG);
461 } else
462 gidlist = (int **)0;
463
464 if (projects && *projects) {
465 if (! (projlist = valid_lproject(projects)))
466 exit(EX_BADARG);
467 } else
468 projlist = (projid_t **)0;
469
470 if (dir) {
471 if (REL_PATH(dir)) {
472 errmsg(M_RELPATH, dir);
473 exit(EX_BADARG);
474 }
475 if (strcmp(pstruct->pw_dir, dir) == 0) {
476 /* home directory is the same so ignore dflag & mflag */
477 dir = NULL;
478 mflag = 0;
479 } else call_pass = 1;
480 }
481
482 if (mflag) {
483 if (stat(dir, &statbuf) == 0) {
484 /* Home directory exists */
485 if (check_perm(statbuf, pstruct->pw_uid,
486 pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
487 errmsg(M_NO_PERM, logname, dir);
488 exit(EX_NO_PERM);
489 }
490
491 } else ret = create_home(dir, NULL, uid, gid);
492
493 if (ret == EX_SUCCESS)
494 ret = move_dir(pstruct->pw_dir, dir, logname);
495
496 if (ret != EX_SUCCESS)
497 exit(ret);
498 }
499
500 if (shell) {
501 if (REL_PATH(shell)) {
502 errmsg(M_RELPATH, shell);
503 exit(EX_BADARG);
504 }
505 if (strcmp(pstruct->pw_shell, shell) == 0) {
506 /* ignore s option if shell is not different */
507 shell = NULL;
508 } else {
509 if (stat(shell, &statbuf) < 0 ||
510 (statbuf.st_mode & S_IFMT) != S_IFREG ||
511 (statbuf.st_mode & 0555) != 0555) {
512
513 errmsg(M_INVALID, shell, "shell");
514 exit(EX_BADARG);
515 }
516
517 call_pass = 1;
518 }
519 }
520
521 if (comment)
522 /* ignore comment if comment is not changed */
523 if (strcmp(pstruct->pw_comment, comment))
524 call_pass = 1;
525 else
526 comment = NULL;
527
528 /* inactive string is a positive integer */
529 if (inactstr) {
530 /* convert inactstr to integer */
531 inact = (int)strtol(inactstr, &ptr, 10);
532 if (*ptr || inact < 0) {
533 errmsg(M_INVALID, inactstr, "inactivity period");
534 exit(EX_BADARG);
535 }
536 call_pass = 1;
537 }
538
539 /* expiration string is a date, newer than today */
540 if (expire) {
541 if (*expire &&
542 valid_expire(expire, (time_t *)0) == INVALID) {
543 errmsg(M_INVALID, expire, "expiration date");
544 exit(EX_BADARG);
545 }
546 call_pass = 1;
547 }
548
549 if (nkeys > 0)
550 call_pass = 1;
551
552 /* that's it for validations - now do the work */
553
554 if (grps) {
555 /* redefine login's supplentary group memberships */
556 ret = edit_group(logname, new_logname, gidlist, 1);
557 if (ret != EX_SUCCESS) {
558 errmsg(M_UPDATE, "modified");
559 exit(ret);
560 }
561 }
562 if (projects) {
563 ret = edit_project(logname, (char *)NULL, projlist, 0);
564 if (ret != EX_SUCCESS) {
565 errmsg(M_UPDATE, "modified");
566 exit(ret);
567 }
568 }
569
570
571 if (!call_pass) exit(ret);
572
573 /* only get to here if need to call passmgmt */
574 /* set up arguments to passmgmt in nargv array */
575 nargv = malloc((30 + nkeys * 2) * sizeof (char *));
576
577 argindex = 0;
578 nargv[argindex++] = PASSMGMT;
579 nargv[argindex++] = "-m"; /* modify */
580
581 if (comment) { /* comment */
582 nargv[argindex++] = "-c";
583 nargv[argindex++] = comment;
584 }
585
586 if (dir) {
587 /* flags for home directory */
588 nargv[argindex++] = "-h";
589 nargv[argindex++] = dir;
590 }
591
592 if (group) {
593 /* set gid flag */
594 nargv[argindex++] = "-g";
595 (void) sprintf(gidstring, "%u", gid);
596 nargv[argindex++] = gidstring;
597 }
598
599 if (shell) { /* shell */
600 nargv[argindex++] = "-s";
601 nargv[argindex++] = shell;
602 }
603
604 if (inactstr) {
605 nargv[argindex++] = "-f";
606 nargv[argindex++] = inactstr;
607 }
608
609 if (expire) {
610 nargv[argindex++] = "-e";
611 nargv[argindex++] = expire;
612 }
613
614 if (uidstr) { /* set uid flag */
615 nargv[argindex++] = "-u";
616 (void) sprintf(uidstring, "%u", uid);
617 nargv[argindex++] = uidstring;
618 }
619
620 if (oflag) nargv[argindex++] = "-o";
621
622 if (new_logname) { /* redefine login name */
623 nargv[argindex++] = "-l";
624 nargv[argindex++] = new_logname;
625 }
626
627 if (nkeys > 0)
628 addkey_args(nargv, &argindex);
629
630 /* finally - login name */
631 nargv[argindex++] = logname;
632
633 /* set the last to null */
634 nargv[argindex++] = NULL;
635
636 /* now call passmgmt */
637 ret = PEX_FAILED;
638 for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
639 switch (ret = call_passmgmt(nargv)) {
640 case PEX_SUCCESS:
641 case PEX_BUSY:
642 break;
643
644 case PEX_HOSED_FILES:
645 errmsg(M_HOSED_FILES);
646 exit(EX_INCONSISTENT);
647 break;
648
649 case PEX_SYNTAX:
650 case PEX_BADARG:
651 /* should NEVER occur that passmgmt usage is wrong */
652 if (is_role(usertype))
653 errmsg(M_MRUSAGE);
654 else
655 errmsg(M_MUSAGE);
656 exit(EX_SYNTAX);
657 break;
658
659 case PEX_BADUID:
660 /* uid in use - shouldn't happen print message anyway */
661 errmsg(M_UID_USED, uid);
662 exit(EX_ID_EXISTS);
663 break;
664
665 case PEX_BADNAME:
666 /* invalid loname */
667 errmsg(M_USED, logname);
668 exit(EX_NAME_EXISTS);
669 break;
670
671 default:
672 errmsg(M_UPDATE, "modified");
673 exit(ret);
674 break;
675 }
676 }
677 if (tries == 0) {
678 errmsg(M_UPDATE, "modified");
679 }
680
681 exit(ret);
682 /*NOTREACHED*/
683 }