]> granicus.if.org Git - shadow/blob - src/gpasswd.c
[svn-upgrade] Integrating new upstream version, shadow (19990709)
[shadow] / src / gpasswd.c
1 /*
2  * Copyright 1990 - 1994, Julianne Frances Haugh
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <config.h>
31
32 #include "rcsid.h"
33 RCSID(PKG_VER "$Id: gpasswd.c,v 1.14 1999/06/07 16:40:45 marekm Exp $")
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <pwd.h>
38 #include <grp.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <errno.h>
42
43 #include "prototypes.h"
44 #include "defines.h"
45
46 #include "groupio.h"
47 #ifdef  SHADOWGRP
48 #include "sgroupio.h"
49 #endif
50
51 static char *Prog;
52 #ifdef SHADOWGRP
53 static int is_shadowgrp;
54 #endif
55
56 static int
57         aflg = 0,
58         Aflg = 0,
59         dflg = 0,
60         Mflg = 0,
61         rflg = 0,
62         Rflg = 0;
63
64 #ifndef RETRIES
65 #define RETRIES 3
66 #endif
67
68 extern char *crypt_make_salt P_((void));
69 extern int optind;
70 extern char *optarg;
71 #ifdef  NDBM
72 #ifdef  SHADOWGRP
73 extern  int     sg_dbm_mode;
74 #endif
75 extern  int     gr_dbm_mode;
76 #endif
77
78 /* local function prototypes */
79 static void usage P_((void));
80 static RETSIGTYPE die P_((int));
81 static int check_list P_((const char *));
82 int main P_((int, char **));
83
84 /*
85  * usage - display usage message
86  */
87
88 static void
89 usage(void)
90 {
91         fprintf(stderr, _("usage: %s [-r|-R] group\n"), Prog);
92         fprintf(stderr, _("       %s [-a user] group\n"), Prog);
93         fprintf(stderr, _("       %s [-d user] group\n"), Prog);
94 #ifdef  SHADOWGRP
95         fprintf(stderr, _("       %s [-A user,...] [-M user,...] group\n"),
96                 Prog);
97 #else
98         fprintf(stderr, _("       %s [-M user,...] group\n"), Prog);
99 #endif
100         exit (1);
101 }
102
103 /*
104  * die - set or reset termio modes.
105  *
106  *      die() is called before processing begins.  signal() is then
107  *      called with die() as the signal handler.  If signal later
108  *      calls die() with a signal number, the terminal modes are
109  *      then reset.
110  */
111
112 static RETSIGTYPE
113 die(int killed)
114 {
115         static TERMIO sgtty;
116
117         if (killed)
118                 STTY(0, &sgtty);
119         else
120                 GTTY(0, &sgtty);
121
122         if (killed) {
123                 putchar ('\n');
124                 fflush (stdout);
125                 exit (killed);
126         }
127 }
128
129 /*
130  * check_list - check a comma-separated list of user names for validity
131  *
132  *      check_list scans a comma-separated list of user names and checks
133  *      that each listed name exists.
134  */
135
136 static int
137 check_list(const char *users)
138 {
139         const char *start, *end;
140         char username[32];
141         int     errors = 0;
142         int     len;
143
144         for (start = users; start && *start; start = end) {
145                 if ((end = strchr (start, ','))) {
146                         len = end - start;
147                         end++;
148                 } else {
149                         len = strlen(start);
150                 }
151
152                 if (len > sizeof(username) - 1)
153                         len = sizeof(username) - 1;
154                 strncpy(username, start, len);
155                 username[len] = '\0';
156
157                 /*
158                  * This user must exist.
159                  */
160
161                 if (!getpwnam(username)) {
162                         fprintf(stderr, _("%s: unknown user %s\n"),
163                                 Prog, username);
164                         errors++;
165                 }
166         }
167         return errors;
168 }
169
170
171 static void
172 failure(void)
173 {
174         fprintf(stderr, _("Permission denied.\n"));
175         exit(1);
176         /*NOTREACHED*/
177 }
178
179
180 /*
181  * gpasswd - administer the /etc/group file
182  *
183  *      -a user         add user to the named group
184  *      -d user         remove user from the named group
185  *      -r              remove password from the named group
186  *      -R              restrict access to the named group
187  *      -A user,...     make list of users the administrative users
188  *      -M user,...     make list of users the group members
189  */
190
191 int
192 main(int argc, char **argv)
193 {
194         int     flag;
195         char    *cp;
196         int     amroot;
197         int     retries;
198         struct group *gr = NULL;
199         struct  group   grent;
200         static char pass[BUFSIZ];
201 #ifdef  SHADOWGRP
202         struct sgrp *sg = NULL;
203         struct  sgrp    sgent;
204         char *admins = NULL;
205 #endif
206         struct passwd *pw = NULL;
207         char *myname;
208         char *user = NULL;
209         char *group = NULL;
210         char *members = NULL;
211
212         sanitize_env();
213         setlocale(LC_ALL, "");
214         bindtextdomain(PACKAGE, LOCALEDIR);
215         textdomain(PACKAGE);
216
217         /*
218          * Make a note of whether or not this command was invoked
219          * by root.  This will be used to bypass certain checks
220          * later on.  Also, set the real user ID to match the
221          * effective user ID.  This will prevent the invoker from
222          * issuing signals which would interfer with this command.
223          */
224
225         amroot = getuid () == 0;
226 #ifdef  NDBM
227 #ifdef  SHADOWGRP
228         sg_dbm_mode = O_RDWR;
229 #endif
230         gr_dbm_mode = O_RDWR;
231 #endif
232
233         Prog = Basename(argv[0]);
234
235         openlog("gpasswd", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
236         setbuf (stdout, (char *) 0);
237         setbuf (stderr, (char *) 0);
238
239 #ifdef SHADOWGRP
240         is_shadowgrp = sgr_file_present();
241 #endif
242         while ((flag = getopt (argc, argv, "a:d:grRA:M:")) != EOF) {
243                 switch (flag) {
244                 case 'a':       /* add a user */
245                         user = optarg;
246                         if (!getpwnam(user)) {
247                                 fprintf(stderr, _("%s: unknown user %s\n"),
248                                         Prog, user);
249                                 exit(1);
250                         }
251                         aflg++;
252                         break;
253 #ifdef SHADOWGRP
254                 case 'A':
255                         if (!amroot)
256                                 failure();
257                         if (!is_shadowgrp) {
258                                 fprintf(stderr,
259                                         _("%s: shadow group passwords required for -A\n"),
260                                         Prog);
261                                 exit(2);
262                         }
263                         admins = optarg;
264                         if (check_list(admins))
265                                 exit(1);
266                         Aflg++;
267                         break;
268 #endif
269                 case 'd':       /* delete a user */
270                         dflg++;
271                         user = optarg;
272                         break;
273                 case 'g':       /* no-op from normal password */
274                         break;
275                 case 'M':
276                         if (!amroot)
277                                 failure();
278                         members = optarg;
279                         if (check_list(members))
280                                 exit(1);
281                         Mflg++;
282                         break;
283                 case 'r':       /* remove group password */
284                         rflg++;
285                         break;
286                 case 'R':       /* restrict group password */
287                         Rflg++;
288                         break;
289                 default:
290                         usage();
291                 }
292         }
293
294         /*
295          * Make sure exclusive flags are exclusive
296          */
297
298         if (aflg + dflg + rflg + Rflg + (Aflg || Mflg) > 1)
299                 usage ();
300
301         /*
302          * Determine the name of the user that invoked this command.
303          * This is really hit or miss because there are so many ways
304          * that command can be executed and so many ways to trip up
305          * the routines that report the user name.
306          */
307
308         pw = get_my_pwent();
309         if (!pw) {
310                 fprintf(stderr, _("Who are you?\n"));
311                 exit(1);
312         }
313         myname = xstrdup(pw->pw_name);
314
315         /*
316          * Get the name of the group that is being affected.  The group
317          * entry will be completely replicated so it may be modified
318          * later on.
319          */
320
321         /*
322          * XXX - should get the entry using gr_locate() and modify
323          * that, getgrnam() could give us a NIS group.  --marekm
324          */
325
326         if (! (group = argv[optind]))
327                 usage ();
328
329         if (! (gr = getgrnam (group))) {
330                 fprintf (stderr, _("unknown group: %s\n"), group);
331                 exit (1);
332         }
333         grent = *gr;
334         grent.gr_name = xstrdup(gr->gr_name);
335         grent.gr_passwd = xstrdup(gr->gr_passwd);
336
337         grent.gr_mem = dup_list(gr->gr_mem);
338 #ifdef  SHADOWGRP
339         if ((sg = getsgnam (group))) {
340                 sgent = *sg;
341                 sgent.sg_name = xstrdup(sg->sg_name);
342                 sgent.sg_passwd = xstrdup(sg->sg_passwd);
343
344                 sgent.sg_mem = dup_list(sg->sg_mem);
345                 sgent.sg_adm = dup_list(sg->sg_adm);
346         } else {
347                 sgent.sg_name = xstrdup(group);
348                 sgent.sg_passwd = grent.gr_passwd;
349                 grent.gr_passwd = "!";  /* XXX warning: const */
350
351                 sgent.sg_mem = dup_list(grent.gr_mem);
352
353                 sgent.sg_adm = (char **) xmalloc(sizeof(char *) * 2);
354 #ifdef FIRST_MEMBER_IS_ADMIN
355                 if (sgent.sg_mem[0]) {
356                         sgent.sg_adm[0] = xstrdup(sgent.sg_mem[0]);
357                         sgent.sg_adm[1] = 0;
358                 } else
359 #endif
360                         sgent.sg_adm[0] = 0;
361
362                 sg = &sgent;
363         }
364
365         /*
366          * The policy here for changing a group is that 1) you must be
367          * root or 2). you must be listed as an administrative member.
368          * Administrative members can do anything to a group that the
369          * root user can.
370          */
371
372         if (!amroot && !is_on_list(sgent.sg_adm, myname))
373                 failure();
374 #else   /* ! SHADOWGRP */
375
376 #ifdef FIRST_MEMBER_IS_ADMIN
377         /*
378          * The policy here for changing a group is that 1) you must bes
379          * root or 2) you must be the first listed member of the group.
380          * The first listed member of a group can do anything to that
381          * group that the root user can.  The rationale for this hack is
382          * that the FIRST user is probably the most important user in
383          * this entire group.
384          */
385
386         if (! amroot) {
387                 if (grent.gr_mem[0] == (char *) 0)
388                         failure();
389
390                 if (strcmp(grent.gr_mem[0], myname) != 0)
391                         failure();
392         }
393 #else
394         /*
395          * This feature enabled by default could be a security problem
396          * when installed on existing systems where the first group
397          * member might be just a normal user...  --marekm
398          */
399
400         if (!amroot)
401                 failure();
402 #endif
403
404 #endif  /* SHADOWGRP */
405
406         /*
407          * Removing a password is straight forward.  Just set the
408          * password field to a "".
409          */
410
411         if (rflg) {
412                 grent.gr_passwd = "";  /* XXX warning: const */
413 #ifdef  SHADOWGRP
414                 sgent.sg_passwd = "";  /* XXX warning: const */
415 #endif
416                 SYSLOG((LOG_INFO, "remove password from group %s by %s\n", group, myname));
417                 goto output;
418         } else if (Rflg) {
419                 /*
420                  * Same thing for restricting the group.  Set the password
421                  * field to "!".
422                  */
423
424                 grent.gr_passwd = "!";  /* XXX warning: const */
425 #ifdef  SHADOWGRP
426                 sgent.sg_passwd = "!";  /* XXX warning: const */
427 #endif
428                 SYSLOG((LOG_INFO, "restrict access to group %s by %s\n", group, myname));
429                 goto output;
430         }
431
432         /*
433          * Adding a member to a member list is pretty straightforward
434          * as well.  Call the appropriate routine and split.
435          */
436
437         if (aflg) {
438                 printf(_("Adding user %s to group %s\n"), user, group);
439                 grent.gr_mem = add_list (grent.gr_mem, user);
440 #ifdef  SHADOWGRP
441                 sgent.sg_mem = add_list (sgent.sg_mem, user);
442 #endif
443                 SYSLOG((LOG_INFO, "add member %s to group %s by %s\n", user, group, myname));
444                 goto output;
445         }
446
447         /*
448          * Removing a member from the member list is the same deal
449          * as adding one, except the routine is different.
450          */
451
452         if (dflg) {
453                 int     removed = 0;
454
455                 printf(_("Removing user %s from group %s\n"), user, group);
456
457                 if (is_on_list(grent.gr_mem, user)) {
458                         removed = 1;
459                         grent.gr_mem = del_list (grent.gr_mem, user);
460                 }
461 #ifdef  SHADOWGRP
462                 if (is_on_list(sgent.sg_mem, user)) {
463                         removed = 1;
464                         sgent.sg_mem = del_list (sgent.sg_mem, user);
465                 }
466 #endif
467                 if (! removed) {
468                         fprintf(stderr, _("%s: unknown member %s\n"),
469                                 Prog, user);
470                         exit (1);
471                 }
472                 SYSLOG((LOG_INFO, "remove member %s from group %s by %s\n",
473                         user, group, myname));
474                 goto output;
475         }
476
477 #ifdef  SHADOWGRP
478         /*
479          * Replacing the entire list of administators is simple.  Check the
480          * list to make sure everyone is a real user.  Then slap the new
481          * list in place.
482          */
483
484         if (Aflg) {
485                 SYSLOG((LOG_INFO, "set administrators of %s to %s\n",
486                                 group, admins));
487                 sgent.sg_adm = comma_to_list(admins);
488                 if (!Mflg)
489                         goto output;
490         }
491 #endif
492
493         /*
494          * Replacing the entire list of members is simple.  Check the list
495          * to make sure everyone is a real user.  Then slap the new list
496          * in place.
497          */
498
499         if (Mflg) {
500                 SYSLOG((LOG_INFO,"set members of %s to %s\n",group,members));
501 #ifdef SHADOWGRP
502                 sgent.sg_mem = comma_to_list(members);
503 #endif
504                 grent.gr_mem = comma_to_list(members);
505                 goto output;
506         }
507
508         /*
509          * If the password is being changed, the input and output must
510          * both be a tty.  The typical keyboard signals are caught
511          * so the termio modes can be restored.
512          */
513
514         if (! isatty (0) || ! isatty (1)) {
515                 fprintf(stderr, _("%s: Not a tty\n"), Prog);
516                 exit (1);
517         }
518
519         die (0);                        /* save tty modes */
520
521         signal (SIGHUP, die);
522         signal (SIGINT, die);
523         signal (SIGQUIT, die);
524         signal (SIGTERM, die);
525 #ifdef  SIGTSTP
526         signal (SIGTSTP, die);
527 #endif
528
529         /*
530          * A new password is to be entered and it must be encrypted,
531          * etc.  The password will be prompted for twice, and both
532          * entries must be identical.  There is no need to validate
533          * the old password since the invoker is either the group
534          * owner, or root.
535          */
536
537         printf(_("Changing the password for group %s\n"), group);
538
539         for (retries = 0; retries < RETRIES; retries++) {
540                 if (! (cp = getpass(_("New Password:"))))
541                         exit (1);
542
543                 STRFCPY(pass, cp);
544                 strzero(cp);
545                 if (! (cp = getpass (_("Re-enter new password:"))))
546                         exit (1);
547
548                 if (strcmp(pass, cp) == 0) {
549                         strzero(cp);
550                         break;
551                 }
552
553                 strzero(cp);
554                 memzero(pass, sizeof pass);
555
556                 if (retries + 1 < RETRIES)
557                         puts(_("They don't match; try again"));
558         }
559
560         if (retries == RETRIES) {
561                 fprintf(stderr, _("%s: Try again later\n"), Prog);
562                 exit(1);
563         }
564
565         cp = pw_encrypt(pass, crypt_make_salt());
566         memzero(pass, sizeof pass);
567 #ifdef  SHADOWGRP
568         sgent.sg_passwd = cp;
569 #else
570         grent.gr_passwd = cp;
571 #endif
572         SYSLOG((LOG_INFO, "change the password for group %s by %s\n", group, myname));
573
574         /*
575          * This is the common arrival point to output the new group
576          * file.  The freshly crafted entry is in allocated space.
577          * The group file will be locked and opened for writing.  The
578          * new entry will be output, etc.
579          */
580
581 output:
582         if (setuid(0)) {
583                 fprintf(stderr, _("Cannot change ID to root.\n"));
584                 SYSLOG((LOG_ERR, "can't setuid(0)"));
585                 closelog();
586                 exit(1);
587         }
588         pwd_init();
589
590         if (! gr_lock ()) {
591                 fprintf(stderr, _("%s: can't get lock\n"), Prog);
592                 SYSLOG((LOG_WARN, "failed to get lock for /etc/group\n"));
593                 exit (1);
594         }
595 #ifdef  SHADOWGRP
596         if (is_shadowgrp && ! sgr_lock ()) {
597                 fprintf(stderr, _("%s: can't get shadow lock\n"), Prog);
598                 SYSLOG((LOG_WARN, "failed to get lock for /etc/gshadow\n"));
599                 exit (1);
600         }
601 #endif
602         if (! gr_open (O_RDWR)) {
603                 fprintf(stderr, _("%s: can't open file\n"), Prog);
604                 SYSLOG((LOG_WARN, "cannot open /etc/group\n"));
605                 exit (1);
606         }
607 #ifdef  SHADOWGRP
608         if (is_shadowgrp && ! sgr_open (O_RDWR)) {
609                 fprintf(stderr, _("%s: can't open shadow file\n"), Prog);
610                 SYSLOG((LOG_WARN, "cannot open /etc/gshadow\n"));
611                 exit (1);
612         }
613 #endif
614         if (! gr_update (&grent)) {
615                 fprintf(stderr, _("%s: can't update entry\n"), Prog);
616                 SYSLOG((LOG_WARN, "cannot update /etc/group\n"));
617                 exit (1);
618         }
619 #ifdef  SHADOWGRP
620         if (is_shadowgrp && ! sgr_update (&sgent)) {
621                 fprintf(stderr, _("%s: can't update shadow entry\n"), Prog);
622                 SYSLOG((LOG_WARN, "cannot update /etc/gshadow\n"));
623                 exit (1);
624         }
625 #endif
626         if (! gr_close ()) {
627                 fprintf(stderr, _("%s: can't re-write file\n"), Prog);
628                 SYSLOG((LOG_WARN, "cannot re-write /etc/group\n"));
629                 exit (1);
630         }
631 #ifdef  SHADOWGRP
632         if (is_shadowgrp && ! sgr_close ()) {
633                 fprintf(stderr, _("%s: can't re-write shadow file\n"), Prog);
634                 SYSLOG((LOG_WARN, "cannot re-write /etc/gshadow\n"));
635                 exit (1);
636         }
637         if (is_shadowgrp)
638                 sgr_unlock ();
639 #endif
640         if (! gr_unlock ()) {
641                 fprintf(stderr, _("%s: can't unlock file\n"), Prog);
642                 exit (1);
643         }
644 #ifdef  NDBM
645         if (gr_dbm_present() && ! gr_dbm_update (&grent)) {
646                 fprintf(stderr, _("%s: can't update DBM files\n"), Prog);
647                 SYSLOG((LOG_WARN, "cannot update /etc/group DBM files\n"));
648                 exit (1);
649         }
650         endgrent ();
651 #ifdef  SHADOWGRP
652         if (is_shadowgrp && sg_dbm_present() && ! sg_dbm_update (&sgent)) {
653                 fprintf(stderr, _("%s: can't update DBM shadow files\n"), Prog);
654                 SYSLOG((LOG_WARN, "cannot update /etc/gshadow DBM files\n"));
655                 exit (1);
656         }
657         endsgent ();
658 #endif
659 #endif
660         exit (0);
661         /*NOTREACHED*/
662 }