static void remove_mailbox(void) { const char *maildir; char mailfile[1024]; int i; maildir = getdef_str("MAIL_DIR"); #ifdef MAIL_SPOOL_DIR if (!maildir && !getdef_str("MAIL_FILE")) maildir = MAIL_SPOOL_DIR; #endif if (!maildir) return; snprintf(mailfile, sizeof mailfile, "%s/%s", maildir, user_name); if (fflg) { unlink(mailfile); /* always remove, ignore errors */ return; } i = is_owner(user_id, mailfile); if (i == 0) { fprintf(stderr, _("%s: warning: %s not owned by %s, not removing\n"), Prog, mailfile, user_name); return; } else if (i == -1) return; /* mailbox doesn't exist */ if (unlink(mailfile)) { fprintf(stderr, _("%s: warning: can't remove "), Prog); perror(mailfile); } }
static int may_change_field (int field) { const char *cp; /* * CHFN_RESTRICT can now specify exactly which fields may be changed * by regular users, by using any combination of the following * letters: * f - full name * r - room number * w - work phone * h - home phone * * This makes it possible to disallow changing the room number * information, for example - this feature was suggested by Maciej * 'Tycoon' Majchrowski. * * For backward compatibility, "yes" is equivalent to "rwh", * "no" is equivalent to "frwh". Only root can change anything * if the string is empty or not defined at all. */ if (amroot) return 1; cp = getdef_str ("CHFN_RESTRICT"); if (!cp) cp = ""; else if (strcmp (cp, "yes") == 0) cp = "rwh"; else if (strcmp (cp, "no") == 0) cp = "frwh"; if (strchr (cp, field)) return 1; return 0; }
static void user_cancel(const char *user) { char *cmd; int pid, wpid; int status; if (!(cmd = getdef_str("USERDEL_CMD"))) return; pid = fork(); if (pid == 0) { execl(cmd, cmd, user, (char *) 0); if (errno == ENOENT) { perror(cmd); _exit(127); } else { perror(cmd); _exit(126); } } else if (pid == -1) { perror("fork"); return; } do { wpid = wait(&status); } while (wpid != pid && wpid != -1); }
int main (int argc, char **argv) { int i; char *cp; struct itemdef *d; def_load (); for (i = 0; i < NUMDEFS; ++i) { d = def_find (def_table[i].name); if (NULL == d) { printf ("error - lookup '%s' failed\n", def_table[i].name); } else { printf ("%4d %-24s %s\n", i + 1, d->name, d->value); } } for (i = 1; i < argc; i++) { cp = getdef_str (argv[1]); if (NULL != cp) { printf ("%s `%s'\n", argv[1], cp); } else { printf ("%s not found\n", argv[1]); } } exit (EXIT_SUCCESS); }
/* * sulog - log a SU command execution result */ void sulog (const char *tty, bool success, const char *oldname, const char *name) { const char *sulog_file; time_t now; struct tm *tm; FILE *fp; mode_t oldmask; gid_t oldgid = 0; if (success) { SYSLOG ((LOG_INFO, "Successful su for %s by %s",name,oldname)); } else { SYSLOG ((LOG_NOTICE, "FAILED su for %s by %s",name,oldname)); } sulog_file = getdef_str ("SULOG_FILE"); if (NULL == sulog_file) { return; } oldgid = getgid (); oldmask = umask (077); /* Switch to group root to avoid creating the sulog file with * the wrong group ownership. */ if ((oldgid != 0) && (setgid (0) != 0)) { SYSLOG ((LOG_INFO, "su session not logged to %s", sulog_file)); /* Continue, but do not switch back to oldgid later */ oldgid = 0; } fp = fopen (sulog_file, "a+"); (void) umask (oldmask); if ((oldgid != 0) && (setgid (oldgid) != 0)) { perror ("setgid"); SYSLOG ((LOG_ERR, "can't switch back to group `%d' in sulog", oldgid)); /* Do not return if the group permission were raised. */ exit (EXIT_FAILURE); } if (fp == (FILE *) 0) { return; /* can't open or create logfile */ } (void) time (&now); tm = localtime (&now); fprintf (fp, "SU %.02d/%.02d %.02d:%.02d %c %s %s-%s\n", tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, success ? '+' : '-', tty, oldname, name); (void) fflush (fp); fsync (fileno (fp)); fclose (fp); /* TODO: log if failure */ }
static void init_env (void) { #ifndef USE_PAM char *cp; #endif char *tmp; tmp = getenv ("LANG"); if (NULL != tmp) { addenv ("LANG", tmp); } /* * Add the timezone environmental variable so that time functions * work correctly. */ tmp = getenv ("TZ"); if (NULL != tmp) { addenv ("TZ", tmp); } #ifndef USE_PAM else { cp = getdef_str ("ENV_TZ"); if (NULL != cp) { addenv (('/' == *cp) ? tz (cp) : cp, NULL); } } #endif /* !USE_PAM */ /* * Add the clock frequency so that profiling commands work * correctly. */ tmp = getenv ("HZ"); if (NULL != tmp) { addenv ("HZ", tmp); } #ifndef USE_PAM else { cp = getdef_str ("ENV_HZ"); if (NULL != cp) { addenv (cp, NULL); } } #endif /* !USE_PAM */ }
/* * This is now rather generic function which decides if "tty" is listed * under "cfgin" in config (directly or indirectly). Fallback to default if * something is bad. */ int is_listed(const char *cfgin, const char *tty, int def) { FILE *fp; char buf[200], *cons, *s; /* * If the CONSOLE configuration definition isn't given, * fallback to default. */ if ((cons = getdef_str(cfgin)) == NULL) return def; /* * If this isn't a filename, then it is a ":" delimited list of * console devices upon which root logins are allowed. */ if (*cons != '/') { cons = strcpy(buf, cons); while ((s = strtok(cons, ":")) != NULL) { if (strcmp(s, tty) == 0) return 1; cons = NULL; } return 0; } /* * If we can't open the console list, then call everything a * console - otherwise root will never be allowed to login. */ if ((fp = fopen(cons, "r")) == NULL) return def; /* * See if this tty is listed in the console file. */ while (fgets(buf, sizeof(buf), fp) != NULL) { buf[strlen(buf) - 1] = '\0'; if (strcmp(buf, tty) == 0) { (void) fclose(fp); return 1; } } /* * This tty isn't a console. */ (void) fclose(fp); return 0; }
int initdir(char *dirp, uid_t uid, gid_t gid) { char buff[PATH_MAX + 1]; char *p; strncpy(buff, dirp, PATH_MAX); buff[PATH_MAX] = '\0'; if (buff[strlen(buff) - 1] != '/') strncat(buff, "/", (PATH_MAX - strlen(buff) - 1)); if ((p=getdef_str("PROFILE")) == 0) { return 0; } strncat(buff, p, (PATH_MAX - strlen(buff) - 1)); if ((p = getdef_str("DEFPROFILE")) == 0) { return 0; } /* file does not exist */ if (access(p, R_OK) == 0 && access(buff, F_OK) == -1) { FILE *f; FILE *g; if ((f=fopen(p,"r")) == 0) { fprintf(stderr,"warning cannot read shell init file (%s):%s\n", p, strerror(errno)); } else if ((g=fopen(buff,"w")) == 0) { fprintf(stderr,"warning cannot create shell init file (%s):%s\n", buff, strerror(errno)); fclose(f); } else { int c; while ((c=getc(f)) != EOF) putc(c,g); fclose(f); fclose(g); if (chown(buff, uid, gid) == -1) { fprintf(stderr, "warning: cannot change owner of (%s):%s\n", buff, strerror(errno)); } else if (chmod(buff, 0644) == -1) { fprintf(stderr, "warning: cannot change file permission of (%s):%s\n", buff, strerror(errno)); } } } return 0; }
/* * change to the user's home directory * set the HOME, SHELL, MAIL, PATH, and LOGNAME or USER environmental * variables. */ void setup_env(struct passwd *info) { char *cp; /* * Change the current working directory to be the home directory * of the user. It is a fatal error for this process to be unable * to change to that directory. There is no "default" home * directory. * * We no longer do it as root - should work better on NFS-mounted * home directories. */ if (chdir(info->pw_dir) == -1) { static char temp_pw_dir[] = "/"; if (!getdef_bool("DEFAULT_HOME") || chdir("/") == -1) { fprintf(stderr, _("Unable to cd to \"%s\"\n"), info->pw_dir); syslog(LOG_WARNING, "unable to cd to `%s' for user `%s'\n", info->pw_dir, info->pw_name); closelog(); exit (1); } puts(_("No directory, logging in with HOME=/")); info->pw_dir = temp_pw_dir; } /* * Create the HOME environmental variable and export it. */ addenv("HOME", info->pw_dir); /* * Create the SHELL environmental variable and export it. */ if (info->pw_shell == (char *) 0 || ! *info->pw_shell) { static char temp_pw_shell[] = "/bin/sh"; info->pw_shell = temp_pw_shell; } addenv("SHELL", info->pw_shell); /* * Create the PATH environmental variable and export it. */ cp = getdef_str("ENV_PATH"); addenv(cp ? cp : "PATH=/bin:/usr/bin", NULL); /* * Export the user name. For BSD derived systems, it's "USER", for * all others it's "LOGNAME". We set both of them. */ addenv("USER", info->pw_name); addenv("LOGNAME", info->pw_name); }
/* * Generate 8 base64 ASCII characters of random salt. If MD5_CRYPT_ENAB * in /etc/login.defs is "yes", the salt string will be prefixed by "$1$" * (magic) and pw_encrypt() will execute the MD5-based FreeBSD-compatible * version of crypt() instead of the standard one. * Other methods can be set with ENCRYPT_METHOD * * The method can be forced with the meth parameter. * If NULL, the method will be defined according to the MD5_CRYPT_ENAB and * ENCRYPT_METHOD login.defs variables. * * If meth is specified, an additional parameter can be provided. * * For the SHA256 and SHA512 method, this specifies the number of rounds * (if not NULL). */ /*@observer@*/const char *crypt_make_salt (/*@null@*/const char *meth, /*@null@*/void *arg) { /* Max result size for the SHA methods: * +3 $5$ * +17 rounds=999999999$ * +16 salt * +1 \0 */ static char result[40]; size_t salt_len = 8; const char *method; result[0] = '\0'; if (NULL != meth) method = meth; else { method = getdef_str ("ENCRYPT_METHOD"); if (NULL == method) { method = getdef_bool ("MD5_CRYPT_ENAB") ? "MD5" : "DES"; } } if (0 == strcmp (method, "MD5")) { MAGNUM(result, '1'); #ifdef USE_SHA_CRYPT } else if (0 == strcmp (method, "SHA256")) { MAGNUM(result, '5'); strcat(result, SHA_salt_rounds((int *)arg)); salt_len = SHA_salt_size(); } else if (0 == strcmp (method, "SHA512")) { MAGNUM(result, '6'); strcat(result, SHA_salt_rounds((int *)arg)); salt_len = SHA_salt_size(); #endif /* USE_SHA_CRYPT */ } else if (0 != strcmp (method, "DES")) { fprintf (stderr, _("Invalid ENCRYPT_METHOD value: '%s'.\n" "Defaulting to DES.\n"), method); result[0] = '\0'; } /* * Concatenate a pseudo random salt. */ assert (sizeof (result) > strlen (result) + salt_len); strncat (result, gensalt (salt_len), sizeof (result) - strlen (result) - 1); return result; }
/* * hushed - determine if a user receives login messages * * Look in the hushed-logins file (or user's home directory) to see * if the user is to receive the login-time messages. */ bool hushed (const char *username) { struct passwd *pw; const char *hushfile; char buf[BUFSIZ]; bool found; FILE *fp; /* * Get the name of the file to use. If this option is not * defined, default to a noisy login. */ hushfile = getdef_str ("HUSHLOGIN_FILE"); if (NULL == hushfile) { return false; } pw = getpwnam (username); if (NULL == pw) { return false; } /* * If this is not a fully rooted path then see if the * file exists in the user's home directory. */ if (hushfile[0] != '/') { (void) snprintf (buf, sizeof (buf), "%s/%s", pw->pw_dir, hushfile); return (access (buf, F_OK) == 0); } /* * If this is a fully rooted path then go through the file * and see if this user, or its shell is in there. */ fp = fopen (hushfile, "r"); if (NULL == fp) { return false; } for (found = false; !found && (fgets (buf, (int) sizeof buf, fp) == buf);) { buf[strlen (buf) - 1] = '\0'; found = (strcmp (buf, pw->pw_shell) == 0) || (strcmp (buf, pw->pw_name) == 0); } (void) fclose (fp); return found; }
static void user_cancel (const char *user) { const char *cmd; const char *argv[3]; int status; cmd = getdef_str ("USERDEL_CMD"); if (NULL == cmd) { return; } argv[0] = cmd; argv[1] = user; argv[2] = (char *)0; (void) run_command (cmd, argv, NULL, &status); }
void chown_tty (const struct passwd *info) { struct group *grent; gid_t gid; /* * See if login.defs has some value configured for the port group * ID. Otherwise, use the user's primary group ID. */ grent = getgr_nam_gid (getdef_str ("TTYGROUP")); if (NULL != grent) { gid = grent->gr_gid; } else { gid = info->pw_gid; } /* * Change the permissions on the TTY to be owned by the user with * the group as determined above. */ if ( (fchown (STDIN_FILENO, info->pw_uid, gid) != 0) || (fchmod (STDIN_FILENO, getdef_num ("TTYPERM", 0600)) != 0)) { int err = errno; fprintf (stderr, _("Unable to change owner or mode of tty stdin: %s"), strerror (err)); SYSLOG ((LOG_WARN, "unable to change owner or mode of tty stdin for user `%s': %s\n", info->pw_name, strerror (err))); if (EROFS != err) { closelog (); exit (EXIT_FAILURE); } } #ifdef __linux__ /* * Please don't add code to chown /dev/vcs* to the user logging in - * it's a potential security hole. I wouldn't like the previous user * to hold the file descriptor open and watch my screen. We don't * have the *BSD revoke() system call yet, and vhangup() only works * for tty devices (which vcs* is not). --marekm */ #endif }
static void check_nologin (bool login_to_root) { char *fname; /* * Check to see if system is turned off for non-root users. * This would be useful to prevent users from logging in * during system maintenance. We make sure the message comes * out for root so she knows to remove the file if she's * forgotten about it ... */ fname = getdef_str ("NOLOGINS_FILE"); if ((NULL != fname) && (access (fname, F_OK) == 0)) { FILE *nlfp; int c; /* * Cat the file if it can be opened, otherwise just * print a default message */ nlfp = fopen (fname, "r"); if (NULL != nlfp) { while ((c = getc (nlfp)) != EOF) { if (c == '\n') { (void) putchar ('\r'); } (void) putchar (c); } (void) fflush (stdout); (void) fclose (nlfp); } else { (void) puts (_("\nSystem closed for routine maintenance")); } /* * Non-root users must exit. Root gets the message, but * gets to login. */ if (!login_to_root) { closelog (); exit (0); } (void) puts (_("\n[Disconnect bypassed -- root login allowed.]")); } }
int hushed(const struct passwd *pw) { char *hushfile; char buf[BUFSIZ]; int found; FILE *fp; /* * Get the name of the file to use. If this option is not * defined, default to a noisy login. */ if ( (hushfile=getdef_str("HUSHLOGIN_FILE")) == NULL ) return 0; /* * If this is not a fully rooted path then see if the * file exists in the user's home directory. */ if (hushfile[0] != '/') { snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, hushfile); return (access(buf, F_OK) == 0); } /* * If this is a fully rooted path then go through the file * and see if this user is in there. */ if ((fp = fopen(hushfile, "r")) == NULL) return 0; for (found = 0;! found && fgets (buf, sizeof buf, fp);) { buf[strlen (buf) - 1] = '\0'; found = ! strcmp (buf, buf[0] == '/' ? pw->pw_shell:pw->pw_name); } (void) fclose(fp); return found; }
int main(int argc, char **argv) { int i; char *cp; struct itemdef *d; def_load (); for (i = 0 ; i < NUMDEFS ; ++i) { if ((d = def_find(def_table[i].name)) == NULL) printf("error - lookup '%s' failed\n", def_table[i].name); else printf("%4d %-24s %s\n", i+1, d->name, d->value); } for (i = 1;i < argc;i++) { if (cp = getdef_str (argv[1])) printf ("%s `%s'\n", argv[1], cp); else printf ("%s not found\n", argv[1]); } exit(0); }
static void user_cancel (const char *user) { char *cmd; pid_t pid, wpid; int status; cmd = getdef_str ("USERDEL_CMD"); if (NULL == cmd) { return; } pid = fork (); if (pid == 0) { execl (cmd, cmd, user, (char *) 0); perror (cmd); exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC); } else if ((pid_t)-1 == pid) { perror ("fork"); return; } do { wpid = wait (&status); } while ((wpid != pid) && ((pid_t)-1 != wpid)); }
/* * ttytype - set ttytype from port to terminal type mapping database */ void ttytype (const char *line) { FILE *fp; char buf[BUFSIZ]; char *typefile; char *cp; char type[BUFSIZ]; char port[BUFSIZ]; if (getenv ("TERM")) return; if ((typefile = getdef_str ("TTYTYPE_FILE")) == NULL) return; if (access (typefile, F_OK)) return; if (!(fp = fopen (typefile, "r"))) { perror (typefile); return; } while (fgets (buf, sizeof buf, fp)) { if (buf[0] == '#') continue; if ((cp = strchr (buf, '\n'))) *cp = '\0'; if (sscanf (buf, "%s %s", type, port) == 2 && strcmp (line, port) == 0) break; } if (!feof (fp) && !ferror (fp)) addenv ("TERM", type); fclose (fp); }
/* * login - create a new login session for a user * * login is typically called by getty as the second step of a * new user session. getty is responsible for setting the line * characteristics to a reasonable set of values and getting * the name of the user to be logged in. login may also be * called to create a new user session on a pty for a variety * of reasons, such as X servers or network logins. * * the flags which login supports are * * -p - preserve the environment * -r - perform autologin protocol for rlogin * -f - do not perform authentication, user is preauthenticated * -h - the name of the remote host */ int main (int argc, char **argv) { const char *tmptty; char tty[BUFSIZ]; #ifdef RLOGIN char term[128] = ""; #endif /* RLOGIN */ #if defined(HAVE_STRFTIME) && !defined(USE_PAM) char ptime[80]; #endif unsigned int delay; unsigned int retries; bool failed; bool subroot = false; #ifndef USE_PAM bool is_console; #endif int err; const char *cp; char *tmp; char fromhost[512]; struct passwd *pwd = NULL; char **envp = environ; const char *failent_user; /*@null@*/struct utmp *utent; #ifdef USE_PAM int retcode; pid_t child; char *pam_user = NULL; #else struct spwd *spwd = NULL; #endif /* * Some quick initialization. */ sanitize_env (); (void) setlocale (LC_ALL, ""); (void) bindtextdomain (PACKAGE, LOCALEDIR); (void) textdomain (PACKAGE); initenv (); amroot = (getuid () == 0); Prog = Basename (argv[0]); if (geteuid() != 0) { fprintf (stderr, _("%s: Cannot possibly work without effective root\n"), Prog); exit (1); } process_flags (argc, argv); if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) { exit (1); /* must be a terminal */ } utent = get_current_utmp (); /* * Be picky if run by normal users (possible if installed setuid * root), but not if run by root. This way it still allows logins * even if your getty is broken, or if something corrupts utmp, * but users must "exec login" which will use the existing utmp * entry (will not overwrite remote hostname). --marekm */ if (!amroot && (NULL == utent)) { (void) puts (_("No utmp entry. You must exec \"login\" from the lowest level \"sh\"")); exit (1); } /* NOTE: utent might be NULL afterwards */ tmptty = ttyname (0); if (NULL == tmptty) { tmptty = "UNKNOWN"; } STRFCPY (tty, tmptty); #ifndef USE_PAM is_console = console (tty); #endif if (rflg || hflg) { /* * Add remote hostname to the environment. I think * (not sure) I saw it once on Irix. --marekm */ addenv ("REMOTEHOST", hostname); } if (fflg) { preauth_flag = true; } if (hflg) { reason = PW_RLOGIN; } #ifdef RLOGIN if (rflg) { assert (NULL == username); username = xmalloc (USER_NAME_MAX_LENGTH + 1); username[USER_NAME_MAX_LENGTH] = '\0'; if (do_rlogin (hostname, username, USER_NAME_MAX_LENGTH, term, sizeof term)) { preauth_flag = true; } else { free (username); username = NULL; } } #endif /* RLOGIN */ OPENLOG ("login"); setup_tty (); #ifndef USE_PAM (void) umask (getdef_num ("UMASK", GETDEF_DEFAULT_UMASK)); { /* * Use the ULIMIT in the login.defs file, and if * there isn't one, use the default value. The * user may have one for themselves, but otherwise, * just take what you get. */ long limit = getdef_long ("ULIMIT", -1L); if (limit != -1) { set_filesize_limit (limit); } } #endif /* * The entire environment will be preserved if the -p flag * is used. */ if (pflg) { while (NULL != *envp) { /* add inherited environment, */ addenv (*envp, NULL); /* some variables change later */ envp++; } } #ifdef RLOGIN if (term[0] != '\0') { addenv ("TERM", term); } else #endif /* RLOGIN */ { /* preserve TERM from getty */ if (!pflg) { tmp = getenv ("TERM"); if (NULL != tmp) { addenv ("TERM", tmp); } } } init_env (); if (optind < argc) { /* now set command line variables */ set_env (argc - optind, &argv[optind]); } if (rflg || hflg) { cp = hostname; #ifdef HAVE_STRUCT_UTMP_UT_HOST } else if ((NULL != utent) && ('\0' != utent->ut_host[0])) { cp = utent->ut_host; #endif /* HAVE_STRUCT_UTMP_UT_HOST */ } else { cp = ""; } if ('\0' != *cp) { snprintf (fromhost, sizeof fromhost, " on '%.100s' from '%.200s'", tty, cp); } else { snprintf (fromhost, sizeof fromhost, " on '%.100s'", tty); } top: /* only allow ALARM sec. for login */ (void) signal (SIGALRM, alarm_handler); timeout = getdef_unum ("LOGIN_TIMEOUT", ALARM); if (timeout > 0) { (void) alarm (timeout); } environ = newenvp; /* make new environment active */ delay = getdef_unum ("FAIL_DELAY", 1); retries = getdef_unum ("LOGIN_RETRIES", RETRIES); #ifdef USE_PAM retcode = pam_start ("login", username, &conv, &pamh); if (retcode != PAM_SUCCESS) { fprintf (stderr, _("login: PAM Failure, aborting: %s\n"), pam_strerror (pamh, retcode)); SYSLOG ((LOG_ERR, "Couldn't initialize PAM: %s", pam_strerror (pamh, retcode))); exit (99); } /* * hostname & tty are either set to NULL or their correct values, * depending on how much we know. We also set PAM's fail delay to * ours. * * PAM_RHOST and PAM_TTY are used for authentication, only use * information coming from login or from the caller (e.g. no utmp) */ retcode = pam_set_item (pamh, PAM_RHOST, hostname); PAM_FAIL_CHECK; retcode = pam_set_item (pamh, PAM_TTY, tty); PAM_FAIL_CHECK; #ifdef HAS_PAM_FAIL_DELAY retcode = pam_fail_delay (pamh, 1000000 * delay); PAM_FAIL_CHECK; #endif /* if fflg, then the user has already been authenticated */ if (!fflg) { unsigned int failcount = 0; char hostn[256]; char loginprompt[256]; /* That's one hell of a prompt :) */ /* Make the login prompt look like we want it */ if (gethostname (hostn, sizeof (hostn)) == 0) { snprintf (loginprompt, sizeof (loginprompt), _("%s login: "******"login: "******"TOO MANY LOGIN TRIES (%u)%s FOR '%s'", failcount, fromhost, failent_user)); fprintf(stderr, _("Maximum number of tries exceeded (%u)\n"), failcount); PAM_END; exit(0); } else if (retcode == PAM_ABORT) { /* Serious problems, quit now */ (void) fputs (_("login: abort requested by PAM\n"), stderr); SYSLOG ((LOG_ERR,"PAM_ABORT returned from pam_authenticate()")); PAM_END; exit(99); } else if (retcode != PAM_SUCCESS) { SYSLOG ((LOG_NOTICE,"FAILED LOGIN (%u)%s FOR '%s', %s", failcount, fromhost, failent_user, pam_strerror (pamh, retcode))); failed = true; } if (!failed) { break; } #ifdef WITH_AUDIT audit_fd = audit_open (); audit_log_acct_message (audit_fd, AUDIT_USER_LOGIN, NULL, /* Prog. name */ "login", failent_user, AUDIT_NO_ID, hostname, NULL, /* addr */ tty, 0); /* result */ close (audit_fd); #endif /* WITH_AUDIT */ (void) puts (""); (void) puts (_("Login incorrect")); if (failcount >= retries) { SYSLOG ((LOG_NOTICE, "TOO MANY LOGIN TRIES (%u)%s FOR '%s'", failcount, fromhost, failent_user)); fprintf(stderr, _("Maximum number of tries exceeded (%u)\n"), failcount); PAM_END; exit(0); } /* * Let's give it another go around. * Even if a username was given on the command * line, prompt again for the username. */ retcode = pam_set_item (pamh, PAM_USER, NULL); PAM_FAIL_CHECK; } /* We don't get here unless they were authenticated above */ (void) alarm (0); } /* Check the account validity */ retcode = pam_acct_mgmt (pamh, 0); if (retcode == PAM_NEW_AUTHTOK_REQD) { retcode = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); } PAM_FAIL_CHECK; /* Open the PAM session */ get_pam_user (&pam_user); retcode = pam_open_session (pamh, hushed (pam_user) ? PAM_SILENT : 0); PAM_FAIL_CHECK; /* Grab the user information out of the password file for future usage * First get the username that we are actually using, though. * * From now on, we will discard changes of the user (PAM_USER) by * PAM APIs. */ get_pam_user (&pam_user); if (NULL != username) { free (username); } username = pam_user; failent_user = get_failent_user (username); pwd = xgetpwnam (username); if (NULL == pwd) { SYSLOG ((LOG_ERR, "cannot find user %s", failent_user)); exit (1); } /* This set up the process credential (group) and initialize the * supplementary group access list. * This has to be done before pam_setcred */ if (setup_groups (pwd) != 0) { exit (1); } retcode = pam_setcred (pamh, PAM_ESTABLISH_CRED); PAM_FAIL_CHECK; /* NOTE: If pam_setcred changes PAM_USER, this will not be taken * into account. */ #else /* ! USE_PAM */ while (true) { /* repeatedly get login/password pairs */ /* user_passwd is always a pointer to this constant string * or a passwd or shadow password that will be memzero by * pw_free / spw_free. * Do not free() user_passwd. */ const char *user_passwd = "!"; /* Do some cleanup to avoid keeping entries we do not need * anymore. */ if (NULL != pwd) { pw_free (pwd); pwd = NULL; } if (NULL != spwd) { spw_free (spwd); spwd = NULL; } failed = false; /* haven't failed authentication yet */ if (NULL == username) { /* need to get a login id */ if (subroot) { closelog (); exit (1); } preauth_flag = false; username = xmalloc (USER_NAME_MAX_LENGTH + 1); username[USER_NAME_MAX_LENGTH] = '\0'; login_prompt (_("\n%s login: "******"!", * the account is locked and the user cannot * login, even if they have been * "pre-authenticated." */ if ( ('!' == user_passwd[0]) || ('*' == user_passwd[0])) { failed = true; } } if (strcmp (user_passwd, SHADOW_PASSWD_STRING) == 0) { spwd = xgetspnam (username); if (NULL != spwd) { user_passwd = spwd->sp_pwdp; } else { /* The user exists in passwd, but not in * shadow. SHADOW_PASSWD_STRING indicates * that the password shall be in shadow. */ SYSLOG ((LOG_WARN, "no shadow password for '%s'%s", username, fromhost)); } } /* * The -r and -f flags provide a name which has already * been authenticated by some server. */ if (preauth_flag) { goto auth_ok; } if (pw_auth (user_passwd, username, reason, (char *) 0) == 0) { goto auth_ok; } SYSLOG ((LOG_WARN, "invalid password for '%s' %s", failent_user, fromhost)); failed = true; auth_ok: /* * This is the point where all authenticated users wind up. * If you reach this far, your password has been * authenticated and so on. */ if ( !failed && (NULL != pwd) && (0 == pwd->pw_uid) && !is_console) { SYSLOG ((LOG_CRIT, "ILLEGAL ROOT LOGIN %s", fromhost)); failed = true; } if ( !failed && !login_access (username, ('\0' != *hostname) ? hostname : tty)) { SYSLOG ((LOG_WARN, "LOGIN '%s' REFUSED %s", username, fromhost)); failed = true; } if ( (NULL != pwd) && getdef_bool ("FAILLOG_ENAB") && !failcheck (pwd->pw_uid, &faillog, failed)) { SYSLOG ((LOG_CRIT, "exceeded failure limit for '%s' %s", username, fromhost)); failed = true; } if (!failed) { break; } /* don't log non-existent users */ if ((NULL != pwd) && getdef_bool ("FAILLOG_ENAB")) { failure (pwd->pw_uid, tty, &faillog); } if (getdef_str ("FTMP_FILE") != NULL) { #ifdef USE_UTMPX struct utmpx *failent = prepare_utmpx (failent_user, tty, /* FIXME: or fromhost? */hostname, utent); #else /* !USE_UTMPX */ struct utmp *failent = prepare_utmp (failent_user, tty, hostname, utent); #endif /* !USE_UTMPX */ failtmp (failent_user, failent); free (failent); } retries--; if (retries <= 0) { SYSLOG ((LOG_CRIT, "REPEATED login failures%s", fromhost)); } /* * If this was a passwordless account and we get here, login * was denied (securetty, faillog, etc.). There was no * password prompt, so do it now (will always fail - the bad * guys won't see that the passwordless account exists at * all). --marekm */ if (user_passwd[0] == '\0') { pw_auth ("!", username, reason, (char *) 0); } /* * Authentication of this user failed. * The username must be confirmed in the next try. */ free (username); username = NULL; /* * Wait a while (a la SVR4 /usr/bin/login) before attempting * to login the user again. If the earlier alarm occurs * before the sleep() below completes, login will exit. */ if (delay > 0) { (void) sleep (delay); } (void) puts (_("Login incorrect")); /* allow only one attempt with -r or -f */ if (rflg || fflg || (retries <= 0)) { closelog (); exit (1); } } /* while (true) */ #endif /* ! USE_PAM */ assert (NULL != username); assert (NULL != pwd); (void) alarm (0); /* turn off alarm clock */ #ifndef USE_PAM /* PAM does this */ /* * porttime checks moved here, after the user has been * authenticated. now prints a message, as suggested * by Ivan Nejgebauer <*****@*****.**>. --marekm */ if ( getdef_bool ("PORTTIME_CHECKS_ENAB") && !isttytime (username, tty, time ((time_t *) 0))) { SYSLOG ((LOG_WARN, "invalid login time for '%s'%s", username, fromhost)); closelog (); bad_time_notify (); exit (1); } check_nologin (pwd->pw_uid == 0); #endif if (getenv ("IFS")) { /* don't export user IFS ... */ addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */ } if (pwd->pw_shell[0] == '*') { /* subsystem root */ pwd->pw_shell++; /* skip the '*' */ subsystem (pwd); /* figure out what to execute */ subroot = true; /* say I was here again */ endpwent (); /* close all of the file which were */ endgrent (); /* open in the original rooted file */ endspent (); /* system. they will be re-opened */ #ifdef SHADOWGRP endsgent (); /* in the new rooted file system */ #endif goto top; /* go do all this all over again */ } #ifdef WITH_AUDIT audit_fd = audit_open (); audit_log_acct_message (audit_fd, AUDIT_USER_LOGIN, NULL, /* Prog. name */ "login", username, AUDIT_NO_ID, hostname, NULL, /* addr */ tty, 1); /* result */ close (audit_fd); #endif /* WITH_AUDIT */ #ifndef USE_PAM /* pam_lastlog handles this */ if (getdef_bool ("LASTLOG_ENAB")) { /* give last login and log this one */ dolastlog (&ll, pwd, tty, hostname); } #endif #ifndef USE_PAM /* PAM handles this as well */ /* * Have to do this while we still have root privileges, otherwise we * don't have access to /etc/shadow. */ if (NULL != spwd) { /* check for age of password */ if (expire (pwd, spwd)) { /* The user updated her password, get the new * entries. * Use the x variants because we need to keep the * entry for a long time, and there might be other * getxxyy in between. */ pw_free (pwd); pwd = xgetpwnam (username); if (NULL == pwd) { SYSLOG ((LOG_ERR, "cannot find user %s after update of expired password", username)); exit (1); } spw_free (spwd); spwd = xgetspnam (username); } } setup_limits (pwd); /* nice, ulimit etc. */ #endif /* ! USE_PAM */ chown_tty (pwd); #ifdef USE_PAM /* * We must fork before setuid() because we need to call * pam_close_session() as root. */ (void) signal (SIGINT, SIG_IGN); child = fork (); if (child < 0) { /* error in fork() */ fprintf (stderr, _("%s: failure forking: %s"), Prog, strerror (errno)); PAM_END; exit (0); } else if (child != 0) { /* * parent - wait for child to finish, then cleanup * session */ wait (NULL); PAM_END; exit (0); } /* child */ #endif /* If we were init, we need to start a new session */ if (getppid() == 1) { setsid(); if (ioctl(0, TIOCSCTTY, 1) != 0) { fprintf (stderr, _("TIOCSCTTY failed on %s"), tty); } } /* * The utmp entry needs to be updated to indicate the new status * of the session, the new PID and SID. */ update_utmp (username, tty, hostname, utent); /* The pwd and spwd entries for the user have been copied. * * Close all the files so that unauthorized access won't occur. */ endpwent (); /* stop access to password file */ endgrent (); /* stop access to group file */ endspent (); /* stop access to shadow passwd file */ #ifdef SHADOWGRP endsgent (); /* stop access to shadow group file */ #endif /* Drop root privileges */ #ifndef USE_PAM if (setup_uid_gid (pwd, is_console)) #else /* The group privileges were already dropped. * See setup_groups() above. */ if (change_uid (pwd)) #endif { exit (1); } setup_env (pwd); /* set env vars, cd to the home dir */ #ifdef USE_PAM { const char *const *env; env = (const char *const *) pam_getenvlist (pamh); while ((NULL != env) && (NULL != *env)) { addenv (*env, NULL); env++; } } #endif (void) setlocale (LC_ALL, ""); (void) bindtextdomain (PACKAGE, LOCALEDIR); (void) textdomain (PACKAGE); if (!hushed (username)) { addenv ("HUSHLOGIN=FALSE", NULL); /* * pam_unix, pam_mail and pam_lastlog should take care of * this */ #ifndef USE_PAM motd (); /* print the message of the day */ if ( getdef_bool ("FAILLOG_ENAB") && (0 != faillog.fail_cnt)) { failprint (&faillog); /* Reset the lockout times if logged in */ if ( (0 != faillog.fail_max) && (faillog.fail_cnt >= faillog.fail_max)) { (void) puts (_("Warning: login re-enabled after temporary lockout.")); SYSLOG ((LOG_WARN, "login '%s' re-enabled after temporary lockout (%d failures)", username, (int) faillog.fail_cnt)); } } if ( getdef_bool ("LASTLOG_ENAB") && (ll.ll_time != 0)) { time_t ll_time = ll.ll_time; #ifdef HAVE_STRFTIME (void) strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", localtime (&ll_time)); printf (_("Last login: %s on %s"), ptime, ll.ll_line); #else printf (_("Last login: %.19s on %s"), ctime (&ll_time), ll.ll_line); #endif #ifdef HAVE_LL_HOST /* __linux__ || SUN4 */ if ('\0' != ll.ll_host[0]) { printf (_(" from %.*s"), (int) sizeof ll.ll_host, ll.ll_host); } #endif printf (".\n"); } agecheck (spwd); mailcheck (); /* report on the status of mail */ #endif /* !USE_PAM */ } else { addenv ("HUSHLOGIN=TRUE", NULL); } ttytype (tty); (void) signal (SIGQUIT, SIG_DFL); /* default quit signal */ (void) signal (SIGTERM, SIG_DFL); /* default terminate signal */ (void) signal (SIGALRM, SIG_DFL); /* default alarm signal */ (void) signal (SIGHUP, SIG_DFL); /* added this. --marekm */ (void) signal (SIGINT, SIG_DFL); /* default interrupt signal */ if (0 == pwd->pw_uid) { SYSLOG ((LOG_NOTICE, "ROOT LOGIN %s", fromhost)); } else if (getdef_bool ("LOG_OK_LOGINS")) { SYSLOG ((LOG_INFO, "'%s' logged in %s", username, fromhost)); } closelog (); tmp = getdef_str ("FAKE_SHELL"); if (NULL != tmp) { err = shell (tmp, pwd->pw_shell, newenvp); /* fake shell */ } else { /* exec the shell finally */ err = shell (pwd->pw_shell, (char *) 0, newenvp); } return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC); }
struct passwd * newacc(char *username) { char buf[PATH_MAX + 1]; struct passwd *pw; int i; if ((pw=getpwnam(username)) != 0) { fprintf(stderr,"user %s already exists\n", username); return 0; } if ((pw=malloc(sizeof *pw)) == 0) { return 0; } pw->pw_name = username; if ((pw->pw_uid = enter_uid()) == -1) { return 0; } if ((pw->pw_gid = enter_gid()) == -1) { return 0; } buf[0] = '\0'; if (get_string("Real name", buf) == -1) { return 0; } if ((pw->pw_comment=strdup(buf)) == 0) { return 0; } strncpy(buf, getdef_str("BASEDIR"), PATH_MAX); buf[PATH_MAX] = '\0'; if ((i = strlen(buf)) > 0) { if (buf[i - 1] != '/') { buf[i++] = '/'; } strncpy(buf + i, username, PATH_MAX - i); } if (get_string("Home directory", buf) == -1) { return 0; } if ((pw->pw_dir = strdup(buf)) == 0) { return 0; } strncpy(buf, getdef_str("SHELL"), PATH_MAX); buf[PATH_MAX] = '\0'; if (get_string("Login shell", buf) == -1) return 0; if ((pw->pw_shell = strdup(buf)) == 0) { return 0; } pw->pw_passwd = passwd_stub; while (mkdirp(pw->pw_dir, pw->pw_uid, pw->pw_gid) == -1) { *buf = '\0'; if (get_string("Enter a different home directory or enter to exit ", buf) == -1) return 0; if (*buf == '\0') return 0; free(pw->pw_dir); if ((pw->pw_dir=strdup(buf)) == 0) return 0; } return pw; }
int pw_auth (const char *cipher, const char *user, int reason, const char *input) { char prompt[1024]; char *clear = NULL; const char *cp; int retval; #ifdef SKEY int use_skey = 0; char challenge_info[40]; struct skey skey; #endif /* * There are programs for adding and deleting authentication data. */ if (reason == PW_ADD || reason == PW_DELETE) return 0; /* * There are even programs for changing the user name ... */ if (reason == PW_CHANGE && input != (char *) 0) return 0; /* * WARNING: * * When we change a password and we are root, we don't prompt. * This is so root can change any password without having to * know it. This is a policy decision that might have to be * revisited. */ if (reason == PW_CHANGE && getuid () == 0) return 0; /* * WARNING: * * When we are logging in a user with no ciphertext password, * we don't prompt for the password or anything. In reality * the user could just hit <ENTER>, so it doesn't really * matter. */ if (cipher == (char *) 0 || *cipher == '\0') return 0; #ifdef SKEY /* * If the user has an S/KEY entry show them the pertinent info * and then we can try validating the created cyphertext and the SKEY. * If there is no SKEY information we default to not using SKEY. */ if (skeychallenge (&skey, user, challenge_info) == 0) use_skey = 1; #endif /* * Prompt for the password as required. FTPD and REXECD both * get the cleartext password for us. */ if (reason != PW_FTP && reason != PW_REXEC && !input) { if (!(cp = getdef_str ("LOGIN_STRING"))) cp = _(PROMPT); #ifdef SKEY if (use_skey) printf ("[%s]\n", challenge_info); #endif snprintf (prompt, sizeof prompt, cp, user); clear = getpass (prompt); if (!clear) { static char c[1]; c[0] = '\0'; clear = c; } input = clear; } /* * Convert the cleartext password into a ciphertext string. * If the two match, the return value will be zero, which is * SUCCESS. Otherwise we see if SKEY is being used and check * the results there as well. */ retval = strcmp (pw_encrypt (input, cipher), cipher); #ifdef SKEY /* * If (1) The password fails to match, and * (2) The password is empty and * (3) We are using OPIE or S/Key, then * ...Re-prompt, with echo on. * -- AR 8/22/1999 */ if (retval && !input[0] && (use_skey)) { strncat (prompt, "(Echo on) ", (sizeof (prompt) - strlen (prompt))); clear = getpass_with_echo (prompt); if (!clear) { static char c[1]; c[0] = '\0'; clear = c; } input = clear; } if (retval && use_skey) { int passcheck = -1; if (skeyverify (&skey, input) == 0) passcheck = skey.n; if (passcheck > 0) retval = 0; } #endif /* * Things like RADIUS authentication may need the password - * if the external variable wipe_clear_pass is zero, we will * not wipe it (the caller should wipe clear_pass when it is * no longer needed). --marekm */ clear_pass = clear; if (wipe_clear_pass && clear && *clear) strzero (clear); return retval; }
static int remove_mailbox (void) { const char *maildir; char mailfile[1024]; int i; int errors = 0; maildir = getdef_str ("MAIL_DIR"); #ifdef MAIL_SPOOL_DIR if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) { maildir = MAIL_SPOOL_DIR; } #endif /* MAIL_SPOOL_DIR */ if (NULL == maildir) { return 0; } snprintf (mailfile, sizeof mailfile, "%s/%s", maildir, user_name); if (access (mailfile, F_OK) != 0) { if (ENOENT == errno) { fprintf (stderr, _("%s: %s mail spool (%s) not found\n"), Prog, user_name, mailfile); return 0; } else { fprintf (stderr, _("%s: warning: can't remove %s: %s\n"), Prog, mailfile, strerror (errno)); SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); #ifdef WITH_AUDIT audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_FAILURE); #endif /* WITH_AUDIT */ return -1; } } if (fflg) { if (unlink (mailfile) != 0) { fprintf (stderr, _("%s: warning: can't remove %s: %s\n"), Prog, mailfile, strerror (errno)); SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); #ifdef WITH_AUDIT audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_FAILURE); #endif /* WITH_AUDIT */ errors = 1; /* continue */ } #ifdef WITH_AUDIT else { audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_SUCCESS); } #endif /* WITH_AUDIT */ return errors; } i = is_owner (user_id, mailfile); if (i == 0) { fprintf (stderr, _("%s: %s not owned by %s, not removing\n"), Prog, mailfile, user_name); SYSLOG ((LOG_ERR, "%s not owned by %s, not removed", mailfile, strerror (errno))); #ifdef WITH_AUDIT audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_FAILURE); #endif /* WITH_AUDIT */ return 1; } else if (i == -1) { return 0; /* mailbox doesn't exist */ } if (unlink (mailfile) != 0) { fprintf (stderr, _("%s: warning: can't remove %s: %s\n"), Prog, mailfile, strerror (errno)); SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); #ifdef WITH_AUDIT audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_FAILURE); #endif /* WITH_AUDIT */ errors = 1; /* continue */ } #ifdef WITH_AUDIT else { audit_logger (AUDIT_DEL_USER, Prog, "deleting mail file", user_name, (unsigned int) user_id, SHADOW_AUDIT_SUCCESS); } #endif /* WITH_AUDIT */ return errors; }
/* * login_prompt - prompt the user for their login name * * login_prompt() displays the standard login prompt. If ISSUE_FILE * is set in login.defs, this file is displayed before the prompt. */ void login_prompt(const char *prompt, char *name, int namesize) { char buf[1024]; #define MAX_ENV 32 char *envp[MAX_ENV]; int envc; char *cp; int i; FILE *fp; RETSIGTYPE (*sigquit)(int); #ifdef SIGTSTP RETSIGTYPE (*sigtstp)(int); #endif /* * There is a small chance that a QUIT character will be part of * some random noise during a prompt. Deal with this by exiting * instead of core dumping. If SIGTSTP is defined, do the same * thing for that signal. */ sigquit = signal(SIGQUIT, login_exit); #ifdef SIGTSTP sigtstp = signal(SIGTSTP, login_exit); #endif /* * See if the user has configured the issue file to * be displayed and display it before the prompt. */ if (prompt) { cp = getdef_str("ISSUE_FILE"); if (cp && (fp = fopen(cp, "r"))) { while ((i = getc(fp)) != EOF) putc(i, stdout); fclose(fp); } gethostname(buf, sizeof buf); printf(prompt, buf); fflush(stdout); } /* * Read the user's response. The trailing newline will be * removed. */ memzero(buf, sizeof buf); if (fgets(buf, sizeof buf, stdin) != buf) exit(1); cp = strchr(buf, '\n'); if (!cp) exit(1); *cp = '\0'; /* remove \n [ must be there ] */ /* * Skip leading whitespace. This makes " username" work right. * Then copy the rest (up to the end or the first "non-graphic" * character into the username. */ for (cp = buf;*cp == ' ' || *cp == '\t';cp++) ; // for (i = 0; i < namesize - 1 && isgraph(*cp); name[i++] = *cp++); /* * Allow double names for Fidonet login style. */ for (i = 0; i < namesize - 1 && isprint(*cp); name[i++] = *cp++); while (isgraph(*cp)) cp++; if (*cp) cp++; name[i] = '\0'; /* * This is a disaster, at best. The user may have entered extra * environmental variables at the prompt. There are several ways * to do this, and I just take the easy way out. */ if (*cp != '\0') { /* process new variables */ char *nvar; int count = 1; for (envc = 0; envc < MAX_ENV; envc++) { nvar = strtok(envc ? (char *)0 : cp, " \t,"); if (!nvar) break; if (strchr(nvar, '=')) { envp[envc] = nvar; } else { envp[envc] = xmalloc(strlen(nvar) + 32); snprintf(envp[envc], strlen(nvar) + 32, "L%d=%s", count++, nvar); } } set_env(envc, envp); } /* * Set the SIGQUIT handler back to its original value */ signal(SIGQUIT, sigquit); #ifdef SIGTSTP signal(SIGTSTP, sigtstp); #endif }
/* * new_password - validate old password and replace with new (both old and * new in global "char crypt_passwd[128]") */ static int new_password (const struct passwd *pw) { char *clear; /* Pointer to clear text */ char *cipher; /* Pointer to cipher text */ char *cp; /* Pointer to getpass() response */ char orig[200]; /* Original password */ char pass[200]; /* New password */ int i; /* Counter for retries */ int warned; int pass_max_len = -1; char *method; #ifdef HAVE_LIBCRACK_HIST int HistUpdate (const char *, const char *); #endif /* HAVE_LIBCRACK_HIST */ /* * Authenticate the user. The user will be prompted for their own * password. */ if (!amroot && crypt_passwd[0]) { clear = getpass (_("Old password: "******"incorrect password for %s", pw->pw_name)); sleep (1); fprintf (stderr, _("Incorrect password for %s.\n"), pw->pw_name); return -1; } STRFCPY (orig, clear); strzero (clear); strzero (cipher); } else { orig[0] = '\0'; } /* * Get the new password. The user is prompted for the new password * and has five tries to get it right. The password will be tested * for strength, unless it is the root user. This provides an escape * for initial login passwords. */ if ((method = getdef_str ("ENCRYPT_METHOD")) == NULL) { if (!getdef_bool ("MD5_CRYPT_ENAB")) { pass_max_len = getdef_num ("PASS_MAX_LEN", 8); } } else { if ( (strcmp (method, "MD5") == 0) #ifdef USE_SHA_CRYPT || (strcmp (method, "SHA256") == 0) || (strcmp (method, "SHA512") == 0) #endif /* USE_SHA_CRYPT */ ) { pass_max_len = -1; } else { pass_max_len = getdef_num ("PASS_MAX_LEN", 8); } } if (!qflg) { if (pass_max_len == -1) { printf (_( "Enter the new password (minimum of %d characters)\n" "Please use a combination of upper and lower case letters and numbers.\n"), getdef_num ("PASS_MIN_LEN", 5)); } else { printf (_( "Enter the new password (minimum of %d, maximum of %d characters)\n" "Please use a combination of upper and lower case letters and numbers.\n"), getdef_num ("PASS_MIN_LEN", 5), pass_max_len); } } warned = 0; for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) { cp = getpass (_("New password: "******"Try again.")); continue; } /* * If enabled, warn about weak passwords even if you are * root (enter this password again to use it anyway). * --marekm */ if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN") && (!obscure (orig, pass, pw) || reuse (pass, pw))) { puts (_("\nWarning: weak password (enter it again to use it anyway).")); warned++; continue; } cp = getpass (_("Re-enter new password: "******"They don't match; try again.\n"), stderr); } else { strzero (cp); break; } } memzero (orig, sizeof orig); if (i == 0) { memzero (pass, sizeof pass); return -1; } /* * Encrypt the password, then wipe the cleartext password. */ cp = pw_encrypt (pass, crypt_make_salt (NULL, NULL)); memzero (pass, sizeof pass); #ifdef HAVE_LIBCRACK_HIST HistUpdate (pw->pw_name, crypt_passwd); #endif /* HAVE_LIBCRACK_HIST */ STRFCPY (crypt_passwd, cp); return 0; }
/* * process_flags - perform command line argument setting * * process_flags() interprets the command line arguments and sets the * values that the user will be created with accordingly. The values * are checked for sanity. */ static void process_flags (int argc, char **argv) { const struct group *grp; const struct passwd *pwd; const struct spwd *spwd = NULL; int anyflag = 0; int arg; if (argc == 1 || argv[argc - 1][0] == '-') usage (); if (!(pwd = getpwnam (argv[argc - 1]))) { fprintf (stderr, _("%s: user %s does not exist\n"), Prog, argv[argc - 1]); exit (E_NOTFOUND); } user_name = argv[argc - 1]; user_id = pwd->pw_uid; user_gid = pwd->pw_gid; user_comment = xstrdup (pwd->pw_gecos); user_home = xstrdup (pwd->pw_dir); user_shell = xstrdup (pwd->pw_shell); #ifdef WITH_AUDIT user_newname = user_name; user_newid = user_id; user_newgid = user_gid; user_newcomment = user_comment; user_newhome = user_home; user_newshell = user_shell; #endif #ifdef USE_NIS /* * Now make sure it isn't an NIS user. */ if (__ispwNIS ()) { char *nis_domain; char *nis_master; fprintf (stderr, _("%s: user %s is a NIS user\n"), Prog, user_name); if (!yp_get_default_domain (&nis_domain) && !yp_master (nis_domain, "passwd.byname", &nis_master)) { fprintf (stderr, _("%s: %s is the NIS master\n"), Prog, nis_master); } exit (E_NOTFOUND); } #endif if (is_shadow_pwd && (spwd = getspnam (user_name))) { user_expire = spwd->sp_expire; user_inactive = spwd->sp_inact; #ifdef WITH_AUDIT user_newexpire = user_expire; user_newinactive = user_inactive; #endif } { /* * Parse the command line options. */ int c; static struct option long_options[] = { {"append", required_argument, NULL, 'a'}, {"comment", required_argument, NULL, 'c'}, {"home", required_argument, NULL, 'd'}, {"expiredate", required_argument, NULL, 'e'}, {"inactive", required_argument, NULL, 'f'}, {"gid", required_argument, NULL, 'g'}, {"groups", required_argument, NULL, 'G'}, {"help", no_argument, NULL, 'h'}, {"login", required_argument, NULL, 'l'}, {"lock", no_argument, NULL, 'L'}, {"move-home", no_argument, NULL, 'm'}, {"non-unique", no_argument, NULL, 'o'}, {"password", required_argument, NULL, 'p'}, {"shell", required_argument, NULL, 's'}, {"uid", required_argument, NULL, 'u'}, {"unlock", no_argument, NULL, 'U'}, {NULL, 0, NULL, '\0'} }; while ((c = getopt_long (argc, argv, "ac:d:e:f:g:G:l:Lmop:s:u:U", long_options, NULL)) != -1) { switch (c) { case 'a': aflg++; break; case 'c': if (!VALID (optarg)) { fprintf (stderr, _("%s: invalid field `%s'\n"), Prog, optarg); exit (E_BAD_ARG); } #ifdef WITH_AUDIT user_newcomment = optarg; #else user_comment = optarg; #endif cflg++; break; case 'd': if (!VALID (optarg)) { fprintf (stderr, _("%s: invalid field `%s'\n"), Prog, optarg); exit (E_BAD_ARG); } dflg++; user_newhome = optarg; break; case 'e': if (*optarg) { #ifdef WITH_AUDIT user_newexpire = strtoday (optarg); if (user_newexpire == -1) { #else user_expire = strtoday (optarg); if (user_expire == -1) { #endif fprintf (stderr, _ ("%s: invalid date `%s'\n"), Prog, optarg); exit (E_BAD_ARG); } #ifdef WITH_AUDIT user_newexpire *= DAY / SCALE; #else user_expire *= DAY / SCALE; #endif } else #ifdef WITH_AUDIT user_newexpire = -1; #else user_expire = -1; #endif eflg++; break; case 'f': #ifdef WITH_AUDIT user_newinactive = get_number (optarg); #else user_inactive = get_number (optarg); #endif fflg++; break; case 'g': grp = getgr_nam_gid (optarg); if (!grp) { fprintf (stderr, _("%s: unknown group %s\n"), Prog, optarg); exit (E_NOTFOUND); } user_newgid = grp->gr_gid; gflg++; break; case 'G': if (get_groups (optarg)) exit (E_NOTFOUND); Gflg++; break; case 'l': if (!check_user_name (optarg)) { fprintf (stderr, _("%s: invalid field `%s'\n"), Prog, optarg); exit (E_BAD_ARG); } /* * If the name does not really change, we mustn't * set the flag as this will cause rather serious * problems later! */ if (strcmp (user_name, optarg)) lflg++; user_newname = optarg; break; case 'L': if (Uflg || pflg) usage (); Lflg++; break; case 'm': if (!dflg) usage (); mflg++; break; case 'o': if (!uflg) usage (); oflg++; break; case 'p': if (Lflg || Uflg) usage (); user_pass = optarg; pflg++; break; case 's': if (!VALID (optarg)) { fprintf (stderr, _("%s: invalid field `%s'\n"), Prog, optarg); exit (E_BAD_ARG); } #ifdef WITH_AUDIT user_newshell = optarg; #else user_shell = optarg; #endif sflg++; break; case 'u': user_newid = get_id (optarg); uflg++; break; case 'U': if (Lflg && pflg) usage (); Uflg++; break; default: usage (); } anyflag++; } } if (anyflag == 0) { fprintf (stderr, _("%s: no flags given\n"), Prog); exit (E_USAGE); } if (!is_shadow_pwd && (eflg || fflg)) { fprintf (stderr, _ ("%s: shadow passwords required for -e and -f\n"), Prog); exit (E_USAGE); } if (optind != argc - 1) usage (); if (aflg && (!Gflg)) { fprintf (stderr, _("%s: -a flag is ONLY allowed with the -G flag\n"), Prog); usage (); exit (E_USAGE); } if (dflg && strcmp (user_home, user_newhome) == 0) dflg = mflg = 0; if (uflg && user_id == user_newid) uflg = oflg = 0; if (lflg && getpwnam (user_newname)) { fprintf (stderr, _("%s: user %s exists\n"), Prog, user_newname); exit (E_NAME_IN_USE); } if (uflg && !oflg && getpwuid (user_newid)) { fprintf (stderr, _("%s: uid %lu is not unique\n"), Prog, (unsigned long) user_newid); exit (E_UID_IN_USE); } } /* * close_files - close all of the files that were opened * * close_files() closes all of the files that were opened for this new * user. This causes any modified entries to be written out. */ static void close_files (void) { if (!pw_close ()) { fprintf (stderr, _("%s: cannot rewrite password file\n"), Prog); fail_exit (E_PW_UPDATE); } if (is_shadow_pwd && !spw_close ()) { fprintf (stderr, _("%s: cannot rewrite shadow password file\n"), Prog); fail_exit (E_PW_UPDATE); } if (is_shadow_pwd) spw_unlock (); (void) pw_unlock (); /* * Close the DBM and/or flat files */ endpwent (); endspent (); endgrent (); #ifdef SHADOWGRP endsgent (); #endif } /* * open_files - lock and open the password files * * open_files() opens the two password files. */ static void open_files (void) { if (!pw_lock ()) { fprintf (stderr, _("%s: unable to lock password file\n"), Prog); exit (E_PW_UPDATE); } if (!pw_open (O_RDWR)) { fprintf (stderr, _("%s: unable to open password file\n"), Prog); fail_exit (E_PW_UPDATE); } if (is_shadow_pwd && !spw_lock ()) { fprintf (stderr, _("%s: cannot lock shadow password file\n"), Prog); fail_exit (E_PW_UPDATE); } if (is_shadow_pwd && !spw_open (O_RDWR)) { fprintf (stderr, _("%s: cannot open shadow password file\n"), Prog); fail_exit (E_PW_UPDATE); } } /* * usr_update - create the user entries * * usr_update() creates the password file entries for this user and * will update the group entries if required. */ static void usr_update (void) { struct passwd pwent; const struct passwd *pwd; struct spwd spent; const struct spwd *spwd = NULL; /* * Locate the entry in /etc/passwd, which MUST exist. */ pwd = pw_locate (user_name); if (!pwd) { fprintf (stderr, _("%s: %s not found in /etc/passwd\n"), Prog, user_name); fail_exit (E_NOTFOUND); } pwent = *pwd; new_pwent (&pwent); /* * Locate the entry in /etc/shadow. It doesn't have to exist, and * won't be created if it doesn't. */ if (is_shadow_pwd && (spwd = spw_locate (user_name))) { spent = *spwd; new_spent (&spent); } if (lflg || uflg || gflg || cflg || dflg || sflg || pflg || Lflg || Uflg) { if (!pw_update (&pwent)) { fprintf (stderr, _("%s: error changing password entry\n"), Prog); fail_exit (E_PW_UPDATE); } if (lflg && !pw_remove (user_name)) { fprintf (stderr, _("%s: error removing password entry\n"), Prog); fail_exit (E_PW_UPDATE); } } if (spwd && (lflg || eflg || fflg || pflg || Lflg || Uflg)) { if (!spw_update (&spent)) { fprintf (stderr, _ ("%s: error adding new shadow password entry\n"), Prog); fail_exit (E_PW_UPDATE); } if (lflg && !spw_remove (user_name)) { fprintf (stderr, _ ("%s: error removing shadow password entry\n"), Prog); fail_exit (E_PW_UPDATE); } } } /* * move_home - move the user's home directory * * move_home() moves the user's home directory to a new location. The * files will be copied if the directory cannot simply be renamed. */ static void move_home (void) { struct stat sb; if (mflg && stat (user_home, &sb) == 0) { /* * Don't try to move it if it is not a directory * (but /dev/null for example). --marekm */ if (!S_ISDIR (sb.st_mode)) return; if (access (user_newhome, F_OK) == 0) { fprintf (stderr, _("%s: directory %s exists\n"), Prog, user_newhome); fail_exit (E_HOMEDIR); } else if (rename (user_home, user_newhome)) { if (errno == EXDEV) { if (mkdir (user_newhome, sb.st_mode & 0777)) { fprintf (stderr, _ ("%s: can't create %s\n"), Prog, user_newhome); } if (chown (user_newhome, sb.st_uid, sb.st_gid)) { fprintf (stderr, _("%s: can't chown %s\n"), Prog, user_newhome); rmdir (user_newhome); fail_exit (E_HOMEDIR); } if (copy_tree (user_home, user_newhome, uflg ? user_newid : -1, gflg ? user_newgid : -1) == 0) { if (remove_tree (user_home) != 0 || rmdir (user_home) != 0) fprintf (stderr, _ ("%s: warning: failed to completely remove old home directory %s"), Prog, user_home); #ifdef WITH_AUDIT audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "moving home directory", user_newname, user_newid, 1); #endif return; } (void) remove_tree (user_newhome); (void) rmdir (user_newhome); } fprintf (stderr, _ ("%s: cannot rename directory %s to %s\n"), Prog, user_home, user_newhome); fail_exit (E_HOMEDIR); } #ifdef WITH_AUDIT audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "moving home directory", user_newname, user_newid, 1); #endif } if (uflg || gflg) { #ifdef WITH_AUDIT audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing home directory owner", user_newname, user_newid, 1); #endif chown (dflg ? user_newhome : user_home, uflg ? user_newid : user_id, gflg ? user_newgid : user_gid); } } /* * update_files - update the lastlog and faillog files */ static void update_files (void) { struct lastlog ll; struct faillog fl; int fd; /* * Relocate the "lastlog" entries for the user. The old entry is * left alone in case the UID was shared. It doesn't hurt anything * to just leave it be. */ if ((fd = open (LASTLOG_FILE, O_RDWR)) != -1) { lseek (fd, (off_t) user_id * sizeof ll, SEEK_SET); if (read (fd, (char *) &ll, sizeof ll) == sizeof ll) { lseek (fd, (off_t) user_newid * sizeof ll, SEEK_SET); write (fd, (char *) &ll, sizeof ll); } close (fd); } /* * Relocate the "faillog" entries in the same manner. */ if ((fd = open (FAILLOG_FILE, O_RDWR)) != -1) { lseek (fd, (off_t) user_id * sizeof fl, SEEK_SET); if (read (fd, (char *) &fl, sizeof fl) == sizeof fl) { lseek (fd, (off_t) user_newid * sizeof fl, SEEK_SET); write (fd, (char *) &fl, sizeof fl); } close (fd); } } #ifndef NO_MOVE_MAILBOX /* * This is the new and improved code to carefully chown/rename the user's * mailbox. Maybe I am too paranoid but the mail spool dir sometimes * happens to be mode 1777 (this makes mail user agents work without * being setgid mail, but is NOT recommended; they all should be fixed * to use movemail). --marekm */ static void move_mailbox (void) { const char *maildir; char mailfile[1024], newmailfile[1024]; int fd; struct stat st; maildir = getdef_str ("MAIL_DIR"); #ifdef MAIL_SPOOL_DIR if (!maildir && !getdef_str ("MAIL_FILE")) maildir = MAIL_SPOOL_DIR; #endif if (!maildir) return; /* * O_NONBLOCK is to make sure open won't hang on mandatory locks. * We do fstat/fchown to make sure there are no races (someone * replacing /var/spool/mail/luser with a hard link to /etc/passwd * between stat and chown). --marekm */ snprintf (mailfile, sizeof mailfile, "%s/%s", maildir, user_name); fd = open (mailfile, O_RDONLY | O_NONBLOCK, 0); if (fd < 0) { /* no need for warnings if the mailbox doesn't exist */ if (errno != ENOENT) perror (mailfile); return; } if (fstat (fd, &st) < 0) { perror ("fstat"); close (fd); return; } if (st.st_uid != user_id) { /* better leave it alone */ fprintf (stderr, _("%s: warning: %s not owned by %s\n"), Prog, mailfile, user_name); close (fd); return; } if (uflg) { if (fchown (fd, user_newid, (gid_t) - 1) < 0) { perror (_("failed to change mailbox owner")); } #ifdef WITH_AUDIT else { audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing mail file owner", user_newname, user_newid, 1); } #endif } close (fd); if (lflg) { snprintf (newmailfile, sizeof newmailfile, "%s/%s", maildir, user_newname); if (link (mailfile, newmailfile) || unlink (mailfile)) { perror (_("failed to rename mailbox")); } #ifdef WITH_AUDIT else { audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing mail file name", user_newname, user_newid, 1); } #endif } } #endif /* * main - usermod command */ int main (int argc, char **argv) { int grp_err = 0; #ifdef USE_PAM pam_handle_t *pamh = NULL; struct passwd *pampw; int retval; #endif #ifdef WITH_AUDIT audit_help_open (); #endif /* * Get my name so that I can use it to report errors. */ Prog = Basename (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); sys_ngroups = sysconf (_SC_NGROUPS_MAX); user_groups = malloc ((1 + sys_ngroups) * sizeof (char *)); user_groups[0] = (char *) 0; OPENLOG ("usermod"); is_shadow_pwd = spw_file_present (); #ifdef SHADOWGRP is_shadow_grp = sgr_file_present (); #endif process_flags (argc, argv); #ifdef USE_PAM retval = PAM_SUCCESS; pampw = getpwuid (getuid ()); if (pampw == NULL) { retval = PAM_USER_UNKNOWN; } if (retval == PAM_SUCCESS) { retval = pam_start ("usermod", pampw->pw_name, &conv, &pamh); } if (retval == PAM_SUCCESS) { retval = pam_authenticate (pamh, 0); if (retval != PAM_SUCCESS) { pam_end (pamh, retval); } } if (retval == PAM_SUCCESS) { retval = pam_acct_mgmt (pamh, 0); if (retval != PAM_SUCCESS) { pam_end (pamh, retval); } } if (retval != PAM_SUCCESS) { fprintf (stderr, _("%s: PAM authentication failed\n"), Prog); exit (1); } #endif /* USE_PAM */ /* * Do the hard stuff - open the files, change the user entries, * change the home directory, then close and update the files. */ open_files (); usr_update (); nscd_flush_cache ("passwd"); nscd_flush_cache ("group"); close_files (); if (Gflg || lflg) grp_err = grp_update (); if (mflg) move_home (); #ifndef NO_MOVE_MAILBOX if (lflg || uflg) move_mailbox (); #endif if (uflg) { update_files (); /* * Change the UID on all of the files owned by `user_id' to * `user_newid' in the user's home directory. */ chown_tree (dflg ? user_newhome : user_home, user_id, user_newid, user_gid, gflg ? user_newgid : user_gid); } if (grp_err) exit (E_GRP_UPDATE); #ifdef USE_PAM if (retval == PAM_SUCCESS) pam_end (pamh, PAM_SUCCESS); #endif /* USE_PAM */ exit (E_SUCCESS); /* NOT REACHED */ }
#ifdef USE_UTMPX const struct utmpx *failent #else /* !USE_UTMPX */ const struct utmp *failent #endif /* !USE_UTMPX */ ) { const char *ftmp; int fd; /* * Get the name of the failure file. If no file has been defined * in login.defs, don't do this. */ ftmp = getdef_str ("FTMP_FILE"); if (NULL == ftmp) { return; } /* * Open the file for append. It must already exist for this * feature to be used. */ if (access (ftmp, F_OK) != 0) { return; } fd = open (ftmp, O_WRONLY | O_APPEND); if (-1 == fd) {
/*ARGSUSED*/ int main (int argc, char **argv) { #ifndef USE_PAM const char *env; #endif /* !USE_PAM */ char **envp = environ; TERMIO termio; int err = 0; #ifdef USE_TERMIO ioctl (0, TCGETA, &termio); termio.c_iflag |= (ICRNL | IXON); termio.c_oflag |= (OPOST | ONLCR); termio.c_cflag |= (CREAD); termio.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK); ioctl (0, TCSETAF, &termio); #endif #ifdef USE_TERMIOS tcgetattr (0, &termio); termio.c_iflag |= (ICRNL | IXON); termio.c_oflag |= (CREAD); termio.c_lflag |= (ECHO | ECHOE | ECHOK | ICANON | ISIG); tcsetattr (0, TCSANOW, &termio); #endif Prog = Basename (argv[0]); (void) setlocale (LC_ALL, ""); (void) bindtextdomain (PACKAGE, LOCALEDIR); (void) textdomain (PACKAGE); #ifdef USE_SYSLOG OPENLOG ("sulogin"); #endif initenv (); if (argc > 1) { close (0); close (1); close (2); if (open (argv[1], O_RDWR) >= 0) { dup (0); dup (0); } else { #ifdef USE_SYSLOG SYSLOG (LOG_WARN, "cannot open %s\n", argv[1]); closelog (); #endif exit (1); } } if (access (PASSWD_FILE, F_OK) == -1) { /* must be a password file! */ (void) puts (_("No password file")); #ifdef USE_SYSLOG SYSLOG (LOG_WARN, "No password file\n"); closelog (); #endif exit (1); } #if !defined(DEBUG) && defined(SULOGIN_ONLY_INIT) if (getppid () != 1) { /* parent must be INIT */ #ifdef USE_SYSLOG SYSLOG (LOG_WARN, "Pid == %d, not 1\n", getppid ()); closelog (); #endif exit (1); } #endif if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) { #ifdef USE_SYSLOG closelog (); #endif exit (1); /* must be a terminal */ } /* If we were init, we need to start a new session */ if (getppid() == 1) { setsid(); if (ioctl(0, TIOCSCTTY, 1) != 0) { (void) fputs (_("TIOCSCTTY failed"), stderr); } } while (NULL != *envp) { /* add inherited environment, */ addenv (*envp, NULL); /* some variables change later */ envp++; } #ifndef USE_PAM env = getdef_str ("ENV_TZ"); if (NULL != env) { addenv (('/' == *env) ? tz (env) : env, NULL); } env = getdef_str ("ENV_HZ"); if (NULL != env) { addenv (env, NULL); /* set the default $HZ, if one */ } #endif /* !USE_PAM */ (void) strcpy (name, "root"); /* KLUDGE!!! */ (void) signal (SIGALRM, catch_signals); /* exit if the timer expires */ (void) alarm (ALARM); /* only wait so long ... */ while (true) { /* repeatedly get login/password pairs */ char *cp; pw_entry (name, &pwent); /* get entry from password file */ if (pwent.pw_name == (char *) 0) { /* * Fail secure */ (void) puts (_("No password entry for 'root'")); #ifdef USE_SYSLOG SYSLOG (LOG_WARN, "No password entry for 'root'\n"); closelog (); #endif exit (1); } /* * Here we prompt for the root password, or if no password * is given we just exit. */ /* get a password for root */ cp = getpass (_( "\n" "Type control-d to proceed with normal startup,\n" "(or give root password for system maintenance):")); /* * XXX - can't enter single user mode if root password is * empty. I think this doesn't happen very often :-). But * it will work with standard getpass() (no NULL on EOF). * --marekm */ if ((NULL == cp) || ('\0' == *cp)) { #ifdef USE_SYSLOG SYSLOG (LOG_INFO, "Normal startup\n"); closelog (); #endif (void) puts (""); #ifdef TELINIT execl (PATH_TELINIT, "telinit", RUNLEVEL, (char *) 0); #endif exit (0); } else { STRFCPY (pass, cp); strzero (cp); } if (valid (pass, &pwent)) { /* check encrypted passwords ... */ break; /* ... encrypted passwords matched */ } #ifdef USE_SYSLOG SYSLOG (LOG_WARN, "Incorrect root password\n"); #endif sleep (2); (void) puts (_("Login incorrect")); } strzero (pass); (void) alarm (0); (void) signal (SIGALRM, SIG_DFL); environ = newenvp; /* make new environment active */ (void) puts (_("Entering System Maintenance Mode")); #ifdef USE_SYSLOG SYSLOG (LOG_INFO, "System Maintenance Mode\n"); #endif #ifdef USE_SYSLOG closelog (); #endif /* exec the shell finally. */ err = shell (pwent.pw_shell, (char *) 0, environ); return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC); }