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