xref: /NextBSD/contrib/opie/opiesu.c (revision eb1a5f8de9f7ea602c373a710f531abbf81141c4)
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 }
513