diff options
Diffstat (limited to 'login-utils')
-rw-r--r-- | login-utils/Makemodule.am | 146 | ||||
-rw-r--r-- | login-utils/chfn.1 | 82 | ||||
-rw-r--r-- | login-utils/chfn.c | 486 | ||||
-rw-r--r-- | login-utils/chsh.1 | 65 | ||||
-rw-r--r-- | login-utils/chsh.c | 380 | ||||
-rw-r--r-- | login-utils/islocal.c | 115 | ||||
-rw-r--r-- | login-utils/islocal.h | 1 | ||||
-rw-r--r-- | login-utils/last.1 | 62 | ||||
-rw-r--r-- | login-utils/last.c | 481 | ||||
-rw-r--r-- | login-utils/login.1 | 334 | ||||
-rw-r--r-- | login-utils/login.c | 1477 | ||||
-rw-r--r-- | login-utils/logindefs.c | 297 | ||||
-rw-r--r-- | login-utils/logindefs.h | 12 | ||||
-rw-r--r-- | login-utils/newgrp.1 | 34 | ||||
-rw-r--r-- | login-utils/newgrp.c | 185 | ||||
-rw-r--r-- | login-utils/selinux_utils.c | 51 | ||||
-rw-r--r-- | login-utils/selinux_utils.h | 2 | ||||
-rw-r--r-- | login-utils/setpwnam.c | 218 | ||||
-rw-r--r-- | login-utils/setpwnam.h | 29 | ||||
-rw-r--r-- | login-utils/su.c | 813 | ||||
-rw-r--r-- | login-utils/sulogin.8 | 90 | ||||
-rw-r--r-- | login-utils/sulogin.c | 609 | ||||
-rw-r--r-- | login-utils/utmpdump.1 | 74 | ||||
-rw-r--r-- | login-utils/utmpdump.c | 363 | ||||
-rw-r--r-- | login-utils/vigr.8 | 1 | ||||
-rw-r--r-- | login-utils/vipw.8 | 90 | ||||
-rw-r--r-- | login-utils/vipw.c | 353 |
27 files changed, 6850 insertions, 0 deletions
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am new file mode 100644 index 0000000..41e32aa --- /dev/null +++ b/login-utils/Makemodule.am @@ -0,0 +1,146 @@ + +if BUILD_LAST +usrbin_exec_PROGRAMS += last +dist_man_MANS += login-utils/last.1 +last_SOURCES = login-utils/last.c +endif + +if BUILD_SULOGIN +sbin_PROGRAMS += sulogin +dist_man_MANS += login-utils/sulogin.8 +sulogin_SOURCES = \ + login-utils/sulogin.c +sulogin_LDADD = $(LDADD) libcommon.la +if HAVE_LIBCRYPT +sulogin_LDADD += -lcrypt +endif +if HAVE_SELINUX +sulogin_LDADD += -lselinux +endif +endif # BUILD_SULOGIN + + +if BUILD_LOGIN +bin_PROGRAMS += login +dist_man_MANS += login-utils/login.1 +login_SOURCES = \ + login-utils/login.c \ + login-utils/logindefs.c \ + login-utils/logindefs.h +login_LDADD = $(LDADD) libcommon.la -lpam -lpam_misc +if HAVE_AUDIT +login_LDADD += -laudit +endif +if HAVE_SELINUX +login_LDADD += -lselinux +endif +endif # BUILD_LOGIN + + +if BUILD_UTMPDUMP +usrbin_exec_PROGRAMS += utmpdump +dist_man_MANS += login-utils/utmpdump.1 +utmpdump_SOURCES = login-utils/utmpdump.c +endif + + +if BUILD_CHFN_CHSH +usrbin_exec_PROGRAMS += chfn chsh +dist_man_MANS += \ + login-utils/chfn.1 \ + login-utils/chsh.1 + +chfn_chsh_sources = \ + login-utils/islocal.c \ + login-utils/islocal.h \ + login-utils/setpwnam.c \ + login-utils/setpwnam.h +chfn_chsh_cflags = $(SUID_CFLAGS) $(AM_CFLAGS) +chfn_chsh_ldflags = $(SUID_LDFLAGS) $(AM_LDFLAGS) +chfn_chsh_ldadd = libcommon.la -lpam -lpam_misc + +if HAVE_SELINUX +chfn_chsh_sources += \ + login-utils/selinux_utils.c \ + login-utils/selinux_utils.h +chfn_chsh_ldadd += -lselinux +endif + +chfn_SOURCES = login-utils/chfn.c $(chfn_chsh_sources) +chfn_CFLAGS = $(chfn_chsh_cflags) +chfn_LDFLAGS = $(chfn_chsh_ldflags) +chfn_LDADD = $(LDADD) $(chfn_chsh_ldadd) + +chsh_SOURCES = login-utils/chsh.c $(chfn_chsh_sources) +chsh_CFLAGS = $(chfn_chsh_cflags) +chsh_LDFLAGS = $(chfn_chsh_ldflags) +chsh_LDADD = $(LDADD) $(chfn_chsh_ldadd) +endif # BUILD_CHFN_CHSH + + +if BUILD_SU +bin_PROGRAMS += su +su_SOURCES = \ + login-utils/su.c \ + login-utils/logindefs.c \ + login-utils/logindefs.h +su_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS) +su_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS) +su_LDADD = $(LDADD) -lpam -lpam_misc +endif + + +if BUILD_NEWGRP +usrbin_exec_PROGRAMS += newgrp +dist_man_MANS += login-utils/newgrp.1 +newgrp_SOURCES = login-utils/newgrp.c +newgrp_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS) +newgrp_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS) +newgrp_LDADD = $(LDADD) +if HAVE_LIBCRYPT +newgrp_LDADD += -lcrypt +endif +endif # BUILD_NEWGRP + + +if BUILD_VIPW +usrsbin_exec_PROGRAMS += vipw +dist_man_MANS += \ + login-utils/vigr.8 \ + login-utils/vipw.8 +vipw_SOURCES = \ + login-utils/vipw.c \ + login-utils/setpwnam.h +vipw_LDADD = $(LDADD) libcommon.la +if HAVE_SELINUX +vipw_LDADD += -lselinux +endif +install-exec-hook-vipw:: + cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr + +INSTALL_EXEC_HOOKS += install-exec-hook-vipw +endif # BUILD_VIPW + + +check_PROGRAMS += \ + test_islocal \ + test_logindefs + +test_islocal_SOURCES = login-utils/islocal.c +test_islocal_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS) + +test_logindefs_SOURCES = \ + login-utils/logindefs.c \ + login-utils/logindefs.h +test_logindefs_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS) + + +install-exec-hook: +if BUILD_SU +if MAKEINSTALL_DO_SETUID + chmod 4755 $(DESTDIR)$(bindir)/su +endif +endif +if BUILD_VIPW + cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr +endif diff --git a/login-utils/chfn.1 b/login-utils/chfn.1 new file mode 100644 index 0000000..5329408 --- /dev/null +++ b/login-utils/chfn.1 @@ -0,0 +1,82 @@ +.\" +.\" chfn.1 -- change your finger information +.\" (c) 1994 by salvatore valente <svalente@athena.mit.edu> +.\" +.\" this program is free software. you can redistribute it and +.\" modify it under the terms of the gnu general public license. +.\" there is no warranty. +.\" +.\" $Author: faith $ +.\" $Revision: 1.1 $ +.\" $Date: 1995/03/12 01:29:16 $ +.\" +.TH CHFN 1 "July 2009" "util-linux" "User Commands" +.SH NAME +chfn \- change your finger information +.SH SYNOPSIS +.B chfn +.RB [ \-f +.IR full-name ] +.RB [ \-o +.IR office ] +,RB [ \-p +.IR office-phone ] +.RB [ \-h +.IR home-phone ] +.BR \-u ] +.RB [ \-v ] +.RI [ username ] +.SH DESCRIPTION +.B chfn +is used to change your finger information. This information is +stored in the +.I /etc/passwd +file, and is displayed by the +.B finger +program. The Linux +.B finger +command will display four pieces of information that can be changed by +.BR chfn : +your real name, your work room and phone, and your home phone. + +.B chfn +is used to change local entries only. Use ypchfn, lchfn or any other +implementation for non-local entries. +.SS COMMAND LINE +Any of the four pieces of information can be specified on the command +line. If no information is given on the command line, +.B chfn +enters interactive mode. +.SS INTERACTIVE MODE +In interactive mode, +.B chfn +will prompt for each field. At a prompt, you can enter the new information, +or just press return to leave the field unchanged. Enter the keyword +"none" to make the field blank. +.SH OPTIONS +.TP +.BI "\-f, \-\-full-name " full-name +Specify your real name. +.TP +.BI "\-o, \-\-office " office +Specify your office room number. +.TP +.BI "\-p, \-\-office-phone " office-phone +Specify your office phone number. +.TP +.BI "\-h, \-\-home-phone " home-phone +Specify your home phone number. +.TP +.B "\-u, \-\-help" +Print a usage message and exit. +.TP +.B "-v, \-\-version" +Print version information and exit. +.SH "SEE ALSO" +.BR finger (1), +.BR passwd (5) +.SH AUTHOR +Salvatore Valente <svalente@mit.edu> +.SH AVAILABILITY +The chfn command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/login-utils/chfn.c b/login-utils/chfn.c new file mode 100644 index 0000000..02014c7 --- /dev/null +++ b/login-utils/chfn.c @@ -0,0 +1,486 @@ +/* + * chfn.c -- change your finger information + * (c) 1994 by salvatore valente <svalente@athena.mit.edu> + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + * $Author: aebr $ + * $Revision: 1.18 $ + * $Date: 1998/06/11 22:30:11 $ + * + * Updated Thu Oct 12 09:19:26 1995 by faith@cs.unc.edu with security + * patches from Zefram <A.Main@dcs.warwick.ac.uk> + * + * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de, + * to remove trailing empty fields. Oct 5, 96. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + */ + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "env.h" +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "pamfail.h" +#include "setpwnam.h" +#include "strutils.h" +#include "xalloc.h" + +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +# include <selinux/av_permissions.h> +# include "selinux_utils.h" +#endif + +static char buf[1024]; + +struct finfo { + struct passwd *pw; + char *username; + char *full_name; + char *office; + char *office_phone; + char *home_phone; + char *other; +}; + +static int parse_argv(int argc, char *argv[], struct finfo *pinfo); +static void parse_passwd(struct passwd *pw, struct finfo *pinfo); +static void ask_info(struct finfo *oldfp, struct finfo *newfp); +static char *prompt(char *question, char *def_val); +static int check_gecos_string(char *msg, char *gecos); +static int set_changed_data(struct finfo *oldfp, struct finfo *newfp); +static int save_new_data(struct finfo *pinfo); + +/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */ +#define MAX_FIELD_SIZE 256 + +static void __attribute__((__noreturn__)) usage(FILE *fp) +{ + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] [username]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, fp); + fputs(_(" -f, --full-name <full-name> real name\n"), fp); + fputs(_(" -o, --office <office> office number\n"), fp); + fputs(_(" -p, --office-phone <phone> office phone number\n"), fp); + fputs(_(" -h, --home-phone <phone> home phone number\n"), fp); + fputs(USAGE_SEPARATOR, fp); + fputs(_(" -u, --help display this help and exit\n"), fp); + fputs(_(" -v, --version output version information and exit\n"), fp); + fprintf(fp, USAGE_MAN_TAIL("chfn(1)")); + exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + uid_t uid; + struct finfo oldf, newf; + int interactive; + int status; + + sanitize_env(); + setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */ + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + /* + * "oldf" contains the users original finger information. + * "newf" contains the changed finger information, and contains NULL + * in fields that haven't been changed. + * in the end, "newf" is folded into "oldf". + * + * the reason the new finger information is not put _immediately_ + * into "oldf" is that on the command line, new finger information + * can be specified before we know what user the information is + * being specified for. + */ + uid = getuid(); + memset(&oldf, 0, sizeof(oldf)); + memset(&newf, 0, sizeof(newf)); + + interactive = parse_argv(argc, argv, &newf); + if (!newf.username) { + parse_passwd(getpwuid(uid), &oldf); + if (!oldf.username) + errx(EXIT_FAILURE, _("you (user %d) don't exist."), + uid); + } else { + parse_passwd(getpwnam(newf.username), &oldf); + if (!oldf.username) + errx(EXIT_FAILURE, _("user \"%s\" does not exist."), + newf.username); + } + + if (!(is_local(oldf.username))) + errx(EXIT_FAILURE, _("can only change local entries")); + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + if (uid == 0) { + if (checkAccess(oldf.username, PASSWD__CHFN) != 0) { + security_context_t user_context; + if (getprevcon(&user_context) < 0) + user_context = NULL; + errx(EXIT_FAILURE, + _("%s is not authorized to change " + "the finger info of %s"), + user_context ? : _("Unknown user context"), + oldf.username); + } + } + if (setupDefaultContext(_PATH_PASSWD)) + errx(EXIT_FAILURE, + _("can't set default context for %s"), _PATH_PASSWD); + } +#endif + + /* Reality check */ + if (uid != 0 && uid != oldf.pw->pw_uid) { + errno = EACCES; + err(EXIT_FAILURE, NULL); + } + + printf(_("Changing finger information for %s.\n"), oldf.username); + +#ifdef REQUIRE_PASSWORD + if (uid != 0) { + pam_handle_t *pamh = NULL; + struct pam_conv conv = { misc_conv, NULL }; + int retcode; + + retcode = pam_start("chfn", oldf.username, &conv, &pamh); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_authenticate(pamh, 0); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_acct_mgmt(pamh, 0); + if (retcode == PAM_NEW_AUTHTOK_REQD) + retcode = + pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_setcred(pamh, 0); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + pam_end(pamh, 0); + /* no need to establish a session; this isn't a + * session-oriented activity... */ + } +#endif /* REQUIRE_PASSWORD */ + + if (interactive) + ask_info(&oldf, &newf); + + if (!set_changed_data(&oldf, &newf)) { + printf(_("Finger information not changed.\n")); + return EXIT_SUCCESS; + } + status = save_new_data(&oldf); + return status; +} + +/* + * parse_argv () -- + * parse the command line arguments. + * returns true if no information beyond the username was given. + */ +static int parse_argv(int argc, char *argv[], struct finfo *pinfo) +{ + int index, c, status; + int info_given; + + static struct option long_options[] = { + {"full-name", required_argument, 0, 'f'}, + {"office", required_argument, 0, 'o'}, + {"office-phone", required_argument, 0, 'p'}, + {"home-phone", required_argument, 0, 'h'}, + {"help", no_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {NULL, no_argument, 0, '0'}, + }; + + optind = 0; + info_given = false; + while (true) { + c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options, + &index); + if (c == -1) + break; + /* version? output version and exit. */ + if (c == 'v') { + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + } + if (c == 'u') + usage(stdout); + /* all other options must have an argument. */ + if (!optarg) + usage(stderr); + /* ok, we were given an argument */ + info_given = true; + status = 0; + + /* now store the argument */ + switch (c) { + case 'f': + pinfo->full_name = optarg; + status = check_gecos_string(_("Name"), optarg); + break; + case 'o': + pinfo->office = optarg; + status = check_gecos_string(_("Office"), optarg); + break; + case 'p': + pinfo->office_phone = optarg; + status = check_gecos_string(_("Office Phone"), optarg); + break; + case 'h': + pinfo->home_phone = optarg; + status = check_gecos_string(_("Home Phone"), optarg); + break; + default: + usage(stderr); + } + if (status < 0) + exit(status); + } + /* done parsing arguments. check for a username. */ + if (optind < argc) { + if (optind + 1 < argc) + usage(stderr); + pinfo->username = argv[optind]; + } + return (!info_given); +} + +/* + * parse_passwd () -- + * take a struct password and fill in the fields of the struct finfo. + */ +static void parse_passwd(struct passwd *pw, struct finfo *pinfo) +{ + char *gecos; + char *cp; + + if (pw) { + pinfo->pw = pw; + pinfo->username = pw->pw_name; + /* use pw_gecos - we take a copy since PAM destroys the original */ + gecos = xstrdup(pw->pw_gecos); + cp = (gecos ? gecos : ""); + pinfo->full_name = cp; + cp = strchr(cp, ','); + if (cp) { + *cp = 0, cp++; + } else + return; + pinfo->office = cp; + cp = strchr(cp, ','); + if (cp) { + *cp = 0, cp++; + } else + return; + pinfo->office_phone = cp; + cp = strchr(cp, ','); + if (cp) { + *cp = 0, cp++; + } else + return; + pinfo->home_phone = cp; + /* extra fields contain site-specific information, and can + * not be changed by this version of chfn. */ + cp = strchr(cp, ','); + if (cp) { + *cp = 0, cp++; + } else + return; + pinfo->other = cp; + } +} + +/* + * ask_info () -- + * prompt the user for the finger information and store it. + */ +static void ask_info(struct finfo *oldfp, struct finfo *newfp) +{ + newfp->full_name = prompt(_("Name"), oldfp->full_name); + newfp->office = prompt(_("Office"), oldfp->office); + newfp->office_phone = prompt(_("Office Phone"), oldfp->office_phone); + newfp->home_phone = prompt(_("Home Phone"), oldfp->home_phone); + printf("\n"); +} + +/* + * prompt () -- + * ask the user for a given field and check that the string is legal. + */ +static char *prompt(char *question, char *def_val) +{ + static char *blank = "none"; + int len; + char *ans, *cp; + + while (true) { + if (!def_val) + def_val = ""; + printf("%s [%s]: ", question, def_val); + *buf = 0; + if (fgets(buf, sizeof(buf), stdin) == NULL) + errx(EXIT_FAILURE, _("Aborted.")); + /* remove the newline at the end of buf. */ + ans = buf; + while (isspace(*ans)) + ans++; + len = strlen(ans); + while (len > 0 && isspace(ans[len - 1])) + len--; + if (len <= 0) + return NULL; + ans[len] = 0; + if (!strcasecmp(ans, blank)) + return ""; + if (check_gecos_string(NULL, ans) >= 0) + break; + } + cp = (char *)xmalloc(len + 1); + strcpy(cp, ans); + return cp; +} + +/* + * check_gecos_string () -- + * check that the given gecos string is legal. if it's not legal, + * output "msg" followed by a description of the problem, and return (-1). + */ +static int check_gecos_string(char *msg, char *gecos) +{ + unsigned int i, c; + + if (strlen(gecos) > MAX_FIELD_SIZE) { + if (msg) + warnx(_("field %s is too long"), msg); + else + warnx(_("field is too long")); + return -1; + } + + for (i = 0; i < strlen(gecos); i++) { + c = gecos[i]; + if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') { + if (msg) + warnx(_("%s: '%c' is not allowed"), msg, c); + else + warnx(_("'%c' is not allowed"), c); + return -1; + } + if (iscntrl(c)) { + if (msg) + warnx(_ + ("%s: control characters are not allowed"), + msg); + else + warnx(_("control characters are not allowed")); + return -1; + } + } + return 0; +} + +/* + * set_changed_data () -- + * incorporate the new data into the old finger info. + */ +static int set_changed_data(struct finfo *oldfp, struct finfo *newfp) +{ + int changed = false; + + if (newfp->full_name) { + oldfp->full_name = newfp->full_name; + changed = true; + } + if (newfp->office) { + oldfp->office = newfp->office; + changed = true; + } + if (newfp->office_phone) { + oldfp->office_phone = newfp->office_phone; + changed = true; + } + if (newfp->home_phone) { + oldfp->home_phone = newfp->home_phone; + changed = true; + } + + return changed; +} + +/* + * save_new_data () -- + * save the given finger info in /etc/passwd. + * return zero on success. + */ +static int save_new_data(struct finfo *pinfo) +{ + char *gecos; + int len; + + /* null fields will confuse printf(). */ + if (!pinfo->full_name) + pinfo->full_name = ""; + if (!pinfo->office) + pinfo->office = ""; + if (!pinfo->office_phone) + pinfo->office_phone = ""; + if (!pinfo->home_phone) + pinfo->home_phone = ""; + if (!pinfo->other) + pinfo->other = ""; + + /* create the new gecos string */ + len = (strlen(pinfo->full_name) + strlen(pinfo->office) + + strlen(pinfo->office_phone) + strlen(pinfo->home_phone) + + strlen(pinfo->other) + 4); + gecos = (char *)xmalloc(len + 1); + sprintf(gecos, "%s,%s,%s,%s,%s", pinfo->full_name, pinfo->office, + pinfo->office_phone, pinfo->home_phone, pinfo->other); + + /* remove trailing empty fields (but not subfields of pinfo->other) */ + if (!pinfo->other[0]) { + while (len > 0 && gecos[len - 1] == ',') + len--; + gecos[len] = 0; + } + + /* write the new struct passwd to the passwd file. */ + pinfo->pw->pw_gecos = gecos; + if (setpwnam(pinfo->pw) < 0) { + warn("setpwnam"); + printf(_ + ("Finger information *NOT* changed. Try again later.\n")); + return -1; + } + printf(_("Finger information changed.\n")); + return 0; +} diff --git a/login-utils/chsh.1 b/login-utils/chsh.1 new file mode 100644 index 0000000..393ccb7 --- /dev/null +++ b/login-utils/chsh.1 @@ -0,0 +1,65 @@ +.\" +.\" chsh.1 -- change your login shell +.\" (c) 1994 by salvatore valente <svalente@athena.mit.edu> +.\" +.\" this program is free software. you can redistribute it and +.\" modify it under the terms of the gnu general public license. +.\" there is no warranty. +.\" +.\" $Author: faith $ +.\" $Revision: 1.1 $ +.\" $Date: 1995/03/12 01:28:58 $ +.\" +.TH CHSH 1 "July 2009" "util-linux" "User Commands" +.SH NAME +chsh \- change your login shell +.SH SYNOPSIS +.B chsh +.RB [ \-s +.IR shell ] +.RB [ \-l ] +.RB [ \-u ] +.RB [ \-v ] +.RI [ username ] +.SH DESCRIPTION +.B chsh +is used to change your login shell. +If a shell is not given on the command line, +.B chsh +prompts for one. + +.B chsh +is used to change local entries only. Use ypchsh, lchsh or any other +implementation for non-local entries. +.SS VALID SHELLS +.B chsh +will accept the full pathname of any executable file on the system. +However, it will issue a warning if the shell is not listed in the +.I /etc/shells +file. +On the other hand, it can also be configured such that it will +only accept shells listed in this file, unless you are root. +.SH OPTIONS +.TP +.BI "\-s, \-\-shell " shell +Specify your login shell. +.TP +.B "\-l, \-\-list-shells" +Print the list of shells listed in +.I /etc/shells +and exit. +.TP +.B "\-u, \-\-help" +Print a usage message and exit. +.TP +.B "-v, \-\-version" +Print version information and exit. +.SH "SEE ALSO" +.BR login (1), +.BR passwd (5), +.BR shells (5) +.SH AUTHOR +Salvatore Valente <svalente@mit.edu> +.SH AVAILABILITY +The chsh command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/login-utils/chsh.c b/login-utils/chsh.c new file mode 100644 index 0000000..7d944e1 --- /dev/null +++ b/login-utils/chsh.c @@ -0,0 +1,380 @@ +/* + * chsh.c -- change your login shell + * (c) 1994 by salvatore valente <svalente@athena.mit.edu> + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + * $Author: aebr $ + * $Revision: 1.19 $ + * $Date: 1998/06/11 22:30:14 $ + * + * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security + * patches from Zefram <A.Main@dcs.warwick.ac.uk> + * + * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security + * suggestion from Zefram. Disallowing users with shells not in /etc/shells + * from changing their shell. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + */ + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "env.h" +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "pamfail.h" +#include "pathnames.h" +#include "setpwnam.h" +#include "xalloc.h" + +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +# include <selinux/av_permissions.h> +# include "selinux_utils.h" +#endif + +struct sinfo { + char *username; + char *shell; +}; + +static void parse_argv(int argc, char **argv, struct sinfo *pinfo); +static char *prompt(char *question, char *def_val); +static int check_shell(char *shell); +static int get_shell_list(char *shell); + +static void __attribute__((__noreturn__)) usage (FILE *fp) +{ + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] [username]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, fp); + fputs(_(" -s, --shell <shell> specify login shell\n"), fp); + fputs(_(" -l, --list-shells print list of shells and exit\n"), fp); + fputs(USAGE_SEPARATOR, fp); + fputs(_(" -u, --help display this help and exit\n"), fp); + fputs(_(" -v, --version output version information and exit\n"), fp); + fprintf(fp, USAGE_MAN_TAIL("chsh(1)")); + exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *shell, *oldshell; + uid_t uid; + struct sinfo info; + struct passwd *pw; + + sanitize_env(); + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + uid = getuid(); + memset(&info, 0, sizeof(info)); + + parse_argv(argc, argv, &info); + pw = NULL; + if (!info.username) { + pw = getpwuid(uid); + if (!pw) + errx(EXIT_FAILURE, _("you (user %d) don't exist."), + uid); + } else { + pw = getpwnam(info.username); + if (!pw) + errx(EXIT_FAILURE, _("user \"%s\" does not exist."), + info.username); + } + + if (!(is_local(pw->pw_name))) + errx(EXIT_FAILURE, _("can only change local entries.")); + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + if (uid == 0) { + if (checkAccess(pw->pw_name, PASSWD__CHSH) != 0) { + security_context_t user_context; + if (getprevcon(&user_context) < 0) + user_context = + (security_context_t) NULL; + + errx(EXIT_FAILURE, + _("%s is not authorized to change the shell of %s"), + user_context ? : _("Unknown user context"), + pw->pw_name); + } + } + if (setupDefaultContext(_PATH_PASSWD) != 0) + errx(EXIT_FAILURE, + _("can't set default context for %s"), _PATH_PASSWD); + } +#endif + + oldshell = pw->pw_shell; + if (oldshell == NULL || *oldshell == '\0') + oldshell = _PATH_BSHELL; /* default */ + + /* reality check */ + if (uid != 0 && uid != pw->pw_uid) { + errno = EACCES; + err(EXIT_FAILURE, + _("running UID doesn't match UID of user we're " + "altering, shell change denied")); + } + if (uid != 0 && !get_shell_list(oldshell)) { + errno = EACCES; + err(EXIT_FAILURE, _("your shell is not in %s, " + "shell change denied"), _PATH_SHELLS); + } + + shell = info.shell; + + printf(_("Changing shell for %s.\n"), pw->pw_name); + +#ifdef REQUIRE_PASSWORD + if (uid != 0) { + pam_handle_t *pamh = NULL; + struct pam_conv conv = { misc_conv, NULL }; + int retcode; + + retcode = pam_start("chsh", pw->pw_name, &conv, &pamh); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_authenticate(pamh, 0); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_acct_mgmt(pamh, 0); + if (retcode == PAM_NEW_AUTHTOK_REQD) + retcode = + pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + retcode = pam_setcred(pamh, 0); + if (pam_fail_check(pamh, retcode)) + exit(EXIT_FAILURE); + + pam_end(pamh, 0); + /* no need to establish a session; this isn't a + * session-oriented activity... */ + } +#endif /* REQUIRE_PASSWORD */ + + if (!shell) { + shell = prompt(_("New shell"), oldshell); + if (!shell) + return EXIT_SUCCESS; + } + + if (check_shell(shell) < 0) + return EXIT_FAILURE; + + if (strcmp(oldshell, shell) == 0) + errx(EXIT_SUCCESS, _("Shell not changed.")); + pw->pw_shell = shell; + if (setpwnam(pw) < 0) { + warn(_("setpwnam failed\n" + "Shell *NOT* changed. Try again later.")); + return EXIT_FAILURE; + } + printf(_("Shell changed.\n")); + return EXIT_SUCCESS; +} + +/* + * parse_argv () -- + * parse the command line arguments, and fill in "pinfo" with any + * information from the command line. + */ +static void parse_argv(int argc, char **argv, struct sinfo *pinfo) +{ + int index, c; + + static struct option long_options[] = { + {"shell", required_argument, 0, 's'}, + {"list-shells", no_argument, 0, 'l'}, + {"help", no_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {NULL, no_argument, 0, '0'}, + }; + + optind = c = 0; + while (c != EOF) { + c = getopt_long(argc, argv, "s:luv", long_options, &index); + switch (c) { + case -1: + break; + case 'v': + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + case 'u': + usage(stdout); + case 'l': + get_shell_list(NULL); + exit(EXIT_SUCCESS); + case 's': + if (!optarg) + usage(stderr); + pinfo->shell = optarg; + break; + default: + usage(stderr); + } + } + /* done parsing arguments. check for a username. */ + if (optind < argc) { + if (optind + 1 < argc) + usage(stderr); + pinfo->username = argv[optind]; + } +} + +/* + * prompt () -- + * ask the user for a given field and return it. + */ +static char *prompt(char *question, char *def_val) +{ + int len; + char *ans, *cp; + char buf[BUFSIZ]; + + if (!def_val) + def_val = ""; + printf("%s [%s]: ", question, def_val); + *buf = 0; + if (fgets(buf, sizeof(buf), stdin) == NULL) + errx(EXIT_FAILURE, _("Aborted.")); + /* remove the newline at the end of buf. */ + ans = buf; + while (isspace(*ans)) + ans++; + len = strlen(ans); + while (len > 0 && isspace(ans[len - 1])) + len--; + if (len <= 0) + return NULL; + ans[len] = 0; + cp = (char *)xmalloc(len + 1); + strcpy(cp, ans); + return cp; +} + +/* + * check_shell () -- if the shell is completely invalid, print + * an error and return (-1). + * if the shell is a bad idea, print a warning. + */ +static int check_shell(char *shell) +{ + unsigned int i, c; + + if (!shell) + return -1; + + if (*shell != '/') { + warnx(_("shell must be a full path name")); + return -1; + } + if (access(shell, F_OK) < 0) { + warnx(_("\"%s\" does not exist"), shell); + return -1; + } + if (access(shell, X_OK) < 0) { + printf(_("\"%s\" is not executable"), shell); + return -1; + } + /* keep /etc/passwd clean. */ + for (i = 0; i < strlen(shell); i++) { + c = shell[i]; + if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') { + warnx(_("'%c' is not allowed"), c); + return -1; + } + if (iscntrl(c)) { + warnx(_("control characters are not allowed")); + return -1; + } + } +#ifdef ONLY_LISTED_SHELLS + if (!get_shell_list(shell)) { + if (!getuid()) + warnx(_ + ("Warning: \"%s\" is not listed in %s."), + shell, _PATH_SHELLS); + else + errx(EXIT_FAILURE, + _("\"%s\" is not listed in %s.\n" + "Use %s -l to see list."), shell, _PATH_SHELLS, + program_invocation_short_name); + } +#else + if (!get_shell_list(shell)) { + warnx(_("\"%s\" is not listed in %s.\n" + "Use %s -l to see list."), shell, _PATH_SHELLS, + program_invocation_short_name); + } +#endif + return 0; +} + +/* + * get_shell_list () -- if the given shell appears in /etc/shells, + * return true. if not, return false. + * if the given shell is NULL, /etc/shells is outputted to stdout. + */ +static int get_shell_list(char *shell_name) +{ + FILE *fp; + int found; + int len; + char buf[PATH_MAX]; + + found = false; + fp = fopen(_PATH_SHELLS, "r"); + if (!fp) { + if (!shell_name) + warnx(_("No known shells.")); + return true; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + /* ignore comments */ + if (*buf == '#') + continue; + len = strlen(buf); + /* strip the ending newline */ + if (buf[len - 1] == '\n') + buf[len - 1] = 0; + /* ignore lines that are too damn long */ + else + continue; + /* check or output the shell */ + if (shell_name) { + if (!strcmp(shell_name, buf)) { + found = true; + break; + } + } else + printf("%s\n", buf); + } + fclose(fp); + return found; +} diff --git a/login-utils/islocal.c b/login-utils/islocal.c new file mode 100644 index 0000000..1f85166 --- /dev/null +++ b/login-utils/islocal.c @@ -0,0 +1,115 @@ +/* + * islocal.c - returns true if user is registered in the local + * /etc/passwd file. Written by Alvaro Martinez Echevarria, + * alvaro@enano.etsit.upm.es, to allow peaceful coexistence with yp. Nov 94. + * + * Hacked a bit by poe@daimi.aau.dk + * See also ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil* + * + * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de, + * to distinguish user names where one is a prefix of the other, + * and to use "pathnames.h". Oct 5, 96. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 2008-04-06 James Youngman, jay@gnu.org + * - Completely rewritten to remove assumption that /etc/passwd + * lines are < 1024 characters long. Also added unit tests. + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "pathnames.h" + +static int is_local_in_file(const char *user, const char *filename) +{ + int local = 0; + size_t match; + int chin, skip; + FILE *f; + + if (NULL == (f = fopen(filename, "r"))) + return -1; + + match = 0u; + skip = 0; + while ((chin = getc(f)) != EOF) { + if (skip) { + /* Looking for the start of the next line. */ + if ('\n' == chin) { + /* Start matching username at the next char. */ + skip = 0; + match = 0u; + } + } else { + if (':' == chin) { + if (0 == user[match]) { + /* Success. */ + local = 1; + /* next line has no test coverage, + * but it is just an optimisation + * anyway. */ + break; + } else { + /* we read a whole username, but it + * is the wrong user. Skip to the + * next line. */ + skip = 1; + } + } else if ('\n' == chin) { + /* This line contains no colon; it's + * malformed. No skip since we are already + * at the start of the next line. */ + match = 0u; + } else if (chin != user[match]) { + /* username does not match. */ + skip = 1; + } else { + ++match; + } + } + } + fclose(f); + return local; +} + +int is_local(const char *user) +{ + int rv; + if ((rv = is_local_in_file(user, _PATH_PASSWD)) < 0) { + perror(_PATH_PASSWD); + fprintf(stderr, _("cannot open %s"), _PATH_PASSWD); + exit(1); + } else { + return rv; + } +} + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + atexit(close_stdout); + if (argc <= 2) { + fprintf(stderr, "usage: %s <passwdfile> <username> [...]\n", + argv[0]); + return 1; + } else { + int i; + for (i = 2; i < argc; i++) { + const int rv = is_local_in_file(argv[i], argv[1]); + if (rv < 0) { + perror(argv[1]); + return 2; + } + printf("%d:%s\n", rv, argv[i]); + } + return 0; + } +} +#endif diff --git a/login-utils/islocal.h b/login-utils/islocal.h new file mode 100644 index 0000000..2c58799 --- /dev/null +++ b/login-utils/islocal.h @@ -0,0 +1 @@ +extern int is_local(const char *user); diff --git a/login-utils/last.1 b/login-utils/last.1 new file mode 100644 index 0000000..beb6917 --- /dev/null +++ b/login-utils/last.1 @@ -0,0 +1,62 @@ +.TH LAST 1 "March 1992" "util-linux" "User Commands" +.SH NAME +last \(em indicate last logins by user or terminal +.SH SYNOPSIS +.ad l +.B last +.RB [ \-\fP\fInumber\fP ] +.RB [ \-f +.IR filename ] +.RB [ \-t +.IR tty ] +.RB [ \-h +.IR hostname ] +.RB [ \-i +.IR address ] +.RB [ \-l ] +.RB [ \-y ] +.RI [ name ...] +.ad b +.SH DESCRIPTION +\fBLast\fP looks back in the \fBwtmp\fP file which records all logins +and logouts for information about a user, a teletype or any group of +users and teletypes. Arguments specify names of users or teletypes of +interest. If multiple arguments are given, the information which +applies to any of the arguments is printed. For example ``\fBlast root +console\fP'' would list all of root's sessions as well as all sessions +on the console terminal. \fBLast\fP displays the sessions of the +specified users and teletypes, most recent first, indicating the times +at which the session began, the duration of the session, and the +teletype which the session took place on. If the session is still +continuing or was cut short by a reboot, \fBlast\fP so indicates. +.LP +The pseudo-user \fBreboot\fP logs in at reboots of the system. +.LP +\fBLast\fP with no arguments displays a record of all logins and +logouts, in reverse order. +.LP +If \fBlast\fP is interrupted, it indicates how far the search has +progressed in \fBwtmp\fP. If interrupted with a quit signal \fBlast\fP +indicates how far the search has progressed so far, and the search +continues. +.SH OPTIONS +.IP \fB\-\fP\fInumber\fP +limit the number of entries displayed to that specified by \fInumber\fP. +.IP "\fB\-f\fP \fIfilename\fP" +Use \fIfilename\fP as the name of the accounting file instead of +.BR /var/log/wtmp . +.IP "\fB\-h\fP \fIhostname\fP" +List only logins from \fIhostname\fP. +.IP "\fB\-i\fP \fIIP address\fP" +List only logins from \fIIP address\fP. +.IP "\fB\-l\fP" +List IP addresses of remote hosts instead of truncated host names. +.IP "\fB\-t\fP \fItty\fP" +List only logins on \fItty\fP. +.IP "\fB\-y\fP" +Also report year of dates. +.SH FILES +/var/log/wtmp \(em login data base +.SH AVAILABILITY +The last command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/login-utils/last.c b/login-utils/last.c new file mode 100644 index 0000000..77a890a --- /dev/null +++ b/login-utils/last.c @@ -0,0 +1,481 @@ +/* + * Berkeley last for Linux. Currently maintained by poe@daimi.aau.dk at + * ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil* + * + * Copyright (c) 1987 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + + /* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + */ + + /* 2001-02-14 Marek Zelem <marek@fornax.sk> + * - using mmap() on Linux - great speed improvement + */ + +/* + * This command is deprecated. The utility is in maintenance mode, + * meaning we keep them in source tree for backward compatibility + * only. Do not waste time making this command better, unless the + * fix is about security or other very critical issue. + * + * See Documentation/deprecated.txt for more information. + */ + +/* + * last + */ +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <signal.h> +#include <string.h> +#include <time.h> +#include <utmp.h> +#include <stdio.h> +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "closestream.h" +#include "pathnames.h" +#include "nls.h" +#include "xalloc.h" +#include "c.h" + +#define SECDAY (24*60*60) /* seconds in a day */ +#define NO 0 /* false/no */ +#define YES 1 /* true/yes */ + +static struct utmp utmpbuf; + +#define HMAX (int)sizeof(utmpbuf.ut_host) /* size of utmp host field */ +#define LMAX (int)sizeof(utmpbuf.ut_line) /* size of utmp tty field */ +#define NMAX (int)sizeof(utmpbuf.ut_name) /* size of utmp name field */ + +/* maximum sizes used for printing */ +/* probably we want a two-pass version that computes the right length */ +#define P_HMAX min(HMAX, 16) +#define P_LMAX min(LMAX, 8) +#define P_NMAX min(NMAX, 16) + +typedef struct arg { + char *name; /* argument */ +#define HOST_TYPE -2 +#define TTY_TYPE -3 +#define USER_TYPE -4 +#define INET_TYPE -5 + int type; /* type of arg */ + struct arg *next; /* linked list pointer */ +} ARG; +ARG *arglist; /* head of linked list */ + +typedef struct ttytab { + long logout; /* log out time */ + char tty[LMAX + 1]; /* terminal name */ + struct ttytab *next; /* linked list pointer */ +} TTY; +TTY *ttylist; /* head of linked list */ + +static long currentout, /* current logout value */ + maxrec; /* records to display */ +static char *file = _PATH_WTMP; /* wtmp file */ + +static int doyear = 0; /* output year in dates */ +static int dolong = 0; /* print also ip-addr */ + +static void wtmp(void); +static void addarg(int, char *); +static void hostconv(char *); +static void onintr(int); +static int want(struct utmp *, int); +TTY *addtty(char *); +static char *ttyconv(char *); + +int +main(int argc, char **argv) { + int ch; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt(argc, argv, "0123456789yli:f:h:t:")) != -1) + switch((char)ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* + * kludge: last was originally designed to take + * a number after a dash. + */ + if (!maxrec) + maxrec = atol(argv[optind - 1] + 1); + break; + case 'f': + file = optarg; + break; + case 'h': + hostconv(optarg); + addarg(HOST_TYPE, optarg); + break; + case 't': + addarg(TTY_TYPE, ttyconv(optarg)); + break; + case 'y': + doyear = 1; + break; + case 'l': + dolong = 1; + break; + case 'i': + addarg(INET_TYPE, optarg); + break; + case '?': + default: + fputs(_("usage: last [-#] [-f file] [-t tty] [-h hostname] [user ...]\n"), stderr); + exit(EXIT_FAILURE); + } + for (argv += optind; *argv; ++argv) { +#define COMPATIBILITY +#ifdef COMPATIBILITY + /* code to allow "last p5" to work */ + addarg(TTY_TYPE, ttyconv(*argv)); +#endif + addarg(USER_TYPE, *argv); + } + wtmp(); + + return EXIT_SUCCESS; +} + +static char *utmp_ctime(struct utmp *u) +{ + time_t t = (time_t) u->ut_time; + return ctime(&t); +} + +/* + * print_partial_line -- + * print the first part of each output line according to specified format + */ +static void +print_partial_line(struct utmp *bp) { + char *ct; + + ct = utmp_ctime(bp); + printf("%-*.*s %-*.*s ", P_NMAX, P_NMAX, bp->ut_name, + P_LMAX, P_LMAX, bp->ut_line); + + if (dolong) { + if (bp->ut_addr) { + struct in_addr foo; + foo.s_addr = bp->ut_addr; + printf("%-*.*s ", P_HMAX, P_HMAX, inet_ntoa(foo)); + } else { + printf("%-*.*s ", P_HMAX, P_HMAX, ""); + } + } else { + printf("%-*.*s ", P_HMAX, P_HMAX, bp->ut_host); + } + + if (doyear) { + printf("%10.10s %4.4s %5.5s ", ct, ct + 20, ct + 11); + } else { + printf("%10.10s %5.5s ", ct, ct + 11); + } +} + +/* + * wtmp -- + * read through the wtmp file + */ +static void +wtmp(void) { + register struct utmp *bp; /* current structure */ + register TTY *T; /* tty list entry */ + long delta; /* time difference */ + char *crmsg = NULL; + char *ct = NULL; + int fd; + struct utmp *utl; + struct stat st; + int utl_len; + int listnr = 0; + int i; + + utmpname(file); + + { +#if defined(_HAVE_UT_TV) + struct timeval tv; + gettimeofday(&tv, NULL); + utmpbuf.ut_tv.tv_sec = tv.tv_sec; + utmpbuf.ut_tv.tv_usec = tv.tv_usec; +#else + time_t t; + time(&t); + utmpbuf.ut_time = t; +#endif + } + + (void)signal(SIGINT, onintr); + (void)signal(SIGQUIT, onintr); + + if ((fd = open(file,O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), file); + + fstat(fd, &st); + utl_len = st.st_size; + utl = mmap(NULL, utl_len, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_FILE, fd, 0); + if (utl == NULL) + err(EXIT_FAILURE, _("%s: mmap failed"), file); + + listnr = utl_len/sizeof(struct utmp); + + if(listnr) + ct = utmp_ctime(&utl[0]); + + for(i = listnr - 1; i >= 0; i--) { + bp = utl+i; + /* + * if the terminal line is '~', the machine stopped. + * see utmp(5) for more info. + */ + if (!strncmp(bp->ut_line, "~", LMAX)) { + /* + * utmp(5) also mentions that the user + * name should be 'shutdown' or 'reboot'. + * Not checking the name causes e.g. runlevel + * changes to be displayed as 'crash'. -thaele + */ + if (!strncmp(bp->ut_user, "reboot", NMAX) || + !strncmp(bp->ut_user, "shutdown", NMAX)) { + /* everybody just logged out */ + for (T = ttylist; T; T = T->next) + T->logout = -bp->ut_time; + } + + currentout = -bp->ut_time; + crmsg = (strncmp(bp->ut_name, "shutdown", NMAX) + ? "crash" : "down "); + if (!bp->ut_name[0]) + (void)strcpy(bp->ut_name, "reboot"); + if (want(bp, NO)) { + ct = utmp_ctime(bp); + if(bp->ut_type != LOGIN_PROCESS) { + print_partial_line(bp); + putchar('\n'); + } + if (maxrec && !--maxrec) + return; + } + continue; + } + /* find associated tty */ + for (T = ttylist;; T = T->next) { + if (!T) { + /* add new one */ + T = addtty(bp->ut_line); + break; + } + if (!strncmp(T->tty, bp->ut_line, LMAX)) + break; + } + if (bp->ut_name[0] && bp->ut_type != LOGIN_PROCESS + && bp->ut_type != DEAD_PROCESS + && want(bp, YES)) { + + print_partial_line(bp); + + if (!T->logout) + puts(_(" still logged in")); + else { + if (T->logout < 0) { + T->logout = -T->logout; + printf("- %s", crmsg); + } + else + printf("- %5.5s", ctime(&T->logout)+11); + delta = T->logout - bp->ut_time; + if (delta < SECDAY) + printf(" (%5.5s)\n", asctime(gmtime(&delta))+11); + else + printf(" (%ld+%5.5s)\n", delta / SECDAY, asctime(gmtime(&delta))+11); + } + if (maxrec != -1 && !--maxrec) + return; + } + T->logout = bp->ut_time; + utmpbuf.ut_time = bp->ut_time; + } + munmap(utl,utl_len); + close(fd); + if(ct) printf(_("\nwtmp begins %s"), ct); /* ct already ends in \n */ +} + +/* + * want -- + * see if want this entry + */ +static int +want(struct utmp *bp, int check) { + register ARG *step; + + if (check) { + /* + * when uucp and ftp log in over a network, the entry in + * the utmp file is the name plus their process id. See + * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. + */ + if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) + bp->ut_line[3] = '\0'; + else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) + bp->ut_line[4] = '\0'; + } + if (!arglist) + return YES; + + for (step = arglist; step; step = step->next) + switch(step->type) { + case HOST_TYPE: + if (!strncmp(step->name, bp->ut_host, HMAX)) + return YES; + break; + case TTY_TYPE: + if (!strncmp(step->name, bp->ut_line, LMAX)) + return YES; + break; + case USER_TYPE: + if (!strncmp(step->name, bp->ut_name, NMAX)) + return YES; + break; + case INET_TYPE: + if ((in_addr_t) bp->ut_addr == inet_addr(step->name)) + return YES; + break; + default: + abort(); + } + return NO; +} + +/* + * addarg -- + * add an entry to a linked list of arguments + */ +static void +addarg(int type, char *arg) { + register ARG *cur; + + cur = xmalloc(sizeof(ARG)); + cur->next = arglist; + cur->type = type; + cur->name = arg; + arglist = cur; +} + +/* + * addtty -- + * add an entry to a linked list of ttys + */ +TTY * +addtty(char *ttyname) { + register TTY *cur; + + cur = xmalloc(sizeof(TTY)); + cur->next = ttylist; + cur->logout = currentout; + memcpy(cur->tty, ttyname, LMAX); + return(ttylist = cur); +} + +/* + * hostconv -- + * convert the hostname to search pattern; if the supplied host name + * has a domain attached that is the same as the current domain, rip + * off the domain suffix since that's what login(1) does. + */ +static void +hostconv(char *arg) { + static int first = 1; + static char *hostdot, + name[MAXHOSTNAMELEN]; + char *argdot; + + if (!(argdot = strchr(arg, '.'))) + return; + if (first) { + first = 0; + if (gethostname(name, sizeof(name))) + err(EXIT_FAILURE, _("gethostname failed")); + + hostdot = strchr(name, '.'); + } + if (hostdot && !strcmp(hostdot, argdot)) + *argdot = '\0'; +} + +/* + * ttyconv -- + * convert tty to correct name. + */ +static char * +ttyconv(char *arg) { + char *mval; + + /* + * kludge -- we assume that all tty's end with + * a two character suffix. + */ + if (strlen(arg) == 2) { + /* either 6 for "ttyxx" or 8 for "console" */ + mval = xmalloc(8); + if (!strncmp(arg, "co", 2)) + (void)strcpy(mval, "console"); + else { + (void)strcpy(mval, "tty"); + (void)strncpy(mval + 3, arg, 4); + } + return mval; + } + if (!strncmp(arg, "/dev/", sizeof("/dev/") - 1)) + return arg + 5; + + return arg; +} + +/* + * onintr -- + * on interrupt, we inform the user how far we've gotten + */ +static void +onintr(int signo) { + char *ct; + + ct = utmp_ctime(&utmpbuf); + printf(_("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11); + if (signo == SIGINT) + _exit(EXIT_FAILURE); + fflush(stdout); /* fix required for rsh */ +} diff --git a/login-utils/login.1 b/login-utils/login.1 new file mode 100644 index 0000000..092213d --- /dev/null +++ b/login-utils/login.1 @@ -0,0 +1,334 @@ +.\" Copyright 1993 Rickard E. Faith (faith@cs.unc.edu) +.\" May be distributed under the GNU General Public License +.TH LOGIN "1" "June 2012" "util-linux" "User Commands" +.SH NAME +login \- begin session on the system +.SH SYNOPSIS +.B login +[ +.BR \-p +] [ +.BR \-h +.IR host +] [ +.BR \-H +] [ +.BR \-f +.IR username +| +.IR username +] +.SH DESCRIPTION +.B login +is used when signing onto a system. If no argument is given, +.B login +prompts for the username. +.PP +The user is then prompted for a password, where approprate. Echoing +is disabled to prevent revealing the password. Only a small number +of password failures are permitted before +.B login +exits and the communications link is severed. +.PP +If password aging has been enabled for the account, the user may be +prompted for a new password before proceeding. He will be forced to +provide his old password and the new password before continuing. +Please refer to +.BR passwd (1) +for more information. +.PP +The user and group ID will be set according to their values in the +.I /etc/passwd +file. There is one exception if the user ID is zero: in this case, +only the primary group ID of the account is set. This should allow +the system adminitrator to login even in case of network problems. +The value for +.BR $HOME , +.BR $USER , +.BR $SHELL , +.BR $PATH , +.BR $LOGNAME , +and +.B $MAIL +are set according to the appropriate fields in the password entry. +.B $PATH +defaults to +.I /usr\:/local\:/bin:\:/bin:\:/usr\:/bin +for normal users, and to +.I /usr\:/local\:/sbin:\:/usr\:/local\:/bin:\:/sbin:\:/bin:\:/usr\:/sbin:\:/usr\:/bin +for root if not other configured. +.P +The environment variable +.B $TERM +will be preserved, if it exists (other environment variables are +preserved if the +.B \-p +option is given) or be initialize to the terminal type on your tty. +.PP +Then the user's shell is started. If no shell is specified for the +user in +.BR /etc\:/passwd , +then +.B /bin\:/sh +is used. If there is no directory specified in +.IR /etc\:/passwd , +then +.I / +is used (the home directory is checked for the +.I .hushlogin +file described below). +.PP +If the file +.I .hushlogin +exists, then a "quiet" login is performed (this disables the checking +of mail and the printing of the last login time and message of the +day). Otherwise, if +.I /var\:/log\:/lastlog +exists, the last login time is printed (and the current login is +recorded). +.SH OPTIONS +.TP +.B \-p +Used by +.BR getty (8) +to tell +.B login +not to destroy the environment. +.TP +.B \-f +Used to skip a second login authentication. This specifically does +.B not +work for root, and does not appear to work well under Linux. +.TP +.B \-h +Used by other servers (i.e., +.BR telnetd (8)) +to pass the name of the remote host to +.B login +so that it may be placed in utmp and wtmp. Only the superuser may +use this option. +.IP +Note that the +.B \-h +option has impact on the +.B PAM service +.BR name . +The standard service name is +.IR login , +with the +.B \-h +option the name is +.IR remote . +It is necessary to create a proper PAM config files (e.g. +.I /etc\:/pam.d\:/login +and +.IR /etc\:/pam.d\:/remote ). +.TP +.B \-H +Used by other servers (i.e., +.BR telnetd (8)) +to tell +.B login +that printing the hostname should be suppressed in the login: prompt. +.TP +.B \-V +Print version and exit. +.SH CONFIG FILE ITEMS +.B login +reads the +.IR /etc\:/login.defs (5) +configuration file. Note that the configuration file could be +distributed with another package (e.g. shadow-utils). The following +configuration items are relevant for +.BR login (1): +.PP +.B MOTD_FILE +(string) +.RS 4 +If defined, ":" delimited list of "message of the day" files to be +displayed upon login. The default value is +.IR /etc\:/motd . +If the +.B MOTD_FILE +item is empty or quiet login is enabled then the message of the day +is not displayed. Note that the same functionality is also provided +by +.BR pam_motd (8) +PAM module. +.RE +.PP +.B LOGIN_TIMEOUT +(number) +.RS 4 +Max time in seconds for login. The default value is +.IR 60 . +.RE +.PP +.B LOGIN_RETRIES +(number) +.RS 4 +Maximum number of login retries in case of bad password. The default +value is +.IR 3 . +.RE +.PP +.B FAIL_DELAY +(number) +.RS 4 +Delay in seconds before being allowed another three tries after a +login failure. The default value is +.IR 5 . +.RE +.PP +.B TTYPERM +(string) +.RS 4 +The terminal permissions. The default value is +.IR 0600 . +.RE +.PP +.B TTYGROUP +(string) +.RS 4 +The login tty will be owned by the +.BR TTYGROUP . +The default value is +.IR tty . +If the +.B TTYGROUP +does not exist then the ownership of the terminal is set to the +user\'s primary group. +.PP +The +.B TTYGROUP +can be either the name of a group or a numeric group identifier. +.RE +.PP +.B HUSHLOGIN_FILE +(string) +.RS 4 +If defined, this file can inhibit all the usual chatter during the +login sequence. If a full pathname (e.g. +.IR /etc\:/hushlogins ) +is specified, then hushed mode will be enabled if the user\'s name or +shell are found in the file. If this global hush login file is empty +then the hushed mode will be enabled for all users. +.PP +If not a full pathname is specified, then hushed mode will be enabled +if the file exists in the user\'s home directory. +.PP +The default is to check +.I /etc\:/hushlogins +and if does not exist then +.I ~/.hushlogin +.PP +If the +.B HUSHLOGIN_FILE +item is empty then all checks are disabled. +.RE +.PP +.B DEFAULT_HOME +(boolean) +.RS 4 +Indicate if login is allowed if we can not change directory to the +home directory. If set to +.IR yes , +the user will login in the root (/) directory if it is not possible +to change directory to her home. The default value is +.IR yes . +.RE +.PP +.B LOG_UNKFAIL_ENAB +(boolean) +.RS 4 +Enable display of unknown usernames when login failures are recorded. +The default value is +.IR no . +.PP +Note that logging unknown usernames may be a security issue if an +user enter her password instead of her login name. +.RE +.PP +.B ENV_PATH +(string) +.RS 4 +If set, it will be used to define the PATH environment variable when +a regular user login. The default value is +.I /usr\:/local\:/bin:\:/bin:\:/usr\:/bin +.RE +.PP +.B ENV_ROOTPATH +(string) +.br +.B ENV_SUPATH +(string) +.RS 4 +If set, it will be used to define the PATH environment variable when +the superuser login. The default value is +.I /usr\:/local\:/sbin:\:/usr\:/local\:/bin:\:/sbin:\:/bin:\:/usr\:/sbin:\:/usr\:/bin +.RE +.SH FILES +.nf +.I /var/run/utmp +.I /var/log/wtmp +.I /var/log/lastlog +.I /var/spool/mail/* +.I /etc/motd +.I /etc/passwd +.I /etc/nologin +.I /etc/pam.d/login +.I /etc/pam.d/remote +.I /etc/hushlogins +.I .hushlogin +.fi +.SH "SEE ALSO" +.BR init (8), +.BR getty (8), +.BR mail (1), +.BR passwd (1), +.BR passwd (5), +.BR environ (7), +.BR shutdown (8) +.SH BUGS +The undocumented BSD +.B \-r +option is not supported. This may be required by some +.BR rlogind (8) +programs. +.PP +A recursive login, as used to be possible in the good old days, no +longer works; for most purposes +.BR su (1) +is a satisfactory substitute. Indeed, for security reasons, login +does a vhangup() system call to remove any possible listening +processes on the tty. This is to avoid password sniffing. If one +uses the command +.BR login , +then the surrounding shell gets killed by vhangup() because it's no +longer the true owner of the tty. This can be avoided by using +.B exec login +in a top-level shell or xterm. +.SH AUTHOR +Derived from BSD login 5.40 (5/9/89) by +.MT glad@\:daimi.\:dk +Michael Glad +.ME +for HP-UX +.br +Ported to Linux 0.12: +.MT poe@\:daimi.\:aau.\:dk +Peter Orbaek +.ME +.br +Rewritten to PAM-only version by +.MT kzak@\:redhat.\:com +Karel Zak +.ME +.SH AVAILABILITY +The +.B login +command is part of the util-linux package and is +available from +.UR ftp:\://ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/login-utils/login.c b/login-utils/login.c new file mode 100644 index 0000000..c0cc00a --- /dev/null +++ b/login-utils/login.c @@ -0,0 +1,1477 @@ +/* + * login(1) + * + * This program is derived from 4.3 BSD software and is subject to the + * copyright notice below. + * + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * Rewritten to PAM-only version. + * + * Michael Glad (glad@daimi.dk) + * Computer Science Department, Aarhus University, Denmark + * 1990-07-04 + * + * Copyright (c) 1980, 1987, 1988 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ +#include <sys/param.h> +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <getopt.h> +#include <memory.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/file.h> +#include <termios.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <signal.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <utmp.h> +#include <stdlib.h> +#include <sys/syslog.h> +#include <sys/sysmacros.h> +#ifdef HAVE_LINUX_MAJOR_H +# include <linux/major.h> +#endif +#include <netdb.h> +#include <lastlog.h> +#include <security/pam_appl.h> +#include <security/pam_misc.h> +#include <sys/sendfile.h> + +#ifdef HAVE_LIBAUDIT +# include <libaudit.h> +#endif + +#include "c.h" +#include "setproctitle.h" +#include "pathnames.h" +#include "strutils.h" +#include "nls.h" +#include "xalloc.h" +#include "all-io.h" +#include "fileutils.h" + +#include "logindefs.h" + +#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) + +#define LOGIN_MAX_TRIES 3 +#define LOGIN_EXIT_TIMEOUT 5 +#define LOGIN_TIMEOUT 60 + +#ifdef USE_TTY_GROUP +# define TTY_MODE 0620 +#else +# define TTY_MODE 0600 +#endif + +#define TTYGRPNAME "tty" /* name of group to own ttys */ +#define VCS_PATH_MAX 64 + +/* + * Login control struct + */ +struct login_context { + const char *tty_path; /* ttyname() return value */ + const char *tty_name; /* tty_path without /dev prefix */ + const char *tty_number; /* end of the tty_path */ + mode_t tty_mode; /* chmod() mode */ + + char *username; /* from command line or PAM */ + + struct passwd *pwd; /* user info */ + + pam_handle_t *pamh; /* PAM handler */ + struct pam_conv conv; /* PAM conversation */ + +#ifdef LOGIN_CHOWN_VCS + char vcsn[VCS_PATH_MAX]; /* virtual console name */ + char vcsan[VCS_PATH_MAX]; +#endif + + char thishost[MAXHOSTNAMELEN + 1]; /* this machine */ + char *thisdomain; /* this machine domain */ + char *hostname; /* remote machine */ + char hostaddress[16]; /* remote address */ + + pid_t pid; + int quiet; /* 1 is hush file exists */ + + unsigned int remote:1, /* login -h */ + nohost:1, /* login -H */ + noauth:1, /* login -f */ + keep_env:1; /* login -p */ +}; + +/* + * This bounds the time given to login. Not a define so it can + * be patched on machines where it's too small. + */ +static unsigned int timeout = LOGIN_TIMEOUT; +static int child_pid = 0; +static volatile int got_sig = 0; + +#ifdef LOGIN_CHOWN_VCS +/* true if the filedescriptor fd is a console tty, very Linux specific */ +static int is_consoletty(int fd) +{ + struct stat stb; + + if ((fstat(fd, &stb) >= 0) + && (major(stb.st_rdev) == TTY_MAJOR) + && (minor(stb.st_rdev) < 64)) { + return 1; + } + return 0; +} +#endif + + +/* + * Robert Ambrose writes: + * A couple of my users have a problem with login processes hanging around + * soaking up pts's. What they seem to hung up on is trying to write out the + * message 'Login timed out after %d seconds' when the connection has already + * been dropped. + * What I did was add a second timeout while trying to write the message so + * the process just exits if the second timeout expires. + */ +static void __attribute__ ((__noreturn__)) +timedout2(int sig __attribute__ ((__unused__))) +{ + struct termios ti; + + /* reset echo */ + tcgetattr(0, &ti); + ti.c_lflag |= ECHO; + tcsetattr(0, TCSANOW, &ti); + exit(EXIT_SUCCESS); /* %% */ +} + +static void timedout(int sig __attribute__ ((__unused__))) +{ + signal(SIGALRM, timedout2); + alarm(10); + /* TRANSLATORS: The standard value for %u is 60. */ + warnx(_("timed out after %u seconds"), timeout); + signal(SIGALRM, SIG_IGN); + alarm(0); + timedout2(0); +} + +/* + * This handler allows to inform a shell about signals to login. If you have + * (root) permissions you can kill all login childrent by one signal to login + * process. + * + * Also, parent who is session leader is able (before setsid() in child) to + * inform child when controlling tty goes away (e.g. modem hangup, SIGHUP). + */ +static void sig_handler(int signal) +{ + if (child_pid) + kill(-child_pid, signal); + else + got_sig = 1; + if (signal == SIGTERM) + kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */ +} + +/* + * Let use delay for all exit() calls when user is not authenticated or + * session fully initialized (loginpam_session()). + */ +static void __attribute__ ((__noreturn__)) sleepexit(int eval) +{ + sleep((unsigned int)getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT)); + exit(eval); +} + +static const char *get_thishost(struct login_context *cxt, const char **domain) +{ + if (!*cxt->thishost) { + if (gethostname(cxt->thishost, sizeof(cxt->thishost))) { + if (domain) + *domain = NULL; + return NULL; + } + cxt->thishost[sizeof(cxt->thishost) -1] = '\0'; + cxt->thisdomain = strchr(cxt->thishost, '.'); + if (cxt->thisdomain) + *cxt->thisdomain++ = '\0'; + } + + if (domain) + *domain = cxt->thisdomain; + return cxt->thishost; +} + +/* + * Output the /etc/motd file + * + * motd() determines the name of a login announcement file and outputs it to + * the user's terminal at login time. The MOTD_FILE configuration option is a + * colon-delimited list of filenames. The empty MOTD_FILE option disables motd + * printing at all. + */ +static void motd(void) +{ + char *motdlist, *motdfile; + const char *mb; + + mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE); + if (!mb || !*mb) + return; + + motdlist = xstrdup(mb); + + for (motdfile = strtok(motdlist, ":"); motdfile; + motdfile = strtok(NULL, ":")) { + + struct stat st; + int fd; + + if (stat(motdfile, &st) || !st.st_size) + continue; + fd = open(motdfile, O_RDONLY, 0); + if (fd < 0) + continue; + + sendfile(fileno(stdout), fd, NULL, st.st_size); + close(fd); + } + + free(motdlist); +} + +/* + * Nice and simple code provided by Linus Torvalds 16-Feb-93 + * Nonblocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999. + * + * He writes: "Login performs open() on a tty in a blocking mode. + * In some cases it may make login wait in open() for carrier infinitely, + * for example if the line is a simplistic case of a three-wire serial + * connection. I believe login should open the line in the non-blocking mode + * leaving the decision to make a connection to getty (where it actually + * belongs). + */ +static void open_tty(const char *tty) +{ + int i, fd, flags; + + fd = open(tty, O_RDWR | O_NONBLOCK); + if (fd == -1) { + syslog(LOG_ERR, _("FATAL: can't reopen tty: %m")); + sleepexit(EXIT_FAILURE); + } + + if (!isatty(fd)) { + close(fd); + syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty); + sleepexit(EXIT_FAILURE); + } + + flags = fcntl(fd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(fd, F_SETFL, flags); + + for (i = 0; i < fd; i++) + close(i); + for (i = 0; i < 3; i++) + if (fd != i) + dup2(fd, i); + if (fd >= 3) + close(fd); +} + +#define chown_err(_what, _uid, _gid) \ + syslog(LOG_ERR, _("chown (%s, %lu, %lu) failed: %m"), \ + (_what), (unsigned long) (_uid), (unsigned long) (_gid)) + +#define chmod_err(_what, _mode) \ + syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), (_what), (_mode)) + +static void chown_tty(struct login_context *cxt) +{ + const char *grname; + uid_t uid = cxt->pwd->pw_uid; + gid_t gid = cxt->pwd->pw_gid; + + grname = getlogindefs_str("TTYGROUP", TTYGRPNAME); + if (grname && *grname) { + struct group *gr = getgrnam(grname); + if (gr) /* group by name */ + gid = gr->gr_gid; + else /* group by ID */ + gid = (gid_t) getlogindefs_num("TTYGROUP", gid); + } + if (fchown(0, uid, gid)) /* tty */ + chown_err(cxt->tty_name, uid, gid); + if (fchmod(0, cxt->tty_mode)) + chmod_err(cxt->tty_name, cxt->tty_mode); + +#ifdef LOGIN_CHOWN_VCS + if (is_consoletty(0)) { + if (chown(cxt->vcsn, uid, gid)) /* vcs */ + chown_err(cxt->vcsn, uid, gid); + if (chmod(cxt->vcsn, cxt->tty_mode)) + chmod_err(cxt->vcsn, cxt->tty_mode); + + if (chown(cxt->vcsan, uid, gid)) /* vcsa */ + chown_err(cxt->vcsan, uid, gid); + if (chmod(cxt->vcsan, cxt->tty_mode)) + chmod_err(cxt->vcsan, cxt->tty_mode); + } +#endif +} + +/* + * Reads the currect terminal path and initialize cxt->tty_* variables. + */ +static void init_tty(struct login_context *cxt) +{ + const char *p; + struct stat st; + struct termios tt, ttt; + + cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE); + + cxt->tty_path = ttyname(0); /* libc calls istty() here */ + + /* + * In case login is suid it was possible to use a hardlink as stdin + * and exploit races for a local root exploit. (Wojciech Purczynski). + * + * More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn); + * here ttyname() might return "/tmp/x", a hardlink to a pseudotty. + * All of this is a problem only when login is suid, which it isn't. + */ + if (!cxt->tty_path || !*cxt->tty_path || + lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) || + (st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5)) || + access(cxt->tty_path, R_OK | W_OK) != 0) { + + syslog(LOG_ERR, _("FATAL: bad tty")); + sleepexit(EXIT_FAILURE); + } + + if (strncmp(cxt->tty_path, "/dev/", 5) == 0) + cxt->tty_name = cxt->tty_path + 5; + else + cxt->tty_name = cxt->tty_path; + + for (p = cxt->tty_name; p && *p; p++) { + if (isdigit(*p)) { + cxt->tty_number = p; + break; + } + } + +#ifdef LOGIN_CHOWN_VCS + if (cxt->tty_number) { + /* find names of Virtual Console devices, for later mode change */ + snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number); + snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number); + } +#endif + + tcgetattr(0, &tt); + ttt = tt; + ttt.c_cflag &= ~HUPCL; + + if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) { + + syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"), + cxt->tty_path); + sleepexit(EXIT_FAILURE); + } + + /* Kill processes left on this tty */ + tcsetattr(0, TCSAFLUSH, &ttt); + + /* + * Let's close file decriptors before vhangup + * https://lkml.org/lkml/2012/6/5/145 + */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + signal(SIGHUP, SIG_IGN); /* so vhangup() wont kill us */ + vhangup(); + signal(SIGHUP, SIG_DFL); + + /* open stdin,stdout,stderr to the tty */ + open_tty(cxt->tty_path); + + /* restore tty modes */ + tcsetattr(0, TCSAFLUSH, &tt); +} + + +/* + * Log failed login attempts in _PATH_BTMP if that exists. + * Must be called only with username the name of an actual user. + * The most common login failure is to give password instead of username. + */ +static void log_btmp(struct login_context *cxt) +{ + struct utmp ut; + struct timeval tv; + + memset(&ut, 0, sizeof(ut)); + + strncpy(ut.ut_user, + cxt->username ? cxt->username : "(unknown)", + sizeof(ut.ut_user)); + + if (cxt->tty_number) + strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id)); + if (cxt->tty_name) + xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); + +#if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */ + gettimeofday(&tv, NULL); + ut.ut_tv.tv_sec = tv.tv_sec; + ut.ut_tv.tv_usec = tv.tv_usec; +#else + { + time_t t; + time(&t); + ut.ut_time = t; /* ut_time is not always a time_t */ + } +#endif + + ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ + ut.ut_pid = cxt->pid; + + if (cxt->hostname) { + xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host)); + if (*cxt->hostaddress) + memcpy(&ut.ut_addr_v6, cxt->hostaddress, + sizeof(ut.ut_addr_v6)); + } + + updwtmp(_PATH_BTMP, &ut); +} + + +#ifdef HAVE_LIBAUDIT +static void log_audit(struct login_context *cxt, int status) +{ + int audit_fd; + struct passwd *pwd = cxt->pwd; + + audit_fd = audit_open(); + if (audit_fd == -1) + return; + if (!pwd && cxt->username) + pwd = getpwnam(cxt->username); + + audit_log_acct_message(audit_fd, + AUDIT_USER_LOGIN, + NULL, + "login", + cxt->username ? cxt->username : "(unknown)", + pwd ? pwd->pw_uid : (unsigned int) -1, + cxt->hostname, + NULL, + cxt->tty_name, + status); + + close(audit_fd); +} +#else /* !HAVE_LIBAUDIT */ +# define log_audit(cxt, status) +#endif /* HAVE_LIBAUDIT */ + +static void log_lastlog(struct login_context *cxt) +{ + struct lastlog ll; + time_t t; + int fd; + + if (!cxt->pwd) + return; + + fd = open(_PATH_LASTLOG, O_RDWR, 0); + if (fd < 0) + return; + + lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET); + + /* + * Print last log message + */ + if (!cxt->quiet) { + if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) && + ll.ll_time != 0) { + time_t ll_time = (time_t) ll.ll_time; + + printf(_("Last login: %.*s "), 24 - 5, ctime(&ll_time)); + if (*ll.ll_host != '\0') + printf(_("from %.*s\n"), + (int)sizeof(ll.ll_host), ll.ll_host); + else + printf(_("on %.*s\n"), + (int)sizeof(ll.ll_line), ll.ll_line); + } + lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET); + } + + memset((char *)&ll, 0, sizeof(ll)); + + time(&t); + ll.ll_time = t; /* ll_time is always 32bit */ + + if (cxt->tty_name) + xstrncpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line)); + if (cxt->hostname) + xstrncpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host)); + + if (write_all(fd, (char *)&ll, sizeof(ll))) + warn(_("write lastlog failed")); + + close(fd); +} + +/* + * Update wtmp and utmp logs + */ +static void log_utmp(struct login_context *cxt) +{ + struct utmp ut; + struct utmp *utp; + struct timeval tv; + + utmpname(_PATH_UTMP); + setutent(); + + /* Find pid in utmp. + * + * login sometimes overwrites the runlevel entry in /var/run/utmp, + * confusing sysvinit. I added a test for the entry type, and the + * problem was gone. (In a runlevel entry, st_pid is not really a pid + * but some number calculated from the previous and current runlevel). + * -- Michael Riepe <michael@stud.uni-hannover.de> + */ + while ((utp = getutent())) + if (utp->ut_pid == cxt->pid + && utp->ut_type >= INIT_PROCESS + && utp->ut_type <= DEAD_PROCESS) + break; + + /* If we can't find a pre-existing entry by pid, try by line. + * BSD network daemons may rely on this. + */ + if (utp == NULL) { + setutent(); + ut.ut_type = LOGIN_PROCESS; + if (cxt->tty_name) + strncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); + utp = getutline(&ut); + } + + if (utp) + memcpy(&ut, utp, sizeof(ut)); + else + /* some gettys/telnetds don't initialize utmp... */ + memset(&ut, 0, sizeof(ut)); + + if (cxt->tty_number && ut.ut_id[0] == 0) + strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id)); + if (cxt->username) + strncpy(ut.ut_user, cxt->username, sizeof(ut.ut_user)); + if (cxt->tty_name) + xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); + +#ifdef _HAVE_UT_TV /* in <utmpbits.h> included by <utmp.h> */ + gettimeofday(&tv, NULL); + ut.ut_tv.tv_sec = tv.tv_sec; + ut.ut_tv.tv_usec = tv.tv_usec; +#else + { + time_t t; + time(&t); + ut.ut_time = t; /* ut_time is not always a time_t */ + /* glibc2 #defines it as ut_tv.tv_sec */ + } +#endif + ut.ut_type = USER_PROCESS; + ut.ut_pid = cxt->pid; + if (cxt->hostname) { + xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host)); + if (*cxt->hostaddress) + memcpy(&ut.ut_addr_v6, cxt->hostaddress, + sizeof(ut.ut_addr_v6)); + } + + pututline(&ut); + endutent(); + + updwtmp(_PATH_WTMP, &ut); +} + +static void log_syslog(struct login_context *cxt) +{ + struct passwd *pwd = cxt->pwd; + + if (!cxt->tty_name) + return; + + if (!strncmp(cxt->tty_name, "ttyS", 4)) + syslog(LOG_INFO, _("DIALUP AT %s BY %s"), + cxt->tty_name, pwd->pw_name); + + if (!pwd->pw_uid) { + if (cxt->hostname) + syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"), + cxt->tty_name, cxt->hostname); + else + syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), cxt->tty_name); + } else { + if (cxt->hostname) + syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"), + cxt->tty_name, pwd->pw_name, cxt->hostname); + else + syslog(LOG_INFO, _("LOGIN ON %s BY %s"), cxt->tty_name, + pwd->pw_name); + } +} + +static struct passwd *get_passwd_entry(const char *username, + char **pwdbuf, + struct passwd *pwd) +{ + struct passwd *res = NULL; + size_t sz = 16384; + int x; + + if (!pwdbuf || !username) + return NULL; + +#ifdef _SC_GETPW_R_SIZE_MAX + { + long xsz = sysconf(_SC_GETPW_R_SIZE_MAX); + if (xsz > 0) + sz = (size_t) xsz; + } +#endif + *pwdbuf = xrealloc(*pwdbuf, sz); + + x = getpwnam_r(username, pwd, *pwdbuf, sz, &res); + if (!res) { + errno = x; + return NULL; + } + return res; +} + +/* encapsulate stupid "void **" pam_get_item() API */ +static int loginpam_get_username(pam_handle_t *pamh, char **name) +{ + const void *item = (void *)*name; + int rc; + rc = pam_get_item(pamh, PAM_USER, &item); + *name = (char *)item; + return rc; +} + +static void loginpam_err(pam_handle_t *pamh, int retcode) +{ + const char *msg = pam_strerror(pamh, retcode); + + if (msg) { + fprintf(stderr, "\n%s\n", msg); + syslog(LOG_ERR, "%s", msg); + } + pam_end(pamh, retcode); + sleepexit(EXIT_FAILURE); +} + +/* + * Composes "<host> login: " string; or returns "login: " is -H is given + */ +static const char *loginpam_get_prompt(struct login_context *cxt) +{ + const char *host; + char *prompt, *dflt_prompt = _("login: "); + size_t sz; + + if (cxt->nohost || !(host = get_thishost(cxt, NULL))) + return dflt_prompt; + + sz = strlen(host) + 1 + strlen(dflt_prompt) + 1; + + prompt = xmalloc(sz); + snprintf(prompt, sz, "%s %s", host, dflt_prompt); + + return prompt; +} + +static pam_handle_t *init_loginpam(struct login_context *cxt) +{ + pam_handle_t *pamh = NULL; + int rc; + + /* + * username is initialized to NULL and if specified on the command line + * it is set. Therefore, we are safe not setting it to anything + */ + rc = pam_start(cxt->remote ? "remote" : "login", + cxt->username, &cxt->conv, &pamh); + if (rc != PAM_SUCCESS) { + warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc)); + syslog(LOG_ERR, _("Couldn't initialize PAM: %s"), + pam_strerror(pamh, rc)); + sleepexit(EXIT_FAILURE); + } + + /* hostname & tty are either set to NULL or their correct values, + * depending on how much we know + */ + rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname); + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + rc = pam_set_item(pamh, PAM_TTY, cxt->tty_name); + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + /* + * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM so that + * the "login: " prompt gets localized. Unfortunately, PAM doesn't have + * an interface to specify the "Password: " string (yet). + */ + rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt)); + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + /* we need't the original username. We have to follow PAM. */ + free(cxt->username); + cxt->username = NULL; + cxt->pamh = pamh; + + return pamh; +} + +static void loginpam_auth(struct login_context *cxt) +{ + int rc, show_unknown; + unsigned int retries, failcount = 0; + const char *hostname = cxt->hostname ? cxt->hostname : + cxt->tty_name ? cxt->tty_name : "<unknown>"; + pam_handle_t *pamh = cxt->pamh; + + /* if we didn't get a user on the command line, set it to NULL */ + loginpam_get_username(pamh, &cxt->username); + + show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0); + retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES); + + /* + * There may be better ways to deal with some of these conditions, but + * at least this way I don't think we'll be giving away information... + * + * Perhaps someday we can trust that all PAM modules will pay attention + * to failure count and get rid of LOGIN_MAX_TRIES? + */ + rc = pam_authenticate(pamh, 0); + + while ((++failcount < retries) && + ((rc == PAM_AUTH_ERR) || + (rc == PAM_USER_UNKNOWN) || + (rc == PAM_CRED_INSUFFICIENT) || + (rc == PAM_AUTHINFO_UNAVAIL))) { + + if (rc == PAM_USER_UNKNOWN && !show_unknown) + /* + * logging unknown usernames may be a security issue if + * an user enter her password instead of her login name + */ + cxt->username = NULL; + else + loginpam_get_username(pamh, &cxt->username); + + syslog(LOG_NOTICE, + _("FAILED LOGIN %u FROM %s FOR %s, %s"), + failcount, hostname, + cxt->username ? cxt->username : "(unknown)", + pam_strerror(pamh, rc)); + + log_btmp(cxt); + log_audit(cxt, 0); + + fprintf(stderr, _("Login incorrect\n\n")); + + pam_set_item(pamh, PAM_USER, NULL); + rc = pam_authenticate(pamh, 0); + } + + if (is_pam_failure(rc)) { + + if (rc == PAM_USER_UNKNOWN && !show_unknown) + cxt->username = NULL; + else + loginpam_get_username(pamh, &cxt->username); + + if (rc == PAM_MAXTRIES) + syslog(LOG_NOTICE, + _("TOO MANY LOGIN TRIES (%u) FROM %s FOR %s, %s"), + failcount, hostname, + cxt->username ? cxt->username : "(unknown)", + pam_strerror(pamh, rc)); + else + syslog(LOG_NOTICE, + _("FAILED LOGIN SESSION FROM %s FOR %s, %s"), + hostname, + cxt->username ? cxt->username : "(unknown)", + pam_strerror(pamh, rc)); + + log_btmp(cxt); + log_audit(cxt, 0); + + fprintf(stderr, _("\nLogin incorrect\n")); + pam_end(pamh, rc); + sleepexit(EXIT_SUCCESS); + } +} + +static void loginpam_acct(struct login_context *cxt) +{ + int rc; + pam_handle_t *pamh = cxt->pamh; + + rc = pam_acct_mgmt(pamh, 0); + + if (rc == PAM_NEW_AUTHTOK_REQD) + rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + /* + * Grab the user information out of the password file for future usage + * First get the username that we are actually using, though. + */ + rc = loginpam_get_username(pamh, &cxt->username); + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + if (!cxt->username || !*cxt->username) { + warnx(_("\nSession setup problem, abort.")); + syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."), + __FUNCTION__, __LINE__); + pam_end(pamh, PAM_SYSTEM_ERR); + sleepexit(EXIT_FAILURE); + } +} + +/* + * Note that position of the pam_setcred() call is discussable: + * + * - the PAM docs recommends pam_setcred() before pam_open_session() + * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt + * uses pam_setcred() after pam_open_session() + * + * The old login versions (before year 2011) followed the RFC. This is probably + * not optimal, because there could be dependence between some session modules + * and user's credentials. + * + * The best is probably to follow openssh and call pam_setcred() before and + * after pam_open_session(). -- kzak@redhat.com (18-Nov-2011) + * + */ +static void loginpam_session(struct login_context *cxt) +{ + int rc; + pam_handle_t *pamh = cxt->pamh; + + rc = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (is_pam_failure(rc)) + loginpam_err(pamh, rc); + + rc = pam_open_session(pamh, 0); + if (is_pam_failure(rc)) { + pam_setcred(cxt->pamh, PAM_DELETE_CRED); + loginpam_err(pamh, rc); + } + + rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED); + if (is_pam_failure(rc)) { + pam_close_session(pamh, 0); + loginpam_err(pamh, rc); + } +} + +/* + * We need to check effective UID/GID. For example $HOME could be on root + * squashed NFS or on NFS with UID mapping and access(2) uses real UID/GID. + * The open(2) seems as the surest solution. + * -- kzak@redhat.com (10-Apr-2009) + */ +static int effective_access(const char *path, int mode) +{ + int fd = open(path, mode); + if (fd != -1) + close(fd); + return fd == -1 ? -1 : 0; +} + +/* + * Check per accout or global hush-login setting. + * + * Hushed mode is enabled: + * + * a) if global (e.g. /etc/hushlogins) hush file exists: + * 1) for ALL ACCOUNTS if the file is empty + * 2) for the current user if the username or shell are found in the file + * + * b) if ~/.hushlogin file exists + * + * The ~/.hushlogin is ignored if the global hush file exists. + * + * The HUSHLOGIN_FILE login.def variable overwrites the default hush filename. + * + * Note that shadow-utils login(1) does not support "a1)". The "a1)" is + * necessary if you want to use PAM for "Last login" message. + * + * -- Karel Zak <kzak@redhat.com> (26-Aug-2011) + * + * + * Per-account check requires some explanation: As root we may not be able to + * read the directory of the user if it is on an NFS mounted filesystem. We + * temporarily set our effective uid to the user-uid making sure that we keep + * root privs. in the real uid. + * + * A portable solution would require a fork(), but we rely on Linux having the + * BSD setreuid() + */ +static int get_hushlogin_status(struct passwd *pwd) +{ + const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL }; + const char *file; + char buf[BUFSIZ]; + int i; + + file = getlogindefs_str("HUSHLOGIN_FILE", NULL); + if (file) { + if (!*file) + return 0; /* empty HUSHLOGIN_FILE defined */ + + files[0] = file; + files[1] = NULL; + } + + for (i = 0; files[i]; i++) { + int ok = 0; + + file = files[i]; + + /* Global hush-file*/ + if (*file == '/') { + struct stat st; + FILE *f; + + if (stat(file, &st) != 0) + continue; /* file does not exist */ + + if (st.st_size == 0) + return 1; /* for all accounts */ + + f = fopen(file, "r"); + if (!f) + continue; /* ignore errors... */ + + while (ok == 0 && fgets(buf, sizeof(buf), f)) { + buf[strlen(buf) - 1] = '\0'; + ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell : + pwd->pw_name); + } + fclose(f); + if (ok) + return 1; /* found username/shell */ + + return 0; /* ignore per-account files */ + } + + /* Per-account setting */ + if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf)) + continue; + else { + uid_t ruid = getuid(); + gid_t egid = getegid(); + + sprintf(buf, "%s/%s", pwd->pw_dir, file); + setregid(-1, pwd->pw_gid); + setreuid(0, pwd->pw_uid); + ok = effective_access(buf, O_RDONLY) == 0; + setuid(0); /* setreuid doesn't do it alone! */ + setreuid(ruid, 0); + setregid(-1, egid); + + if (ok) + return 1; /* enabled by user */ + } + } + + return 0; +} + +/* + * Detach the controlling terminal, fork, restore syslog stuff and create a new + * session. + */ +static void fork_session(struct login_context *cxt) +{ + struct sigaction sa, oldsa_hup, oldsa_term; + + signal(SIGALRM, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTSTP, SIG_IGN); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sigaction(SIGINT, &sa, NULL); + + sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */ + + /* + * detach the controlling tty + * -- we needn't the tty in parent who waits for child only. + * The child calls setsid() that detach from the tty as well. + */ + ioctl(0, TIOCNOTTY, NULL); + + /* + * We have care about SIGTERM, because leave PAM session without + * pam_close_session() is pretty bad thing. + */ + sa.sa_handler = sig_handler; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGTERM, &sa, &oldsa_term); + + closelog(); + + /* + * We must fork before setuid() because we need to call + * pam_close_session() as root. + */ + child_pid = fork(); + if (child_pid < 0) { + /* + * fork() error + */ + warn(_("fork failed")); + + pam_setcred(cxt->pamh, PAM_DELETE_CRED); + pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0)); + sleepexit(EXIT_FAILURE); + } + + if (child_pid) { + /* + * parent - wait for child to finish, then cleanup session + */ + close(0); + close(1); + close(2); + sa.sa_handler = SIG_IGN; + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + /* wait as long as any child is there */ + while (wait(NULL) == -1 && errno == EINTR) ; + openlog("login", LOG_ODELAY, LOG_AUTHPRIV); + + pam_setcred(cxt->pamh, PAM_DELETE_CRED); + pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0)); + exit(EXIT_SUCCESS); + } + + /* + * child + */ + sigaction(SIGHUP, &oldsa_hup, NULL); /* restore old state */ + sigaction(SIGTERM, &oldsa_term, NULL); + if (got_sig) + exit(EXIT_FAILURE); + + /* + * Problem: if the user's shell is a shell like ash that doesn't do + * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every + * process in the pgrp, will kill us. + */ + + /* start new session */ + setsid(); + + /* make sure we have a controlling tty */ + open_tty(cxt->tty_path); + openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */ + + /* + * TIOCSCTTY: steal tty from other process group. + */ + if (ioctl(0, TIOCSCTTY, 1)) + syslog(LOG_ERR, _("TIOCSCTTY failed: %m")); + signal(SIGINT, SIG_DFL); +} + +/* + * Initialize $TERM, $HOME, ... + */ +static void init_environ(struct login_context *cxt) +{ + struct passwd *pwd = cxt->pwd; + char *termenv = NULL, **env; + char tmp[PATH_MAX]; + int len, i; + + termenv = getenv("TERM"); + termenv = termenv ? xstrdup(termenv) : "dumb"; + + /* destroy environment unless user has requested preservation (-p) */ + if (!cxt->keep_env) { + environ = (char **) xmalloc(sizeof(char *)); + memset(environ, 0, sizeof(char *)); + } + + setenv("HOME", pwd->pw_dir, 0); /* legal to override */ + setenv("USER", pwd->pw_name, 1); + setenv("SHELL", pwd->pw_shell, 1); + setenv("TERM", termenv, 1); + + if (pwd->pw_uid) + logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH); + + else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0) + logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT); + + /* mailx will give a funny error msg if you forget this one */ + len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name); + if (len > 0 && (size_t) len + 1 <= sizeof(tmp)) + setenv("MAIL", tmp, 0); + + /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll + * not allow modifying it. + */ + setenv("LOGNAME", pwd->pw_name, 1); + + env = pam_getenvlist(cxt->pamh); + for (i = 0; env && env[i]; i++) + putenv(env[i]); +} + +/* + * Called for -h option, initialize cxt->{hostname,hostaddress} + */ +static void init_remote_info(struct login_context *cxt, char *remotehost) +{ + const char *domain; + char *p; + struct addrinfo hints, *info = NULL; + + cxt->remote = 1; + + get_thishost(cxt, &domain); + + if (domain && (p = strchr(remotehost, '.')) && + strcasecmp(p + 1, domain) == 0) + *p = '\0'; + + cxt->hostname = xstrdup(remotehost); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + cxt->hostaddress[0] = 0; + + if (getaddrinfo(cxt->hostname, NULL, &hints, &info) == 0 && info) { + if (info->ai_family == AF_INET) { + struct sockaddr_in *sa = + (struct sockaddr_in *) info->ai_addr; + + memcpy(cxt->hostaddress, &(sa->sin_addr), sizeof(sa->sin_addr)); + + } else if (info->ai_family == AF_INET6) { + struct sockaddr_in6 *sa = + (struct sockaddr_in6 *) info->ai_addr; + + memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr)); + } + freeaddrinfo(info); + } +} + +int main(int argc, char **argv) +{ + int c; + int cnt; + char *childArgv[10]; + char *buff; + int childArgc = 0; + int retcode; + + char *pwdbuf = NULL; + struct passwd *pwd = NULL, _pwd; + + struct login_context cxt = { + .tty_mode = TTY_MODE, /* tty chmod() */ + .pid = getpid(), /* PID */ + .conv = { misc_conv, NULL } /* PAM conversation function */ + }; + + timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT); + + signal(SIGALRM, timedout); + siginterrupt(SIGALRM, 1); /* we have to interrupt syscalls like ioclt() */ + alarm(timeout); + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, SIG_IGN); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + setpriority(PRIO_PROCESS, 0, 0); + initproctitle(argc, argv); + + /* + * -p is used by getty to tell login not to destroy the environment + * -f is used to skip a second login authentication + * -h is used by other servers to pass the name of the remote + * host to login so that it may be placed in utmp and wtmp + */ + while ((c = getopt(argc, argv, "fHh:pV")) != -1) + switch (c) { + case 'f': + cxt.noauth = 1; + break; + + case 'H': + cxt.nohost = 1; + break; + + case 'h': + if (getuid()) { + fprintf(stderr, + _("login: -h for super-user only.\n")); + exit(EXIT_FAILURE); + } + init_remote_info(&cxt, optarg); + break; + + case 'p': + cxt.keep_env = 1; + break; + + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case '?': + default: + fprintf(stderr, _("usage: login [ -p ] [ -h host ] [ -H ] [ -f username | username ]\n")); + exit(EXIT_FAILURE); + } + argc -= optind; + argv += optind; + + if (*argv) { + char *p = *argv; + cxt.username = xstrdup(p); + + /* wipe name - some people mistype their password here */ + /* (of course we are too late, but perhaps this helps a little ..) */ + while (*p) + *p++ = ' '; + } + + for (cnt = get_fd_tabsize() - 1; cnt > 2; cnt--) + close(cnt); + + setpgrp(); /* set pgid to pid this means that setsid() will fail */ + + openlog("login", LOG_ODELAY, LOG_AUTHPRIV); + + init_tty(&cxt); + init_loginpam(&cxt); + + /* login -f, then the user has already been authenticated */ + cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0; + + if (!cxt.noauth) + loginpam_auth(&cxt); + + /* + * Authentication may be skipped (for example, during krlogin, rlogin, + * etc...), but it doesn't mean that we can skip other account checks. + * The account could be disabled or password expired (although + * kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006) + */ + loginpam_acct(&cxt); + + if (!(cxt.pwd = get_passwd_entry(cxt.username, &pwdbuf, &_pwd))) { + warnx(_("\nSession setup problem, abort.")); + syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."), + cxt.username, __FUNCTION__, __LINE__); + pam_end(cxt.pamh, PAM_SYSTEM_ERR); + sleepexit(EXIT_FAILURE); + } + + pwd = cxt.pwd; + cxt.username = pwd->pw_name; + + /* + * Initialize the supplementary group list. This should be done before + * pam_setcred because the PAM modules might add groups during + * pam_setcred. + * + * For root we don't call initgroups, instead we call setgroups with + * group 0. This avoids the need to step through the whole group file, + * which can cause problems if NIS, NIS+, LDAP or something similar + * is used and the machine has network problems. + */ + retcode = pwd->pw_uid ? initgroups(cxt.username, pwd->pw_gid) : /* user */ + setgroups(0, NULL); /* root */ + if (retcode < 0) { + syslog(LOG_ERR, _("groups initialization failed: %m")); + warnx(_("\nSession setup problem, abort.")); + pam_end(cxt.pamh, PAM_SYSTEM_ERR); + sleepexit(EXIT_FAILURE); + } + + /* + * Open PAM session (after successful authentication and account check) + */ + loginpam_session(&cxt); + + /* committed to login -- turn off timeout */ + alarm((unsigned int)0); + + endpwent(); + + cxt.quiet = get_hushlogin_status(pwd); + + log_utmp(&cxt); + log_audit(&cxt, 1); + log_lastlog(&cxt); + + chown_tty(&cxt); + + if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) { + syslog(LOG_ALERT, _("setgid() failed")); + exit(EXIT_FAILURE); + } + + if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0') + pwd->pw_shell = _PATH_BSHELL; + + init_environ(&cxt); /* init $HOME, $TERM ... */ + + setproctitle("login", cxt.username); + + log_syslog(&cxt); + + if (!cxt.quiet) { + motd(); + +#ifdef LOGIN_STAT_MAIL + /* + * This turns out to be a bad idea: when the mail spool + * is NFS mounted, and the NFS connection hangs, the + * login hangs, even root cannot login. + * Checking for mail should be done from the shell. + */ + { + struct stat st; + char *mail; + + mail = getenv("MAIL"); + if (mail && stat(mail, &st) == 0 && st.st_size != 0) { + if (st.st_mtime > st.st_atime) + printf(_("You have new mail.\n")); + else + printf(_("You have mail.\n")); + } + } +#endif + } + + /* + * Detach the controlling terminal, fork() and create, new session + * and reinilizalize syslog stuff. + */ + fork_session(&cxt); + + /* discard permissions last so can't get killed and drop core */ + if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) { + syslog(LOG_ALERT, _("setuid() failed")); + exit(EXIT_FAILURE); + } + + /* wait until here to change directory! */ + if (chdir(pwd->pw_dir) < 0) { + warn(_("%s: change directory failed"), pwd->pw_dir); + + if (!getlogindefs_bool("DEFAULT_HOME", 1)) + exit(0); + if (chdir("/")) + exit(EXIT_FAILURE); + pwd->pw_dir = "/"; + printf(_("Logging in with home = \"/\".\n")); + } + + /* if the shell field has a space: treat it like a shell script */ + if (strchr(pwd->pw_shell, ' ')) { + buff = xmalloc(strlen(pwd->pw_shell) + 6); + + strcpy(buff, "exec "); + strcat(buff, pwd->pw_shell); + childArgv[childArgc++] = "/bin/sh"; + childArgv[childArgc++] = "-sh"; + childArgv[childArgc++] = "-c"; + childArgv[childArgc++] = buff; + } else { + char tbuf[PATH_MAX + 2], *p; + + tbuf[0] = '-'; + xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ? + p + 1 : pwd->pw_shell), sizeof(tbuf) - 1); + + childArgv[childArgc++] = pwd->pw_shell; + childArgv[childArgc++] = xstrdup(tbuf); + } + + childArgv[childArgc++] = NULL; + + execvp(childArgv[0], childArgv + 1); + + if (!strcmp(childArgv[0], "/bin/sh")) + warn(_("couldn't exec shell script")); + else + warn(_("no shell")); + + exit(EXIT_SUCCESS); +} + + diff --git a/login-utils/logindefs.c b/login-utils/logindefs.c new file mode 100644 index 0000000..84f8d93 --- /dev/null +++ b/login-utils/logindefs.c @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2003, 2004, 2005 Thorsten Kukuk + * Author: Thorsten Kukuk <kukuk@suse.de> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain any existing copyright + * notice, and this entire permission notice in its entirety, + * including the disclaimer of warranties. + * + * 2. Redistributions in binary form must reproduce all prior and current + * copyright notices, this list of conditions, and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. The name of any author may not be used to endorse or promote + * products derived from this software without their specific prior + * written permission. + */ +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/syslog.h> + +#include "c.h" +#include "closestream.h" +#include "logindefs.h" +#include "nls.h" +#include "pathnames.h" +#include "xalloc.h" + +struct item { + char *name; /* name of the option. */ + char *value; /* value of the option. */ + char *path; /* name of config file for this option. */ + + struct item *next; /* pointer to next option. */ +}; + +static struct item *list = NULL; + +void (*logindefs_load_defaults)(void) = NULL; + +void free_getlogindefs_data(void) +{ + struct item *ptr; + + ptr = list; + while (ptr) { + struct item *tmp = ptr->next; + + free(ptr->path); + free(ptr->name); + free(ptr->value); + free(ptr); + ptr = tmp; + } + + list = NULL; +} + +static void store(const char *name, const char *value, const char *path) +{ + struct item *new = xmalloc(sizeof(struct item)); + + if (!name) + abort(); + + new->name = xstrdup(name); + new->value = value && *value ? xstrdup(value) : NULL; + new->path = xstrdup(path); + new->next = list; + list = new; +} + +void logindefs_load_file(const char *filename) +{ + FILE *f; + char buf[BUFSIZ]; + + f = fopen(filename, "r"); + if (!f) + return; + + while (fgets(buf, sizeof(buf), f)) { + + char *p, *name, *data = NULL; + + if (*buf == '#' || *buf == '\n') + continue; /* only comment or empty line */ + + p = strchr(buf, '#'); + if (p) + *p = '\0'; + else { + size_t n = strlen(buf); + if (n && *(buf + n - 1) == '\n') + *(buf + n - 1) = '\0'; + } + + if (!*buf) + continue; /* empty line */ + + /* ignore space at begin of the line */ + name = buf; + while (*name && isspace((unsigned)*name)) + name++; + + /* go to the end of the name */ + data = name; + while (*data && !(isspace((unsigned)*data) || *data == '=')) + data++; + if (data > name && *data) + *data++ = '\0'; + + if (!*name || data == name) + continue; + + /* go to the begin of the value */ + while (*data + && (isspace((unsigned)*data) || *data == '=' + || *data == '"')) + data++; + + /* remove space at the end of the value */ + p = data + strlen(data); + if (p > data) + p--; + while (p > data && (isspace((unsigned)*p) || *p == '"')) + *p-- = '\0'; + + store(name, data, filename); + } + + fclose(f); +} + +static void load_defaults() +{ + if (logindefs_load_defaults) + logindefs_load_defaults(); + else + logindefs_load_file(_PATH_LOGINDEFS); +} + +static struct item *search(const char *name) +{ + struct item *ptr; + + if (!list) + load_defaults(); + + ptr = list; + while (ptr != NULL) { + if (strcasecmp(name, ptr->name) == 0) + return ptr; + ptr = ptr->next; + } + + return NULL; +} + +static const char *search_config(const char *name) +{ + struct item *ptr; + + ptr = list; + while (ptr != NULL) { + if (strcasecmp(name, ptr->name) == 0) + return ptr->path; + ptr = ptr->next; + } + + return NULL; +} + +int getlogindefs_bool(const char *name, int dflt) +{ + struct item *ptr = search(name); + return ptr && ptr->value ? (strcasecmp(ptr->value, "yes") == 0) : dflt; +} + +unsigned long getlogindefs_num(const char *name, long dflt) +{ + struct item *ptr = search(name); + char *end = NULL; + unsigned long retval; + + if (!ptr || !ptr->value) + return dflt; + + errno = 0; + retval = strtoul(ptr->value, &end, 0); + if (end && *end == '\0' && !errno) + return retval; + + syslog(LOG_NOTICE, _("%s: %s contains invalid numerical value: %s"), + search_config(name), name, ptr->value); + return dflt; +} + +/* + * Returns: + * @dflt if @name not found + * "" (empty string) if found, but value not defined + * "string" if found + */ +const char *getlogindefs_str(const char *name, const char *dflt) +{ + struct item *ptr = search(name); + + if (!ptr) + return dflt; + if (!ptr->value) + return ""; + return ptr->value; +} + +/* + * For compatibility with shadow-utils we have to support additional + * syntax for environment variables in login.defs(5) file. The standard + * syntax is: + * + * ENV_FOO data + * + * but shadow-utils supports also + * + * ENV_FOO FOO=data + * + * the FOO= prefix has to be remove before we call setenv(). + */ +int logindefs_setenv(const char *name, const char *conf, const char *dflt) +{ + const char *val = getlogindefs_str(conf, dflt); + const char *p; + + if (!val) + return -1; + + p = strchr(val, '='); + if (p) { + size_t sz = strlen(name); + + if (strncmp(val, name, sz) == 0 && *(p + 1)) { + val = p + 1; + if (*val == '"') + val++; + if (!*val) + val = dflt; + } + } + + return val ? setenv(name, val, 1) : -1; +} + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + char *name, *type; + atexit(close_stdout); + + if (argc <= 1) + errx(EXIT_FAILURE, "usage: %s <filename> " + "[<str|num|bool> <valname>]", argv[0]); + + logindefs_load_file(argv[1]); + + if (argc != 4) { /* list all */ + struct item *ptr; + + for (ptr = list; ptr; ptr = ptr->next) + printf("%s: $%s: '%s'\n", ptr->path, ptr->name, + ptr->value); + + return EXIT_SUCCESS; + } + + type = argv[2]; + name = argv[3]; + + if (strcmp(type, "str") == 0) + printf("$%s: '%s'\n", name, getlogindefs_str(name, "DEFAULT")); + else if (strcmp(type, "num") == 0) + printf("$%s: '%ld'\n", name, getlogindefs_num(name, 0)); + else if (strcmp(type, "bool") == 0) + printf("$%s: '%s'\n", name, + getlogindefs_bool(name, 0) ? "Y" : "N"); + + return EXIT_SUCCESS; +} +#endif diff --git a/login-utils/logindefs.h b/login-utils/logindefs.h new file mode 100644 index 0000000..c5ccbc9 --- /dev/null +++ b/login-utils/logindefs.h @@ -0,0 +1,12 @@ +#ifndef UTIL_LINUX_LOGINDEFS_H +#define UTIL_LINUX_LOGINDEFS_H + +extern void logindefs_load_file(const char *filename); +extern void (*logindefs_load_defaults)(void); +extern int getlogindefs_bool(const char *name, int dflt); +extern unsigned long getlogindefs_num(const char *name, long dflt); +extern const char *getlogindefs_str(const char *name, const char *dflt); +extern void free_getlogindefs_data(void); +extern int logindefs_setenv(const char *name, const char *conf, const char *dflt); + +#endif /* UTIL_LINUX_LOGINDEFS_H */ diff --git a/login-utils/newgrp.1 b/login-utils/newgrp.1 new file mode 100644 index 0000000..bb5eb00 --- /dev/null +++ b/login-utils/newgrp.1 @@ -0,0 +1,34 @@ +.\" Original author unknown. This man page is in the public domain. +.\" Modified Sat Oct 9 17:46:48 1993 by faith@cs.unc.edu +.TH NEWGRP 1 "October 1993" "util-linux" "User Commands" +.SH NAME +newgrp \- log in to a new group +.SH SYNOPSIS +.B newgrp +.RI [ group ] +.SH DESCRIPTION +.B newgrp +changes the group identification of its caller, analogously to +.BR login (1). +The same person remains logged in, and the current directory +is unchanged, but calculations of access permissions to files are performed +with respect to the new group ID. +.LP +If no group is specified, the GID is changed to the login GID. +.LP +.SH FILES +.I /etc/group +.br +.I /etc/passwd + +.SH "SEE ALSO" +.BR login (1), +.BR group (5) + +.SH AUTHOR +Originally by Michael Haardt. Currently maintained by +Peter Orbaek (poe@daimi.aau.dk). + +.SH AVAILABILITY +The newgrp command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/login-utils/newgrp.c b/login-utils/newgrp.c new file mode 100644 index 0000000..4f6de12 --- /dev/null +++ b/login-utils/newgrp.c @@ -0,0 +1,185 @@ +/* setgrp.c - by Michael Haardt. Set the gid if possible + * Added a bit more error recovery/reporting - poe + * Vesa Roukonen added code for asking password */ + +/* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + */ + +/* + * This command is deprecated. The utility is in maintenance mode, + * meaning we keep them in source tree for backward compatibility + * only. Do not waste time making this command better, unless the + * fix is about security or other very critical issue. + * + * See Documentation/deprecated.txt for more information. + */ + +#include <errno.h> +#include <getopt.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_CRYPT_H +# include <crypt.h> +#endif + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pathnames.h" + +/* try to read password from gshadow */ +static char *get_gshadow_pwd(char *groupname) +{ + char buf[BUFSIZ]; + char *pwd = NULL; + FILE *f; + + if (groupname == NULL || *groupname == '\0') + return NULL; + + f = fopen(_PATH_GSHADOW, "r"); + if (!f) + return NULL; + + while (fgets(buf, sizeof buf, f)) { + char *cp = strchr(buf, ':'); + if (!cp) + /* any junk in gshadow? */ + continue; + *cp = '\0'; + if (strcmp(buf, groupname) == 0) { + if (cp - buf >= BUFSIZ) + /* only group name on line */ + break; + pwd = cp + 1; + if ((cp = strchr(pwd, ':')) && pwd == cp + 1) + /* empty password */ + pwd = NULL; + else if (cp) + *cp = '\0'; + break; + } + } + fclose(f); + return pwd ? strdup(pwd) : NULL; +} + +static int allow_setgid(struct passwd *pe, struct group *ge) +{ + char **look; + int notfound = 1; + char *pwd, *xpwd; + + if (getuid() == 0) + /* root may do anything */ + return TRUE; + if (ge->gr_gid == pe->pw_gid) + /* You can switch back to your default group */ + return TRUE; + + look = ge->gr_mem; + while (*look && (notfound = strcmp(*look++, pe->pw_name))) ; + + if (!notfound) + /* member of group => OK */ + return TRUE; + + /* Ask for password. Often there is no password in /etc/group, so + * contrary to login et al. we let an empty password mean the same + * as in /etc/passwd */ + + /* check /etc/gshadow */ + if (!(pwd = get_gshadow_pwd(ge->gr_name))) + pwd = ge->gr_passwd; + + if (pwd && *pwd && (xpwd = getpass(_("Password: ")))) + if (strcmp(pwd, crypt(xpwd, pwd)) == 0) + /* password accepted */ + return TRUE; + + /* default to denial */ + return FALSE; +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fprintf(out, USAGE_HEADER); + fprintf(out, _(" %s <group>\n"), program_invocation_short_name); + fprintf(out, USAGE_OPTIONS); + fprintf(out, USAGE_HELP); + fprintf(out, USAGE_VERSION); + fprintf(out, USAGE_MAN_TAIL("newgrp(1)")); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct passwd *pw_entry; + struct group *gr_entry; + char *shell; + char ch; + static const struct option longopts[] = { + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (ch) { + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + if (!(pw_entry = getpwuid(getuid()))) + err(EXIT_FAILURE, _("who are you?")); + + shell = (pw_entry->pw_shell && *pw_entry->pw_shell ? + pw_entry->pw_shell : _PATH_BSHELL); + + if (argc < 2) { + if (setgid(pw_entry->pw_gid) < 0) + err(EXIT_FAILURE, _("setgid failed")); + } else { + errno = 0; + if (!(gr_entry = getgrnam(argv[1]))) { + if (errno) + err(EXIT_FAILURE, _("no such group")); + else + /* No group */ + errx(EXIT_FAILURE, _("no such group")); + } else { + if (allow_setgid(pw_entry, gr_entry)) { + if (setgid(gr_entry->gr_gid) < 0) + err(EXIT_FAILURE, _("setgid failed")); + } else + errx(EXIT_FAILURE, _("permission denied")); + } + } + + if (setuid(getuid()) < 0) + err(EXIT_FAILURE, _("setuid failed")); + + fflush(stdout); + fflush(stderr); + execl(shell, shell, (char *)0); + warn(_("exec %s failed"), shell); + fflush(stderr); + + return EXIT_FAILURE; +} diff --git a/login-utils/selinux_utils.c b/login-utils/selinux_utils.c new file mode 100644 index 0000000..e709d00 --- /dev/null +++ b/login-utils/selinux_utils.c @@ -0,0 +1,51 @@ +#include <selinux/av_permissions.h> +#include <selinux/context.h> +#include <selinux/flask.h> +#include <selinux/selinux.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "selinux_utils.h" + +int checkAccess(char *chuser, int access) +{ + int status = -1; + security_context_t user_context; + const char *user = NULL; + if (getprevcon(&user_context) == 0) { + context_t c = context_new(user_context); + user = context_user_get(c); + if (strcmp(chuser, user) == 0) { + status = 0; + } else { + struct av_decision avd; + int retval = security_compute_av(user_context, + user_context, + SECCLASS_PASSWD, + access, + &avd); + if ((retval == 0) && + ((access & avd.allowed) == (unsigned)access)) + status = 0; + } + context_free(c); + freecon(user_context); + } + return status; +} + +int setupDefaultContext(char *orig_file) +{ + if (is_selinux_enabled() > 0) { + security_context_t scontext; + if (getfilecon(orig_file, &scontext) < 0) + return 1; + if (setfscreatecon(scontext) < 0) { + freecon(scontext); + return 1; + } + freecon(scontext); + } + return 0; +} diff --git a/login-utils/selinux_utils.h b/login-utils/selinux_utils.h new file mode 100644 index 0000000..5bf393c --- /dev/null +++ b/login-utils/selinux_utils.h @@ -0,0 +1,2 @@ +extern int checkAccess(char *name,int access); +extern int setupDefaultContext(char *orig_file); diff --git a/login-utils/setpwnam.c b/login-utils/setpwnam.c new file mode 100644 index 0000000..23aef53 --- /dev/null +++ b/login-utils/setpwnam.c @@ -0,0 +1,218 @@ +/* + * setpwnam.c -- edit an entry in a password database. + * + * (c) 1994 Salvatore Valente <svalente@mit.edu> + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Edited 11/10/96 (DD/MM/YY ;-) by Nicolai Langfeldt (janl@math.uio.no) + * to read /etc/passwd directly so that passwd, chsh and chfn can work on + * machines that run NIS (né YP). Changes will not be made to usernames + * starting with +. + * + * This file is distributed with no warranty. + * + * Usage: + * 1) get a struct passwd * from getpwnam(). + * You should assume a struct passwd has an infinite number of fields, so + * you should not try to create one from scratch. + * 2) edit the fields you want to edit. + * 3) call setpwnam() with the edited struct passwd. + * + * A _normal user_ program should never directly manipulate etc/passwd but + * /use getpwnam() and (family, as well as) setpwnam(). + * + * But, setpwnam was made to _edit_ the password file. For use by chfn, + * chsh and passwd. _I_ _HAVE_ to read and write /etc/passwd directly. Let + * those who say nay be forever silent and think about how getpwnam (and + * family) works on a machine running YP. + * + * Added checks for failure of malloc() and removed error reporting to + * stderr, this is a library function and should not print on the screen, + * but return appropriate error codes. + * 27-Jan-97 - poe@daimi.aau.dk + * + * Thanks to "two guys named Ian". + * + * $Author: poer $ + * $Revision: 1.13 $ + * $Date: 1997/06/23 08:26:29 $ + */ + +#undef DEBUG + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <pwd.h> +#include <shadow.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "fileutils.h" +#include "closestream.h" +#include "setpwnam.h" + +static void pw_init(void); + +/* + * setpwnam () -- + * takes a struct passwd in which every field is filled in and valid. + * If the given username exists in the passwd file, the entry is + * replaced with the given entry. + */ +int setpwnam(struct passwd *pwd) +{ + FILE *fp = NULL, *pwf = NULL; + int save_errno; + int found; + int namelen; + int buflen = 256; + int contlen, rc; + char *linebuf = NULL; + char *tmpname = NULL; + char *atomic_dir = "/etc"; + + pw_init(); + + if ((fp = xfmkstemp(&tmpname, atomic_dir)) == NULL) + return -1; + + /* ptmp should be owned by root.root or root.wheel */ + if (fchown(fileno(fp), (uid_t) 0, (gid_t) 0) < 0) + goto fail; + + /* acquire exclusive lock */ + if (lckpwdf() < 0) + goto fail; + pwf = fopen(PASSWD_FILE, "r"); + if (!pwf) + goto fail; + + namelen = strlen(pwd->pw_name); + + linebuf = malloc(buflen); + if (!linebuf) + goto fail; + + /* parse the passwd file */ + found = false; + + /* Do you wonder why I don't use getpwent? Read comments at top of + * file */ + while (fgets(linebuf, buflen, pwf) != NULL) { + contlen = strlen(linebuf); + while (linebuf[contlen - 1] != '\n' && !feof(pwf)) { + char *tmp; + /* Extend input buffer if it failed getting the whole line, + * so now we double the buffer size */ + buflen *= 2; + tmp = realloc(linebuf, buflen); + if (tmp == NULL) + goto fail; + linebuf = tmp; + /* And fill the rest of the buffer */ + if (fgets(&linebuf[contlen], buflen / 2, pwf) == NULL) + break; + contlen = strlen(linebuf); + /* That was a lot of work for nothing. Gimme perl! */ + } + + /* Is this the username we were sent to change? */ + if (!found && linebuf[namelen] == ':' && + !strncmp(linebuf, pwd->pw_name, namelen)) { + /* Yes! So go forth in the name of the Lord and + * change it! */ + if (putpwent(pwd, fp) < 0) + goto fail; + found = true; + continue; + } + /* Nothing in particular happened, copy input to output */ + fputs(linebuf, fp); + } + + /* xfmkstemp is too restrictive by default for passwd file */ + if (fchmod(fileno(fp), 0644) < 0) + goto fail; + rc = close_stream(fp); + fp = NULL; + if (rc != 0) + goto fail; + + fclose(pwf); /* I don't think I want to know if this failed */ + pwf = NULL; + + if (!found) { + errno = ENOENT; /* give me something better */ + goto fail; + } + + /* we don't care if we can't remove the backup file */ + unlink(PASSWD_FILE ".OLD"); + /* we don't care if we can't create the backup file */ + ignore_result(link(PASSWD_FILE, PASSWD_FILE ".OLD")); + /* we DO care if we can't rename to the passwd file */ + if (rename(tmpname, PASSWD_FILE) < 0) + goto fail; + /* finally: success */ + ulckpwdf(); + return 0; + + fail: + save_errno = errno; + ulckpwdf(); + if (fp != NULL) + fclose(fp); + if (tmpname != NULL) + unlink(tmpname); + free(tmpname); + if (pwf != NULL) + fclose(pwf); + free(linebuf); + errno = save_errno; + return -1; +} + +/* Set up the limits so that we're not foiled */ +static void pw_init(void) +{ + struct rlimit rlim; + + /* Unlimited resource limits. */ + rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_CPU, &rlim); + setrlimit(RLIMIT_FSIZE, &rlim); + setrlimit(RLIMIT_STACK, &rlim); + setrlimit(RLIMIT_DATA, &rlim); + setrlimit(RLIMIT_RSS, &rlim); + +#ifndef DEBUG + /* Don't drop core (not really necessary, but GP's). */ + rlim.rlim_cur = rlim.rlim_max = 0; + setrlimit(RLIMIT_CORE, &rlim); +#endif + + /* Turn off signals. */ + signal(SIGALRM, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + /* Create with exact permissions. */ + umask(0); +} diff --git a/login-utils/setpwnam.h b/login-utils/setpwnam.h new file mode 100644 index 0000000..7d69445 --- /dev/null +++ b/login-utils/setpwnam.h @@ -0,0 +1,29 @@ +/* + * setpwnam.h -- + * define several paths + * + * (c) 1994 Martin Schulze <joey@infodrom.north.de> + * This file is based on setpwnam.c which is + * (c) 1994 Salvatore Valente <svalente@mit.edu> + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + */ + +#include "pathnames.h" + +#ifndef DEBUG +# define PASSWD_FILE _PATH_PASSWD +# define GROUP_FILE _PATH_GROUP +# define SHADOW_FILE _PATH_SHADOW_PASSWD +# define SGROUP_FILE _PATH_GSHADOW +#else +# define PASSWD_FILE "/tmp/passwd" +# define GROUP_FILE "/tmp/group" +# define SHADOW_FILE "/tmp/shadow" +# define SGROUP_FILE "/tmp/gshadow" +#endif + +extern int setpwnam (struct passwd *pwd); diff --git a/login-utils/su.c b/login-utils/su.c new file mode 100644 index 0000000..35a4277 --- /dev/null +++ b/login-utils/su.c @@ -0,0 +1,813 @@ +/* su for Linux. Run a shell with substitute user and group IDs. + Copyright (C) 1992-2006 Free Software Foundation, Inc. + Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Run a shell with the real and effective UID and GID and groups + of USER, default `root'. + + The shell run is taken from USER's password entry, /bin/sh if + none is specified there. If the account has a password, su + prompts for a password unless run by a user with real UID 0. + + Does not change the current directory. + Sets `HOME' and `SHELL' from the password entry for USER, and if + USER is not root, sets `USER' and `LOGNAME' to USER. + The subshell is not a login shell. + + If one or more ARGs are given, they are passed as additional + arguments to the subshell. + + Does not handle /bin/sh or other shells specially + (setting argv[0] to "-su", passing -c only to certain shells, etc.). + I don't see the point in doing that, and it's ugly. + + Based on an implemenation by David MacKenzie <djm@gnu.ai.mit.edu>. */ + +enum +{ + EXIT_CANNOT_INVOKE = 126, + EXIT_ENOENT = 127 +}; + +#include <config.h> +#include <stdio.h> +#include <getopt.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> +#include <security/pam_appl.h> +#include <security/pam_misc.h> +#include <signal.h> +#include <sys/wait.h> +#include <syslog.h> + +#include "err.h" + +#include <stdbool.h> +#include "c.h" +#include "xalloc.h" +#include "nls.h" +#include "pathnames.h" +#include "env.h" + +/* name of the pam configuration files. separate configs for su and su - */ +#define PAM_SERVICE_NAME "su" +#define PAM_SERVICE_NAME_L "su-l" + +#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) + +#include "logindefs.h" + +/* The shell to run if none is given in the user's passwd entry. */ +#define DEFAULT_SHELL "/bin/sh" + +/* The user to become if none is specified. */ +#define DEFAULT_USER "root" + +#ifndef HAVE_ENVIRON_DECL +extern char **environ; +#endif + +static void run_shell (char const *, char const *, char **, size_t) + __attribute__ ((__noreturn__)); + +/* If true, pass the `-f' option to the subshell. */ +static bool fast_startup; + +/* If true, simulate a login instead of just starting a shell. */ +static bool simulate_login; + +/* If true, change some environment vars to indicate the user su'd to. */ +static bool change_environment; + +/* If true, then don't call setsid() with a command. */ +int same_session = 0; + +static bool _pam_session_opened; +static bool _pam_cred_established; +static sig_atomic_t volatile caught_signal = false; +static pam_handle_t *pamh = NULL; + +static struct option const longopts[] = +{ + {"command", required_argument, NULL, 'c'}, + {"session-command", required_argument, NULL, 'C'}, + {"fast", no_argument, NULL, 'f'}, + {"login", no_argument, NULL, 'l'}, + {"preserve-environment", no_argument, NULL, 'p'}, + {"shell", required_argument, NULL, 's'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {NULL, 0, NULL, 0} +}; + +/* Log the fact that someone has run su to the user given by PW; + if SUCCESSFUL is true, they gave the correct password, etc. */ + +static void +log_su (struct passwd const *pw, bool successful) +{ + const char *new_user, *old_user, *tty; + + new_user = pw->pw_name; + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. */ + old_user = getlogin (); + if (!old_user) + { + /* getlogin can fail -- usually due to lack of utmp entry. + Resort to getpwuid. */ + struct passwd *pwd = getpwuid (getuid ()); + old_user = (pwd ? pwd->pw_name : ""); + } + tty = ttyname (STDERR_FILENO); + if (!tty) + tty = "none"; + + openlog (program_invocation_short_name, 0 , LOG_AUTH); + syslog (LOG_NOTICE, "%s(to %s) %s on %s", + successful ? "" : "FAILED SU ", + new_user, old_user, tty); + closelog (); +} + +static struct pam_conv conv = +{ + misc_conv, + NULL +}; + +static void +cleanup_pam (int retcode) +{ + int saved_errno = errno; + + if (_pam_session_opened) + pam_close_session (pamh, 0); + + if (_pam_cred_established) + pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT); + + pam_end(pamh, retcode); + + errno = saved_errno; +} + +/* Signal handler for parent process. */ +static void +su_catch_sig (int sig __attribute__((__unused__))) +{ + caught_signal = true; +} + +/* Export env variables declared by PAM modules. */ +static void +export_pamenv (void) +{ + char **env; + + /* This is a copy but don't care to free as we exec later anyways. */ + env = pam_getenvlist (pamh); + while (env && *env) + { + if (putenv (*env) != 0) + err (EXIT_FAILURE, NULL); + env++; + } +} + +static void +create_watching_parent (void) +{ + pid_t child; + sigset_t ourset; + int status = 0; + int retval; + + retval = pam_open_session (pamh, 0); + if (is_pam_failure(retval)) + { + cleanup_pam (retval); + errx (EXIT_FAILURE, _("cannot not open session: %s"), + pam_strerror (pamh, retval)); + } + else + _pam_session_opened = 1; + + child = fork (); + if (child == (pid_t) -1) + { + cleanup_pam (PAM_ABORT); + err (EXIT_FAILURE, _("cannot create child process")); + } + + /* the child proceeds to run the shell */ + if (child == 0) + return; + + /* In the parent watch the child. */ + + /* su without pam support does not have a helper that keeps + sitting on any directory so let's go to /. */ + if (chdir ("/") != 0) + warn (_("cannot change directory to %s"), "/"); + + sigfillset (&ourset); + if (sigprocmask (SIG_BLOCK, &ourset, NULL)) + { + warn (_("cannot block signals")); + caught_signal = true; + } + if (!caught_signal) + { + struct sigaction action; + action.sa_handler = su_catch_sig; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + sigemptyset (&ourset); + if (!same_session) + { + if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT)) + { + warn (_("cannot set signal handler")); + caught_signal = true; + } + } + if (!caught_signal && (sigaddset(&ourset, SIGTERM) + || sigaddset(&ourset, SIGALRM) + || sigaction(SIGTERM, &action, NULL) + || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) { + warn (_("cannot set signal handler")); + caught_signal = true; + } + if (!caught_signal && !same_session && (sigaction(SIGINT, &action, NULL) + || sigaction(SIGQUIT, &action, NULL))) + { + warn (_("cannot set signal handler")); + caught_signal = true; + } + } + if (!caught_signal) + { + pid_t pid; + for (;;) + { + pid = waitpid (child, &status, WUNTRACED); + + if (pid != (pid_t)-1 && WIFSTOPPED (status)) + { + kill (getpid (), SIGSTOP); + /* once we get here, we must have resumed */ + kill (pid, SIGCONT); + } + else + break; + } + if (pid != (pid_t)-1) + if (WIFSIGNALED (status)) + status = WTERMSIG (status) + 128; + else + status = WEXITSTATUS (status); + else + status = 1; + } + else + status = 1; + + if (caught_signal) + { + fprintf (stderr, _("\nSession terminated, killing shell...")); + kill (child, SIGTERM); + } + + cleanup_pam (PAM_SUCCESS); + + if (caught_signal) + { + sleep (2); + kill (child, SIGKILL); + fprintf (stderr, _(" ...killed.\n")); + } + exit (status); +} + +static void +authenticate (const struct passwd *pw) +{ + const struct passwd *lpw; + const char *cp; + int retval; + + retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME, + pw->pw_name, &conv, &pamh); + if (is_pam_failure(retval)) + goto done; + + if (isatty (0) && (cp = ttyname (0)) != NULL) + { + const char *tty; + + if (strncmp (cp, "/dev/", 5) == 0) + tty = cp + 5; + else + tty = cp; + retval = pam_set_item (pamh, PAM_TTY, tty); + if (is_pam_failure(retval)) + goto done; + } + + lpw = getpwuid (getuid ()); + if (lpw && lpw->pw_name) + { + retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name); + if (is_pam_failure(retval)) + goto done; + } + + retval = pam_authenticate (pamh, 0); + if (is_pam_failure(retval)) + goto done; + + retval = pam_acct_mgmt (pamh, 0); + if (retval == PAM_NEW_AUTHTOK_REQD) + { + /* Password has expired. Offer option to change it. */ + retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + } + +done: + + log_su (pw, !is_pam_failure(retval)); + + if (is_pam_failure(retval)) + { + const char *msg = pam_strerror(pamh, retval); + pam_end(pamh, retval); + sleep (getlogindefs_num ("FAIL_DELAY", 1)); + errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password")); + } +} + +/* Add or clear /sbin and /usr/sbin for the su command + used without `-'. */ + +/* Set if /sbin is found in path. */ +#define SBIN_MASK 0x01 +/* Set if /usr/sbin is found in path. */ +#define USBIN_MASK 0x02 + +static char * +addsbin (const char *const path) +{ + unsigned char smask = 0; + char *ptr, *tmp, *cur, *ret = NULL; + size_t len; + + if (!path || *path == 0) + return NULL; + + tmp = xstrdup (path); + cur = tmp; + for (ptr = strsep (&cur, ":"); ptr != NULL; ptr = strsep (&cur, ":")) + { + if (!strcmp (ptr, "/sbin")) + smask |= SBIN_MASK; + if (!strcmp (ptr, "/usr/sbin")) + smask |= USBIN_MASK; + } + + if ((smask & (USBIN_MASK|SBIN_MASK)) == (USBIN_MASK|SBIN_MASK)) + { + free (tmp); + return NULL; + } + + len = strlen (path); + if (!(smask & USBIN_MASK)) + len += strlen ("/usr/sbin:"); + + if (!(smask & SBIN_MASK)) + len += strlen (":/sbin"); + + ret = xmalloc (len + 1); + strcpy (tmp, path); + + *ret = 0; + cur = tmp; + for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":")) + { + if (!strcmp (ptr, ".")) + continue; + if (*ret) + strcat (ret, ":"); + if (!(smask & USBIN_MASK) && !strcmp (ptr, "/bin")) + { + strcat (ret, "/usr/sbin:"); + strcat (ret, ptr); + smask |= USBIN_MASK; + continue; + } + if (!(smask & SBIN_MASK) && !strcmp (ptr, "/usr/bin")) + { + strcat (ret, ptr); + strcat (ret, ":/sbin"); + smask |= SBIN_MASK; + continue; + } + strcat (ret, ptr); + } + free (tmp); + + if (!(smask & USBIN_MASK)) + strcat (ret, ":/usr/sbin"); + + if (!(smask & SBIN_MASK)) + strcat (ret, ":/sbin"); + + return ret; +} + +static char * +clearsbin (const char *const path) +{ + char *ptr, *tmp, *cur, *ret = NULL; + + if (!path || *path == 0) + return NULL; + + tmp = xstrdup (path); + + ret = xmalloc (strlen (path) + 1); + *ret = 0; + cur = tmp; + for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":")) + { + if (!strcmp (ptr, "/sbin")) + continue; + if (!strcmp (ptr, "/usr/sbin")) + continue; + if (!strcmp (ptr, "/usr/local/sbin")) + continue; + if (*ret) + strcat (ret, ":"); + strcat (ret, ptr); + } + free (tmp); + + return ret; +} + +static void +set_path(const struct passwd* pw) +{ + int r; + if (pw->pw_uid) + r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH); + + else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0) + r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT); + + if (r != 0) + err (EXIT_FAILURE, _("failed to set PATH")); +} + +/* Update `environ' for the new shell based on PW, with SHELL being + the value for the SHELL environment variable. */ + +static void +modify_environment (const struct passwd *pw, const char *shell) +{ + if (simulate_login) + { + /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. + Unset all other environment variables. */ + char const *term = getenv ("TERM"); + if (term) + term = xstrdup (term); + environ = xmalloc ((6 + !!term) * sizeof (char *)); + environ[0] = NULL; + if (term) + xsetenv ("TERM", term, 1); + xsetenv ("HOME", pw->pw_dir, 1); + xsetenv ("SHELL", shell, 1); + xsetenv ("USER", pw->pw_name, 1); + xsetenv ("LOGNAME", pw->pw_name, 1); + set_path(pw); + } + else + { + /* Set HOME, SHELL, and if not becoming a super-user, + USER and LOGNAME. */ + if (change_environment) + { + xsetenv ("HOME", pw->pw_dir, 1); + xsetenv ("SHELL", shell, 1); + if (getlogindefs_bool ("ALWAYS_SET_PATH", 0)) + set_path(pw); + else + { + char const *path = getenv ("PATH"); + char *new = NULL; + + if (pw->pw_uid) + new = clearsbin (path); + else + new = addsbin (path); + + if (new) + { + xsetenv ("PATH", new, 1); + free (new); + } + } + if (pw->pw_uid) + { + xsetenv ("USER", pw->pw_name, 1); + xsetenv ("LOGNAME", pw->pw_name, 1); + } + } + } + + export_pamenv (); +} + +/* Become the user and group(s) specified by PW. */ + +static void +init_groups (const struct passwd *pw) +{ + int retval; + errno = 0; + if (initgroups (pw->pw_name, pw->pw_gid) == -1) + { + cleanup_pam (PAM_ABORT); + err (EXIT_FAILURE, _("cannot set groups")); + } + endgrent (); + + retval = pam_setcred (pamh, PAM_ESTABLISH_CRED); + if (is_pam_failure(retval)) + errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval)); + else + _pam_cred_established = 1; +} + +static void +change_identity (const struct passwd *pw) +{ + if (setgid (pw->pw_gid)) + err (EXIT_FAILURE, _("cannot set group id")); + if (setuid (pw->pw_uid)) + err (EXIT_FAILURE, _("cannot set user id")); +} + +/* Run SHELL, or DEFAULT_SHELL if SHELL is empty. + If COMMAND is nonzero, pass it to the shell with the -c option. + Pass ADDITIONAL_ARGS to the shell as more arguments; there + are N_ADDITIONAL_ARGS extra arguments. */ + +static void +run_shell (char const *shell, char const *command, char **additional_args, + size_t n_additional_args) +{ + size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1; + char const **args = xcalloc (n_args, sizeof *args); + size_t argno = 1; + + if (simulate_login) + { + char *arg0; + char *shell_basename; + + shell_basename = basename (shell); + arg0 = xmalloc (strlen (shell_basename) + 2); + arg0[0] = '-'; + strcpy (arg0 + 1, shell_basename); + args[0] = arg0; + } + else + args[0] = basename (shell); + if (fast_startup) + args[argno++] = "-f"; + if (command) + { + args[argno++] = "-c"; + args[argno++] = command; + } + memcpy (args + argno, additional_args, n_additional_args * sizeof *args); + args[argno + n_additional_args] = NULL; + execv (shell, (char **) args); + + { + int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + warn ("%s", shell); + exit (exit_status); + } +} + +/* Return true if SHELL is a restricted shell (one not returned by + getusershell), else false, meaning it is a standard shell. */ + +static bool +restricted_shell (const char *shell) +{ + char *line; + + setusershell (); + while ((line = getusershell ()) != NULL) + { + if (*line != '#' && !strcmp (line, shell)) + { + endusershell (); + return false; + } + } + endusershell (); + return true; +} + +static void __attribute__((__noreturn__)) +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_invocation_short_name); + else + { + fputs(USAGE_HEADER, stdout); + printf (_(" %s [options] [-] [USER [arg]...]\n"), program_invocation_short_name); + fputs (_("\n\ + Change the effective user id and group id to that of USER.\n\ + A mere - implies -l. If USER not given, assume root.\n"), stdout); + fputs(USAGE_OPTIONS, stdout); + fputs (_("\ + -, -l, --login make the shell a login shell\n\ + -c, --command <command> pass a single command to the shell with -c\n\ + --session-command <command> pass a single command to the shell with -c\n\ + and do not create a new session\n\ + -f, --fast pass -f to the shell (for csh or tcsh)\n\ + -m, --preserve-environment do not reset environment variables\n\ + -p same as -m\n\ + -s, --shell <shell> run shell if /etc/shells allows it\n\ +"), stdout); + + fputs(USAGE_SEPARATOR, stdout); + fputs(USAGE_HELP, stdout); + fputs(USAGE_VERSION, stdout); + printf(USAGE_MAN_TAIL("su(1)")); + } + exit (status); +} + +static +void load_config(void) +{ + logindefs_load_file("/etc/default/su"); + logindefs_load_file(_PATH_LOGINDEFS); +} + +int +main (int argc, char **argv) +{ + int optc; + const char *new_user = DEFAULT_USER; + char *command = NULL; + int request_same_session = 0; + char *shell = NULL; + struct passwd *pw; + struct passwd pw_copy; + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + fast_startup = false; + simulate_login = false; + change_environment = true; + + while ((optc = getopt_long (argc, argv, "c:flmps:hV", longopts, NULL)) != -1) + { + switch (optc) + { + case 'c': + command = optarg; + break; + + case 'C': + command = optarg; + request_same_session = 1; + break; + + case 'f': + fast_startup = true; + break; + + case 'l': + simulate_login = true; + break; + + case 'm': + case 'p': + change_environment = false; + break; + + case 's': + shell = optarg; + break; + + case 'h': + usage(0); + + case 'V': + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + if (optind < argc && !strcmp (argv[optind], "-")) + { + simulate_login = true; + ++optind; + } + if (optind < argc) + new_user = argv[optind++]; + + logindefs_load_defaults = load_config; + + pw = getpwnam (new_user); + if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0] + && pw->pw_passwd)) + errx (EXIT_FAILURE, _("user %s does not exist"), new_user); + + /* Make a copy of the password information and point pw at the local + copy instead. Otherwise, some systems (e.g. Linux) would clobber + the static data through the getlogin call from log_su. + Also, make sure pw->pw_shell is a nonempty string. + It may be NULL when NEW_USER is a username that is retrieved via NIS (YP), + but that doesn't have a default shell listed. */ + pw_copy = *pw; + pw = &pw_copy; + pw->pw_name = xstrdup (pw->pw_name); + pw->pw_passwd = xstrdup (pw->pw_passwd); + pw->pw_dir = xstrdup (pw->pw_dir); + pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0] + ? pw->pw_shell + : DEFAULT_SHELL); + endpwent (); + + authenticate (pw); + + if (request_same_session || !command || !pw->pw_uid) + same_session = 1; + + if (!shell && !change_environment) + shell = getenv ("SHELL"); + if (shell && getuid () != 0 && restricted_shell (pw->pw_shell)) + { + /* The user being su'd to has a nonstandard shell, and so is + probably a uucp account or has restricted access. Don't + compromise the account by allowing access with a standard + shell. */ + warnx (_("using restricted shell %s"), pw->pw_shell); + shell = NULL; + } + shell = xstrdup (shell ? shell : pw->pw_shell); + + init_groups (pw); + + create_watching_parent (); + /* Now we're in the child. */ + + change_identity (pw); + if (!same_session) + setsid (); + + /* Set environment after pam_open_session, which may put KRB5CCNAME + into the pam_env, etc. */ + + modify_environment (pw, shell); + + if (simulate_login && chdir (pw->pw_dir) != 0) + warn (_("warning: cannot change directory to %s"), pw->pw_dir); + + run_shell (shell, command, argv + optind, max (0, argc - optind)); +} + +// vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1 diff --git a/login-utils/sulogin.8 b/login-utils/sulogin.8 new file mode 100644 index 0000000..b9dec16 --- /dev/null +++ b/login-utils/sulogin.8 @@ -0,0 +1,90 @@ +'\" -*- coding: UTF-8 -*- +.\" Copyright (C) 1998-2006 Miquel van Smoorenburg. +.\" Copyright (C) 2012 Karel Zak <kzak@redhat.com> +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +.\" +.TH SULOGIN "8" "Jul 2012" "util-linux" "System Administration" +.SH NAME +sulogin \- Single-user login +.SH SYNOPSIS +.B sulogin +.RB [ options ] +.RB [ tty ] +.SH DESCRIPTION +.I sulogin +is invoked by +.B init +when the system goes into single user mode. +.PP +The user is prompted: +.IP "" .5i +Give root password for system maintenance +.br +(or type Control\-D for normal startup): +.PP +.I sulogin +will be connected to the current terminal, or to the optional tty device that +can be specified on the command line (typically +.BR /dev/console ). +.PP +After the user exits the single-user shell or presses control\-D at the +prompt, the system will continue to boot. +.SH OPTIONS +.IP "\fB\-e\fR, \fB\-\-force\fP" +If the default method of obtaining the root password via +.BR getpwnam (3) +from the system fails, manually examine +.I /etc/passwd +and +.I /etc/shadow +to get the password. If they are damaged or nonexistent, sulogin will start +a root shell without asking for a password. +.IP +Only use the +.B \-e +option if you are sure the console is physically protected against +unauthorized access. +.IP "\fB\-p\fR, \fB\-\-login\-shell\fP" +Specifying this option causes sulogin to start the shell process as a login +shell. +.IP "\fB\-t\fR, \fB\-\-timeout \fIseconds\fP" +Specify the maximum amount of time to wait for user input. By default, +sulogin will wait forever. +.IP "\fB\-h\fR, \fB\-\-help\fP" +Print a help message. +.IP "\fB\-V\fR, \fB\-\-version\fP" +Output version. +.SH ENVIRONMENT VARIABLES +.I sulogin +looks for the environment variable +.B SUSHELL +or +.B sushell +to determine what shell to start. If the environment variable is not set, it +will try to execute root's shell from +.IR /etc/passwd . +If that fails it +will fall back to +.IR /bin/sh . +.SH AUTHOR +.B sulogin +was written by Miquel van Smoorenburg for sysvinit and later ported +to util-linux by Dave Reisner and Karel Zak. +.SH AVAILABILITY +The sulogin command is part of the util-linux package and is available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c new file mode 100644 index 0000000..d7d44b6 --- /dev/null +++ b/login-utils/sulogin.c @@ -0,0 +1,609 @@ +/* + * sulogin + * + * This program gives Linux machines a reasonable secure way to boot single + * user. It forces the user to supply the root password before a shell is + * started. If there is a shadow password file and the encrypted root password + * is "x" the shadow password will be used. + * + * Copyright (C) 1998-2003 Miquel van Smoorenburg. + * Copyright (C) 2012 Karel Zak <kzak@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <pwd.h> +#include <shadow.h> +#include <termios.h> +#include <errno.h> +#include <getopt.h> +#include <sys/ioctl.h> +#ifdef HAVE_CRYPT_H +# include <crypt.h> +#endif + +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +# include <selinux/get_context_list.h> +#endif + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pathnames.h" +#include "strutils.h" +#include "ttyutils.h" + +static unsigned int timeout; +static int profile; + +struct sigaction saved_sigint; +struct sigaction saved_sigtstp; +struct sigaction saved_sigquit; + +/* + * Called at timeout. + */ +static void alrm_handler(int sig __attribute__((unused))) +{ + return; +} + +static void mask_signal(int signal, void (*handler)(int), + struct sigaction *origaction) +{ + struct sigaction newaction; + + newaction.sa_handler = handler; + sigemptyset(&newaction.sa_mask); + newaction.sa_flags = 0; + + sigaction(signal, NULL, origaction); + sigaction(signal, &newaction, NULL); +} + +static void unmask_signal(int signal, struct sigaction *sa) +{ + sigaction(signal, sa, NULL); +} + +/* + * See if an encrypted password is valid. The encrypted password is checked for + * traditional-style DES and FreeBSD-style MD5 encryption. + */ +static int valid(const char *pass) +{ + const char *s; + char id[5]; + size_t len; + off_t off; + + if (pass[0] == 0) + return 1; + if (pass[0] != '$') + goto check_des; + + /* + * up to 4 bytes for the signature e.g. $1$ + */ + for (s = pass+1; *s && *s != '$'; s++); + + if (*s++ != '$') + return 0; + + if ((off = (off_t)(s-pass)) > 4 || off < 3) + return 0; + + memset(id, '\0', sizeof(id)); + strncpy(id, pass, off); + + /* + * up to 16 bytes for the salt + */ + for (; *s && *s != '$'; s++); + + if (*s++ != '$') + return 0; + + if ((off_t)(s-pass) > 16) + return 0; + + len = strlen(s); + + /* + * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes + */ + if ((strcmp(id, "$1$") == 0) && (len < 22 || len > 24)) + return 0; + + /* + * the SHA-256 hash 43 bytes + */ + if ((strcmp(id, "$5$") == 0) && (len < 42 || len > 44)) + return 0; + + /* + * the SHA-512 hash 86 bytes + */ + if ((strcmp(id, "$6$") == 0) && (len < 85 || len > 87)) + return 0; + + /* + * e.g. Blowfish hash + */ + return 1; +check_des: + if (strlen(pass) != 13) + return 0; + + for (s = pass; *s; s++) { + if ((*s < '0' || *s > '9') && + (*s < 'a' || *s > 'z') && + (*s < 'A' || *s > 'Z') && + *s != '.' && *s != '/') + return 0; + } + return 1; +} + +/* + * Set a variable if the value is not NULL. + */ +static inline void set(char **var, char *val) +{ + if (val) + *var = val; +} + +/* + * Get the root password entry. + */ +static struct passwd *getrootpwent(int try_manually) +{ + static struct passwd pwd; + struct passwd *pw; + struct spwd *spw; + FILE *fp; + static char line[256]; + static char sline[256]; + char *p; + + /* + * First, we try to get the password the standard way using normal + * library calls. + */ + if ((pw = getpwnam("root")) && + !strcmp(pw->pw_passwd, "x") && + (spw = getspnam("root"))) + pw->pw_passwd = spw->sp_pwdp; + + if (pw || !try_manually) + return pw; + + /* + * If we come here, we could not retrieve the root password through + * library calls and we try to read the password and shadow files + * manually. + */ + pwd.pw_name = "root"; + pwd.pw_passwd = ""; + pwd.pw_gecos = "Super User"; + pwd.pw_dir = "/"; + pwd.pw_shell = ""; + pwd.pw_uid = 0; + pwd.pw_gid = 0; + + if ((fp = fopen(_PATH_PASSWD, "r")) == NULL) { + warn(_("cannot open %s"), _PATH_PASSWD); + return &pwd; + } + + /* + * Find root in the password file. + */ + while ((p = fgets(line, 256, fp)) != NULL) { + if (strncmp(line, "root:", 5) != 0) + continue; + p += 5; + set(&pwd.pw_passwd, strsep(&p, ":")); + strsep(&p, ":"); + strsep(&p, ":"); + set(&pwd.pw_gecos, strsep(&p, ":")); + set(&pwd.pw_dir, strsep(&p, ":")); + set(&pwd.pw_shell, strsep(&p, "\n")); + p = line; + break; + } + + fclose(fp); + + /* + * If the encrypted password is valid or not found, return. + */ + if (p == NULL) { + warnx(_("%s: no entry for root\n"), _PATH_PASSWD); + return &pwd; + } + if (valid(pwd.pw_passwd)) + return &pwd; + + /* + * The password is invalid. If there is a shadow password, try it. + */ + strcpy(pwd.pw_passwd, ""); + if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) { + warn(_("cannot open %s"), _PATH_PASSWD); + return &pwd; + } + while ((p = fgets(sline, 256, fp)) != NULL) { + if (strncmp(sline, "root:", 5) != 0) + continue; + p += 5; + set(&pwd.pw_passwd, strsep(&p, ":")); + break; + } + fclose(fp); + + /* + * If the password is still invalid, NULL it, and return. + */ + if (p == NULL) { + warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); + strcpy(pwd.pw_passwd, ""); + } + if (!valid(pwd.pw_passwd)) { + warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); + strcpy(pwd.pw_passwd, ""); + } + return &pwd; +} + +/* + * Ask for the password. Note that there is no default timeout as we normally + * skip this during boot. + */ +static char *getpasswd(char *crypted) +{ + struct sigaction sa; + struct termios old, tty; + static char pass[128]; + char *ret = pass; + size_t i; + + if (crypted[0]) + printf(_("Give root password for maintenance\n")); + else + printf(_("Press enter for maintenance")); + printf(_("(or type Control-D to continue): ")); + fflush(stdout); + + tcgetattr(0, &old); + tcgetattr(0, &tty); + tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); + tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); + tcsetattr(0, TCSANOW, &tty); + + pass[sizeof(pass) - 1] = 0; + + sa.sa_handler = alrm_handler; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); + if (timeout) + alarm(timeout); + + if (read(0, pass, sizeof(pass) - 1) <= 0) + ret = NULL; + else { + for (i = 0; i < sizeof(pass) && pass[i]; i++) + if (pass[i] == '\r' || pass[i] == '\n') { + pass[i] = 0; + break; + } + } + alarm(0); + tcsetattr(0, TCSANOW, &old); + printf("\n"); + + return ret; +} + +/* + * Password was OK, execute a shell. + */ +static void sushell(struct passwd *pwd) +{ + char shell[PATH_MAX]; + char home[PATH_MAX]; + char *p; + char *su_shell; + + /* + * Set directory and shell. + */ + if (chdir(pwd->pw_dir) != 0) { + warn(_("%s: change directory failed"), pwd->pw_dir); + printf(_("Logging in with home = \"/\".\n")); + + if (chdir("/") != 0) + warn(_("change directory to system root failed")); + } + + if ((p = getenv("SUSHELL")) != NULL) + su_shell = p; + else if ((p = getenv("sushell")) != NULL) + su_shell = p; + else { + if (pwd->pw_shell[0]) + su_shell = pwd->pw_shell; + else + su_shell = "/bin/sh"; + } + if ((p = strrchr(su_shell, '/')) == NULL) + p = su_shell; + else + p++; + + snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p); + + /* + * Set some important environment variables. + */ + if (getcwd(home, sizeof(home)) != NULL) + setenv("HOME", home, 1); + + setenv("LOGNAME", "root", 1); + setenv("USER", "root", 1); + if (!profile) + setenv("SHLVL","0",1); + + /* + * Try to execute a shell. + */ + setenv("SHELL", su_shell, 1); + unmask_signal(SIGINT, &saved_sigint); + unmask_signal(SIGTSTP, &saved_sigtstp); + unmask_signal(SIGQUIT, &saved_sigquit); + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + security_context_t scon=NULL; + char *seuser=NULL; + char *level=NULL; + if (getseuserbyname("root", &seuser, &level) == 0) { + if (get_default_context_with_level(seuser, level, 0, &scon) == 0) { + if (setexeccon(scon) != 0) + warnx(_("setexeccon failed")); + freecon(scon); + } + } + free(seuser); + free(level); + } +#endif + execl(su_shell, shell, NULL); + warn(_("%s: exec failed"), su_shell); + + setenv("SHELL", "/bin/sh", 1); + execl("/bin/sh", profile ? "-sh" : "sh", NULL); + warn(_("%s: exec failed"), "/bin/sh"); +} + +static void fixtty(void) +{ + struct termios tp; + int x = 0, fl = 0; + + /* Skip serial console */ + if (ioctl(STDIN_FILENO, TIOCMGET, (char *) &x) == 0) + return; + +#if defined(IUTF8) && defined(KDGKBMODE) + /* Detect mode of current keyboard setup, e.g. for UTF-8 */ + if (ioctl(STDIN_FILENO, KDGKBMODE, &x) == 0 && x == K_UNICODE) { + setlocale(LC_CTYPE, "C.UTF-8"); + fl |= UL_TTY_UTF8; + } +#else + setlocale(LC_CTYPE, "POSIX"); +#endif + memset(&tp, 0, sizeof(struct termios)); + if (tcgetattr(STDIN_FILENO, &tp) < 0) { + warn(_("tcgetattr failed")); + return; + } + + reset_virtual_console(&tp, fl); + + if (tcsetattr(0, TCSADRAIN, &tp)) + warn(_("tcsetattr failed")); +} + +static void usage(FILE *out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, _( + " %s [options] [tty device]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --login-shell start a login shell\n" + " -t, --timeout <seconds> max time to wait for a password (default: no limit)\n" + " -e, --force examine password files directly if getpwnam(3) fails\n"), + out); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("sulogin(8)")); +} + +int main(int argc, char **argv) +{ + char *tty = NULL; + char *p; + struct passwd *pwd; + int c, fd = -1; + int opt_e = 0; + pid_t pid, pgrp, ppgrp, ttypgrp; + struct sigaction saved_sighup; + + static const struct option longopts[] = { + { "login-shell", 0, 0, 'p' }, + { "timeout", 1, 0, 't' }, + { "force", 0, 0, 'e' }, + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'V' }, + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + /* + * See if we have a timeout flag. + */ + while ((c = getopt_long(argc, argv, "ehpt:V", longopts, NULL)) != -1) { + switch(c) { + case 't': + timeout = strtou32_or_err(optarg, _("invalid timeout argument")); + break; + case 'p': + profile = 1; + break; + case 'e': + opt_e = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + return EXIT_SUCCESS; + default: + usage(stderr); + /* Do not exit! */ + break; + } + } + + if (geteuid() != 0) + errx(EXIT_FAILURE, _("only root can run this program.")); + + /* + * See if we need to open an other tty device. + */ + mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); + mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); + mask_signal(SIGINT, SIG_IGN, &saved_sigint); + if (optind < argc) + tty = argv[optind]; + + if (tty || (tty = getenv("CONSOLE"))) { + + if ((fd = open(tty, O_RDWR)) < 0) { + warn(_("cannot open %s"), tty); + fd = dup(0); + } + + if (!isatty(fd)) { + warn(_("%s: not a tty"), tty); + close(fd); + } else { + + /* + * Only go through this trouble if the new tty doesn't + * fall in this process group. + */ + pid = getpid(); + pgrp = getpgid(0); + ppgrp = getpgid(getppid()); + ttypgrp = tcgetpgrp(fd); + + if (pgrp != ttypgrp && ppgrp != ttypgrp) { + if (pid != getsid(0)) { + if (pid == getpgid(0)) + setpgid(0, getpgid(getppid())); + setsid(); + } + + sigaction(SIGHUP, NULL, &saved_sighup); + if (ttypgrp > 0) + ioctl(0, TIOCNOTTY, (char *)1); + sigaction(SIGHUP, &saved_sighup, NULL); + close(0); + close(1); + close(2); + if (fd > 2) + close(fd); + if ((fd = open(tty, O_RDWR|O_NOCTTY)) < 0) + warn(_("cannot open %s"), tty); + else { + ioctl(0, TIOCSCTTY, (char *)1); + tcsetpgrp(fd, ppgrp); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) + close(fd); + } + } else + if (fd > 2) + close(fd); + } + } else if (getpid() == 1) { + /* We are init. We hence need to set a session anyway */ + setsid(); + if (ioctl(0, TIOCSCTTY, (char *)1)) + warn(_("TIOCSCTTY: ioctl failed")); + } + + fixtty(); + + /* + * Get the root password. + */ + if ((pwd = getrootpwent(opt_e)) == NULL) { + warnx(_("cannot open password database.")); + sleep(2); + } + + /* + * Ask for the password. + */ + while (pwd) { + if ((p = getpasswd(pwd->pw_passwd)) == NULL) + break; + if (pwd->pw_passwd[0] == 0 || + strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0) + sushell(pwd); + mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); + mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); + mask_signal(SIGINT, SIG_IGN, &saved_sigint); + fprintf(stderr, _("Login incorrect\n\n")); + } + + /* + * User pressed Control-D. + */ + return EXIT_SUCCESS; +} diff --git a/login-utils/utmpdump.1 b/login-utils/utmpdump.1 new file mode 100644 index 0000000..35565a4 --- /dev/null +++ b/login-utils/utmpdump.1 @@ -0,0 +1,74 @@ +'\" -*- coding: UTF-8 -*- +.\" Copyright (C) 2010 Michael Krapp +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +.\" +.TH UTMPDUMP "1" "July 2012" "util-linux" "System Administration" +.SH NAME +utmpdump \- dump UTMP and WTMP files in raw format +.SH SYNOPSIS +utmpdump [\-frh] [ filename ] +.SH DESCRIPTION +.B utmpdump +is a simple program to dump UTMP and WTMP files in raw format, so they +can be examined. +.B utmpdump +eads from stdin unless a +.I filename +is passed. +.SH OPTIONS +.IP "\fB\-f\fR, \fB\-\-follow\fP" +Output appended data as the file grows. +.IP "\fB\-r\fR, \fB\-\-reverse\fP +Undump, write back edited login information into utmp or wtmp files. +.IP "\fB\-h\fR, \fB\-\-help\fP" +Print a help text and exit. +.IP "\fB\-V\fR, \fB\-\-version\fP" +Output version information and exit. +.SH NOTES +.B utmpdump +can be useful in cases of corrupted utmp or wtmp entries. It can dump +out utmp/wtmp to an ASCII file, then that file can be edited to remove +bogus entries and reintegrated, using +.PP +.RS +utmpdump -r < ascii_file > wtmp +.RE +.PP +but be warned as +.B utmpdump +was written for debugging purpose only. +.SH BUGS +You may +.B not +use the option +.B \-r +as the format for the utmp/wtmp files strongly depends on the input +format. This tool was +.B not +written for normal use but for debugging. +.SH AUTHOR +Michael Krapp +.SH "SEE ALSO" +.BR last (1), +.BR w (1), +.BR who (1), +.BR utmp (5) +.SH AVAILABILITY +The utmpdump command is part of the util-linux package and is available +from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/login-utils/utmpdump.c b/login-utils/utmpdump.c new file mode 100644 index 0000000..b015e1b --- /dev/null +++ b/login-utils/utmpdump.c @@ -0,0 +1,363 @@ +/* + * utmpdump + * + * Simple program to dump UTMP and WTMP files in raw format, so they can be + * examined. + * + * Based on utmpdump dump from sysvinit suite. + * + * Copyright (C) 1991-2000 Miquel van Smoorenburg <miquels@cistron.nl> + * + * Copyright (C) 1998 Danek Duvall <duvall@alumni.princeton.edu> + * Copyright (C) 2012 Karel Zak <kzak@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <utmp.h> +#include <time.h> +#include <ctype.h> +#include <getopt.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/stat.h> +#ifdef HAVE_INOTIFY_INIT +#include <sys/inotify.h> +#endif + +#include "c.h" +#include "nls.h" +#include "xalloc.h" +#include "closestream.h" + +static char *timetostr(const time_t time) +{ + static char s[29]; /* [Sun Sep 01 00:00:00 1998 PST] */ + + if (time != 0) + strftime(s, 29, "%a %b %d %T %Y %Z", localtime(&time)); + else + s[0] = '\0'; + + return s; +} + +static time_t strtotime(const char *s_time) +{ + struct tm tm; + + memset(&tm, '\0', sizeof(struct tm)); + + if (s_time[0] == ' ' || s_time[0] == '\0') + return (time_t)0; + + strptime(s_time, "%a %b %d %T %Y", &tm); + + /* Cheesy way of checking for DST */ + if (s_time[26] == 'D') + tm.tm_isdst = 1; + + return mktime(&tm); +} + +#define cleanse(x) xcleanse(x, sizeof(x)) +static void xcleanse(char *s, int len) +{ + for ( ; *s && len-- > 0; s++) + if (!isprint(*s) || *s == '[' || *s == ']') + *s = '?'; +} + +static void print_utline(struct utmp ut) +{ + char *addr_string, *time_string; + struct in_addr in; + + in.s_addr = ut.ut_addr; + addr_string = inet_ntoa(in); + time_string = timetostr(ut.ut_time); + cleanse(ut.ut_id); + cleanse(ut.ut_user); + cleanse(ut.ut_line); + cleanse(ut.ut_host); + + /* pid id user line host addr time */ + printf("[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15.15s] [%-28.28s]\n", + ut.ut_type, ut.ut_pid, ut.ut_id, 8, UT_NAMESIZE, ut.ut_user, + 12, UT_LINESIZE, ut.ut_line, 20, UT_HOSTSIZE, ut.ut_host, + addr_string, time_string); +} + +#ifdef HAVE_INOTIFY_INIT +#define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT) +#define NEVENTS 4 + +static void roll_file(const char *filename, off_t *size) +{ + FILE *fp; + struct stat st; + struct utmp ut; + off_t pos; + + if (!(fp = fopen(filename, "r"))) + err(EXIT_FAILURE, _("cannot open %s"), filename); + + if (fstat(fileno(fp), &st) == -1) + err(EXIT_FAILURE, _("%s: stat failed"), filename); + + if (st.st_size == *size) + goto done; + + if (fseek(fp, *size, SEEK_SET) != (off_t) -1) { + while (fread(&ut, sizeof(ut), 1, fp) == 1) + print_utline(ut); + } + + pos = ftello(fp); + /* If we've successfully read something, use the file position, this + * avoids data duplication. If we read nothing or hit an error, + * reset to the reported size, this handles truncated files. + */ + *size = (pos != -1 && pos != *size) ? pos : st.st_size; + +done: + fclose(fp); +} + +static int follow_by_inotify(FILE *fp, const char *filename) +{ + char buf[NEVENTS * sizeof(struct inotify_event)]; + int fd, wd, event; + ssize_t length; + off_t size; + + fd = inotify_init(); + if (fd == -1) + return -1; /* probably reached any limit ... */ + + size = ftello(fp); + fclose(fp); + + wd = inotify_add_watch(fd, filename, EVENTS); + if (wd == -1) + err(EXIT_FAILURE, _("%s: cannot add inotify watch."), filename); + + while (wd >= 0) { + errno = 0; + length = read(fd, buf, sizeof(buf)); + + if (length < 0 && (errno == EINTR || errno == EAGAIN)) + continue; + if (length < 0) + err(EXIT_FAILURE, _("%s: cannot read inotify events"), + filename); + + for (event = 0; event < length;) { + struct inotify_event *ev = + (struct inotify_event *) &buf[event]; + + if (ev->mask & IN_MODIFY) + roll_file(filename, &size); + else { + close(wd); + wd = -1; + break; + } + event += sizeof(struct inotify_event) + ev->len; + } + } + + close(fd); + return 0; +} +#endif /* HAVE_INOTIFY_INIT */ + +static FILE *dump(FILE *fp, const char *filename, int follow) +{ + struct utmp ut; + + if (follow) + fseek(fp, -10 * sizeof(ut), SEEK_END); + + while (fread(&ut, sizeof(ut), 1, fp) == 1) + print_utline(ut); + + if (!follow) + return fp; + +#ifdef HAVE_INOTIFY_INIT + if (follow_by_inotify(fp, filename) == 0) + return NULL; /* file already closed */ + else +#endif + /* fallback for systems without inotify or with non-free + * inotify instances */ + for (;;) { + while (fread(&ut, sizeof(ut), 1, fp) == 1) + print_utline(ut); + sleep(1); + } + + return fp; +} + + +/* This function won't work properly if there's a ']' or a ' ' in the real + * token. Thankfully, this should never happen. */ +static int gettok(char *line, char *dest, int size, int eatspace) +{ + int bpos, epos, eaten; + + bpos = strchr(line, '[') - line; + if (bpos < 0) + errx(EXIT_FAILURE, _("Extraneous newline in file. Exiting.")); + + line += 1 + bpos; + epos = strchr(line, ']') - line; + if (epos < 0) + errx(EXIT_FAILURE, _("Extraneous newline in file. Exiting.")); + + line[epos] = '\0'; + eaten = bpos + epos + 1; + + if (eatspace) { + char *t; + if ((t = strchr(line, ' '))) + *t = 0; + } + strncpy(dest, line, size); + + return eaten + 1; +} + +static void undump(FILE *fp) +{ + struct utmp ut; + char s_addr[16], s_time[29], *linestart, *line; + int count = 0; + + line = linestart = xmalloc(1024 * sizeof(*linestart)); + s_addr[15] = 0; + s_time[28] = 0; + + while (fgets(linestart, 1023, fp)) { + line = linestart; + memset(&ut, '\0', sizeof(ut)); + sscanf(line, "[%hd] [%d] [%4c] ", &ut.ut_type, &ut.ut_pid, ut.ut_id); + + line += 19; + line += gettok(line, ut.ut_user, sizeof(ut.ut_user), 1); + line += gettok(line, ut.ut_line, sizeof(ut.ut_line), 1); + line += gettok(line, ut.ut_host, sizeof(ut.ut_host), 1); + line += gettok(line, s_addr, sizeof(s_addr) - 1, 1); + line += gettok(line, s_time, sizeof(s_time) - 1, 0); + + ut.ut_addr = inet_addr(s_addr); + ut.ut_time = strtotime(s_time); + + ignore_result( fwrite(&ut, sizeof(ut), 1, stdout) ); + + ++count; + } + + free(linestart); +} + +static void __attribute__((__noreturn__)) usage(FILE *out) +{ + fputs(USAGE_HEADER, out); + + fprintf(out, + _(" %s [options] [filename]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -f, --follow output appended data as the file grows\n" + " -r, --reverse write back dumped data into utmp file\n"), out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fprintf(out, USAGE_MAN_TAIL("utmpdump(1)")); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c; + FILE *fp = NULL; + int reverse = 0, follow = 0; + const char *filename = NULL; + + static const struct option longopts[] = { + { "follow", 0, 0, 'f' }, + { "reverse", 0, 0, 'r' }, + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'V' }, + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "frhV", longopts, NULL)) != -1) { + switch (c) { + case 'r': + reverse = 1; + break; + + case 'f': + follow = 1; + break; + + case 'h': + usage(stdout); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + default: + usage(stderr); + } + } + + if (optind < argc) { + filename = argv[optind]; + fp = fopen(filename, "r"); + if (!fp) + err(EXIT_FAILURE, _("cannot open %s"), filename); + } else { + if (follow) + errx(EXIT_FAILURE, _("following standard input is unsupported")); + filename = "/dev/stdin"; + fp = stdin; + } + + if (reverse) { + fprintf(stderr, _("Utmp undump of %s\n"), filename); + undump(fp); + } else { + fprintf(stderr, _("Utmp dump of %s\n"), filename); + fp = dump(fp, filename, follow); + } + + if (fp && fp != stdin) + fclose(fp); + + return EXIT_SUCCESS; +} diff --git a/login-utils/vigr.8 b/login-utils/vigr.8 new file mode 100644 index 0000000..ff72d7a --- /dev/null +++ b/login-utils/vigr.8 @@ -0,0 +1 @@ +.so man8/vipw.8 diff --git a/login-utils/vipw.8 b/login-utils/vipw.8 new file mode 100644 index 0000000..62dcc85 --- /dev/null +++ b/login-utils/vipw.8 @@ -0,0 +1,90 @@ +.\" Copyright (c) 1983, 1991 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)vipw.8 6.7 (Berkeley) 3/16/91 +.\" +.TH VIPW "8" "September 2011" "util-linux" "System Administration" +.SH NAME +vipw, vigr \- edit the password or group file +.SH SYNOPSIS +.B vipw +[options] +.br +.B vigr +[options] +.SH DESCRIPTION +.B vipw +edits the password file after setting the appropriate locks, +and does any necessary processing after the password file is unlocked. +If the password file is already locked for editing by another user, +.B vipw +will ask you +to try again later. The default editor for +.B vipw +and +.B vigr +is +.BR vi (1). +.B vigr +edits the group file in the same manner as +.B vipw +does the passwd file. +.SH ENVIRONMENT +If the following environment variable exists it will be utilized by +.B vipw +and +.BR vigr : +.I +.TP +.B EDITOR +The editor specified by the string +.B EDITOR +will be invoked instead of the default editor +.BR vi (1). +.El +.SH SEE ALSO +.BR passwd (1), +.BR flock (2), +.BR vi (1), +.BR passwd (5) +.SH HISTORY +The +.B vipw +command appeared in 4.0BSD. +.br +The +.B vigr +command appeared in Util-Linux 2.6. +.SH AVAILABILITY +The vigr and vipw commands are part of the util-linux package and are available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/login-utils/vipw.c b/login-utils/vipw.c new file mode 100644 index 0000000..1eeeb0d --- /dev/null +++ b/login-utils/vipw.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 1987 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Updated Thu Oct 12 09:56:55 1995 by faith@cs.unc.edu with security + * patches from Zefram <A.Main@dcs.warwick.ac.uk> + * + * Updated Thu Nov 9 21:58:53 1995 by Martin Schulze + * <joey@finlandia.infodrom.north.de>. Support for vigr. + * + * Martin Schulze's patches adapted to Util-Linux by Nicolai Langfeldt. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - fixed strerr(errno) in gettext calls + */ + +/* + * This command is deprecated. The utility is in maintenance mode, + * meaning we keep them in source tree for backward compatibility + * only. Do not waste time making this command better, unless the + * fix is about security or other very critical issue. + * + * See Documentation/deprecated.txt for more information. + */ + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <pwd.h> +#include <shadow.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/file.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "c.h" +#include "fileutils.h" +#include "closestream.h" +#include "nls.h" +#include "setpwnam.h" +#include "strutils.h" +#include "xalloc.h" + +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +#endif + +#define FILENAMELEN 67 + +enum { + VIPW, + VIGR +}; +int program; +char orig_file[FILENAMELEN]; /* original file /etc/passwd or /etc/group */ +char *tmp_file; /* tmp file */ + +void pw_error __P((char *, int, int)); + +static void copyfile(int from, int to) +{ + int nr, nw, off; + char buf[8 * 1024]; + + while ((nr = read(from, buf, sizeof(buf))) > 0) + for (off = 0; off < nr; nr -= nw, off += nw) + if ((nw = write(to, buf + off, nr)) < 0) + pw_error(tmp_file, 1, 1); + + if (nr < 0) + pw_error(orig_file, 1, 1); +} + +static void pw_init(void) +{ + struct rlimit rlim; + + /* Unlimited resource limits. */ + rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; + (void)setrlimit(RLIMIT_CPU, &rlim); + (void)setrlimit(RLIMIT_FSIZE, &rlim); + (void)setrlimit(RLIMIT_STACK, &rlim); + (void)setrlimit(RLIMIT_DATA, &rlim); + (void)setrlimit(RLIMIT_RSS, &rlim); + + /* Don't drop core (not really necessary, but GP's). */ + rlim.rlim_cur = rlim.rlim_max = 0; + (void)setrlimit(RLIMIT_CORE, &rlim); + + /* Turn off signals. */ + (void)signal(SIGALRM, SIG_IGN); + (void)signal(SIGHUP, SIG_IGN); + (void)signal(SIGINT, SIG_IGN); + (void)signal(SIGPIPE, SIG_IGN); + (void)signal(SIGQUIT, SIG_IGN); + (void)signal(SIGTERM, SIG_IGN); + (void)signal(SIGTSTP, SIG_IGN); + (void)signal(SIGTTOU, SIG_IGN); + + /* Create with exact permissions. */ + (void)umask(0); +} + +static FILE * pw_tmpfile(int lockfd) +{ + FILE *fd; + char *tmpname = NULL; + char *dir = "/etc"; + + if ((fd = xfmkstemp(&tmpname, dir)) == NULL) { + ulckpwdf(); + err(EXIT_FAILURE, _("can't open temporary file")); + } + + copyfile(lockfd, fileno(fd)); + tmp_file = tmpname; + return fd; +} + +static void pw_write(void) +{ + char tmp[FILENAMELEN + 4]; + + sprintf(tmp, "%s%s", orig_file, ".OLD"); + unlink(tmp); + + if (link(orig_file, tmp)) + warn(_("%s: create a link to %s failed"), orig_file, tmp); + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + security_context_t passwd_context = NULL; + int ret = 0; + if (getfilecon(orig_file, &passwd_context) < 0) { + warnx(_("Can't get context for %s"), orig_file); + pw_error(orig_file, 1, 1); + } + ret = setfilecon(tmp_file, passwd_context); + freecon(passwd_context); + if (ret != 0) { + warnx(_("Can't set context for %s"), tmp_file); + pw_error(tmp_file, 1, 1); + } + } +#endif + + if (rename(tmp_file, orig_file) == -1) { + int errsv = errno; + errx(EXIT_FAILURE, + ("cannot write %s: %s (your changes are still in %s)"), + orig_file, strerror(errsv), tmp_file); + } + unlink(tmp_file); + free(tmp_file); +} + +static void pw_edit(int notsetuid) +{ + int pstat; + pid_t pid; + char *p, *editor, *tk; + + editor = getenv("EDITOR"); + editor = xstrdup(editor ? editor : _PATH_VI); + + tk = strtok(editor, " \t"); + if (tk && (p = strrchr(tk, '/')) != NULL) + ++p; + else + p = editor; + + pid = fork(); + if (pid < 0) + err(EXIT_FAILURE, _("fork failed")); + + if (!pid) { + if (notsetuid) { + (void)setgid(getgid()); + (void)setuid(getuid()); + } + execlp(editor, p, tmp_file, NULL); + /* Shouldn't get here */ + _exit(EXIT_FAILURE); + } + for (;;) { + pid = waitpid(pid, &pstat, WUNTRACED); + if (WIFSTOPPED(pstat)) { + /* the editor suspended, so suspend us as well */ + kill(getpid(), SIGSTOP); + kill(pid, SIGCONT); + } else { + break; + } + } + if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0) + pw_error(editor, 1, 1); + + free(editor); +} + +void __attribute__((__noreturn__)) +pw_error(char *name, int err, int eval) +{ + if (err) { + if (name) + warn("%s: ", name); + else + warn(NULL); + } + warnx(_("%s unchanged"), orig_file); + unlink(tmp_file); + ulckpwdf(); + exit(eval); +} + +static void edit_file(int is_shadow) +{ + struct stat begin, end; + int passwd_file, ch_ret; + FILE *tmp_fd; + + pw_init(); + + /* acquire exclusive lock */ + if (lckpwdf() < 0) + err(EXIT_FAILURE, _("cannot get lock")); + + passwd_file = open(orig_file, O_RDONLY, 0); + if (passwd_file < 0) + err(EXIT_FAILURE, _("cannot open %s"), orig_file); + tmp_fd = pw_tmpfile(passwd_file); + + if (fstat(fileno(tmp_fd), &begin)) + pw_error(tmp_file, 1, 1); + + pw_edit(0); + + if (fstat(fileno(tmp_fd), &end)) + pw_error(tmp_file, 1, 1); + /* Some editors, such as Vim with 'writebackup' mode enabled, + * use "atomic save" in which the old file is deleted and a new + * one with the same name created in its place. */ + if (end.st_nlink == 0) { + if (close_stream(tmp_fd) != 0) + err(EXIT_FAILURE, _("write error")); + tmp_fd = fopen(tmp_file, "r"); + if (!tmp_file) + err(EXIT_FAILURE, _("cannot open %s"), tmp_file); + if (fstat(fileno(tmp_fd), &end)) + pw_error(tmp_file, 1, 1); + } + if (begin.st_mtime == end.st_mtime) { + warnx(_("no changes made")); + pw_error((char *)NULL, 0, 0); + } + /* pw_tmpfile() will create the file with mode 600 */ + if (!is_shadow) + ch_ret = fchmod(fileno(tmp_fd), 0644); + else + ch_ret = fchmod(fileno(tmp_fd), 0400); + if (ch_ret < 0) + err(EXIT_FAILURE, "%s: %s", _("cannot chmod file"), orig_file); + if (close_stream(tmp_fd) != 0) + err(EXIT_FAILURE, _("write error")); + pw_write(); + close(passwd_file); + ulckpwdf(); +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (!strcmp(program_invocation_short_name, "vigr")) { + program = VIGR; + xstrncpy(orig_file, GROUP_FILE, sizeof(orig_file)); + } else { + program = VIPW; + xstrncpy(orig_file, PASSWD_FILE, sizeof(orig_file)); + } + + if ((argc > 1) && + (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version"))) { + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + } + + edit_file(0); + + if (program == VIGR) { + strncpy(orig_file, SGROUP_FILE, FILENAMELEN - 1); + } else { + strncpy(orig_file, SHADOW_FILE, FILENAMELEN - 1); + } + + if (access(orig_file, F_OK) == 0) { + char response[80]; + + printf((program == VIGR) + ? _("You are using shadow groups on this system.\n") + : _("You are using shadow passwords on this system.\n")); + /* TRANSLATORS: this program uses for y and n rpmatch(3), + * which means they can be translated. */ + printf(_("Would you like to edit %s now [y/n]? "), orig_file); + + if (fgets(response, sizeof(response), stdin)) { + if (rpmatch(response) == 1) + edit_file(1); + } + } + exit(EXIT_SUCCESS); +} |