]> granicus.if.org Git - shadow/blob - src/chsh.c
[svn-upgrade] Integrating new upstream version, shadow (4.0.13)
[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.35 2005/10/04 21:02:22 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 #ifdef USE_PAM
111 static struct pam_conv conv = {
112         misc_conv,
113         NULL
114 };
115 #endif                          /* USE_PAM */
116
117 /*
118  * chsh - this command controls changes to the user's shell
119  *
120  *      The only supported option is -s which permits the the login shell to
121  *      be set from the command line.
122  */
123 int main (int argc, char **argv)
124 {
125         char *user;             /* User name                         */
126         int flag;               /* Current command line flag         */
127         int sflg = 0;           /* -s - set shell from command line  */
128         const struct passwd *pw;        /* Password entry from /etc/passwd   */
129         struct passwd pwent;    /* New password entry                */
130
131 #ifdef USE_PAM
132         pam_handle_t *pamh = NULL;
133         struct passwd *pampw;
134         int retval;
135 #endif
136
137         sanitize_env ();
138
139         setlocale (LC_ALL, "");
140         bindtextdomain (PACKAGE, LOCALEDIR);
141         textdomain (PACKAGE);
142
143         /*
144          * This command behaves different for root and non-root users.
145          */
146         amroot = getuid () == 0;
147
148         /*
149          * Get the program name. The program name is used as a prefix to
150          * most error messages.
151          */
152         Prog = Basename (argv[0]);
153
154         OPENLOG ("chsh");
155
156         /*
157          * There is only one option, but use getopt() anyway to
158          * keep things consistent.
159          */
160
161         while ((flag = getopt (argc, argv, "s:")) != EOF) {
162                 switch (flag) {
163                 case 's':
164                         sflg++;
165                         STRFCPY (loginsh, optarg);
166                         break;
167                 default:
168                         usage ();
169                 }
170         }
171
172         /*
173          * There should be only one remaining argument at most and it should
174          * be the user's name.
175          */
176         if (argc > optind + 1)
177                 usage ();
178
179         /*
180          * Get the name of the user to check. It is either the command line
181          * name, or the name getlogin() returns.
182          */
183         if (optind < argc) {
184                 user = argv[optind];
185                 pw = getpwnam (user);
186                 if (!pw) {
187                         fprintf (stderr,
188                                  _("%s: unknown user %s\n"), Prog, user);
189                         exit (1);
190                 }
191         } else {
192                 pw = get_my_pwent ();
193                 if (!pw) {
194                         fprintf (stderr,
195                                  _
196                                  ("%s: Cannot determine your user name.\n"),
197                                  Prog);
198                         exit (1);
199                 }
200                 user = xstrdup (pw->pw_name);
201         }
202
203 #ifdef  USE_NIS
204         /*
205          * Now we make sure this is a LOCAL password entry for this user ...
206          */
207         if (__ispwNIS ()) {
208                 char *nis_domain;
209                 char *nis_master;
210
211                 fprintf (stderr,
212                          _("%s: cannot change user `%s' on NIS client.\n"),
213                          Prog, user);
214
215                 if (!yp_get_default_domain (&nis_domain) &&
216                     !yp_master (nis_domain, "passwd.byname", &nis_master)) {
217                         fprintf (stderr,
218                                  _
219                                  ("%s: `%s' is the NIS master for this client.\n"),
220                                  Prog, nis_master);
221                 }
222                 exit (1);
223         }
224 #endif
225
226         /*
227          * Non-privileged users are only allowed to change the shell if the
228          * UID of the user matches the current real UID.
229          */
230         if (!amroot && pw->pw_uid != getuid ()) {
231                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
232                 closelog ();
233                 fprintf (stderr,
234                          _("You may not change the shell for %s.\n"), user);
235                 exit (1);
236         }
237
238         /*
239          * Non-privileged users are only allowed to change the shell if it
240          * is not a restricted one.
241          */
242         if (!amroot && restricted_shell (pw->pw_shell)) {
243                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
244                 closelog ();
245                 fprintf (stderr,
246                          _("You may not change the shell for %s.\n"), user);
247                 exit (1);
248         }
249 #ifdef WITH_SELINUX
250         /*
251          * If the UID of the user does not match the current real UID,
252          * check if the change is allowed by SELinux policy.
253          */
254         if ((pw->pw_uid != getuid ())
255             && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
256                 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
257                 closelog ();
258                 fprintf (stderr,
259                          _("You may not change the shell for %s.\n"), user);
260                 exit (1);
261         }
262 #endif
263
264 #ifndef USE_PAM
265         /*
266          * Non-privileged users are optionally authenticated (must enter
267          * the password of the user whose information is being changed)
268          * before any changes can be made. Idea from util-linux
269          * chfn/chsh.  --marekm
270          */
271         if (!amroot && getdef_bool ("CHSH_AUTH"))
272                 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
273
274 #else                           /* !USE_PAM */
275         retval = PAM_SUCCESS;
276
277         pampw = getpwuid (getuid ());
278         if (pampw == NULL) {
279                 retval = PAM_USER_UNKNOWN;
280         }
281
282         if (retval == PAM_SUCCESS) {
283                 retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
284         }
285
286         if (retval == PAM_SUCCESS) {
287                 retval = pam_authenticate (pamh, 0);
288                 if (retval != PAM_SUCCESS) {
289                         pam_end (pamh, retval);
290                 }
291         }
292
293         if (retval == PAM_SUCCESS) {
294                 retval = pam_acct_mgmt (pamh, 0);
295                 if (retval != PAM_SUCCESS) {
296                         pam_end (pamh, retval);
297                 }
298         }
299
300         if (retval != PAM_SUCCESS) {
301                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
302                 exit (E_NOPERM);
303         }
304 #endif                          /* USE_PAM */
305
306         /*
307          * Now get the login shell. Either get it from the password
308          * file, or use the value from the command line.
309          */
310         if (!sflg)
311                 STRFCPY (loginsh, pw->pw_shell);
312
313         /*
314          * If the login shell was not set on the command line, let the user
315          * interactively change it.
316          */
317         if (!sflg) {
318                 printf (_("Changing the login shell for %s\n"), user);
319                 new_fields ();
320         }
321
322         /*
323          * Check all of the fields for valid information. The shell
324          * field may not contain any illegal characters. Non-privileged
325          * users are restricted to using the shells in /etc/shells.
326          * The shell must be executable by the user.
327          */
328         if (valid_field (loginsh, ":,=")) {
329                 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
330                 closelog ();
331                 exit (1);
332         }
333         if (!amroot && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
334                 fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
335                 closelog ();
336                 exit (1);
337         }
338
339         /*
340          * Before going any further, raise the ulimit to prevent
341          * colliding into a lowered ulimit, and set the real UID
342          * to root to protect against unexpected signals. Any
343          * keyboard signals are set to be ignored.
344          */
345         if (setuid (0)) {
346                 SYSLOG ((LOG_ERR, "can't setuid(0)"));
347                 closelog ();
348                 fprintf (stderr, _("Cannot change ID to root.\n"));
349                 exit (1);
350         }
351         pwd_init ();
352
353         /*
354          * The passwd entry is now ready to be committed back to
355          * the password file. Get a lock on the file and open it.
356          */
357         if (!pw_lock ()) {
358                 SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
359                 closelog ();
360                 fprintf (stderr,
361                          _
362                          ("Cannot lock the password file; try again later.\n"));
363                 exit (1);
364         }
365         if (!pw_open (O_RDWR)) {
366                 SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
367                 closelog ();
368                 fprintf (stderr, _("Cannot open the password file.\n"));
369                 pw_unlock ();
370                 exit (1);
371         }
372
373         /*
374          * Get the entry to update using pw_locate() - we want the real
375          * one from /etc/passwd, not the one from getpwnam() which could
376          * contain the shadow password if (despite the warnings) someone
377          * enables AUTOSHADOW (or SHADOW_COMPAT in libc).  --marekm
378          */
379         pw = pw_locate (user);
380         if (!pw) {
381                 pw_unlock ();
382                 fprintf (stderr,
383                          _("%s: %s not found in /etc/passwd\n"), Prog, user);
384                 exit (1);
385         }
386
387         /*
388          * Make a copy of the entry, then change the shell field. The other
389          * fields remain unchanged.
390          */
391         pwent = *pw;
392         pwent.pw_shell = loginsh;
393
394         /*
395          * Update the passwd file entry. If there is a DBM file, update
396          * that entry as well.
397          */
398         if (!pw_update (&pwent)) {
399                 SYSLOG ((LOG_ERR, "error updating passwd entry"));
400                 closelog ();
401                 fprintf (stderr, _("Error updating the password entry.\n"));
402                 pw_unlock ();
403                 exit (1);
404         }
405
406         /*
407          * Changes have all been made, so commit them and unlock the file.
408          */
409         if (!pw_close ()) {
410                 SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
411                 closelog ();
412                 fprintf (stderr, _("Cannot commit password file changes.\n"));
413                 pw_unlock ();
414                 exit (1);
415         }
416         if (!pw_unlock ()) {
417                 SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
418                 closelog ();
419                 fprintf (stderr, _("Cannot unlock the password file.\n"));
420                 exit (1);
421         }
422         SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user, loginsh));
423
424         nscd_flush_cache ("passwd");
425
426 #ifdef USE_PAM
427         if (retval == PAM_SUCCESS)
428                 pam_end (pamh, PAM_SUCCESS);
429 #endif                          /* USE_PAM */
430
431         closelog ();
432         exit (E_SUCCESS);
433 }