Add -O and -T to the SYNOPSIS.
[dragonfly.git] / contrib / opie / opiesu.c
1 /* opiesu.c: main body of code for the su(1m) program
2
3 %%% portions-copyright-cmetz-96
4 Portions of this software are Copyright 1996-1999 by Craig Metz, All Rights
5 Reserved. The Inner Net License Version 2 applies to these portions of
6 the software.
7 You should have received a copy of the license with this software. If
8 you didn't get a copy, you may request one from <license@inner.net>.
9
10 Portions of this software are Copyright 1995 by Randall Atkinson and Dan
11 McDonald, All Rights Reserved. All Rights under this copyright are assigned
12 to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
13 License Agreement applies to this software.
14
15         History:
16
17         Modified by cmetz for OPIE 2.4. Check euid on startup. Use
18                 opiestrncpy().
19         Modified by cmetz for OPIE 2.32. Set up TERM and PATH correctly.
20         Modified by cmetz for OPIE 2.31. Fix sulog(). Replaced Getlogin() with
21                 currentuser. Fixed fencepost error in month printed by sulog().
22         Modified by cmetz for OPIE 2.3. Limit the length of TERM on full login.
23                 Use HAVE_SULOG instead of DOSULOG.
24         Modified by cmetz for OPIE 2.2. Don't try to clear non-blocking I/O.
25                 Use opiereadpass(). Minor speedup. Removed termios manipulation
26                 -- that's opiereadpass()'s job. Change opiereadpass() calls
27                 to add echo arg. Removed useless strings (I don't think that
28                 removing the ucb copyright one is a problem -- please let me
29                 know if I'm wrong). Use FUNCTION declaration et al. Ifdef
30                 around some headers. Make everything static. Removed
31                 closelog() prototype. Use the same catchexit() trickery as
32                 opielogin.
33         Modified at NRL for OPIE 2.2. Changed opiestrip_crlf to
34                 opiestripcrlf.
35         Modified at NRL for OPIE 2.1. Added struct group declaration.
36                 Added Solaris(+others?) sulog capability. Symbol changes
37                 for autoconf. Removed des_crypt.h. File renamed to
38                 opiesu.c. Symbol+misc changes for autoconf. Added bletch
39                 for setpriority.
40         Modified at NRL for OPIE 2.02. Added SU_STAR_CHECK (turning a bug
41                 into a feature ;). Fixed Solaris shadow password problem
42                 introduced in OPIE 2.01 (the shadow password structure is
43                 spwd, not spasswd).
44         Modified at NRL for OPIE 2.01. Changed password lookup handling
45                 to use a static structure to avoid problems with drain-
46                 bamaged shadow password packages. Always log failures.
47                 Make sure to close syslog by function to avoid problems 
48                 with drain bamaged syslog implementations. Log a few 
49                 interesting errors.
50         Modified at NRL for OPIE 2.0.
51         Modified at Bellcore for the S/Key Version 1 software distribution.
52         Originally from BSD.
53 */
54
55 /*
56  * Copyright (c) 1980 Regents of the University of California.
57  * All rights reserved.  The Berkeley software License Agreement
58  * specifies the terms and conditions for redistribution.
59  */
60
61 #include "opie_cfg.h"
62
63 #include <stdio.h>
64 #if HAVE_PWD_H
65 #include <pwd.h>
66 #endif /* HAVE_PWD_H */
67 #include <grp.h>
68 #include <syslog.h>
69 #include <sys/types.h>
70 #if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
71 #if TIME_WITH_SYS_TIME
72 # include <sys/time.h>
73 # include <time.h>
74 #else /* TIME_WITH_SYS_TIME */
75 #if HAVE_SYS_TIME_H
76 #include <sys/time.h>
77 #else /* HAVE_SYS_TIME_H */
78 #include <time.h>
79 #endif /* HAVE_SYS_TIME_H */
80 #endif /* TIME_WITH_SYS_TIME */
81 #include <sys/resource.h>
82 #else /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
83 #if TM_IN_SYS_TIME
84 #include <sys/time.h>
85 #else /* TM_IN_SYS_TIME */
86 #include <time.h>
87 #endif /* TM_IN_SYS_TIME */
88 #endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
89 #if HAVE_STDLIB_H
90 #include <stdlib.h>
91 #endif /* HAVE_STDLIB_H */
92 #if HAVE_UNISTD_H
93 #include <unistd.h>
94 #endif /* HAVE_UNISTD_H */
95 #if HAVE_STRING_H
96 #include <string.h>
97 #endif /* HAVE_STRING_H */
98 #include <errno.h>
99
100 #include "opie.h"
101
102 static char userbuf[16] = "USER=";
103 static char homebuf[128] = "HOME=";
104 static char shellbuf[128] = "SHELL=";
105 static char pathbuf[sizeof("PATH") + sizeof(DEFAULT_PATH) - 1] = "PATH=";
106 static char termbuf[32] = "TERM=";
107 static char *cleanenv[] = {userbuf, homebuf, shellbuf, pathbuf, 0, 0};
108 static char *user = "root";
109 static char *shell = "/bin/sh";
110 static int fulllogin;
111 #if 0
112 static int fastlogin;
113 #else /* 0 */
114 static int force = 0;
115 #endif /* 0 */
116
117 static char currentuser[65];
118
119 extern char **environ;
120 static struct passwd thisuser, nouser;
121
122 #if HAVE_SHADOW_H
123 #include <shadow.h>
124 #endif /* HAVE_SHADOW_H */
125
126 #if HAVE_CRYPT_H
127 #include <crypt.h>
128 #endif /* HAVE_CRYPT_H */
129
130 static VOIDRET catchexit FUNCTION_NOARGS
131 {
132   int i;
133   closelog();
134   for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
135     close(i);
136 }
137
138 /* We allow the malloc()s to potentially leak data out because we can
139 only call this routine about four times in the lifetime of this process
140 and the kernel will free all heap memory when we exit or exec. */
141 static int lookupuser FUNCTION((name), char *name)
142 {
143   struct passwd *pwd;
144 #if HAVE_SHADOW
145   struct spwd *spwd;
146 #endif /* HAVE_SHADOW */
147
148   memcpy(&thisuser, &nouser, sizeof(thisuser));
149
150   if (!(pwd = getpwnam(name)))
151     return -1;
152
153   thisuser.pw_uid = pwd->pw_uid;
154   thisuser.pw_gid = pwd->pw_gid;
155
156   if (!(thisuser.pw_name = malloc(strlen(pwd->pw_name) + 1)))
157     goto lookupuserbad;
158   strcpy(thisuser.pw_name, pwd->pw_name);
159
160   if (!(thisuser.pw_dir = malloc(strlen(pwd->pw_dir) + 1)))
161     goto lookupuserbad;
162   strcpy(thisuser.pw_dir, pwd->pw_dir);
163
164   if (!(thisuser.pw_shell = malloc(strlen(pwd->pw_shell) + 1)))
165     goto lookupuserbad;
166   strcpy(thisuser.pw_shell, pwd->pw_shell);
167
168 #if HAVE_SHADOW
169   if (!(spwd = getspnam(name)))
170         goto lookupuserbad;
171
172   pwd->pw_passwd = spwd->sp_pwdp;
173
174   endspent();
175 #endif /* HAVE_SHADOW */
176
177   if (!(thisuser.pw_passwd = malloc(strlen(pwd->pw_passwd) + 1)))
178     goto lookupuserbad;
179   strcpy(thisuser.pw_passwd, pwd->pw_passwd);
180
181   endpwent();
182
183 #if SU_STAR_CHECK
184   return ((thisuser.pw_passwd[0] == '*') || (thisuser.pw_passwd[0] == '#'));
185 #else /* SU_STAR_CHECK */
186   return 0;
187 #endif /* SU_STAR_CHECK */
188
189 lookupuserbad:
190   memcpy(&thisuser, &nouser, sizeof(thisuser));
191   return -1;
192 }
193
194 static VOIDRET lsetenv FUNCTION((ename, eval, buf), char *ename AND char *eval AND char *buf)
195 {
196   register char *cp, *dp;
197   register char **ep = environ;
198
199   /* this assumes an environment variable "ename" already exists */
200   while (dp = *ep++) {
201     for (cp = ename; *cp == *dp && *cp; cp++, dp++)
202       continue;
203     if (*cp == 0 && (*dp == '=' || *dp == 0)) {
204       strcat(buf, eval);
205       *--ep = buf;
206       return;
207     }
208   }
209 }
210
211 #if HAVE_SULOG
212 static int sulog FUNCTION((status, who), int status AND char *who)
213 {
214   char *from;
215   char *ttynam;
216   struct tm *tm;
217   FILE *f;
218   time_t now;
219
220   if (who)
221     from = who;
222   else
223     from = currentuser;
224
225   if (!strncmp(ttynam = ttyname(2), "/dev/", 5))
226     ttynam += 5;
227
228   now = time(NULL);
229   tm = localtime(&now);
230   
231   if (!(f = fopen("/var/adm/sulog", "a"))) {
232     fprintf(stderr, "Can't update su log!\n");
233     exit(1);
234   }
235
236   fprintf(f, "SU %02d/%02d %02d:%02d %c %s %s-%s\n",
237           tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
238           status ? '+' : '-', ttynam, from, user);
239   fclose(f);
240 }
241 #endif /* HAVE_SULOG */
242
243 int main FUNCTION((argc, argv), int argc AND char *argv[])
244 {
245   char *p;
246   struct opie opie;
247   int i;
248   char pbuf[256];
249   char opieprompt[80];
250   int console = 0;
251   char *argvbuf;
252
253   for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
254     close(i);
255
256   openlog("su", LOG_ODELAY, LOG_AUTH);
257   atexit(catchexit);
258
259   {
260   int argvsize = 0;
261   for (i = 0; i < argc; argvsize += strlen(argv[i++]));
262   argvsize += argc;
263   if (!(argvbuf = malloc(argvsize))) {
264     syslog(LOG_ERR, "can't allocate memory to store command line");
265     exit(1);
266   };
267   for (i = 0, *argvbuf = 0; i < argc;) {
268     strcat(argvbuf, argv[i]);
269     if (++i < argc)
270       strcat(argvbuf, " ");
271   };
272   };
273
274   strcat(pathbuf, DEFAULT_PATH);
275
276 again:
277   if (argc > 1 && strcmp(argv[1], "-f") == 0) {
278 #if 0
279     fastlogin++;
280 #else /* 0 */
281 #if INSECURE_OVERRIDE
282     force = 1;
283 #else /* INSECURE_OVERRIDE */
284     fprintf(stderr, "Sorry, but the -f option is not supported by this build of OPIE.\n");
285 #endif /* INSECURE_OVERRIDE */
286 #endif /* 0 */
287     argc--, argv++;
288     goto again;
289   }
290   if (argc > 1 && strcmp(argv[1], "-c") == 0) {
291     console++;
292     argc--, argv++;
293     goto again;
294   }
295   if (argc > 1 && strcmp(argv[1], "-") == 0) {
296     fulllogin++;
297     argc--;
298     argv++;
299     goto again;
300   }
301   if (argc > 1 && argv[1][0] != '-') {
302     user = argv[1];
303     argc--;
304     argv++;
305   }
306
307
308   {
309   struct passwd *pwd;
310   char *p = getlogin();
311   char buf[32];
312
313   if ((pwd = getpwuid(getuid())) == NULL) {
314     syslog(LOG_CRIT, "'%s' failed for unknown uid %d on %s", argvbuf, getuid(), ttyname(2));
315 #if HAVE_SULOG
316     sulog(0, "unknown");
317 #endif /* HAVE_SULOG */
318     exit(1);
319   }
320   opiestrncpy(buf, pwd->pw_name, sizeof(buf));
321
322   if (!p)
323     p = "unknown";
324
325   opiestrncpy(currentuser, p, 31);
326
327   if (p && *p && strcmp(currentuser, buf)) {
328     strcat(currentuser, "(");
329     strcat(currentuser, buf);
330     strcat(currentuser, ")");
331   };
332
333   if (lookupuser(user)) {
334     syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2));
335 #if HAVE_SULOG
336     sulog(0, NULL);
337 #endif /* HAVE_SULOG */
338     fprintf(stderr, "Unknown user: %s\n", user);
339     exit(1);
340   }
341
342   if (geteuid()) {
343     syslog(LOG_CRIT, "'%s' failed for %s on %s: not running with superuser priveleges", argvbuf, currentuser, ttyname(2));
344 #if HAVE_SULOG
345     sulog(0, NULL);
346 #endif /* HAVE_SULOG */
347     fprintf(stderr, "You do not have permission to su %s\n", user);
348     exit(1);
349   };
350
351 /* Implement the BSD "wheel group" su restriction. */
352 #if DOWHEEL
353   /* Only allow those in group zero to su to root? */
354   if (thisuser.pw_uid == 0) {
355     struct group *gr;
356     if ((gr = getgrgid(0)) != NULL) {
357       for (i = 0; gr->gr_mem[i] != NULL; i++)
358         if (strcmp(buf, gr->gr_mem[i]) == 0)
359           goto userok;
360       fprintf(stderr, "You do not have permission to su %s\n", user);
361       exit(1);
362     }
363 userok:
364     ;
365 #if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
366     setpriority(PRIO_PROCESS, 0, -2);
367 #endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
368   }
369 #endif  /* DOWHEEL */
370   };
371
372   if (!thisuser.pw_passwd[0] || getuid() == 0)
373     goto ok;
374
375   if (console) {
376     if (!opiealways(thisuser.pw_dir)) {
377       fprintf(stderr, "That account requires OTP responses.\n");
378       exit(1);
379     };
380     /* Get user's secret password */
381     fprintf(stderr, "Reminder - Only use this method from the console; NEVER from remote. If you\n");
382     fprintf(stderr, "are using telnet, xterm, or a dial-in, type ^C now or exit with no password.\n");
383     fprintf(stderr, "Then run su without the -c parameter.\n");
384     if (opieinsecure()) {
385       fprintf(stderr, "Sorry, but you don't seem to be on the console or a secure terminal.\n");
386 #if INSECURE_OVERRIDE
387     if (force)
388       fprintf(stderr, "Warning: Continuing could disclose your secret pass phrase to an attacker!\n");
389     else
390 #endif /* INSECURE_OVERRIDE */
391       exit(1);
392     };
393 #if NEW_PROMPTS
394     printf("%s's system password: ", thisuser.pw_name);
395     if (!opiereadpass(pbuf, sizeof(pbuf), 0))
396       goto error;
397 #endif /* NEW_PROMPTS */
398   } else {
399     /* Attempt an OTP challenge */
400     i = opiechallenge(&opie, user, opieprompt);
401     printf("%s\n", opieprompt);
402 #if NEW_PROMPTS
403     printf("%s's response: ", thisuser.pw_name);
404     if (!opiereadpass(pbuf, sizeof(pbuf), 1))
405       goto error;
406 #else /* NEW_PROMPTS */
407     printf("(OTP response required)\n");
408 #endif /* NEW_PROMPTS */
409     fflush(stdout);
410   };
411 #if !NEW_PROMPTS
412   printf("%s's password: ", thisuser.pw_name);
413   if (!opiereadpass(pbuf, sizeof(pbuf), 0))
414     goto error;
415 #endif /* !NEW_PROMPTS */
416
417 #if !NEW_PROMPTS
418   if (!pbuf[0] && !console) {
419     /* Null line entered; turn echoing back on and read again */
420     printf(" (echo on)\n%s's password: ", thisuser.pw_name);
421     if (!opiereadpass(pbuf, sizeof(pbuf), 1))
422       goto error;
423   }
424 #endif /* !NEW_PROMPTS */
425
426   if (console) {
427     /* Try regular password check, if allowed */
428     if (!strcmp(crypt(pbuf, thisuser.pw_passwd), thisuser.pw_passwd))
429       goto ok;
430   } else {
431     int i = opiegetsequence(&opie);
432     if (!opieverify(&opie, pbuf)) {
433       /* OPIE authentication succeeded */
434       if (i < 5)
435         fprintf(stderr, "Warning: Change %s's OTP secret pass phrase NOW!\n", user);
436       else
437         if (i < 10)
438           fprintf(stderr, "Warning: Change %s's OTP secret pass phrase soon.\n", user);
439       goto ok;
440     };
441   };
442 error:
443   if (!console)
444     opieverify(&opie, "");
445   fprintf(stderr, "Sorry\n");
446   syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2));
447 #if HAVE_SULOG
448   sulog(0, NULL);
449 #endif /* HAVE_SULOG */
450   exit(2);
451
452 ok:
453   syslog(LOG_NOTICE, "'%s' by %s on %s", argvbuf, currentuser, ttyname(2));
454 #if HAVE_SULOG
455   sulog(1, NULL);
456 #endif /* HAVE_SULOG */
457   
458   if (setgid(thisuser.pw_gid) < 0) {
459     perror("su: setgid");
460     exit(3);
461   }
462   if (initgroups(user, thisuser.pw_gid)) {
463     fprintf(stderr, "su: initgroups failed (errno=%d)\n", errno);
464     exit(4);
465   }
466   if (setuid(thisuser.pw_uid) < 0) {
467     perror("su: setuid");
468     exit(5);
469   }
470   if (thisuser.pw_shell && *thisuser.pw_shell)
471     shell = thisuser.pw_shell;
472   if (fulllogin) {
473     if ((p = getenv("TERM")) && (strlen(termbuf) + strlen(p) - 1 < sizeof(termbuf))) {
474       strcat(termbuf, p);
475       cleanenv[4] = termbuf;
476     }
477     environ = cleanenv;
478   }
479   if (fulllogin || strcmp(user, "root") != 0)
480     lsetenv("USER", thisuser.pw_name, userbuf);
481   lsetenv("SHELL", shell, shellbuf);
482   lsetenv("HOME", thisuser.pw_dir, homebuf);
483
484 #if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
485   setpriority(PRIO_PROCESS, 0, 0);
486 #endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
487
488 #if 0
489   if (fastlogin) {
490     *argv-- = "-f";
491     *argv = "su";
492   } else
493 #endif /* 0 */
494     if (fulllogin) {
495       if (chdir(thisuser.pw_dir) < 0) {
496         fprintf(stderr, "No directory\n");
497         exit(6);
498       }
499       *argv = "-su";
500     } else {
501       *argv = "su";
502     }
503
504   catchexit();
505
506 #if DEBUG
507   syslog(LOG_DEBUG, "execing %s", shell);
508 #endif /* DEBUG */
509   execv(shell, argv);
510   fprintf(stderr, "No shell\n");
511   exit(7);
512 }