]> granicus.if.org Git - shadow/blob - src/chsh.c
[svn-upgrade] Integrating new upstream version, shadow (20000826)
[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 #include "rcsid.h"
33 RCSID(PKG_VER "$Id: chsh.c,v 1.16 2000/08/26 18:27:18 marekm Exp $")
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include "prototypes.h"
40 #include "defines.h"
41
42 #include <pwd.h>
43 #include "pwio.h"
44 #include "getdef.h"
45 #include "pwauth.h"
46
47 #ifdef HAVE_SHADOW_H
48 #include <shadow.h>
49 #endif
50
51 #ifdef USE_PAM
52 #include "pam_defs.h"
53 #endif
54
55 #ifndef SHELLS_FILE
56 #define SHELLS_FILE "/etc/shells"
57 #endif
58
59 /*
60  * Global variables.
61  */
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 /*
68  * External identifiers
69  */
70
71 extern  int     optind;
72 extern  char    *optarg;
73 #ifdef  NDBM
74 extern  int     pw_dbm_mode;
75 #endif
76
77 /*
78  * #defines for messages.  This facilitates foreign language conversion
79  * since all messages are defined right here.
80  */
81
82 #define WRONGPWD2       "incorrect password for `%s'"
83 #define NOPERM2         "can't change shell for `%s'\n"
84 #define PWDBUSY2        "can't lock /etc/passwd\n"
85 #define OPNERROR2       "can't open /etc/passwd\n"
86 #define UPDERROR2       "error updating passwd entry\n"
87 #define DBMERROR2       "error updating DBM passwd entry.\n"
88 #define NOTROOT2        "can't setuid(0).\n"
89 #define CLSERROR2       "can't rewrite /etc/passwd.\n"
90 #define UNLKERROR2      "can't unlock /etc/passwd.\n"
91 #define CHGSHELL        "changed user `%s' shell to `%s'\n"
92
93 /* local function prototypes */
94 static void usage(void);
95 static void new_fields(void);
96 static int restricted_shell(const char *);
97
98 /*
99  * usage - print command line syntax and exit
100  */
101
102 static void
103 usage(void)
104 {
105         fprintf(stderr, _("Usage: %s [ -s shell ] [ name ]\n"), Prog);
106         exit(1);
107 }
108
109 /*
110  * new_fields - change the user's login shell information interactively
111  *
112  * prompt the user for the login shell and change it according to the
113  * response, or leave it alone if nothing was entered.
114  */
115
116 static void
117 new_fields(void)
118 {
119         printf(_("Enter the new value, or press return for the default\n"));
120         change_field(loginsh, sizeof loginsh, _("Login Shell"));
121 }
122
123 /*
124  * restricted_shell - return true if the named shell begins with 'r' or 'R'
125  *
126  * If the first letter of the filename is 'r' or 'R', the shell is
127  * considered to be restricted.
128  */
129
130 static int
131 restricted_shell(const char *sh)
132 {
133 #if 0
134         char *cp = Basename((char *) sh);
135         return *cp == 'r' || *cp == 'R';
136 #else
137         /*
138          * Shells not listed in /etc/shells are considered to be
139          * restricted.  Changed this to avoid confusion with "rc"
140          * (the plan9 shell - not restricted despite the name
141          * starting with 'r').  --marekm
142          */
143         return !check_shell(sh);
144 #endif
145 }
146
147
148 /*
149  * chsh - this command controls changes to the user's shell
150  *
151  *      The only supported option is -s which permits the
152  *      the login shell to be set from the command line.
153  */
154
155 int
156 main(int argc, char **argv)
157 {
158         char    *user;                  /* User name                         */
159         int     flag;                   /* Current command line flag         */
160         int     sflg = 0;               /* -s - set shell from command line  */
161         const struct passwd *pw;        /* Password entry from /etc/passwd   */
162         struct  passwd  pwent;          /* New password entry                */
163
164         sanitize_env();
165
166         setlocale(LC_ALL, "");
167         bindtextdomain(PACKAGE, LOCALEDIR);
168         textdomain(PACKAGE);
169
170         /*
171          * This command behaves different for root and non-root
172          * users.
173          */
174
175         amroot = getuid () == 0;
176 #ifdef  NDBM
177         pw_dbm_mode = O_RDWR;
178 #endif
179
180         /*
181          * Get the program name.  The program name is used as a
182          * prefix to most error messages.  It is also used as input
183          * to the openlog() function for error logging.
184          */
185
186         Prog = Basename(argv[0]);
187
188         openlog("chsh", LOG_PID, LOG_AUTH);
189
190         /*
191          * There is only one option, but use getopt() anyway to
192          * keep things consistent.
193          */
194
195         while ((flag = getopt (argc, argv, "s:")) != EOF) {
196                 switch (flag) {
197                         case 's':
198                                 sflg++;
199                                 STRFCPY(loginsh, optarg);
200                                 break;
201                         default:
202                                 usage ();
203                 }
204         }
205
206         /*
207          * There should be only one remaining argument at most
208          * and it should be the user's name.
209          */
210
211         if (argc > optind + 1)
212                 usage ();
213
214         /*
215          * Get the name of the user to check.  It is either
216          * the command line name, or the name getlogin()
217          * returns.
218          */
219
220         if (optind < argc) {
221                 user = argv[optind];
222                 pw = getpwnam(user);
223                 if (!pw) {
224                         fprintf(stderr,
225                                 _("%s: Unknown user %s\n"),
226                                 Prog, user);
227                         exit(1);
228                 }
229         } else {
230                 pw = get_my_pwent();
231                 if (!pw) {
232                         fprintf(stderr,
233                                 _("%s: Cannot determine your user name.\n"),
234                                 Prog);
235                         exit(1);
236                 }
237                 user = xstrdup(pw->pw_name);
238         }
239
240 #ifdef  USE_NIS
241         /*
242          * Now we make sure this is a LOCAL password entry for
243          * this user ...
244          */
245
246         if (__ispwNIS ()) {
247                 char    *nis_domain;
248                 char    *nis_master;
249
250                 fprintf(stderr,
251                         _("%s: cannot change user `%s' on NIS client.\n"),
252                         Prog, user);
253
254                 if (! yp_get_default_domain (&nis_domain) &&
255                                 ! yp_master (nis_domain, "passwd.byname",
256                                 &nis_master)) {
257                         fprintf(stderr,
258                                 _("%s: `%s' is the NIS master for this client.\n"),
259                                 Prog, nis_master);
260                 }
261                 exit (1);
262         }
263 #endif
264
265         /*
266          * Non-privileged users are only allowed to change the
267          * shell if the UID of the user matches the current
268          * real UID.
269          */
270
271         if (! amroot && pw->pw_uid != getuid()) {
272                 SYSLOG((LOG_WARN, NOPERM2, user));
273                 closelog();
274                 fprintf(stderr, _("You may not change the shell for %s.\n"),
275                         user);
276                 exit(1);
277         }
278
279         /*
280          * Non-privileged users are only allowed to change the
281          * shell if it is not a restricted one.
282          */
283
284         if (! amroot && restricted_shell(pw->pw_shell)) {
285                 SYSLOG((LOG_WARN, NOPERM2, user));
286                 closelog();
287                 fprintf(stderr, _("You may not change the shell for %s.\n"),
288                         user);
289                 exit(1);
290         }
291
292         /*
293         * Non-privileged users are optionally authenticated
294         * (must enter the password of the user whose information
295         * is being changed) before any changes can be made.
296         * Idea from util-linux chfn/chsh.  --marekm
297         */
298
299         if (!amroot && getdef_bool("CHFN_AUTH"))
300                 passwd_check(pw->pw_name, pw->pw_passwd, "chsh");
301
302         /*
303          * Now get the login shell.  Either get it from the password
304          * file, or use the value from the command line.
305          */
306
307         if (! sflg)
308                 STRFCPY(loginsh, pw->pw_shell);
309
310         /*
311          * If the login shell was not set on the command line,
312          * let the user interactively change it.
313          */
314
315         if (! sflg) {
316                 printf(_("Changing the login shell for %s\n"), user);
317                 new_fields();
318         }
319
320         /*
321          * Check all of the fields for valid information.  The shell
322          * field may not contain any illegal characters.  Non-privileged
323          * users are restricted to using the shells in /etc/shells.
324          * The shell must be executable by the user.
325          */
326
327         if (valid_field (loginsh, ":,=")) {
328                 fprintf(stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
329                 closelog();
330                 exit(1);
331         }
332         if (!amroot && (!check_shell(loginsh) || access(loginsh, X_OK) != 0)) {
333                 fprintf(stderr, _("%s is an invalid shell.\n"), loginsh);
334                 closelog();
335                 exit(1);
336         }
337
338         /*
339          * Before going any further, raise the ulimit to prevent
340          * colliding into a lowered ulimit, and set the real UID
341          * to root to protect against unexpected signals.  Any
342          * keyboard signals are set to be ignored.
343          */
344
345         if (setuid(0)) {
346                 SYSLOG((LOG_ERR, NOTROOT2));
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
358         if (!pw_lock()) {
359                 SYSLOG((LOG_WARN, PWDBUSY2));
360                 closelog();
361                 fprintf(stderr,
362                         _("Cannot lock the password file; try again later.\n"));
363                 exit(1);
364         }
365         if (! pw_open (O_RDWR)) {
366                 SYSLOG((LOG_ERR, OPNERROR2));
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,
396          * update that entry as well.
397          */
398
399         if (!pw_update(&pwent)) {
400                 SYSLOG((LOG_ERR, UPDERROR2));
401                 closelog();
402                 fprintf(stderr, _("Error updating the password entry.\n"));
403                 pw_unlock();
404                 exit(1);
405         }
406 #ifdef NDBM
407         if (pw_dbm_present() && ! pw_dbm_update (&pwent)) {
408                 SYSLOG((LOG_ERR, DBMERROR2));
409                 closelog();
410                 fprintf (stderr, _("Error updating the DBM password entry.\n"));
411                 pw_unlock();
412                 exit(1);
413         }
414         endpwent();
415 #endif
416
417         /*
418          * Changes have all been made, so commit them and unlock the
419          * file.
420          */
421
422         if (!pw_close()) {
423                 SYSLOG((LOG_ERR, CLSERROR2));
424                 closelog();
425                 fprintf(stderr, _("Cannot commit password file changes.\n"));
426                 pw_unlock();
427                 exit(1);
428         }
429         if (!pw_unlock()) {
430                 SYSLOG((LOG_ERR, UNLKERROR2));
431                 closelog();
432                 fprintf(stderr, _("Cannot unlock the password file.\n"));
433                 exit(1);
434         }
435         SYSLOG((LOG_INFO, CHGSHELL, user, loginsh));
436         closelog();
437         exit (0);
438 }