]> granicus.if.org Git - shadow/blob - src/chgpasswd.c
* src/chfn.c, src/chgpasswd.c, src/chpasswd.c, src/gpasswd.c,
[shadow] / src / chgpasswd.c
1 /*
2  * Copyright (c) 1990 - 1994, Julianne Frances Haugh
3  * Copyright (c) 2006       , Tomasz Kłoczko
4  * Copyright (c) 2006       , Jonas Meurer
5  * Copyright (c) 2007 - 2008, Nicolas François
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the copyright holders or contributors may not be used to
17  *    endorse or promote products derived from this software without
18  *    specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <config.h>
34
35 #ident "$Id$"
36
37 #include <fcntl.h>
38 #include <getopt.h>
39 #include <pwd.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #ifdef USE_PAM
43 #include "pam_defs.h"
44 #endif                          /* USE_PAM */
45 #include "defines.h"
46 #include "exitcodes.h"
47 #include "nscd.h"
48 #include "prototypes.h"
49 #include "groupio.h"
50 #ifdef  SHADOWGRP
51 #include "sgroupio.h"
52 #endif
53 /*
54  * Global variables
55  */
56 static char *Prog;
57 static bool cflg   = false;
58 static bool eflg   = false;
59 static bool md5flg = false;
60 static bool sflg   = false;
61
62 static const char *crypt_method = NULL;
63 static long sha_rounds = 5000;
64
65 #ifdef SHADOWGRP
66 static bool is_shadow_grp;
67 static bool sgr_locked = false;
68 #endif
69 static bool gr_locked = false;
70
71 #ifdef USE_PAM
72 static pam_handle_t *pamh = NULL;
73 #endif
74
75 /* local function prototypes */
76 static void fail_exit (int code);
77 static void usage (void);
78 static void process_flags (int argc, char **argv);
79 static void check_flags (void);
80 static void check_perms (void);
81 static void open_files (void);
82 static void close_files (void);
83
84 /*
85  * fail_exit - exit with a failure code after unlocking the files
86  */
87 static void fail_exit (int code)
88 {
89         if (gr_locked) {
90                 if (gr_unlock () == 0) {
91                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
92                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
93                         /* continue */
94                 }
95         }
96
97 #ifdef  SHADOWGRP
98         if (sgr_locked) {
99                 if (sgr_unlock () == 0) {
100                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
101                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
102                         /* continue */
103                 }
104         }
105 #endif
106
107         exit (code);
108 }
109
110 /*
111  * usage - display usage message and exit
112  */
113 static void usage (void)
114 {
115         fprintf (stderr, _("Usage: %s [options]\n"
116                            "\n"
117                            "Options:\n"
118                            "  -c, --crypt-method            the crypt method (one of %s)\n"
119                            "  -e, --encrypted               supplied passwords are encrypted\n"
120                            "  -h, --help                    display this help message and exit\n"
121                            "  -m, --md5                     encrypt the clear text password using\n"
122                            "                                the MD5 algorithm\n"
123                            "%s"
124                            "\n"),
125                          Prog,
126 #ifndef USE_SHA_CRYPT
127                          "NONE DES MD5", ""
128 #else
129                          "NONE DES MD5 SHA256 SHA512",
130                          _("  -s, --sha-rounds              number of SHA rounds for the SHA*\n"
131                            "                                crypt algorithms\n")
132 #endif
133                          );
134         exit (E_USAGE);
135 }
136
137 /*
138  * process_flags - parse the command line options
139  *
140  *      It will not return if an error is encountered.
141  */
142 static void process_flags (int argc, char **argv)
143 {
144         int option_index = 0;
145         int c;
146         static struct option long_options[] = {
147                 {"crypt-method", required_argument, NULL, 'c'},
148                 {"encrypted", no_argument, NULL, 'e'},
149                 {"help", no_argument, NULL, 'h'},
150                 {"md5", no_argument, NULL, 'm'},
151 #ifdef USE_SHA_CRYPT
152                 {"sha-rounds", required_argument, NULL, 's'},
153 #endif
154                 {NULL, 0, NULL, '\0'}
155         };
156
157         while ((c = getopt_long (argc, argv,
158 #ifdef USE_SHA_CRYPT
159                                  "c:ehms:",
160 #else
161                                  "c:ehm",
162 #endif
163                                  long_options, &option_index)) != -1) {
164                 switch (c) {
165                 case 'c':
166                         cflg = true;
167                         crypt_method = optarg;
168                         break;
169                 case 'e':
170                         eflg = true;
171                         break;
172                 case 'h':
173                         usage ();
174                         break;
175                 case 'm':
176                         md5flg = true;
177                         break;
178 #ifdef USE_SHA_CRYPT
179                 case 's':
180                         sflg = true;
181                         if (getlong(optarg, &sha_rounds) != 0) {
182                                 fprintf (stderr,
183                                          _("%s: invalid numeric argument '%s'\n"),
184                                          Prog, optarg);
185                                 usage ();
186                         }
187                         break;
188 #endif
189                 case 0:
190                         /* long option */
191                         break;
192                 default:
193                         usage ();
194                         break;
195                 }
196         }
197
198         /* validate options */
199         check_flags ();
200 }
201
202 /*
203  * check_flags - check flags and parameters consistency
204  *
205  *      It will not return if an error is encountered.
206  */
207 static void check_flags (void)
208 {
209         if (sflg && !cflg) {
210                 fprintf (stderr,
211                          _("%s: %s flag is only allowed with the %s flag\n"),
212                          Prog, "-s", "-c");
213                 usage ();
214         }
215
216         if ((eflg && (md5flg || cflg)) ||
217             (md5flg && cflg)) {
218                 fprintf (stderr,
219                          _("%s: the -c, -e, and -m flags are exclusive\n"),
220                          Prog);
221                 usage ();
222         }
223
224         if (cflg) {
225                 if (   (0 != strcmp (crypt_method, "DES"))
226                     && (0 != strcmp (crypt_method, "MD5"))
227                     && (0 != strcmp (crypt_method, "NONE"))
228 #ifdef USE_SHA_CRYPT
229                     && (0 != strcmp (crypt_method, "SHA256"))
230                     && (0 != strcmp (crypt_method, "SHA512"))
231 #endif
232                     ) {
233                         fprintf (stderr,
234                                  _("%s: unsupported crypt method: %s\n"),
235                                  Prog, crypt_method);
236                         usage ();
237                 }
238         }
239 }
240
241 /*
242  * check_perms - check if the caller is allowed to add a group
243  *
244  *      With PAM support, the setuid bit can be set on chgpasswd to allow
245  *      non-root users to groups.
246  *      Without PAM support, only users who can write in the group databases
247  *      can add groups.
248  *
249  *      It will not return if the user is not allowed.
250  */
251 static void check_perms (void)
252 {
253 #ifdef USE_PAM
254         int retval = PAM_SUCCESS;
255         struct passwd *pampw;
256
257         pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
258         if (NULL == pampw) {
259                 retval = PAM_USER_UNKNOWN;
260         }
261
262         if (PAM_SUCCESS == retval) {
263                 retval = pam_start ("chgpasswd", pampw->pw_name, &conv, &pamh);
264         }
265
266         if (PAM_SUCCESS == retval) {
267                 retval = pam_authenticate (pamh, 0);
268         }
269
270         if (PAM_SUCCESS == retval) {
271                 retval = pam_acct_mgmt (pamh, 0);
272         }
273
274         if (PAM_SUCCESS != retval) {
275                 (void) pam_end (pamh, retval);
276                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
277                 exit (1);
278         }
279 #endif                          /* USE_PAM */
280 }
281
282 /*
283  * open_files - lock and open the group databases
284  */
285 static void open_files (void)
286 {
287         /*
288          * Lock the group file and open it for reading and writing. This will
289          * bring all of the entries into memory where they may be updated.
290          */
291         if (gr_lock () == 0) {
292                 fprintf (stderr,
293                          _("%s: cannot lock %s; try again later.\n"),
294                          Prog, gr_dbname ());
295                 fail_exit (1);
296         }
297         gr_locked = true;
298         if (gr_open (O_RDWR) == 0) {
299                 fprintf (stderr,
300                          _("%s: cannot open %s\n"), Prog, gr_dbname ());
301                 fail_exit (1);
302         }
303
304 #ifdef SHADOWGRP
305         /* Do the same for the shadowed database, if it exist */
306         if (is_shadow_grp) {
307                 if (sgr_lock () == 0) {
308                         fprintf (stderr,
309                                  _("%s: cannot lock %s; try again later.\n"),
310                                  Prog, sgr_dbname ());
311                         fail_exit (1);
312                 }
313                 sgr_locked = true;
314                 if (sgr_open (O_RDWR) == 0) {
315                         fprintf (stderr, _("%s: cannot open %s\n"),
316                                  Prog, sgr_dbname ());
317                         fail_exit (1);
318                 }
319         }
320 #endif
321 }
322
323 /*
324  * close_files - close and unlock the group databases
325  */
326 static void close_files (void)
327 {
328 #ifdef SHADOWGRP
329         if (is_shadow_grp) {
330                 if (sgr_close () == 0) {
331                         fprintf (stderr,
332                                  _("%s: failure while writing changes to %s\n"),
333                                  Prog, sgr_dbname ());
334                         SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
335                         fail_exit (1);
336                 }
337                 if (sgr_unlock () == 0) {
338                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
339                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
340                         /* continue */
341                 }
342                 sgr_locked = false;
343         }
344 #endif
345
346         if (gr_close () == 0) {
347                 fprintf (stderr,
348                          _("%s: failure while writing changes to %s\n"),
349                          Prog, gr_dbname ());
350                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
351                 fail_exit (1);
352         }
353         if (gr_unlock () == 0) {
354                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
355                 SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
356                 /* continue */
357         }
358         gr_locked = false;
359 }
360
361 int main (int argc, char **argv)
362 {
363         char buf[BUFSIZ];
364         char *name;
365         char *newpwd;
366         char *cp;
367
368 #ifdef  SHADOWGRP
369         const struct sgrp *sg;
370         struct sgrp newsg;
371 #endif
372
373         const struct group *gr;
374         struct group newgr;
375         int errors = 0;
376         int line = 0;
377         int ok;
378
379         Prog = Basename (argv[0]);
380
381         (void) setlocale (LC_ALL, "");
382         (void) bindtextdomain (PACKAGE, LOCALEDIR);
383         (void) textdomain (PACKAGE);
384
385         process_flags (argc, argv);
386
387         OPENLOG ("chgpasswd");
388
389         check_perms ();
390
391 #ifdef SHADOWGRP
392         is_shadow_grp = sgr_file_present ();
393 #endif
394
395         open_files ();
396
397         /*
398          * Read each line, separating the group name from the password. The
399          * group entry for each group will be looked up in the appropriate
400          * file (gshadow or group) and the password changed.
401          */
402         while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
403                 line++;
404                 cp = strrchr (buf, '\n');
405                 if (NULL != cp) {
406                         *cp = '\0';
407                 } else {
408                         fprintf (stderr, _("%s: line %d: line too long\n"),
409                                  Prog, line);
410                         errors++;
411                         continue;
412                 }
413
414                 /*
415                  * The group's name is the first field. It is separated from
416                  * the password with a ":" character which is replaced with a
417                  * NUL to give the new password. The new password will then
418                  * be encrypted in the normal fashion with a new salt
419                  * generated, unless the '-e' is given, in which case it is
420                  * assumed to already be encrypted.
421                  */
422
423                 name = buf;
424                 cp = strchr (name, ':');
425                 if (NULL != cp) {
426                         *cp = '\0';
427                         cp++;
428                 } else {
429                         fprintf (stderr,
430                                  _("%s: line %d: missing new password\n"),
431                                  Prog, line);
432                         errors++;
433                         continue;
434                 }
435                 newpwd = cp;
436                 if (!eflg &&
437                     (NULL == crypt_method ||
438                      0 != strcmp(crypt_method, "NONE"))) {
439                         void *arg = NULL;
440                         if (md5flg) {
441                                 crypt_method = "MD5";
442                         } else if (crypt_method != NULL) {
443                                 if (sflg) {
444                                         arg = &sha_rounds;
445                                 }
446                         } else {
447                                 crypt_method = NULL;
448                         }
449                         cp = pw_encrypt (newpwd,
450                                          crypt_make_salt(crypt_method, arg));
451                 }
452
453                 /*
454                  * Get the group file entry for this group. The group must
455                  * already exist.
456                  */
457                 gr = gr_locate (name);
458                 if (NULL == gr) {
459                         fprintf (stderr,
460                                  _("%s: line %d: group '%s' does not exist\n"), Prog,
461                                  line, name);
462                         errors++;
463                         continue;
464                 }
465 #ifdef SHADOWGRP
466                 if (is_shadow_grp) {
467                         sg = sgr_locate (name);
468                 } else {
469                         sg = NULL;
470                 }
471 #endif
472
473                 /*
474                  * The freshly encrypted new password is merged into the
475                  * group's entry.
476                  */
477 #ifdef SHADOWGRP
478                 if (NULL != sg) {
479                         newsg = *sg;
480                         newsg.sg_passwd = cp;
481                 } else
482 #endif
483                 {
484                         newgr = *gr;
485                         newgr.gr_passwd = cp;
486                 }
487
488                 /* 
489                  * The updated group file entry is then put back and will
490                  * be written to the group file later, after all the
491                  * other entries have been updated as well.
492                  */
493 #ifdef SHADOWGRP
494                 if (NULL != sg) {
495                         ok = sgr_update (&newsg);
496                 } else
497 #endif
498                 {
499                         ok = gr_update (&newgr);
500                 }
501
502                 if (0 == ok) {
503                         fprintf (stderr,
504                                  _("%s: line %d: cannot update group entry\n"),
505                                  Prog, line);
506                         errors++;
507                         continue;
508                 }
509         }
510
511         /*
512          * Any detected errors will cause the entire set of changes to be
513          * aborted. Unlocking the group file will cause all of the
514          * changes to be ignored. Otherwise the file is closed, causing the
515          * changes to be written out all at once, and then unlocked
516          * afterwards.
517          */
518         if (0 != errors) {
519                 fprintf (stderr,
520                          _("%s: error detected, changes ignored\n"), Prog);
521                 fail_exit (1);
522         }
523
524         close_files ();
525
526         nscd_flush_cache ("group");
527
528 #ifdef USE_PAM
529         (void) pam_end (pamh, PAM_SUCCESS);
530 #endif                          /* USE_PAM */
531
532         return (0);
533 }
534