]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chsh.c
Imported from util-linux-2.7.1 tarball.
[thirdparty/util-linux.git] / login-utils / chsh.c
CommitLineData
6dbe3af9
KZ
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 *
fd6b7a7f
KZ
9 * $Author: aebr $
10 * $Revision: 1.16 $
11 * $Date: 1997/07/06 00:12:08 $
726f69e2
KZ
12 *
13 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
fd6b7a7f
KZ
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 * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
21 * to allow peaceful coexistence with yp: using the changes by
22 * Alvaro Martinez Echevarria, alvaro@enano.etsit.upm.es, from
23 * passwd.c (now moved to setpwnam.c). Oct 5, 96.
6dbe3af9
KZ
24 *
25 */
26
fd6b7a7f 27static char rcsId[] = "$Version: $Id: chsh.c,v 1.16 1997/07/06 00:12:08 aebr Exp $ $";
726f69e2 28
fd6b7a7f 29#define _XOPEN_SOURCE /* to get definition of crypt() */
726f69e2 30#if 0
6dbe3af9 31#define _POSIX_SOURCE 1
726f69e2 32#endif
6dbe3af9
KZ
33
34#include <sys/types.h>
35#include <stdio.h>
36#include <string.h>
37#include <stdlib.h>
38#include <unistd.h>
39#include <pwd.h>
40#include <errno.h>
41#include <ctype.h>
42#include <getopt.h>
fd6b7a7f
KZ
43#include "../version.h"
44
45#if REQUIRE_PASSWORD && USE_PAM
46#include <security/pam_appl.h>
47#include <security/pam_misc.h>
48#endif
49
50extern int is_local(char *);
6dbe3af9
KZ
51
52#undef P
53#if __STDC__
54#define P(foo) foo
55#else
56#define P(foo) ()
57#endif
58
59typedef unsigned char boolean;
60#define false 0
61#define true 1
62
fd6b7a7f
KZ
63/* Only root is allowed to assign a luser a non-listed shell, by default */
64#define ONLY_LISTED_SHELLS 1
65
66
6dbe3af9
KZ
67static char *whoami;
68
69static char buf[FILENAME_MAX];
70
71struct sinfo {
72 char *username;
73 char *shell;
74};
75
76static void parse_argv P((int argc, char *argv[], struct sinfo *pinfo));
77static void usage P((FILE *fp));
78static char *prompt P((char *question, char *def_val));
79static int check_shell P((char *shell));
80static boolean get_shell_list P((char *shell));
81static void *xmalloc P((int bytes));
82extern int setpwnam P((struct passwd *pwd));
83#define memzero(ptr, size) memset((char *) ptr, 0, size)
84
85int main (argc, argv)
86 int argc;
87 char *argv[];
88{
726f69e2 89 char *cp, *shell, *oldshell, *pwdstr;
6dbe3af9
KZ
90 uid_t uid;
91 struct sinfo info;
92 struct passwd *pw;
93 extern int errno;
fd6b7a7f
KZ
94#if REQUIRE_PASSWORD && USE_PAM
95 pam_handle_t *pamh = NULL;
96 int retcode;
97 struct pam_conv conv = { misc_conv, NULL };
98#endif
6dbe3af9
KZ
99
100 /* whoami is the program name for error messages */
101 whoami = argv[0];
102 if (! whoami) whoami = "chsh";
103 for (cp = whoami; *cp; cp++)
104 if (*cp == '/') whoami = cp + 1;
105
6dbe3af9
KZ
106 uid = getuid ();
107 memzero (&info, sizeof (info));
108
109 parse_argv (argc, argv, &info);
110 pw = NULL;
111 if (! info.username) {
112 pw = getpwuid (uid);
113 if (! pw) {
114 fprintf (stderr, "%s: you (user %d) don't exist.\n", whoami, uid);
115 return (-1); }
116 }
117 else {
118 pw = getpwnam (info.username);
119 if (! pw) {
120 cp = info.username;
121 fprintf (stderr, "%s: user \"%s\" does not exist.\n", whoami, cp);
122 return (-1); }
123 }
124
fd6b7a7f
KZ
125 if (!(is_local(pw->pw_name))) {
126 fprintf (stderr, "%s: can only change local entries; use yp%s instead.\n
127",
128 whoami, whoami);
129 exit(1);
130 }
131
726f69e2
KZ
132 oldshell = pw->pw_shell;
133 if (!oldshell[0]) oldshell = "/bin/sh";
134
6dbe3af9 135 /* reality check */
726f69e2 136 if (uid != 0 && (uid != pw->pw_uid || !get_shell_list(oldshell))) {
6dbe3af9 137 errno = EACCES;
fd6b7a7f
KZ
138 fprintf(stderr,"%s: Your shell is not in /etc/shells, shell change"
139 " denied\n",whoami);
6dbe3af9
KZ
140 return (-1);
141 }
142
143 shell = info.shell;
726f69e2
KZ
144
145 printf( "Changing shell for %s.\n", pw->pw_name );
146
fd6b7a7f
KZ
147#if REQUIRE_PASSWORD
148# if USE_PAM
149 if(uid != 0) {
150 if (pam_start("chsh", pw->pw_name, &conv, &pamh)) {
151 puts("Password error.");
152 exit(1);
153 }
154 if (pam_authenticate(pamh, 0)) {
155 puts("Password error.");
156 exit(1);
157 }
158 retcode = pam_acct_mgmt(pamh, 0);
159 if (retcode == PAM_AUTHTOKEN_REQD) {
160 retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
161 } else if (retcode) {
162 puts("Password error.");
163 exit(1);
164 }
165 if (pam_setcred(pamh, 0)) {
166 puts("Password error.");
167 exit(1);
168 }
169 /* no need to establish a session; this isn't a session-oriented
170 * activity... */
171 }
172# else /* USE_PAM */
726f69e2
KZ
173 /* require password, unless root */
174 if(uid != 0 && pw->pw_passwd && pw->pw_passwd[0]) {
175 pwdstr = getpass("Password: ");
fd6b7a7f
KZ
176 if(strncmp(pw->pw_passwd,
177 crypt(pwdstr, pw->pw_passwd), 13)) {
726f69e2
KZ
178 puts("Incorrect password.");
179 exit(1);
180 }
181 }
fd6b7a7f
KZ
182# endif /* USE_PAM */
183#endif /* REQUIRE_PASSWORD */
726f69e2 184
6dbe3af9 185 if (! shell) {
726f69e2 186 shell = prompt ("New shell", oldshell);
6dbe3af9
KZ
187 if (! shell) return 0;
188 }
726f69e2 189
6dbe3af9
KZ
190 if (check_shell (shell) < 0) return (-1);
191
192 if (! strcmp (pw->pw_shell, shell)) {
193 printf ("Shell not changed.\n");
194 return 0;
195 }
726f69e2 196 if (!strcmp(shell, "/bin/sh")) shell = "";
6dbe3af9
KZ
197 pw->pw_shell = shell;
198 if (setpwnam (pw) < 0) {
199 perror ("setpwnam");
726f69e2 200 printf( "Shell *NOT* changed. Try again later.\n" );
6dbe3af9
KZ
201 return (-1);
202 }
203 printf ("Shell changed.\n");
204 return 0;
205}
206
207/*
208 * parse_argv () --
209 * parse the command line arguments, and fill in "pinfo" with any
210 * information from the command line.
211 */
212static void parse_argv (argc, argv, pinfo)
213 int argc;
214 char *argv[];
215 struct sinfo *pinfo;
216{
217 int index, c;
218
219 static struct option long_options[] = {
220 { "shell", required_argument, 0, 's' },
221 { "list-shells", no_argument, 0, 'l' },
222 { "help", no_argument, 0, 'u' },
223 { "version", no_argument, 0, 'v' },
224 { NULL, no_argument, 0, '0' },
225 };
226
227 optind = c = 0;
228 while (c != EOF) {
229 c = getopt_long (argc, argv, "s:luv", long_options, &index);
230 switch (c) {
231 case EOF:
232 break;
233 case 'v':
fd6b7a7f 234 printf ("%s\n", util_linux_version);
6dbe3af9
KZ
235 exit (0);
236 case 'u':
237 usage (stdout);
238 exit (0);
239 case 'l':
240 get_shell_list (NULL);
241 exit (0);
242 case 's':
243 if (! optarg) {
244 usage (stderr);
245 exit (-1);
246 }
247 pinfo->shell = optarg;
248 break;
249 default:
250 usage (stderr);
251 exit (-1);
252 }
253 }
254 /* done parsing arguments. check for a username. */
255 if (optind < argc) {
256 if (optind + 1 < argc) {
257 usage (stderr);
258 exit (-1);
259 }
260 pinfo->username = argv[optind];
261 }
262}
263
264/*
265 * usage () --
266 * print out a usage message.
267 */
268static void usage (fp)
269 FILE *fp;
270{
271 fprintf (fp, "Usage: %s [ -s shell ] ", whoami);
272 fprintf (fp, "[ --list-shells ] [ --help ] [ --version ]\n");
273 fprintf (fp, " [ username ]\n");
274}
275
276/*
277 * prompt () --
278 * ask the user for a given field and return it.
279 */
280static char *prompt (question, def_val)
281 char *question;
282 char *def_val;
283{
284 int len;
285 char *ans, *cp;
286
287 if (! def_val) def_val = "";
288 printf("%s [%s]: ", question, def_val);
289 *buf = 0;
290 if (fgets (buf, sizeof (buf), stdin) == NULL) {
291 printf ("\nAborted.\n");
292 exit (-1);
293 }
294 /* remove the newline at the end of buf. */
295 ans = buf;
296 while (isspace (*ans)) ans++;
297 len = strlen (ans);
298 while (len > 0 && isspace (ans[len-1])) len--;
299 if (len <= 0) return NULL;
300 ans[len] = 0;
301 cp = (char *) xmalloc (len + 1);
fd6b7a7f 302 strcpy (cp, ans);
6dbe3af9
KZ
303 return cp;
304}
305
306/*
307 * check_shell () -- if the shell is completely invalid, print
308 * an error and return (-1).
309 * if the shell is a bad idea, print a warning.
310 */
311static int check_shell (shell)
312 char *shell;
313{
314 int i, c;
315
316 if (*shell != '/') {
317 printf ("%s: shell must be a full path name.\n", whoami);
318 return (-1);
319 }
320 if (access (shell, F_OK) < 0) {
321 printf ("%s: \"%s\" does not exist.\n", whoami, shell);
322 return (-1);
323 }
324 if (access (shell, X_OK) < 0) {
325 printf ("%s: \"%s\" is not executable.\n", whoami, shell);
326 return (-1);
327 }
328 /* keep /etc/passwd clean. */
329 for (i = 0; i < strlen (shell); i++) {
330 c = shell[i];
331 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
332 printf ("%s: '%c' is not allowed.\n", whoami, c);
333 return (-1);
334 }
335 if (iscntrl (c)) {
336 printf ("%s: Control characters are not allowed.\n", whoami);
337 return (-1);
338 }
339 }
726f69e2
KZ
340#if ONLY_LISTED_SHELLS
341 if (! get_shell_list (shell)) {
342 if (!getuid())
fd6b7a7f 343 printf ("Warning: \"%s\" is not listed in /etc/shells\n", shell);
726f69e2 344 else {
fd6b7a7f 345 printf ("%s: \"%s\" is not listed in /etc/shells.\n",
726f69e2 346 whoami, shell);
fd6b7a7f 347 printf( "%s: use -l option to see list\n", whoami );
726f69e2
KZ
348 exit(1);
349 }
350 }
351#else
352 if (! get_shell_list (shell)) {
fd6b7a7f 353 printf ("Warning: \"%s\" is not listed in /etc/shells.\n", shell);
726f69e2
KZ
354 printf( "Use %s -l to see list.\n", whoami );
355 }
356#endif
6dbe3af9
KZ
357 return 0;
358}
359
360/*
361 * get_shell_list () -- if the given shell appears in /etc/shells,
362 * return true. if not, return false.
363 * if the given shell is NULL, /etc/shells is outputted to stdout.
364 */
365static boolean get_shell_list (shell_name)
366 char *shell_name;
367{
368 FILE *fp;
369 boolean found;
370 int len;
371
372 found = false;
373 fp = fopen ("/etc/shells", "r");
374 if (! fp) {
375 if (! shell_name) printf ("No known shells.\n");
376 return true;
377 }
378 while (fgets (buf, sizeof (buf), fp) != NULL) {
379 /* ignore comments */
380 if (*buf == '#') continue;
381 len = strlen (buf);
382 /* strip the ending newline */
383 if (buf[len - 1] == '\n') buf[len - 1] = 0;
384 /* ignore lines that are too damn long */
385 else continue;
386 /* check or output the shell */
387 if (shell_name) {
388 if (! strcmp (shell_name, buf)) {
389 found = true;
390 break;
391 }
392 }
393 else printf ("%s\n", buf);
394 }
395 fclose (fp);
396 return found;
397}
398
399/*
400 * xmalloc () -- malloc that never fails.
401 */
402static void *xmalloc (bytes)
403 int bytes;
404{
405 void *vp;
406
407 vp = malloc (bytes);
408 if (! vp && bytes > 0) {
409 perror ("malloc failed");
410 exit (-1);
411 }
412 return vp;
413}