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