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