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