]> granicus.if.org Git - shadow/blob - src/chsh.c
* lib/prototypes.h, configure.in, libmisc/Makefile.am,
[shadow] / src / chsh.c
1 /*
2  * Copyright 1989 - 1994, Julianne Frances Haugh
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <config.h>
31
32 #ident "$Id$"
33
34 #include <fcntl.h>
35 #include <getopt.h>
36 #include <pwd.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <sys/types.h>
40 #ifdef WITH_SELINUX
41 #include <selinux/selinux.h>
42 #include <selinux/av_permissions.h>
43 #endif
44 #ifdef HAVE_SHADOW_H
45 #include <shadow.h>
46 #endif
47 #include "defines.h"
48 #include "exitcodes.h"
49 #include "getdef.h"
50 #include "nscd.h"
51 #include "prototypes.h"
52 #include "pwauth.h"
53 #include "pwio.h"
54 #ifdef USE_PAM
55 #include "pam_defs.h"
56 #endif
57 #ifndef SHELLS_FILE
58 #define SHELLS_FILE "/etc/shells"
59 #endif
60 /*
61  * Global variables
62  */
63 static char *Prog;              /* Program name */
64 static int amroot;              /* Real UID is root */
65 static char loginsh[BUFSIZ];    /* Name of new login shell */
66
67 /* external identifiers */
68
69 /* local function prototypes */
70 static void usage (void);
71 static void new_fields (void);
72 static int check_shell (const char *);
73 static int restricted_shell (const char *);
74
75 /*
76  * usage - print command line syntax and exit
77  */
78 static void usage (void)
79 {
80         fprintf (stderr, _("Usage: chsh [options] [LOGIN]\n"
81                            "\n"
82                            "Options:\n"
83                            "  -h, --help                                display this help message and exit\n"
84                            "  -s, --shell SHELL                 new login shell for the user account\n"
85                            "\n"));
86         exit (E_USAGE);
87 }
88
89 /*
90  * new_fields - change the user's login shell information interactively
91  *
92  * prompt the user for the login shell and change it according to the
93  * response, or leave it alone if nothing was entered.
94  */
95 static void new_fields (void)
96 {
97         printf (_("Enter the new value, or press ENTER for the default\n"));
98         change_field (loginsh, sizeof loginsh, _("Login Shell"));
99 }
100
101 /*
102  * restricted_shell - return true if the named shell begins with 'r' or 'R'
103  *
104  * If the first letter of the filename is 'r' or 'R', the shell is
105  * considered to be restricted.
106  */
107 static int restricted_shell (const char *sh)
108 {
109         /*
110          * Shells not listed in /etc/shells are considered to be restricted.
111          * Changed this to avoid confusion with "rc" (the plan9 shell - not
112          * restricted despite the name starting with 'r').  --marekm
113          */
114         return !check_shell (sh);
115 }
116
117 /*
118  * check_shell - see if the user's login shell is listed in /etc/shells
119  *
120  * The /etc/shells file is read for valid names of login shells.  If the
121  * /etc/shells file does not exist the user cannot set any shell unless
122  * they are root.
123  *
124  * If getusershell() is available (Linux, *BSD, possibly others), use it
125  * instead of re-implementing it.
126  */
127 static int check_shell (const char *sh)
128 {
129         char *cp;
130         int found = 0;
131
132 #ifndef HAVE_GETUSERSHELL
133         char buf[BUFSIZ];
134         FILE *fp;
135 #endif
136
137 #ifdef HAVE_GETUSERSHELL
138         setusershell ();
139         while ((cp = getusershell ())) {
140                 if (*cp == '#')
141                         continue;
142
143                 if (strcmp (cp, sh) == 0) {
144                         found = 1;
145                         break;
146                 }
147         }
148         endusershell ();
149 #else
150         if ((fp = fopen (SHELLS_FILE, "r")) == (FILE *) 0)
151                 return 0;
152
153         while (fgets (buf, sizeof (buf), fp)) {
154                 if ((cp = strrchr (buf, '\n')))
155                         *cp = '\0';
156
157                 if (buf[0] == '#')
158                         continue;
159
160                 if (strcmp (buf, sh) == 0) {
161                         found = 1;
162                         break;
163                 }
164         }
165         fclose (fp);
166 #endif
167         return found;
168 }
169
170 /*
171  * chsh - this command controls changes to the user's shell
172  *
173  *      The only supported option is -s which permits the the login shell to
174  *      be set from the command line.
175  */
176 int main (int argc, char **argv)
177 {
178         char *user;             /* User name                         */
179         int sflg = 0;           /* -s - set shell from command line  */
180         const struct passwd *pw;        /* Password entry from /etc/passwd   */
181         struct passwd pwent;    /* New password entry                */
182
183 #ifdef USE_PAM
184         pam_handle_t *pamh = NULL;
185         int retval;
186 #endif
187
188         sanitize_env ();
189
190         setlocale (LC_ALL, "");
191         bindtextdomain (PACKAGE, LOCALEDIR);
192         textdomain (PACKAGE);
193
194         /*
195          * This command behaves different for root and non-root users.
196          */
197         amroot = getuid () == 0;
198
199         /*
200          * Get the program name. The program name is used as a prefix to
201          * most error messages.
202          */
203         Prog = Basename (argv[0]);
204
205         OPENLOG ("chsh");
206
207         /*
208          * There is only one option, but use getopt() anyway to
209          * keep things consistent.
210          */
211         {
212                 int option_index = 0;
213                 int c;
214                 static struct option long_options[] = {
215                         {"help", no_argument, NULL, 'h'},
216                         {"shell", required_argument, NULL, 's'},
217                         {NULL, 0, NULL, '\0'}
218                 };
219
220                 while ((c =
221                         getopt_long (argc, argv, "hs:", long_options,
222                                      &option_index)) != -1) {
223                         switch (c) {
224                         case 'h':
225                                 usage ();
226                                 break;
227                         case 's':
228                                 sflg++;
229                                 STRFCPY (loginsh, optarg);
230                                 break;
231                         default:
232                                 usage ();
233                         }
234                 }
235         }
236
237         /*
238          * There should be only one remaining argument at most and it should
239          * be the user's name.
240          */
241         if (argc > optind + 1)
242                 usage ();
243
244         /*
245          * Get the name of the user to check. It is either the command line
246          * name, or the name getlogin() returns.
247          */
248         if (optind < argc) {
249                 user = argv[optind];
250                 pw = xgetpwnam (user);
251                 if (!pw) {
252                         fprintf (stderr,
253                                  _("%s: unknown user %s\n"), Prog, user);
254                         exit (1);
255                 }
256         } else {
257                 pw = get_my_pwent ();
258                 if (!pw) {
259                         fprintf (stderr,
260                                  _
261                                  ("%s: Cannot determine your user name.\n"),
262                                  Prog);
263                         exit (1);
264                 }
265                 user = xstrdup (pw->pw_name);
266         }
267
268 #ifdef  USE_NIS
269         /*
270          * Now we make sure this is a LOCAL password entry for this user ...
271          */
272         if (__ispwNIS ()) {
273                 char *nis_domain;
274                 char *nis_master;
275
276                 fprintf (stderr,
277                          _("%s: cannot change user '%s' on NIS client.\n"),
278                          Prog, user);
279
280                 if (!yp_get_default_domain (&nis_domain) &&
281                     !yp_master (nis_domain, "passwd.byname", &nis_master)) {
282                         fprintf (stderr,
283                                  _
284                                  ("%s: '%s' is the NIS master for this client.\n"),
285                                  Prog, nis_master);
286                 }
287                 exit (1);
288         }
289 #endif
290
291         /*
292          * Non-privileged users are only allowed to change the shell if the
293          * UID of the user matches the current real UID.
294          */
295         if (!amroot && pw->pw_uid != getuid ()) {
296                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
297                 closelog ();
298                 fprintf (stderr,
299                          _("You may not change the shell for %s.\n"), user);
300                 exit (1);
301         }
302
303         /*
304          * Non-privileged users are only allowed to change the shell if it
305          * is not a restricted one.
306          */
307         if (!amroot && restricted_shell (pw->pw_shell)) {
308                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
309                 closelog ();
310                 fprintf (stderr,
311                          _("You may not change the shell for %s.\n"), user);
312                 exit (1);
313         }
314 #ifdef WITH_SELINUX
315         /*
316          * If the UID of the user does not match the current real UID,
317          * check if the change is allowed by SELinux policy.
318          */
319         if ((pw->pw_uid != getuid ())
320             && (is_selinux_enabled () > 0)
321             && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
322                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
323                 closelog ();
324                 fprintf (stderr,
325                          _("You may not change the shell for %s.\n"), user);
326                 exit (1);
327         }
328 #endif
329
330 #ifndef USE_PAM
331         /*
332          * Non-privileged users are optionally authenticated (must enter
333          * the password of the user whose information is being changed)
334          * before any changes can be made. Idea from util-linux
335          * chfn/chsh.  --marekm
336          */
337         if (!amroot && getdef_bool ("CHSH_AUTH"))
338                 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
339
340 #else                           /* !USE_PAM */
341         retval = PAM_SUCCESS;
342
343         {
344                 struct passwd *pampw;
345                 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
346                 if (pampw == NULL) {
347                         retval = PAM_USER_UNKNOWN;
348                 }
349
350                 if (retval == PAM_SUCCESS) {
351                         retval = pam_start ("chsh", pampw->pw_name,
352                                             &conv, &pamh);
353                 }
354         }
355
356         if (retval == PAM_SUCCESS) {
357                 retval = pam_authenticate (pamh, 0);
358                 if (retval != PAM_SUCCESS) {
359                         pam_end (pamh, retval);
360                 }
361         }
362
363         if (retval == PAM_SUCCESS) {
364                 retval = pam_acct_mgmt (pamh, 0);
365                 if (retval != PAM_SUCCESS) {
366                         pam_end (pamh, retval);
367                 }
368         }
369
370         if (retval != PAM_SUCCESS) {
371                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
372                 exit (E_NOPERM);
373         }
374 #endif                          /* USE_PAM */
375
376         /*
377          * Now get the login shell. Either get it from the password
378          * file, or use the value from the command line.
379          */
380         if (!sflg)
381                 STRFCPY (loginsh, pw->pw_shell);
382
383         /*
384          * If the login shell was not set on the command line, let the user
385          * interactively change it.
386          */
387         if (!sflg) {
388                 printf (_("Changing the login shell for %s\n"), user);
389                 new_fields ();
390         }
391
392         /*
393          * Check all of the fields for valid information. The shell
394          * field may not contain any illegal characters. Non-privileged
395          * users are restricted to using the shells in /etc/shells.
396          * The shell must be executable by the user.
397          */
398         if (valid_field (loginsh, ":,=")) {
399                 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
400                 closelog ();
401                 exit (1);
402         }
403         if (!amroot && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
404                 fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
405                 closelog ();
406                 exit (1);
407         }
408
409         /*
410          * Before going any further, raise the ulimit to prevent
411          * colliding into a lowered ulimit, and set the real UID
412          * to root to protect against unexpected signals. Any
413          * keyboard signals are set to be ignored.
414          */
415         if (setuid (0)) {
416                 SYSLOG ((LOG_ERR, "can't setuid(0)"));
417                 closelog ();
418                 fprintf (stderr, _("Cannot change ID to root.\n"));
419                 exit (1);
420         }
421         pwd_init ();
422
423         /*
424          * The passwd entry is now ready to be committed back to
425          * the password file. Get a lock on the file and open it.
426          */
427         if (!pw_lock ()) {
428                 SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
429                 closelog ();
430                 fprintf (stderr,
431                          _
432                          ("Cannot lock the password file; try again later.\n"));
433                 exit (1);
434         }
435         if (!pw_open (O_RDWR)) {
436                 SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
437                 closelog ();
438                 fprintf (stderr, _("Cannot open the password file.\n"));
439                 pw_unlock ();
440                 exit (1);
441         }
442
443         /*
444          * Get the entry to update using pw_locate() - we want the real
445          * one from /etc/passwd, not the one from getpwnam() which could
446          * contain the shadow password if (despite the warnings) someone
447          * enables AUTOSHADOW (or SHADOW_COMPAT in libc).  --marekm
448          */
449         pw = pw_locate (user);
450         if (!pw) {
451                 pw_unlock ();
452                 fprintf (stderr,
453                          _("%s: %s not found in /etc/passwd\n"), Prog, user);
454                 exit (1);
455         }
456
457         /*
458          * Make a copy of the entry, then change the shell field. The other
459          * fields remain unchanged.
460          */
461         pwent = *pw;
462         pwent.pw_shell = loginsh;
463
464         /*
465          * Update the passwd file entry. If there is a DBM file, update
466          * that entry as well.
467          */
468         if (!pw_update (&pwent)) {
469                 SYSLOG ((LOG_ERR, "error updating passwd entry"));
470                 closelog ();
471                 fprintf (stderr, _("Error updating the password entry.\n"));
472                 pw_unlock ();
473                 exit (1);
474         }
475
476         /*
477          * Changes have all been made, so commit them and unlock the file.
478          */
479         if (!pw_close ()) {
480                 SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
481                 closelog ();
482                 fprintf (stderr, _("Cannot commit password file changes.\n"));
483                 pw_unlock ();
484                 exit (1);
485         }
486         if (!pw_unlock ()) {
487                 SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
488                 closelog ();
489                 fprintf (stderr, _("Cannot unlock the password file.\n"));
490                 exit (1);
491         }
492         SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user, loginsh));
493
494         nscd_flush_cache ("passwd");
495
496 #ifdef USE_PAM
497         if (retval == PAM_SUCCESS)
498                 pam_end (pamh, PAM_SUCCESS);
499 #endif                          /* USE_PAM */
500
501         closelog ();
502         exit (E_SUCCESS);
503 }