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