/* * su - become-super user or another user * * Gunnar Ritter, Freiburg i. Br., Germany, May 2001. */ /* * Copyright (c) 2003 Gunnar Ritter * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute * it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. */ #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4 #define USED __attribute__ ((used)) #elif defined __GNUC__ #define USED __attribute__ ((unused)) #else #define USED #endif static const char sccsid[] USED = "@(#)su.sl 1.25 (gritter) 2/21/06"; #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #elif PAM #include #else /* !PAM */ #ifdef SHADOW_PWD #include #endif /* SHADOW_PWD */ #define PAM_MAX_RESP_SIZE 512 #endif /* !PAM */ enum logtype { LT_NONE, LT_FAIL, LT_ALL }; extern char **environ; static int dash; /* create login environment */ static int dofork; /* fork() before executing shell */ static enum logtype dosyslog = LT_NONE; /* log to syslog */ static int sleeptime = 4; /* sleep time if failed */ static char *user; /* desired login name */ static char *olduser; /* old login name */ static char *progname; /* argv[0] to main() */ static char *path; /* new $PATH */ static char *dir; /* new home directory */ static char *shell; /* new shell */ static char *sulog; /* logging file */ static char *console; /* console to log to (optional) */ static struct passwd *pwd; /* passwd entry for new user */ static void (*oldint)(int); /* old SIGINT handler */ static void (*oldquit)(int); /* old SIGQUIT handler */ static const char defaultfile[] = SUDFL; static const char PATH[] = "PATH=/usr/local/bin:/bin:/usr/bin:"; static const char SUPATH[] = "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:/bin:/usr/bin"; /* * Memory allocation with check. */ static void * smalloc(size_t nbytes) { void *p; if ((p = malloc(nbytes)) == NULL) { write(2, "Out of memory\n", 14); exit(077); } return p; } /* * Adjust environment. */ static void doenv(void) { if (dash) { char *cp; char *term, *oldterm; char *display, *olddisplay; if ((oldterm = getenv("TERM")) != NULL) { term = smalloc(strlen(oldterm) + 6); sprintf(term, "TERM=%s", oldterm); } else term = NULL; if ((olddisplay = getenv("DISPLAY")) != NULL) { display = smalloc(strlen(olddisplay) + 9); sprintf(display, "DISPLAY=%s", olddisplay); } else display = NULL; environ = NULL; cp = smalloc(strlen(dir) + 6); sprintf(cp, "HOME=%s", dir); putenv(cp); cp = smalloc(strlen(user) + 9); sprintf(cp, "LOGNAME=%s", user); putenv(cp); cp = smalloc(strlen(shell) + 7); sprintf(cp, "SHELL=%s", shell); putenv(cp); if (term) putenv(term); if (display) putenv(display); } putenv(path); if (pwd->pw_uid == 0) putenv("PS1=# "); } /* * Things to do in child process. */ static void child(char **args) { if (initgroups(user, pwd->pw_gid) < 0 || setgid(pwd->pw_gid) < 0 || setuid(pwd->pw_uid) < 0) { fprintf(stderr, "%s: Invalid ID\n", progname); dofork ? _exit(1) : exit(1); } if (dash) { args[0] = smalloc(strlen(basename(shell)) + 2); args[0][0] = '-'; strcpy(&args[0][1], basename(shell)); if (dir == NULL || chdir(dir) < 0) { fprintf(stderr, "%s: No directory! Using home=/\n", progname); dir = "/"; chdir(dir); } } else args[0] = basename(shell); doenv(); signal(SIGINT, oldint); signal(SIGQUIT, oldquit); execv(shell, args); fprintf(stderr, "%s: No shell\n", progname); dofork ? _exit(3) : exit(3); } /* * Spawn the child process and wait for it. */ static int dospawn(char **args) { int status; pid_t pid; switch (pid = fork()) { case 0: child(args); /*NOTREACHED*/ case -1: fprintf(stderr, "%s: fork() failed, try again later\n", progname); return 1; } while (wait(&status) != pid); return status; } /* * Write log entries. */ static void dolog(int sign) { FILE *fp; char *tty; const char format[] = "SU %02u/%02u %02u:%02u %c %s %s-%s\n"; struct tm *tm; time_t t; time(&t); tm = localtime(&t); if ((tty = ttyname(0)) == NULL) tty = "/dev/???"; if (dosyslog == LT_ALL || (dosyslog == LT_FAIL && sign == '-')) { openlog("su", LOG_PID, LOG_AUTH); if (sign == '-') syslog(LOG_CRIT | LOG_AUTH, "'su %s' failed for %s on %s", user, olduser, tty); else if (dosyslog != LT_FAIL) syslog((pwd->pw_uid ? LOG_INFO : LOG_NOTICE) | LOG_AUTH, "'su %s' succeeded for %s on %s", user, olduser, tty); closelog(); } if (strncmp(tty, "/dev/", 5) == 0) tty += 5; if (sulog && (fp = fopen(sulog, "a+")) != NULL) { fprintf(fp, format, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, sign, tty, olduser, user); fclose(fp); } if (console && (fp = fopen(console, "a+")) != NULL) { fprintf(fp, format, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, sign, tty, olduser, user); fclose(fp); } } /* * Parse defaults file. */ static void defaults(void) { FILE *fp; if ((fp = fopen(defaultfile, "r")) != NULL) { char buf[LINE_MAX]; char *cp; while (fgets(buf, sizeof buf, fp) != NULL) { if (buf[0] == '\0' || buf[0] == '\n' || buf[0] == '#') continue; if ((cp = strchr(buf, '\n')) != NULL) *cp = '\0'; if (strncmp(buf, pwd->pw_uid ? "PATH=" : "SUPATH=", pwd->pw_uid ? 5 : 7) == 0) { path = smalloc(strlen(buf) + 1); strcpy(path, pwd->pw_uid ? buf : &buf[2]); } else if (strncmp(buf, "SULOG=", 6) == 0) { sulog = smalloc(strlen(buf) - 5); strcpy(sulog, &buf[6]); } else if (strncmp(buf, "CONSOLE=", 8) == 0) { console = smalloc(strlen(buf) - 7); strcpy(console, &buf[8]); } else if (strncmp(buf, "SLEEPTIME=", 10) == 0) { sleeptime = atoi(&buf[10]); if (sleeptime < 0 || sleeptime > 5) sleeptime = 4; } else if (strncmp(buf, "SYSLOG=", 7) == 0) { if (strcasecmp(&buf[7], "yes") == 0) dosyslog = LT_ALL; else if (strcasecmp(&buf[7], "fail") == 0) dosyslog = LT_FAIL; } else if (strncmp(buf, "DOFORK=", 7) == 0) { if (strcasecmp(&buf[7], "yes") == 0) dofork = 1; } } fclose(fp); } if (path == NULL) path = (char *)(pwd->pw_uid ? PATH : SUPATH); } /* * Ask for passwords. */ static char * ask(const char *msg, int echo) { struct termios tio; char *rsp; tcflag_t lflag; int fd, i = 0; char c; size_t sz; if ((fd = open("/dev/tty", O_RDWR)) < 0) fd = 0; if (tcgetattr(fd, &tio) < 0) { if (fd) close(fd); return NULL; } lflag = tio.c_lflag; if (echo == 0) { tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); if (tcsetattr(fd, TCSAFLUSH, &tio) < 0) { if (fd) close(fd); return NULL; } } sz = strlen(msg); if (sz > 2 && msg[sz-1] == ' ' && msg[sz-2] == ':') sz--; fwrite(msg, sizeof *msg, sz, stderr); rsp = smalloc(PAM_MAX_RESP_SIZE + 1); while (read(fd, &c, 1) == 1 && c != '\n') if (i < PAM_MAX_RESP_SIZE) rsp[i++] = c; rsp[i] = '\0'; fputc('\n', stderr); if (echo == 0) { tio.c_lflag = lflag; tcsetattr(fd, TCSADRAIN, &tio); } if (fd) close(fd); return rsp; } #ifdef PAM /* * PAM conversation. */ static int doconv(int num_msg, const struct pam_message **msg, struct pam_response **rsp, void *appdata) { char *cp; int i; if (num_msg <= 0) return PAM_CONV_ERR; for (i = 0; i < num_msg; i++) { switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: if ((cp = ask(msg[i]->msg, msg[i]->msg_style == PAM_PROMPT_ECHO_ON)) == NULL) return PAM_CONV_ERR; rsp[i] = smalloc(sizeof *rsp[i]); rsp[i]->resp = cp; rsp[i]->resp_retcode = 0; break; case PAM_ERROR_MSG: fprintf(stderr, "%s\n", msg[i]->msg); break; case PAM_TEXT_INFO: printf("%s\n", msg[i]->msg); break; } } return PAM_SUCCESS; } static struct pam_conv conv = { doconv, NULL }; #else /* !PAM */ /* * Check if the passed string is possibly a valid password, either a * traditional or a MD5 one. */ #ifdef SHADOW_PWD static int cantbevalid(const char *ncrypt) { if (ncrypt == NULL || strlen(ncrypt) < 13) return 1; while (*ncrypt) if (strchr( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./$", *ncrypt) == NULL) return 1; return 0; } #endif /* SHADOW_PWD */ /* * Traditional password file authentication. */ static int authenticate(const char *name, const char *ncrypt) { char *password; int val; #ifdef SHADOW_PWD if (cantbevalid(ncrypt)) { struct spwd *sp; time_t now; if ((sp = getspnam(name)) == NULL) return 0; time(&now); if (sp->sp_expire > 0 && now >= sp->sp_expire) return 0; if (sp->sp_lstchg > 0 && sp->sp_max >= 0 && sp->sp_inact >= 0 && now >= sp->sp_lstchg + sp->sp_max + sp->sp_inact) return 0; ncrypt = sp->sp_pwdp; } #endif /* SHADOW_PWD */ if (*ncrypt == '\0') return 1; if ((password = ask("Password:", 0)) == NULL) return 0; val = strcmp(ncrypt, crypt(password, ncrypt)) == 0; while (*password) *password++ = '\0'; return val; } #endif /* !PAM */ int main(int argc, char **argv) { #ifdef PAM pam_handle_t *pamh = NULL; int ret; #endif /* PAM */ int status = 0; uid_t myuid; progname = basename(argv[0]); oldint = signal(SIGINT, SIG_IGN); oldquit = signal(SIGQUIT, SIG_IGN); if (argc > 1 && argv[1][0] == '-') { dash++; argc--, argv++; } if (argc > 1) { user = argv[1]; argc--, argv++; } else user = "root"; myuid = getuid(); if ((pwd = getpwuid(myuid)) == NULL) { fprintf(stderr, "%s: you do not exist\n", progname); exit(1); } olduser = smalloc(strlen(pwd->pw_name) + 1); strcpy(olduser, pwd->pw_name); if ((pwd = getpwnam(user)) == NULL) { fprintf(stderr, "%s: Unknown id: %s\n", progname, user); exit(1); } if (pwd->pw_dir != NULL && *pwd->pw_dir != '\0') { dir = smalloc(strlen(pwd->pw_dir) + 1); strcpy(dir, pwd->pw_dir); } if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { shell = smalloc(strlen(pwd->pw_shell) + 1); strcpy(shell, pwd->pw_shell); } else shell = "/bin/sh"; defaults(); #ifdef PAM ret = pam_start("su", user, &conv, &pamh); if (ret == PAM_SUCCESS) ret = pam_authenticate(pamh, 0); if (ret == PAM_SUCCESS) ret = pam_acct_mgmt(pamh, 0); if (ret == PAM_SUCCESS) { #else /* PAM */ if (myuid == 0 || authenticate(user, pwd->pw_passwd) != 0) { #endif /* !PAM */ dolog('+'); if (dofork) { status = dospawn(argv); } else { #ifdef PAM pam_end(pamh, ret); #endif /* PAM */ child(argv); /*NOTREACHED*/ } } else { dolog('-'); if (sleeptime) sleep(sleeptime); fprintf(stderr, "%s: Sorry\n", progname); status = 1; } #ifdef PAM pam_end(pamh, ret); #endif /* PAM */ return status; }