Upload Tizen:Base source
[framework/base/util-linux-ng.git] / login-utils / chsh.c
1 /*
2  *   chsh.c -- change your login shell
3  *   (c) 1994 by salvatore valente <svalente@athena.mit.edu>
4  *
5  *   this program is free software.  you can redistribute it and
6  *   modify it under the terms of the gnu general public license.
7  *   there is no warranty.
8  *
9  *   $Author: aebr $
10  *   $Revision: 1.19 $
11  *   $Date: 1998/06/11 22:30:14 $
12  *
13  * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
14  *   patches from Zefram <A.Main@dcs.warwick.ac.uk>
15  *
16  * Updated Mon Jul  1 18:46:22 1996 by janl@math.uio.no with security
17  *   suggestion from Zefram.  Disallowing users with shells not in /etc/shells
18  *   from changing their shell.
19  *
20  *   1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
21  *   - added Native Language Support
22  *
23  *
24  */
25
26 #if 0
27 #define _POSIX_SOURCE 1
28 #endif
29
30 #include <sys/types.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <pwd.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <getopt.h>
39 #include "my_crypt.h"
40 #include "islocal.h"
41 #include "setpwnam.h"
42 #include "nls.h"
43 #include "env.h"
44 #include "pathnames.h"
45
46 #if defined(REQUIRE_PASSWORD) && defined(HAVE_SECURITY_PAM_MISC_H)
47 #include <security/pam_appl.h>
48 #include <security/pam_misc.h>
49
50 #define PAM_FAIL_CHECK(_ph, _rc) \
51     do { \
52         if ((_rc) != PAM_SUCCESS) { \
53             fprintf(stderr, "\n%s\n", pam_strerror((_ph), (_rc))); \
54             pam_end((_ph), (_rc)); \
55             exit(1); \
56         } \
57     } while(0)
58
59 #endif /* PAM */
60
61 #ifdef HAVE_LIBSELINUX
62 #include <selinux/selinux.h>
63 #include <selinux/av_permissions.h>
64 #include "selinux_utils.h"
65 #endif
66
67 typedef unsigned char boolean;
68 #define false 0
69 #define true 1
70
71 /* Only root is allowed to assign a luser a non-listed shell, by default */
72 #define ONLY_LISTED_SHELLS 1
73
74
75 static char *whoami;
76
77 static char buf[FILENAME_MAX];
78
79 struct sinfo {
80     char *username;
81     char *shell;
82 };
83
84 static void parse_argv (int argc, char *argv[], struct sinfo *pinfo);
85 static void usage (FILE *fp);
86 static char *prompt (char *question, char *def_val);
87 static int check_shell (char *shell);
88 static boolean get_shell_list (char *shell);
89 static void *xmalloc (int bytes);
90
91 #define memzero(ptr, size) memset((char *) ptr, 0, size)
92
93 int
94 main (int argc, char *argv[]) {
95     char *cp, *shell, *oldshell;
96     uid_t uid;
97     struct sinfo info;
98     struct passwd *pw;
99
100     sanitize_env();
101     setlocale(LC_ALL, "");
102     bindtextdomain(PACKAGE, LOCALEDIR);
103     textdomain(PACKAGE);
104
105     /* whoami is the program name for error messages */
106     whoami = argv[0];
107     if (! whoami) whoami = "chsh";
108     for (cp = whoami; *cp; cp++)
109         if (*cp == '/') whoami = cp + 1;
110
111     uid = getuid ();
112     memzero (&info, sizeof (info));
113
114     parse_argv (argc, argv, &info);
115     pw = NULL;
116     if (! info.username) {
117         pw = getpwuid (uid);
118         if (! pw) {
119             fprintf (stderr, _("%s: you (user %d) don't exist.\n"), whoami, uid);
120             return (-1); }
121     }
122     else {
123         pw = getpwnam (info.username);
124         if (! pw) {
125             cp = info.username;
126             fprintf (stderr, _("%s: user \"%s\" does not exist.\n"), whoami, cp);
127             return (-1); }
128     }
129
130     if (!(is_local(pw->pw_name))) {
131        fprintf (stderr, _("%s: can only change local entries; use yp%s instead.\n"),
132            whoami, whoami);
133        exit(1);
134     }
135
136 #ifdef HAVE_LIBSELINUX
137     if (is_selinux_enabled() > 0) {
138       if(uid == 0) {
139         if (checkAccess(pw->pw_name,PASSWD__CHSH)!=0) {
140           security_context_t user_context;
141           if (getprevcon(&user_context) < 0)
142             user_context=(security_context_t) strdup(_("Unknown user context"));
143           fprintf(stderr, _("%s: %s is not authorized to change the shell of %s\n"),
144                   whoami, user_context, pw->pw_name);
145           freecon(user_context);
146           exit(1);
147         }
148       }
149       if (setupDefaultContext("/etc/passwd") != 0) {
150         fprintf(stderr,_("%s: Can't set default context for /etc/passwd"),
151                 whoami);
152         exit(1);
153       }
154     }
155 #endif
156
157     oldshell = pw->pw_shell;
158     if (oldshell == NULL || *oldshell == '\0')
159             oldshell = _PATH_BSHELL;                    /* default */
160
161     /* reality check */
162     if (uid != 0 && uid != pw->pw_uid) {
163         errno = EACCES;
164         fprintf(stderr,_("%s: Running UID doesn't match UID of user we're "
165                          "altering, shell change denied\n"), whoami);
166         return (-1);
167     }
168     if (uid != 0 && !get_shell_list(oldshell)) {
169         errno = EACCES;
170         fprintf(stderr,_("%s: Your shell is not in /etc/shells, shell change"
171                 " denied\n"),whoami);
172         return (-1);
173     }
174
175     shell = info.shell;
176
177     printf( _("Changing shell for %s.\n"), pw->pw_name );
178
179 #ifdef REQUIRE_PASSWORD
180 #ifdef HAVE_SECURITY_PAM_MISC_H
181     if(uid != 0) {
182         pam_handle_t *pamh = NULL;
183         struct pam_conv conv = { misc_conv, NULL };
184         int retcode;
185
186         retcode = pam_start("chsh", pw->pw_name, &conv, &pamh);
187         if(retcode != PAM_SUCCESS) {
188             fprintf(stderr, _("%s: PAM failure, aborting: %s\n"),
189                     whoami, pam_strerror(pamh, retcode));
190             exit(1);
191         }
192
193         retcode = pam_authenticate(pamh, 0);
194         PAM_FAIL_CHECK(pamh, retcode);
195
196         retcode = pam_acct_mgmt(pamh, 0);
197         if (retcode == PAM_NEW_AUTHTOK_REQD)
198             retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
199         PAM_FAIL_CHECK(pamh, retcode);
200
201         retcode = pam_setcred(pamh, 0);
202         PAM_FAIL_CHECK(pamh, retcode);
203
204         pam_end(pamh, 0);
205         /* no need to establish a session; this isn't a session-oriented
206          * activity... */
207     }
208 #else /* HAVE_SECURITY_PAM_MISC_H */
209     /* require password, unless root */
210     if(uid != 0 && pw->pw_passwd && pw->pw_passwd[0]) {
211         char *pwdstr = getpass(_("Password: "));
212         if(strncmp(pw->pw_passwd,
213                    crypt(pwdstr, pw->pw_passwd), 13)) {
214             puts(_("Incorrect password."));
215             exit(1);
216         }
217     }
218 #endif /* HAVE_SECURITY_PAM_MISC_H */
219 #endif /* REQUIRE_PASSWORD */
220
221     if (! shell) {
222         shell = prompt (_("New shell"), oldshell);
223         if (! shell) return 0;
224     }
225
226     if (check_shell (shell) < 0) return (-1);
227
228     if (! strcmp (pw->pw_shell, shell)) {
229         printf (_("Shell not changed.\n"));
230         return 0;
231     }
232     pw->pw_shell = shell;
233     if (setpwnam (pw) < 0) {
234         perror ("setpwnam");
235         printf( _("Shell *NOT* changed.  Try again later.\n") );
236         return (-1);
237     }
238     printf (_("Shell changed.\n"));
239     return 0;
240 }
241
242 /*
243  *  parse_argv () --
244  *      parse the command line arguments, and fill in "pinfo" with any
245  *      information from the command line.
246  */
247 static void
248 parse_argv (int argc, char *argv[], struct sinfo *pinfo) {
249     int index, c;
250
251     static struct option long_options[] = {
252         { "shell",       required_argument, 0, 's' },
253         { "list-shells", no_argument,       0, 'l' },
254         { "help",        no_argument,       0, 'u' },
255         { "version",     no_argument,       0, 'v' },
256         { NULL,          no_argument,       0, '0' },
257     };
258
259     optind = c = 0;
260     while (c != EOF) {
261         c = getopt_long (argc, argv, "s:luv", long_options, &index);
262         switch (c) {
263         case -1:
264             break;
265         case 'v':
266             printf ("%s\n", PACKAGE_STRING);
267             exit (0);
268         case 'u':
269             usage (stdout);
270             exit (0);
271         case 'l':
272             get_shell_list (NULL);
273             exit (0);
274         case 's':
275             if (! optarg) {
276                 usage (stderr);
277                 exit (-1);
278             }
279             pinfo->shell = optarg;
280             break;
281         default:
282             usage (stderr);
283             exit (-1);
284         }
285     }
286     /* done parsing arguments.  check for a username. */
287     if (optind < argc) {
288         if (optind + 1 < argc) {
289             usage (stderr);
290             exit (-1);
291         }
292         pinfo->username = argv[optind];
293     }
294 }
295
296 /*
297  *  usage () --
298  *      print out a usage message.
299  */
300 static void
301 usage (FILE *fp) {
302     fprintf (fp,
303              _("Usage: %s [ -s shell ] [ --list-shells ] "
304                "[ --help ] [ --version ]\n"
305                "       [ username ]\n"), whoami);
306 }
307
308 /*
309  *  prompt () --
310  *      ask the user for a given field and return it.
311  */
312 static char *
313 prompt (char *question, char *def_val) {
314     int len;
315     char *ans, *cp;
316
317     if (! def_val) def_val = "";
318     printf("%s [%s]: ", question, def_val);
319     *buf = 0;
320     if (fgets (buf, sizeof (buf), stdin) == NULL) {
321         printf (_("\nAborted.\n"));
322         exit (-1);
323     }
324     /* remove the newline at the end of buf. */
325     ans = buf;
326     while (isspace (*ans)) ans++;
327     len = strlen (ans);
328     while (len > 0 && isspace (ans[len-1])) len--;
329     if (len <= 0) return NULL;
330     ans[len] = 0;
331     cp = (char *) xmalloc (len + 1);
332     strcpy (cp, ans);
333     return cp;
334 }
335
336 /*
337  *  check_shell () -- if the shell is completely invalid, print
338  *      an error and return (-1).
339  *      if the shell is a bad idea, print a warning.
340  */
341 static int
342 check_shell (char *shell) {
343     int i, c;
344
345     if (!shell)
346         return (-1);
347
348     if (*shell != '/') {
349         printf (_("%s: shell must be a full path name.\n"), whoami);
350         return (-1);
351     }
352     if (access (shell, F_OK) < 0) {
353         printf (_("%s: \"%s\" does not exist.\n"), whoami, shell);
354         return (-1);
355     }
356     if (access (shell, X_OK) < 0) {
357         printf (_("%s: \"%s\" is not executable.\n"), whoami, shell);
358         return (-1);
359     }
360     /* keep /etc/passwd clean. */
361     for (i = 0; i < strlen (shell); i++) {
362         c = shell[i];
363         if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
364             printf (_("%s: '%c' is not allowed.\n"), whoami, c);
365             return (-1);
366         }
367         if (iscntrl (c)) {
368             printf (_("%s: Control characters are not allowed.\n"), whoami);
369             return (-1);
370         }
371     }
372 #ifdef ONLY_LISTED_SHELLS
373     if (! get_shell_list (shell)) {
374        if (!getuid())
375           printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
376        else {
377           printf (_("%s: \"%s\" is not listed in /etc/shells.\n"),
378                   whoami, shell);
379           printf( _("%s: Use -l option to see list.\n"), whoami );
380           exit(1);
381        }
382     }
383 #else
384     if (! get_shell_list (shell)) {
385        printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
386        printf( _("Use %s -l to see list.\n"), whoami );
387     }
388 #endif
389     return 0;
390 }
391
392 /*
393  *  get_shell_list () -- if the given shell appears in /etc/shells,
394  *      return true.  if not, return false.
395  *      if the given shell is NULL, /etc/shells is outputted to stdout.
396  */
397 static boolean
398 get_shell_list (char *shell_name) {
399     FILE *fp;
400     boolean found;
401     int len;
402
403     found = false;
404     fp = fopen ("/etc/shells", "r");
405     if (! fp) {
406         if (! shell_name) printf (_("No known shells.\n"));
407         return true;
408     }
409     while (fgets (buf, sizeof (buf), fp) != NULL) {
410         /* ignore comments */
411         if (*buf == '#') continue;
412         len = strlen (buf);
413         /* strip the ending newline */
414         if (buf[len - 1] == '\n') buf[len - 1] = 0;
415         /* ignore lines that are too damn long */
416         else continue;
417         /* check or output the shell */
418         if (shell_name) {
419             if (! strcmp (shell_name, buf)) {
420                 found = true;
421                 break;
422             }
423         }
424         else printf ("%s\n", buf);
425     }
426     fclose (fp);
427     return found;
428 }
429
430 /*
431  *  xmalloc () -- malloc that never fails.
432  */
433 static void *
434 xmalloc (int bytes) {
435     void *vp;
436
437     vp = malloc (bytes);
438     if (! vp && bytes > 0) {
439         perror (_("malloc failed"));
440         exit (-1);
441     }
442     return vp;
443 }