]> granicus.if.org Git - shadow/blob - src/chsh.c
* src/passwd.c: Overflow when computing the number of days based
[shadow] / src / chsh.c
1 /*
2  * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2001 - 2006, Tomasz Kłoczko
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 <sys/types.h>
42 #ifdef WITH_SELINUX
43 #include <selinux/selinux.h>
44 #include <selinux/av_permissions.h>
45 #endif
46 #include "defines.h"
47 #include "getdef.h"
48 #include "nscd.h"
49 #include "prototypes.h"
50 #include "pwauth.h"
51 #include "pwio.h"
52 #ifdef USE_PAM
53 #include "pam_defs.h"
54 #endif
55 /*@-exitarg@*/
56 #include "exitcodes.h"
57
58 #ifndef SHELLS_FILE
59 #define SHELLS_FILE "/etc/shells"
60 #endif
61 /*
62  * Global variables
63  */
64 const char *Prog;               /* Program name */
65 static bool amroot;             /* Real UID is root */
66 static char loginsh[BUFSIZ];    /* Name of new login shell */
67 /* command line options */
68 static bool sflg = false;       /* -s - set shell from command line  */
69 static bool pw_locked = false;
70
71 /* external identifiers */
72
73 /* local function prototypes */
74 static void fail_exit (int code);
75 static void usage (int status);
76 static void new_fields (void);
77 static bool shell_is_listed (const char *);
78 static bool is_restricted_shell (const char *);
79 static void process_flags (int argc, char **argv);
80 static void check_perms (const struct passwd *pw);
81 static void update_shell (const char *user, char *loginsh);
82
83 /*
84  * fail_exit - do some cleanup and exit with the given error code
85  */
86 static void fail_exit (int code)
87 {
88         if (pw_locked) {
89                 if (pw_unlock () == 0) {
90                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
91                         SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
92                         /* continue */
93                 }
94         }
95
96         closelog ();
97
98         exit (code);
99 }
100
101 /*
102  * usage - print command line syntax and exit
103  */
104 static void usage (int status)
105 {
106         fputs (_("Usage: chsh [options] [LOGIN]\n"
107                  "\n"
108                  "Options:\n"
109                  "  -h, --help                    display this help message and exit\n"
110                  "  -s, --shell SHELL             new login shell for the user account\n"
111                  "\n"), (E_SUCCESS != status) ? stderr : stdout);
112         exit (status);
113 }
114
115 /*
116  * new_fields - change the user's login shell information interactively
117  *
118  * prompt the user for the login shell and change it according to the
119  * response, or leave it alone if nothing was entered.
120  */
121 static void new_fields (void)
122 {
123         puts (_("Enter the new value, or press ENTER for the default"));
124         change_field (loginsh, sizeof loginsh, _("Login Shell"));
125 }
126
127 /*
128  * is_restricted_shell - return true if the shell is restricted
129  *
130  */
131 static bool is_restricted_shell (const char *sh)
132 {
133         /*
134          * Shells not listed in /etc/shells are considered to be restricted.
135          * Changed this to avoid confusion with "rc" (the plan9 shell - not
136          * restricted despite the name starting with 'r').  --marekm
137          */
138         return !shell_is_listed (sh);
139 }
140
141 /*
142  * shell_is_listed - see if the user's login shell is listed in /etc/shells
143  *
144  * The /etc/shells file is read for valid names of login shells.  If the
145  * /etc/shells file does not exist the user cannot set any shell unless
146  * they are root.
147  *
148  * If getusershell() is available (Linux, *BSD, possibly others), use it
149  * instead of re-implementing it.
150  */
151 static bool shell_is_listed (const char *sh)
152 {
153         char *cp;
154         bool found = false;
155
156 #ifndef HAVE_GETUSERSHELL
157         char buf[BUFSIZ];
158         FILE *fp;
159 #endif
160
161 #ifdef HAVE_GETUSERSHELL
162         setusershell ();
163         while ((cp = getusershell ())) {
164                 if (*cp == '#') {
165                         continue;
166                 }
167
168                 if (strcmp (cp, sh) == 0) {
169                         found = true;
170                         break;
171                 }
172         }
173         endusershell ();
174 #else
175         fp = fopen (SHELLS_FILE, "r");
176         if (NULL == fp) {
177                 return false;
178         }
179
180         while (fgets (buf, sizeof (buf), fp) == buf) {
181                 cp = strrchr (buf, '\n');
182                 if (NULL != cp) {
183                         *cp = '\0';
184                 }
185
186                 if (buf[0] == '#') {
187                         continue;
188                 }
189
190                 if (strcmp (buf, sh) == 0) {
191                         found = true;
192                         break;
193                 }
194         }
195         fclose (fp);
196 #endif
197         return found;
198 }
199
200 /*
201  *  * process_flags - parse the command line options
202  *
203  *      It will not return if an error is encountered.
204  */
205 static void process_flags (int argc, char **argv)
206 {
207         int option_index = 0;
208         int c;
209         static struct option long_options[] = {
210                 {"help", no_argument, NULL, 'h'},
211                 {"shell", required_argument, NULL, 's'},
212                 {NULL, 0, NULL, '\0'}
213         };
214
215         while ((c =
216                 getopt_long (argc, argv, "hs:", long_options,
217                              &option_index)) != -1) {
218                 switch (c) {
219                 case 'h':
220                         usage (E_SUCCESS);
221                         break;
222                 case 's':
223                         sflg = true;
224                         STRFCPY (loginsh, optarg);
225                         break;
226                 default:
227                         usage (E_USAGE);
228                 }
229         }
230
231         /*
232          * There should be only one remaining argument at most and it should
233          * be the user's name.
234          */
235         if (argc > (optind + 1)) {
236                 usage (E_USAGE);
237         }
238 }
239
240 /*
241  * check_perms - check if the caller is allowed to add a group
242  *
243  *      Non-root users are only allowed to change their shell, if their current
244  *      shell is not a restricted shell.
245  *
246  *      Non-root users must be authenticated.
247  *
248  *      It will not return if the user is not allowed.
249  */
250 static void check_perms (const struct passwd *pw)
251 {
252 #ifdef USE_PAM
253         pam_handle_t *pamh = NULL;
254         int retval;
255         struct passwd *pampw;
256 #endif
257
258         /*
259          * Non-privileged users are only allowed to change the shell if the
260          * UID of the user matches the current real UID.
261          */
262         if (!amroot && pw->pw_uid != getuid ()) {
263                 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
264                 fprintf (stderr,
265                          _("You may not change the shell for '%s'.\n"),
266                          pw->pw_name);
267                 fail_exit (1);
268         }
269
270         /*
271          * Non-privileged users are only allowed to change the shell if it
272          * is not a restricted one.
273          */
274         if (!amroot && is_restricted_shell (pw->pw_shell)) {
275                 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
276                 fprintf (stderr,
277                          _("You may not change the shell for '%s'.\n"),
278                          pw->pw_name);
279                 fail_exit (1);
280         }
281 #ifdef WITH_SELINUX
282         /*
283          * If the UID of the user does not match the current real UID,
284          * check if the change is allowed by SELinux policy.
285          */
286         if ((pw->pw_uid != getuid ())
287             && (is_selinux_enabled () > 0)
288             && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
289                 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
290                 fprintf (stderr,
291                          _("You may not change the shell for '%s'.\n"),
292                          pw->pw_name);
293                 fail_exit (1);
294         }
295 #endif
296
297 #ifndef USE_PAM
298         /*
299          * Non-privileged users are optionally authenticated (must enter
300          * the password of the user whose information is being changed)
301          * before any changes can be made. Idea from util-linux
302          * chfn/chsh.  --marekm
303          */
304         if (!amroot && getdef_bool ("CHSH_AUTH")) {
305                 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
306         }
307
308 #else                           /* !USE_PAM */
309         pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
310         if (NULL == pampw) {
311                 fprintf (stderr,
312                          _("%s: Cannot determine your user name.\n"),
313                          Prog);
314                 exit (E_NOPERM);
315         }
316
317         retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
318
319         if (PAM_SUCCESS == retval) {
320                 retval = pam_authenticate (pamh, 0);
321         }
322
323         if (PAM_SUCCESS == retval) {
324                 retval = pam_acct_mgmt (pamh, 0);
325         }
326
327         if (NULL != pamh) {
328                 (void) pam_end (pamh, retval);
329         }
330         if (PAM_SUCCESS != retval) {
331                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
332                 exit (E_NOPERM);
333         }
334 #endif                          /* USE_PAM */
335 }
336
337 /*
338  * update_shell - update the user's shell in the passwd database
339  *
340  *      Commit the user's entry after changing her shell field.
341  *
342  *      It will not return in case of error.
343  */
344 static void update_shell (const char *user, char *newshell)
345 {
346         const struct passwd *pw;        /* Password entry from /etc/passwd   */
347         struct passwd pwent;            /* New password entry                */
348
349         /*
350          * Before going any further, raise the ulimit to prevent
351          * colliding into a lowered ulimit, and set the real UID
352          * to root to protect against unexpected signals. Any
353          * keyboard signals are set to be ignored.
354          */
355         if (setuid (0) != 0) {
356                 SYSLOG ((LOG_ERR, "can't setuid(0)"));
357                 fputs (_("Cannot change ID to root.\n"), stderr);
358                 fail_exit (1);
359         }
360         pwd_init ();
361
362         /*
363          * The passwd entry is now ready to be committed back to
364          * the password file. Get a lock on the file and open it.
365          */
366         if (pw_lock () == 0) {
367                 fprintf (stderr, _("%s: cannot lock %s; try again later.\n"),
368                          Prog, pw_dbname ());
369                 fail_exit (1);
370         }
371         pw_locked = true;
372         if (pw_open (O_RDWR) == 0) {
373                 fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
374                 SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
375                 fail_exit (1);
376         }
377
378         /*
379          * Get the entry to update using pw_locate() - we want the real
380          * one from /etc/passwd, not the one from getpwnam() which could
381          * contain the shadow password if (despite the warnings) someone
382          * enables AUTOSHADOW (or SHADOW_COMPAT in libc).  --marekm
383          */
384         pw = pw_locate (user);
385         if (NULL == pw) {
386                 fprintf (stderr,
387                          _("%s: user '%s' does not exist in %s\n"),
388                          Prog, user, pw_dbname ());
389                 fail_exit (1);
390         }
391
392         /*
393          * Make a copy of the entry, then change the shell field. The other
394          * fields remain unchanged.
395          */
396         pwent = *pw;
397         pwent.pw_shell = newshell;
398
399         /*
400          * Update the passwd file entry. If there is a DBM file, update
401          * that entry as well.
402          */
403         if (pw_update (&pwent) == 0) {
404                 fprintf (stderr,
405                          _("%s: failed to prepare the new %s entry '%s'\n"),
406                          Prog, pw_dbname (), pwent.pw_name);
407                 fail_exit (1);
408         }
409
410         /*
411          * Changes have all been made, so commit them and unlock the file.
412          */
413         if (pw_close () == 0) {
414                 fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
415                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
416                 fail_exit (1);
417         }
418         if (pw_unlock () == 0) {
419                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
420                 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
421                 /* continue */
422         }
423         pw_locked= false;
424 }
425
426 /*
427  * chsh - this command controls changes to the user's shell
428  *
429  *      The only supported option is -s which permits the the login shell to
430  *      be set from the command line.
431  */
432 int main (int argc, char **argv)
433 {
434         char *user;             /* User name                         */
435         const struct passwd *pw;        /* Password entry from /etc/passwd   */
436
437         sanitize_env ();
438
439         (void) setlocale (LC_ALL, "");
440         (void) bindtextdomain (PACKAGE, LOCALEDIR);
441         (void) textdomain (PACKAGE);
442
443         /*
444          * This command behaves different for root and non-root users.
445          */
446         amroot = (getuid () == 0);
447
448         /*
449          * Get the program name. The program name is used as a prefix to
450          * most error messages.
451          */
452         Prog = Basename (argv[0]);
453
454         OPENLOG ("chsh");
455
456         /* parse the command line options */
457         process_flags (argc, argv);
458
459         /*
460          * Get the name of the user to check. It is either the command line
461          * name, or the name getlogin() returns.
462          */
463         if (optind < argc) {
464                 user = argv[optind];
465                 pw = xgetpwnam (user);
466                 if (NULL == pw) {
467                         fprintf (stderr,
468                                  _("%s: user '%s' does not exist\n"), Prog, user);
469                         fail_exit (1);
470                 }
471         } else {
472                 pw = get_my_pwent ();
473                 if (NULL == pw) {
474                         fprintf (stderr,
475                                  _("%s: Cannot determine your user name.\n"),
476                                  Prog);
477                         SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
478                                  (unsigned long) getuid ()));
479                         fail_exit (1);
480                 }
481                 user = xstrdup (pw->pw_name);
482         }
483
484 #ifdef  USE_NIS
485         /*
486          * Now we make sure this is a LOCAL password entry for this user ...
487          */
488         if (__ispwNIS ()) {
489                 char *nis_domain;
490                 char *nis_master;
491
492                 fprintf (stderr,
493                          _("%s: cannot change user '%s' on NIS client.\n"),
494                          Prog, user);
495
496                 if (!yp_get_default_domain (&nis_domain) &&
497                     !yp_master (nis_domain, "passwd.byname", &nis_master)) {
498                         fprintf (stderr,
499                                  _("%s: '%s' is the NIS master for this client.\n"),
500                                  Prog, nis_master);
501                 }
502                 fail_exit (1);
503         }
504 #endif
505
506         check_perms (pw);
507
508         /*
509          * Now get the login shell. Either get it from the password
510          * file, or use the value from the command line.
511          */
512         if (!sflg) {
513                 STRFCPY (loginsh, pw->pw_shell);
514         }
515
516         /*
517          * If the login shell was not set on the command line, let the user
518          * interactively change it.
519          */
520         if (!sflg) {
521                 printf (_("Changing the login shell for %s\n"), user);
522                 new_fields ();
523         }
524
525         /*
526          * Check all of the fields for valid information. The shell
527          * field may not contain any illegal characters. Non-privileged
528          * users are restricted to using the shells in /etc/shells.
529          * The shell must be executable by the user.
530          */
531         if (valid_field (loginsh, ":,=\n") != 0) {
532                 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
533                 fail_exit (1);
534         }
535         if (   !amroot
536             && (   is_restricted_shell (loginsh)
537                 || (access (loginsh, X_OK) != 0))) {
538                 fprintf (stderr, _("%s: %s is an invalid shell\n"), Prog, loginsh);
539                 fail_exit (1);
540         }
541
542         /* Even for root, warn if an invalid shell is specified. */
543         if (access (loginsh, F_OK) != 0) {
544                 fprintf (stderr, _("%s: Warning: %s does not exist\n"), Prog, loginsh);
545         } else if (access (loginsh, X_OK) != 0) {
546                 fprintf (stderr, _("%s: Warning: %s is not executable\n"), Prog, loginsh);
547         }
548
549         update_shell (user, loginsh);
550
551         SYSLOG ((LOG_INFO, "changed user '%s' shell to '%s'", user, loginsh));
552
553         nscd_flush_cache ("passwd");
554
555         closelog ();
556         exit (E_SUCCESS);
557 }
558