]> granicus.if.org Git - shadow/blob - src/chgpasswd.c
* src/passwd.c: Overflow when computing the number of days based
[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 - 2009, 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 ACCT_TOOLS_SETUID
43 #ifdef USE_PAM
44 #include "pam_defs.h"
45 #endif                          /* USE_PAM */
46 #endif                          /* ACCT_TOOLS_SETUID */
47 #include "defines.h"
48 #include "nscd.h"
49 #include "prototypes.h"
50 #include "groupio.h"
51 #ifdef  SHADOWGRP
52 #include "sgroupio.h"
53 #endif
54 /*@-exitarg@*/
55 #include "exitcodes.h"
56
57 /*
58  * Global variables
59  */
60 const char *Prog;
61 static bool cflg   = false;
62 static bool eflg   = false;
63 static bool md5flg = false;
64 #ifdef USE_SHA_CRYPT
65 static bool sflg   = false;
66 #endif
67
68 static const char *crypt_method = NULL;
69 #ifdef USE_SHA_CRYPT
70 static long sha_rounds = 5000;
71 #endif
72
73 #ifdef SHADOWGRP
74 static bool is_shadow_grp;
75 static bool sgr_locked = false;
76 #endif
77 static bool gr_locked = false;
78
79 /* local function prototypes */
80 static void fail_exit (int code);
81 static void usage (int status);
82 static void process_flags (int argc, char **argv);
83 static void check_flags (void);
84 static void check_perms (void);
85 static void open_files (void);
86 static void close_files (void);
87
88 /*
89  * fail_exit - exit with a failure code after unlocking the files
90  */
91 static void fail_exit (int code)
92 {
93         if (gr_locked) {
94                 if (gr_unlock () == 0) {
95                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
96                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
97                         /* continue */
98                 }
99         }
100
101 #ifdef  SHADOWGRP
102         if (sgr_locked) {
103                 if (sgr_unlock () == 0) {
104                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
105                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
106                         /* continue */
107                 }
108         }
109 #endif
110
111         exit (code);
112 }
113
114 /*
115  * usage - display usage message and exit
116  */
117 static void usage (int status)
118 {
119         (void) fprintf ((E_SUCCESS != status) ? stderr : stdout,
120                          _("Usage: %s [options]\n"
121                            "\n"
122                            "Options:\n"
123                            "  -c, --crypt-method            the crypt method (one of %s)\n"
124                            "  -e, --encrypted               supplied passwords are encrypted\n"
125                            "  -h, --help                    display this help message and exit\n"
126                            "  -m, --md5                     encrypt the clear text password using\n"
127                            "                                the MD5 algorithm\n"
128                            "%s"
129                            "\n"),
130                          Prog,
131 #ifndef USE_SHA_CRYPT
132                          "NONE DES MD5", ""
133 #else
134                          "NONE DES MD5 SHA256 SHA512",
135                          _("  -s, --sha-rounds              number of SHA rounds for the SHA*\n"
136                            "                                crypt algorithms\n")
137 #endif
138                          );
139         exit (status);
140 }
141
142 /*
143  * process_flags - parse the command line options
144  *
145  *      It will not return if an error is encountered.
146  */
147 static void process_flags (int argc, char **argv)
148 {
149         int option_index = 0;
150         int c;
151         static struct option long_options[] = {
152                 {"crypt-method", required_argument, NULL, 'c'},
153                 {"encrypted", no_argument, NULL, 'e'},
154                 {"help", no_argument, NULL, 'h'},
155                 {"md5", no_argument, NULL, 'm'},
156 #ifdef USE_SHA_CRYPT
157                 {"sha-rounds", required_argument, NULL, 's'},
158 #endif
159                 {NULL, 0, NULL, '\0'}
160         };
161
162         while ((c = getopt_long (argc, argv,
163 #ifdef USE_SHA_CRYPT
164                                  "c:ehms:",
165 #else
166                                  "c:ehm",
167 #endif
168                                  long_options, &option_index)) != -1) {
169                 switch (c) {
170                 case 'c':
171                         cflg = true;
172                         crypt_method = optarg;
173                         break;
174                 case 'e':
175                         eflg = true;
176                         break;
177                 case 'h':
178                         usage (E_SUCCESS);
179                         break;
180                 case 'm':
181                         md5flg = true;
182                         break;
183 #ifdef USE_SHA_CRYPT
184                 case 's':
185                         sflg = true;
186                         if (getlong(optarg, &sha_rounds) == 0) {
187                                 fprintf (stderr,
188                                          _("%s: invalid numeric argument '%s'\n"),
189                                          Prog, optarg);
190                                 usage (E_USAGE);
191                         }
192                         break;
193 #endif
194                 default:
195                         usage (E_USAGE);
196                         break;
197                 }
198         }
199
200         /* validate options */
201         check_flags ();
202 }
203
204 /*
205  * check_flags - check flags and parameters consistency
206  *
207  *      It will not return if an error is encountered.
208  */
209 static void check_flags (void)
210 {
211 #ifdef USE_SHA_CRYPT
212         if (sflg && !cflg) {
213                 fprintf (stderr,
214                          _("%s: %s flag is only allowed with the %s flag\n"),
215                          Prog, "-s", "-c");
216                 usage (E_USAGE);
217         }
218 #endif
219
220         if ((eflg && (md5flg || cflg)) ||
221             (md5flg && cflg)) {
222                 fprintf (stderr,
223                          _("%s: the -c, -e, and -m flags are exclusive\n"),
224                          Prog);
225                 usage (E_USAGE);
226         }
227
228         if (cflg) {
229                 if (   (0 != strcmp (crypt_method, "DES"))
230                     && (0 != strcmp (crypt_method, "MD5"))
231                     && (0 != strcmp (crypt_method, "NONE"))
232 #ifdef USE_SHA_CRYPT
233                     && (0 != strcmp (crypt_method, "SHA256"))
234                     && (0 != strcmp (crypt_method, "SHA512"))
235 #endif
236                     ) {
237                         fprintf (stderr,
238                                  _("%s: unsupported crypt method: %s\n"),
239                                  Prog, crypt_method);
240                         usage (E_USAGE);
241                 }
242         }
243 }
244
245 /*
246  * check_perms - check if the caller is allowed to add a group
247  *
248  *      With PAM support, the setuid bit can be set on chgpasswd to allow
249  *      non-root users to groups.
250  *      Without PAM support, only users who can write in the group databases
251  *      can add groups.
252  *
253  *      It will not return if the user is not allowed.
254  */
255 static void check_perms (void)
256 {
257 #ifdef ACCT_TOOLS_SETUID
258 #ifdef USE_PAM
259         pam_handle_t *pamh = NULL;
260         int retval;
261         struct passwd *pampw;
262
263         pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
264         if (NULL == pampw) {
265                 fprintf (stderr,
266                          _("%s: Cannot determine your user name.\n"),
267                          Prog);
268                 exit (1);
269         }
270
271         retval = pam_start ("chgpasswd", pampw->pw_name, &conv, &pamh);
272
273         if (PAM_SUCCESS == retval) {
274                 retval = pam_authenticate (pamh, 0);
275         }
276
277         if (PAM_SUCCESS == retval) {
278                 retval = pam_acct_mgmt (pamh, 0);
279         }
280
281         if (NULL != pamh) {
282                 (void) pam_end (pamh, retval);
283         }
284         if (PAM_SUCCESS != retval) {
285                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
286                 exit (1);
287         }
288 #endif                          /* USE_PAM */
289 #endif                          /* ACCT_TOOLS_SETUID */
290 }
291
292 /*
293  * open_files - lock and open the group databases
294  */
295 static void open_files (void)
296 {
297         /*
298          * Lock the group file and open it for reading and writing. This will
299          * bring all of the entries into memory where they may be updated.
300          */
301         if (gr_lock () == 0) {
302                 fprintf (stderr,
303                          _("%s: cannot lock %s; try again later.\n"),
304                          Prog, gr_dbname ());
305                 fail_exit (1);
306         }
307         gr_locked = true;
308         if (gr_open (O_RDWR) == 0) {
309                 fprintf (stderr,
310                          _("%s: cannot open %s\n"), Prog, gr_dbname ());
311                 fail_exit (1);
312         }
313
314 #ifdef SHADOWGRP
315         /* Do the same for the shadowed database, if it exist */
316         if (is_shadow_grp) {
317                 if (sgr_lock () == 0) {
318                         fprintf (stderr,
319                                  _("%s: cannot lock %s; try again later.\n"),
320                                  Prog, sgr_dbname ());
321                         fail_exit (1);
322                 }
323                 sgr_locked = true;
324                 if (sgr_open (O_RDWR) == 0) {
325                         fprintf (stderr, _("%s: cannot open %s\n"),
326                                  Prog, sgr_dbname ());
327                         fail_exit (1);
328                 }
329         }
330 #endif
331 }
332
333 /*
334  * close_files - close and unlock the group databases
335  */
336 static void close_files (void)
337 {
338 #ifdef SHADOWGRP
339         if (is_shadow_grp) {
340                 if (sgr_close () == 0) {
341                         fprintf (stderr,
342                                  _("%s: failure while writing changes to %s\n"),
343                                  Prog, sgr_dbname ());
344                         SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
345                         fail_exit (1);
346                 }
347                 if (sgr_unlock () == 0) {
348                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
349                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
350                         /* continue */
351                 }
352                 sgr_locked = false;
353         }
354 #endif
355
356         if (gr_close () == 0) {
357                 fprintf (stderr,
358                          _("%s: failure while writing changes to %s\n"),
359                          Prog, gr_dbname ());
360                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
361                 fail_exit (1);
362         }
363         if (gr_unlock () == 0) {
364                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
365                 SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
366                 /* continue */
367         }
368         gr_locked = false;
369 }
370
371 int main (int argc, char **argv)
372 {
373         char buf[BUFSIZ];
374         char *name;
375         char *newpwd;
376         char *cp;
377
378 #ifdef  SHADOWGRP
379         const struct sgrp *sg;
380         struct sgrp newsg;
381 #endif
382
383         const struct group *gr;
384         struct group newgr;
385         int errors = 0;
386         int line = 0;
387
388         Prog = Basename (argv[0]);
389
390         (void) setlocale (LC_ALL, "");
391         (void) bindtextdomain (PACKAGE, LOCALEDIR);
392         (void) textdomain (PACKAGE);
393
394         process_flags (argc, argv);
395
396         OPENLOG ("chgpasswd");
397
398         check_perms ();
399
400 #ifdef SHADOWGRP
401         is_shadow_grp = sgr_file_present ();
402 #endif
403
404         open_files ();
405
406         /*
407          * Read each line, separating the group name from the password. The
408          * group entry for each group will be looked up in the appropriate
409          * file (gshadow or group) and the password changed.
410          */
411         while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
412                 line++;
413                 cp = strrchr (buf, '\n');
414                 if (NULL != cp) {
415                         *cp = '\0';
416                 } else {
417                         fprintf (stderr, _("%s: line %d: line too long\n"),
418                                  Prog, line);
419                         errors++;
420                         continue;
421                 }
422
423                 /*
424                  * The group's name is the first field. It is separated from
425                  * the password with a ":" character which is replaced with a
426                  * NUL to give the new password. The new password will then
427                  * be encrypted in the normal fashion with a new salt
428                  * generated, unless the '-e' is given, in which case it is
429                  * assumed to already be encrypted.
430                  */
431
432                 name = buf;
433                 cp = strchr (name, ':');
434                 if (NULL != cp) {
435                         *cp = '\0';
436                         cp++;
437                 } else {
438                         fprintf (stderr,
439                                  _("%s: line %d: missing new password\n"),
440                                  Prog, line);
441                         errors++;
442                         continue;
443                 }
444                 newpwd = cp;
445                 if (!eflg &&
446                     (NULL == crypt_method ||
447                      0 != strcmp(crypt_method, "NONE"))) {
448                         void *arg = NULL;
449                         if (md5flg) {
450                                 crypt_method = "MD5";
451                         } else if (crypt_method != NULL) {
452 #ifdef USE_SHA_CRYPT
453                                 if (sflg) {
454                                         arg = &sha_rounds;
455                                 }
456 #endif
457                         } else {
458                                 crypt_method = NULL;
459                         }
460                         cp = pw_encrypt (newpwd,
461                                          crypt_make_salt(crypt_method, arg));
462                 }
463
464                 /*
465                  * Get the group file entry for this group. The group must
466                  * already exist.
467                  */
468                 gr = gr_locate (name);
469                 if (NULL == gr) {
470                         fprintf (stderr,
471                                  _("%s: line %d: group '%s' does not exist\n"), Prog,
472                                  line, name);
473                         errors++;
474                         continue;
475                 }
476 #ifdef SHADOWGRP
477                 if (is_shadow_grp) {
478                         sg = sgr_locate (name);
479                 } else {
480                         sg = NULL;
481                 }
482 #endif
483
484                 /*
485                  * The freshly encrypted new password is merged into the
486                  * group's entry.
487                  */
488 #ifdef SHADOWGRP
489                 if (NULL != sg) {
490                         newsg = *sg;
491                         newsg.sg_passwd = cp;
492                 } else
493 #endif
494                 {
495                         newgr = *gr;
496                         newgr.gr_passwd = cp;
497                 }
498
499                 /* 
500                  * The updated group file entry is then put back and will
501                  * be written to the group file later, after all the
502                  * other entries have been updated as well.
503                  */
504 #ifdef SHADOWGRP
505                 if (NULL != sg) {
506                         if (sgr_update (&newsg) == 0) {
507                                 fprintf (stderr,
508                                          _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
509                                          Prog, line, sgr_dbname (), newsg.sg_name);
510                                 errors++;
511                                 continue;
512                         }
513                 } else
514 #endif
515                 {
516                         if (gr_update (&newgr) == 0) {
517                                 fprintf (stderr,
518                                          _("%s: line %d: failed to prepare the new %s entry '%s'\n"),
519                                          Prog, line, gr_dbname (), newgr.gr_name);
520                                 errors++;
521                                 continue;
522                         }
523                 }
524         }
525
526         /*
527          * Any detected errors will cause the entire set of changes to be
528          * aborted. Unlocking the group file will cause all of the
529          * changes to be ignored. Otherwise the file is closed, causing the
530          * changes to be written out all at once, and then unlocked
531          * afterwards.
532          */
533         if (0 != errors) {
534                 fprintf (stderr,
535                          _("%s: error detected, changes ignored\n"), Prog);
536                 fail_exit (1);
537         }
538
539         close_files ();
540
541         nscd_flush_cache ("group");
542
543         return (0);
544 }
545