From 73b16af8feec390afbabd9356d6e5e83c0390838 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:20:47 +0200 Subject: busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- loginutils/Config.in | 283 ++++++++++++++++++ loginutils/Kbuild | 19 ++ loginutils/addgroup.c | 183 ++++++++++++ loginutils/adduser.c | 178 ++++++++++++ loginutils/chpasswd.c | 72 +++++ loginutils/cryptpw.c | 58 ++++ loginutils/deluser.c | 125 ++++++++ loginutils/getty.c | 777 ++++++++++++++++++++++++++++++++++++++++++++++++++ loginutils/login.c | 504 ++++++++++++++++++++++++++++++++ loginutils/passwd.c | 207 ++++++++++++++ loginutils/su.c | 102 +++++++ loginutils/sulogin.c | 117 ++++++++ loginutils/vlock.c | 106 +++++++ 13 files changed, 2731 insertions(+) create mode 100644 loginutils/Config.in create mode 100644 loginutils/Kbuild create mode 100644 loginutils/addgroup.c create mode 100644 loginutils/adduser.c create mode 100644 loginutils/chpasswd.c create mode 100644 loginutils/cryptpw.c create mode 100644 loginutils/deluser.c create mode 100644 loginutils/getty.c create mode 100644 loginutils/login.c create mode 100644 loginutils/passwd.c create mode 100644 loginutils/su.c create mode 100644 loginutils/sulogin.c create mode 100644 loginutils/vlock.c (limited to 'loginutils') diff --git a/loginutils/Config.in b/loginutils/Config.in new file mode 100644 index 0000000..bb1369c --- /dev/null +++ b/loginutils/Config.in @@ -0,0 +1,283 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Login/Password Management Utilities" + +config FEATURE_SHADOWPASSWDS + bool "Support for shadow passwords" + default n + help + Build support for shadow password in /etc/shadow. This file is only + readable by root and thus the encrypted passwords are no longer + publicly readable. + +config USE_BB_PWD_GRP + bool "Use internal password and group functions rather than system functions" + default n + help + If you leave this disabled, busybox will use the system's password + and group functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the password and group functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/password, /etc/group files (and your system will be + smaller, and I will get fewer emails asking about how glibc NSS + works). When this option is enabled, you will not be able to use + PAM to access remote LDAP password servers and whatnot. And if you + want hostname resolution to work with glibc, you still need the + /lib/libnss_* libraries. + + If you need to use glibc's nsswitch.conf mechanism + (e.g. if user/group database is NOT stored in /etc/passwd etc), + you must NOT use this option. + + If you enable this option, it will add about 1.5k. + +config USE_BB_SHADOW + bool "Use internal shadow password functions" + default y + depends on USE_BB_PWD_GRP && FEATURE_SHADOWPASSWDS + help + If you leave this disabled, busybox will use the system's shadow + password handling functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the shadow password functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/shadow file when handling shadow passwords. This + makes your system smaller (and I will get fewer emails asking about + how glibc NSS works). When this option is enabled, you will not be + able to use PAM to access shadow passwords from remote LDAP + password servers and whatnot. + +config USE_BB_CRYPT + bool "Use internal DES and MD5 crypt functions" + default y + help + Busybox has internal DES and MD5 crypt functions. + They produce results which are identical to corresponding + standard C library functions. + + If you leave this disabled, busybox will use the system's + crypt functions. Most C libraries use large (~70k) + static buffers there, and also combine them with more general + DES encryption/decryption. + + For busybox, having large static buffers is undesirable, + especially on NOMMU machines. Busybox also doesn't need + DES encryption/decryption and can do with smaller code. + + If you enable this option, it will add about 4.8k of code + if you are building dynamically linked executable. + In static build, it makes code _smaller_ by about 1.2k, + and likely many kilobytes less of bss. + +config ADDGROUP + bool "addgroup" + default n + help + Utility for creating a new group account. + +config FEATURE_ADDUSER_TO_GROUP + bool "Support for adding users to groups" + default n + depends on ADDGROUP + help + If called with two non-option arguments, + addgroup will add an existing user to an + existing group. + +config DELGROUP + bool "delgroup" + default n + help + Utility for deleting a group account. + +config FEATURE_DEL_USER_FROM_GROUP + bool "Support for removing users from groups" + default n + depends on DELGROUP + help + If called with two non-option arguments, deluser + or delgroup will remove an user from a specified group. + +config FEATURE_CHECK_NAMES + bool "Enable sanity check on user/group names in adduser and addgroup" + default n + depends on ADDUSER || ADDGROUP + help + Enable sanity check on user and group names in adduser and addgroup. + To avoid problems, the user or group name should consist only of + letters, digits, underscores, periods, at signs and dashes, + and not start with a dash (as defined by IEEE Std 1003.1-2001). + For compatibility with Samba machine accounts "$" is also supported + at the end of the user or group name. + +config ADDUSER + bool "adduser" + default n + help + Utility for creating a new user account. + +config FEATURE_ADDUSER_LONG_OPTIONS + bool "Enable long options" + default n + depends on ADDUSER && GETOPT_LONG + help + Support long options for the adduser applet. + +config DELUSER + bool "deluser" + default n + help + Utility for deleting a user account. + +config GETTY + bool "getty" + default n + select FEATURE_SYSLOG + help + getty lets you log in on a tty, it is normally invoked by init. + +config FEATURE_UTMP + bool "Support utmp file" + depends on GETTY || LOGIN || SU || WHO + default n + help + The file /var/run/utmp is used to track who is currently logged in. + +config FEATURE_WTMP + bool "Support wtmp file" + depends on GETTY || LOGIN || SU || LAST + default n + select FEATURE_UTMP + help + The file /var/run/wtmp is used to track when user's have logged into + and logged out of the system. + +config LOGIN + bool "login" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + login is used when signing onto a system. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config PAM + bool "Support for PAM (Pluggable Authentication Modules)" + default n + depends on LOGIN + help + Use PAM in login(1) instead of direct access to password database. + +config LOGIN_SCRIPTS + bool "Support for login scripts" + depends on LOGIN + default n + help + Enable this if you want login to execute $LOGIN_PRE_SUID_SCRIPT + just prior to switching from root to logged-in user. + +config FEATURE_NOLOGIN + bool "Support for /etc/nologin" + default y + depends on LOGIN + help + The file /etc/nologin is used by (some versions of) login(1). + If it exists, non-root logins are prohibited. + +config FEATURE_SECURETTY + bool "Support for /etc/securetty" + default y + depends on LOGIN + help + The file /etc/securetty is used by (some versions of) login(1). + The file contains the device names of tty lines (one per line, + without leading /dev/) on which root is allowed to login. + +config PASSWD + bool "passwd" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + passwd changes passwords for user and group accounts. A normal user + may only change the password for his/her own account, the super user + may change the password for any account. The administrator of a group + may change the password for the group. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config FEATURE_PASSWD_WEAK_CHECK + bool "Check new passwords for weakness" + default y + depends on PASSWD + help + With this option passwd will refuse new passwords which are "weak". + +config CRYPTPW + bool "cryptpw" + default n + help + Applet for crypting a string. + +config CHPASSWD + bool "chpasswd" + default n + help + chpasswd reads a file of user name and password pairs from + standard input and uses this information to update a group of + existing users. + +config SU + bool "su" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + su is used to become another user during a login session. + Invoked without a username, su defaults to becoming the super user. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config FEATURE_SU_SYSLOG + bool "Enable su to write to syslog" + default y + depends on SU + +config FEATURE_SU_CHECKS_SHELLS + bool "Enable su to check user's shell to be listed in /etc/shells" + depends on SU + default y + +config SULOGIN + bool "sulogin" + default n + select FEATURE_SYSLOG + help + sulogin is invoked when the system goes into single user + mode (this is done through an entry in inittab). + +config VLOCK + bool "vlock" + default n + select FEATURE_SUID + help + Build the "vlock" applet which allows you to lock (virtual) terminals. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +endmenu diff --git a/loginutils/Kbuild b/loginutils/Kbuild new file mode 100644 index 0000000..3d0d777 --- /dev/null +++ b/loginutils/Kbuild @@ -0,0 +1,19 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ADDGROUP) += addgroup.o +lib-$(CONFIG_ADDUSER) += adduser.o +lib-$(CONFIG_CRYPTPW) += cryptpw.o +lib-$(CONFIG_CHPASSWD) += chpasswd.o +lib-$(CONFIG_GETTY) += getty.o +lib-$(CONFIG_LOGIN) += login.o +lib-$(CONFIG_PASSWD) += passwd.o +lib-$(CONFIG_SU) += su.o +lib-$(CONFIG_SULOGIN) += sulogin.o +lib-$(CONFIG_VLOCK) += vlock.o +lib-$(CONFIG_DELUSER) += deluser.o +lib-$(CONFIG_DELGROUP) += deluser.o diff --git a/loginutils/addgroup.c b/loginutils/addgroup.c new file mode 100644 index 0000000..89414d7 --- /dev/null +++ b/loginutils/addgroup.c @@ -0,0 +1,183 @@ +/* vi: set sw=4 ts=4: */ +/* + * addgroup - add groups to /etc/group and /etc/gshadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Copyright (C) 2007 by Tito Ragusa + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "libbb.h" + +static void xgroup_study(struct group *g) +{ + /* Make sure gr_name is unused */ + if (getgrnam(g->gr_name)) { + goto error; + } + + /* Check if the desired gid is free + * or find the first free one */ + while (1) { + if (!getgrgid(g->gr_gid)) { + return; /* found free group: return */ + } + if (option_mask32) { + /* -g N, cannot pick gid other than N: error */ + g->gr_name = itoa(g->gr_gid); + goto error; + } + g->gr_gid++; + if (g->gr_gid <= 0) { + /* overflowed: error */ + bb_error_msg_and_die("no gids left"); + } + } + + error: + /* exit */ + bb_error_msg_and_die("group %s already exists", g->gr_name); +} + +/* append a new user to the passwd file */ +static void new_group(char *group, gid_t gid) +{ + FILE *file; + struct group gr; + + /* make sure gid and group haven't already been allocated */ + gr.gr_gid = gid; + gr.gr_name = group; + xgroup_study(&gr); + + /* add entry to group */ + file = xfopen(bb_path_group_file, "a"); + /* group:passwd:gid:userlist */ + fprintf(file, "%s:x:%u:\n", group, (unsigned)gr.gr_gid); + if (ENABLE_FEATURE_CLEAN_UP) + fclose(file); +#if ENABLE_FEATURE_SHADOWPASSWDS + file = fopen_or_warn(bb_path_gshadow_file, "a"); + if (file) { + fprintf(file, "%s:!::\n", group); + if (ENABLE_FEATURE_CLEAN_UP) + fclose(file); + } +#endif +} + +#if ENABLE_FEATURE_ADDUSER_TO_GROUP +static void add_user_to_group(char **args, + const char *path, + FILE* FAST_FUNC (*fopen_func)(const char *fileName, const char *mode)) +{ + char *line; + int len = strlen(args[1]); + llist_t *plist = NULL; + FILE *group_file; + + group_file = fopen_func(path, "r"); + + if (!group_file) return; + + while ((line = xmalloc_fgetline(group_file)) != NULL) { + /* Find the group */ + if (!strncmp(line, args[1], len) + && line[len] == ':' + ) { + /* Add the new user */ + line = xasprintf("%s%s%s", line, + last_char_is(line, ':') ? "" : ",", + args[0]); + } + llist_add_to_end(&plist, line); + } + + if (ENABLE_FEATURE_CLEAN_UP) { + fclose(group_file); + group_file = fopen_func(path, "w"); + while ((line = llist_pop(&plist))) { + if (group_file) + fprintf(group_file, "%s\n", line); + free(line); + } + if (group_file) + fclose(group_file); + } else { + group_file = fopen_func(path, "w"); + if (group_file) + while ((line = llist_pop(&plist))) + fprintf(group_file, "%s\n", line); + } +} +#endif + +/* + * addgroup will take a login_name as its first parameter. + * + * gid can be customized via command-line parameters. + * If called with two non-option arguments, addgroup + * will add an existing user to an existing group. + */ +int addgroup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int addgroup_main(int argc UNUSED_PARAM, char **argv) +{ + char *group; + gid_t gid = 0; + + /* need to be root */ + if (geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + /* Syntax: + * addgroup group + * addgroup -g num group + * addgroup user group + * Check for min, max and missing args */ + opt_complementary = "-1:?2"; + if (getopt32(argv, "g:", &group)) { + gid = xatoul_range(group, 0, ((unsigned long)(gid_t)ULONG_MAX) >> 1); + } + /* move past the commandline options */ + argv += optind; + //argc -= optind; + +#if ENABLE_FEATURE_ADDUSER_TO_GROUP + if (argv[1]) { + struct group *gr; + + if (option_mask32) { + /* -g was there, but "addgroup -g num user group" + * is a no-no */ + bb_show_usage(); + } + + /* check if group and user exist */ + xuname2uid(argv[0]); /* unknown user: exit */ + xgroup2gid(argv[1]); /* unknown group: exit */ + /* check if user is already in this group */ + gr = getgrnam(argv[1]); + for (; *(gr->gr_mem) != NULL; (gr->gr_mem)++) { + if (!strcmp(argv[0], *(gr->gr_mem))) { + /* user is already in group: do nothing */ + return EXIT_SUCCESS; + } + } + add_user_to_group(argv, bb_path_group_file, xfopen); +#if ENABLE_FEATURE_SHADOWPASSWDS + add_user_to_group(argv, bb_path_gshadow_file, fopen_or_warn); +#endif + } else +#endif /* ENABLE_FEATURE_ADDUSER_TO_GROUP */ + { + die_if_bad_username(argv[0]); + new_group(argv[0], gid); + + } + /* Reached only on success */ + return EXIT_SUCCESS; +} diff --git a/loginutils/adduser.c b/loginutils/adduser.c new file mode 100644 index 0000000..3154806 --- /dev/null +++ b/loginutils/adduser.c @@ -0,0 +1,178 @@ +/* vi: set sw=4 ts=4: */ +/* + * adduser - add users to /etc/passwd and /etc/shadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +#define OPT_DONT_SET_PASS (1 << 4) +#define OPT_SYSTEM_ACCOUNT (1 << 5) +#define OPT_DONT_MAKE_HOME (1 << 6) + + +/* remix */ +/* recoded such that the uid may be passed in *p */ +static void passwd_study(struct passwd *p) +{ + int max; + + if (getpwnam(p->pw_name)) + bb_error_msg_and_die("login '%s' is in use", p->pw_name); + + if (option_mask32 & OPT_SYSTEM_ACCOUNT) { + p->pw_uid = 0; + max = 999; + } else { + p->pw_uid = 1000; + max = 64999; + } + + /* check for a free uid (and maybe gid) */ + while (getpwuid(p->pw_uid) || (!p->pw_gid && getgrgid(p->pw_uid))) + p->pw_uid++; + + if (!p->pw_gid) { + /* new gid = uid */ + p->pw_gid = p->pw_uid; + if (getgrnam(p->pw_name)) + bb_error_msg_and_die("group name '%s' is in use", p->pw_name); + } + + if (p->pw_uid > max) + bb_error_msg_and_die("no free uids left"); +} + +static void addgroup_wrapper(struct passwd *p) +{ + char *cmd; + + cmd = xasprintf("addgroup -g %u '%s'", (unsigned)p->pw_gid, p->pw_name); + system(cmd); + free(cmd); +} + +static void passwd_wrapper(const char *login) NORETURN; + +static void passwd_wrapper(const char *login) +{ + static const char prog[] ALIGN1 = "passwd"; + + BB_EXECLP(prog, prog, login, NULL); + bb_error_msg_and_die("cannot execute %s, you must set password manually", prog); +} + +#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS +static const char adduser_longopts[] ALIGN1 = + "home\0" Required_argument "h" + "gecos\0" Required_argument "g" + "shell\0" Required_argument "s" + "ingroup\0" Required_argument "G" + "disabled-password\0" No_argument "D" + "empty-password\0" No_argument "D" + "system\0" No_argument "S" + "no-create-home\0" No_argument "H" + ; +#endif + +/* + * adduser will take a login_name as its first parameter. + * home, shell, gecos: + * can be customized via command-line parameters. + */ +int adduser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int adduser_main(int argc UNUSED_PARAM, char **argv) +{ + struct passwd pw; + const char *usegroup = NULL; + FILE *file; + +#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS + applet_long_options = adduser_longopts; +#endif + + /* got root? */ + if (geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + pw.pw_gecos = (char *)"Linux User,,,"; + pw.pw_shell = (char *)DEFAULT_SHELL; + pw.pw_dir = NULL; + + /* exactly one non-option arg */ + opt_complementary = "=1"; + getopt32(argv, "h:g:s:G:DSH", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup); + argv += optind; + + /* fill in the passwd struct */ + pw.pw_name = argv[0]; + die_if_bad_username(pw.pw_name); + if (!pw.pw_dir) { + /* create string for $HOME if not specified already */ + pw.pw_dir = xasprintf("/home/%s", argv[0]); + } + pw.pw_passwd = (char *)"x"; + pw.pw_gid = usegroup ? xgroup2gid(usegroup) : 0; /* exits on failure */ + + /* make sure everything is kosher and setup uid && maybe gid */ + passwd_study(&pw); + + /* add to passwd */ + file = xfopen(bb_path_passwd_file, "a"); + //fseek(file, 0, SEEK_END); /* paranoia, "a" should ensure that anyway */ + if (putpwent(&pw, file) != 0) { + bb_perror_nomsg_and_die(); + } + /* do fclose even if !ENABLE_FEATURE_CLEAN_UP. + * We will exec passwd, files must be flushed & closed before that! */ + fclose(file); + +#if ENABLE_FEATURE_SHADOWPASSWDS + /* add to shadow if necessary */ + file = fopen_or_warn(bb_path_shadow_file, "a"); + if (file) { + //fseek(file, 0, SEEK_END); + fprintf(file, "%s:!:%u:0:99999:7:::\n", + pw.pw_name, /* username */ + (unsigned)(time(NULL) / 86400) /* sp->sp_lstchg */ + /*0,*/ /* sp->sp_min */ + /*99999,*/ /* sp->sp_max */ + /*7*/ /* sp->sp_warn */ + ); + fclose(file); + } +#endif + + /* add to group */ + /* addgroup should be responsible for dealing w/ gshadow */ + /* if using a pre-existing group, don't create one */ + if (!usegroup) + addgroup_wrapper(&pw); + + /* Clear the umask for this process so it doesn't + * screw up the permissions on the mkdir and chown. */ + umask(0); + if (!(option_mask32 & OPT_DONT_MAKE_HOME)) { + /* Set the owner and group so it is owned by the new user, + then fix up the permissions to 2755. Can't do it before + since chown will clear the setgid bit */ + if (mkdir(pw.pw_dir, 0755) + || chown(pw.pw_dir, pw.pw_uid, pw.pw_gid) + || chmod(pw.pw_dir, 02755) /* set setgid bit on homedir */ + ) { + bb_simple_perror_msg(pw.pw_dir); + } + } + + if (!(option_mask32 & OPT_DONT_SET_PASS)) { + /* interactively set passwd */ + passwd_wrapper(pw.pw_name); + } + + return 0; +} diff --git a/loginutils/chpasswd.c b/loginutils/chpasswd.c new file mode 100644 index 0000000..c83d1da --- /dev/null +++ b/loginutils/chpasswd.c @@ -0,0 +1,72 @@ +/* vi: set sw=4 ts=4: */ +/* + * chpasswd.c + * + * Written for SLIND (from passwd.c) by Alexander Shishkin + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#if ENABLE_GETOPT_LONG +static const char chpasswd_longopts[] ALIGN1 = + "encrypted\0" No_argument "e" + "md5\0" No_argument "m" + ; +#endif + +#define OPT_ENC 1 +#define OPT_MD5 2 + +int chpasswd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int chpasswd_main(int argc UNUSED_PARAM, char **argv) +{ + char *name, *pass; + char salt[sizeof("$N$XXXXXXXX")]; + int opt, rc; + int rnd = rnd; /* we *want* it to be non-initialized! */ + + if (getuid()) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + + opt_complementary = "m--e:e--m"; + USE_GETOPT_LONG(applet_long_options = chpasswd_longopts;) + opt = getopt32(argv, "em"); + + while ((name = xmalloc_fgetline(stdin)) != NULL) { + pass = strchr(name, ':'); + if (!pass) + bb_error_msg_and_die("missing new password"); + *pass++ = '\0'; + + xuname2uid(name); /* dies if there is no such user */ + + if (!(opt & OPT_ENC)) { + rnd = crypt_make_salt(salt, 1, rnd); + if (opt & OPT_MD5) { + strcpy(salt, "$1$"); + rnd = crypt_make_salt(salt + 3, 4, rnd); + } + pass = pw_encrypt(pass, salt, 0); + } + + /* This is rather complex: if user is not found in /etc/shadow, + * we try to find & change his passwd in /etc/passwd */ +#if ENABLE_FEATURE_SHADOWPASSWDS + rc = update_passwd(bb_path_shadow_file, name, pass); + if (rc == 0) /* no lines updated, no errors detected */ +#endif + rc = update_passwd(bb_path_passwd_file, name, pass); + /* LOGMODE_BOTH logs to syslog also */ + logmode = LOGMODE_BOTH; + if (rc < 0) + bb_error_msg_and_die("an error occurred updating password for %s", name); + if (rc) + bb_info_msg("Password for '%s' changed", name); + logmode = LOGMODE_STDIO; + free(name); + if (!(opt & OPT_ENC)) + free(pass); + } + return EXIT_SUCCESS; +} diff --git a/loginutils/cryptpw.c b/loginutils/cryptpw.c new file mode 100644 index 0000000..db5d959 --- /dev/null +++ b/loginutils/cryptpw.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * cryptpw.c + * + * Cooked from passwd.c by Thomas Lundquist + */ + +#include "libbb.h" + +#define TESTING 0 + +/* +set TESTING to 1 and pipe some file through this script +if you played with bbox's crypt implementation. + +while read line; do + n=`./busybox cryptpw -a des -- "$line"` + o=`./busybox_org cryptpw -a des -- "$line"` + test "$n" != "$o" && { + echo n="$n" + echo o="$o" + exit + } + n=`./busybox cryptpw -- "$line"` + o=`./busybox_org cryptpw -- "$line"` + test "$n" != "$o" && { + echo n="$n" + echo o="$o" + exit + } +done + */ + +int cryptpw_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int cryptpw_main(int argc UNUSED_PARAM, char **argv) +{ + char salt[sizeof("$N$XXXXXXXX")]; + char *opt_a; + + if (!getopt32(argv, "a:", &opt_a) || opt_a[0] != 'd') { + salt[0] = '$'; + salt[1] = '1'; + salt[2] = '$'; + crypt_make_salt(salt + 3, 4, 0); /* md5 */ +#if TESTING + strcpy(salt + 3, "ajg./bcf"); +#endif + } else { + crypt_make_salt(salt, 1, 0); /* des */ +#if TESTING + strcpy(salt, "a."); +#endif + } + + puts(pw_encrypt(argv[optind] ? argv[optind] : xmalloc_fgetline(stdin), salt, 1)); + + return 0; +} diff --git a/loginutils/deluser.c b/loginutils/deluser.c new file mode 100644 index 0000000..5625371 --- /dev/null +++ b/loginutils/deluser.c @@ -0,0 +1,125 @@ +/* vi: set sw=4 ts=4: */ +/* + * deluser/delgroup implementation for busybox + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Copyright (C) 2007 by Tito Ragusa + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + * + */ + +#include "libbb.h" + +/* Status */ +#define STATUS_OK 0 +#define NAME_NOT_FOUND 1 +#define MEMBER_NOT_FOUND 2 + +static void del_line_matching(char **args, + const char *filename, + FILE* FAST_FUNC (*fopen_func)(const char *fileName, const char *mode)) +{ + FILE *passwd; + smallint error = NAME_NOT_FOUND; + char *name = (ENABLE_FEATURE_DEL_USER_FROM_GROUP && args[2]) ? args[2] : args[1]; + char *line, *del; + char *new = xzalloc(1); + + passwd = fopen_func(filename, "r"); + if (passwd) { + while ((line = xmalloc_fgets(passwd))) { + int len = strlen(name); + + if (strncmp(line, name, len) == 0 + && line[len] == ':' + ) { + error = STATUS_OK; + if (ENABLE_FEATURE_DEL_USER_FROM_GROUP) { + struct group *gr; + char *p; + if (args[2] + /* There were two args on commandline */ + && (gr = getgrnam(name)) + /* The group was not deleted in the meanwhile */ + && (p = strrchr(line, ':')) + /* We can find a pointer to the last ':' */ + ) { + error = MEMBER_NOT_FOUND; + /* Move past ':' (worst case to '\0') and cut the line */ + p[1] = '\0'; + /* Reuse p */ + for (p = xzalloc(1); *gr->gr_mem != NULL; gr->gr_mem++) { + /* Add all the other group members */ + if (strcmp(args[1], *gr->gr_mem) != 0) { + del = p; + p = xasprintf("%s%s%s", p, p[0] ? "," : "", *gr->gr_mem); + free(del); + } else + error = STATUS_OK; + } + /* Recompose the line */ + line = xasprintf("%s%s\n", line, p); + if (ENABLE_FEATURE_CLEAN_UP) free(p); + } else + goto skip; + } + } + del = new; + new = xasprintf("%s%s", new, line); + free(del); + skip: + free(line); + } + + if (ENABLE_FEATURE_CLEAN_UP) fclose(passwd); + + if (error) { + if (ENABLE_FEATURE_DEL_USER_FROM_GROUP && error == MEMBER_NOT_FOUND) { + /* Set the correct values for error message */ + filename = name; + name = args[1]; + } + bb_error_msg("can't find %s in %s", name, filename); + } else { + passwd = fopen_func(filename, "w"); + if (passwd) { + fputs(new, passwd); + if (ENABLE_FEATURE_CLEAN_UP) fclose(passwd); + } + } + } + free(new); +} + +int deluser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int deluser_main(int argc, char **argv) +{ + if (argc == 2 + || (ENABLE_FEATURE_DEL_USER_FROM_GROUP + && (applet_name[3] == 'g' && argc == 3)) + ) { + if (geteuid()) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + + if ((ENABLE_FEATURE_DEL_USER_FROM_GROUP && argc != 3) + || ENABLE_DELUSER + || (ENABLE_DELGROUP && ENABLE_DESKTOP) + ) { + if (ENABLE_DELUSER + && (!ENABLE_DELGROUP || applet_name[3] == 'u') + ) { + del_line_matching(argv, bb_path_passwd_file, xfopen); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv, bb_path_shadow_file, fopen_or_warn); + } else if (ENABLE_DESKTOP && ENABLE_DELGROUP && getpwnam(argv[1])) + bb_error_msg_and_die("can't remove primary group of user %s", argv[1]); + } + del_line_matching(argv, bb_path_group_file, xfopen); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv, bb_path_gshadow_file, fopen_or_warn); + return EXIT_SUCCESS; + } else + bb_show_usage(); +} diff --git a/loginutils/getty.c b/loginutils/getty.c new file mode 100644 index 0000000..0f53688 --- /dev/null +++ b/loginutils/getty.c @@ -0,0 +1,777 @@ +/* vi: set sw=4 ts=4: */ +/* agetty.c - another getty program for Linux. By W. Z. Venema 1989 + * Ported to Linux by Peter Orbaek + * This program is freely distributable. The entire man-page used to + * be here. Now read the real man-page agetty.8 instead. + * + * option added by Eric Rasmussen - 12/28/95 + * + * 1999-02-22 Arkadiusz Mickiewicz + * - added Native Language Support + * + * 1999-05-05 Thorsten Kranzkowski + * - enable hardware flow control before displaying /etc/issue + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +#if ENABLE_FEATURE_UTMP +#include /* updwtmp() */ +#endif + +/* + * Some heuristics to find out what environment we are in: if it is not + * System V, assume it is SunOS 4. + */ +#ifdef LOGIN_PROCESS /* defined in System V utmp.h */ +#include +#include +#else /* if !sysV style, wtmp/utmp code is off */ +#undef ENABLE_FEATURE_UTMP +#undef ENABLE_FEATURE_WTMP +#define ENABLE_FEATURE_UTMP 0 +#define ENABLE_FEATURE_WTMP 0 +#endif /* LOGIN_PROCESS */ + +/* + * Things you may want to modify. + * + * You may disagree with the default line-editing etc. characters defined + * below. Note, however, that DEL cannot be used for interrupt generation + * and for line editing at the same time. + */ + +/* I doubt there are systems which still need this */ +#undef HANDLE_ALLCAPS +#undef ANCIENT_BS_KILL_CHARS + +#define _PATH_LOGIN "/bin/login" + +/* If ISSUE is not defined, getty will never display the contents of the + * /etc/issue file. You will not want to spit out large "issue" files at the + * wrong baud rate. + */ +#define ISSUE "/etc/issue" /* displayed before the login prompt */ + +/* Some shorthands for control characters. */ +#define CTL(x) ((x) ^ 0100) /* Assumes ASCII dialect */ +#define CR CTL('M') /* carriage return */ +#define NL CTL('J') /* line feed */ +#define BS CTL('H') /* back space */ +#define DEL CTL('?') /* delete */ + +/* Defaults for line-editing etc. characters; you may want to change this. */ +#define DEF_ERASE DEL /* default erase character */ +#define DEF_INTR CTL('C') /* default interrupt character */ +#define DEF_QUIT CTL('\\') /* default quit char */ +#define DEF_KILL CTL('U') /* default kill char */ +#define DEF_EOF CTL('D') /* default EOF char */ +#define DEF_EOL '\n' +#define DEF_SWITCH 0 /* default switch char */ + +/* + * When multiple baud rates are specified on the command line, the first one + * we will try is the first one specified. + */ +#define MAX_SPEED 10 /* max. nr. of baud rates */ + +/* Storage for command-line options. */ +struct options { + int flags; /* toggle switches, see below */ + unsigned timeout; /* time-out period */ + const char *login; /* login program */ + const char *tty; /* name of tty */ + const char *initstring; /* modem init string */ + const char *issue; /* alternative issue file */ + int numspeed; /* number of baud rates to try */ + int speeds[MAX_SPEED]; /* baud rates to be tried */ +}; + +/* Storage for things detected while the login name was read. */ +struct chardata { + unsigned char erase; /* erase character */ + unsigned char kill; /* kill character */ + unsigned char eol; /* end-of-line character */ + unsigned char parity; /* what parity did we see */ + /* (parity & 1): saw odd parity char with 7th bit set */ + /* (parity & 2): saw even parity char with 7th bit set */ + /* parity == 0: probably 7-bit, space parity? */ + /* parity == 1: probably 7-bit, odd parity? */ + /* parity == 2: probably 7-bit, even parity? */ + /* parity == 3: definitely 8 bit, no parity! */ + /* Hmm... with any value of "parity" 8 bit, no parity is possible */ +#ifdef HANDLE_ALLCAPS + unsigned char capslock; /* upper case without lower case */ +#endif +}; + + +/* Initial values for the above. */ +static const struct chardata init_chardata = { + DEF_ERASE, /* default erase character */ + DEF_KILL, /* default kill character */ + 13, /* default eol char */ + 0, /* space parity */ +#ifdef HANDLE_ALLCAPS + 0, /* no capslock */ +#endif +}; + +static const char opt_string[] ALIGN1 = "I:LH:f:hil:mt:wn"; +#define F_INITSTRING (1 << 0) /* -I initstring is set */ +#define F_LOCAL (1 << 1) /* -L force local */ +#define F_FAKEHOST (1 << 2) /* -H fake hostname */ +#define F_CUSTISSUE (1 << 3) /* -f give alternative issue file */ +#define F_RTSCTS (1 << 4) /* -h enable RTS/CTS flow control */ +#define F_ISSUE (1 << 5) /* -i display /etc/issue */ +#define F_LOGIN (1 << 6) /* -l non-default login program */ +#define F_PARSE (1 << 7) /* -m process modem status messages */ +#define F_TIMEOUT (1 << 8) /* -t time out */ +#define F_WAITCRLF (1 << 9) /* -w wait for CR or LF */ +#define F_NOPROMPT (1 << 10) /* -n don't ask for login name */ + + +#define line_buf bb_common_bufsiz1 + +/* The following is used for understandable diagnostics. */ +#ifdef DEBUGGING +static FILE *dbf; +#define DEBUGTERM "/dev/ttyp0" +#define debug(...) do { fprintf(dbf, __VA_ARGS__); fflush(dbf); } while (0) +#else +#define debug(...) ((void)0) +#endif + + +/* bcode - convert speed string to speed code; return <= 0 on failure */ +static int bcode(const char *s) +{ + int value = bb_strtou(s, NULL, 10); /* yes, int is intended! */ + if (value < 0) /* bad terminating char, overflow, etc */ + return value; + return tty_value_to_baud(value); +} + +/* parse_speeds - parse alternate baud rates */ +static void parse_speeds(struct options *op, char *arg) +{ + char *cp; + + /* NB: at least one iteration is always done */ + debug("entered parse_speeds\n"); + while ((cp = strsep(&arg, ",")) != NULL) { + op->speeds[op->numspeed] = bcode(cp); + if (op->speeds[op->numspeed] <= 0) + bb_error_msg_and_die("bad speed: %s", cp); + op->numspeed++; + if (op->numspeed > MAX_SPEED) + bb_error_msg_and_die("too many alternate speeds"); + } + debug("exiting parse_speeds\n"); +} + +/* parse_args - parse command-line arguments */ +static void parse_args(char **argv, struct options *op, char **fakehost_p) +{ + char *ts; + + opt_complementary = "-2:t+"; /* at least 2 args; -t N */ + op->flags = getopt32(argv, opt_string, + &(op->initstring), fakehost_p, &(op->issue), + &(op->login), &op->timeout); + argv += optind; + if (op->flags & F_INITSTRING) { + const char *p = op->initstring; + char *q; + + op->initstring = q = xstrdup(p); + /* copy optarg into op->initstring decoding \ddd + octal codes into chars */ + while (*p) { + if (*p == '\\') { + p++; + *q++ = bb_process_escape_sequence(&p); + } else { + *q++ = *p++; + } + } + *q = '\0'; + } + op->flags ^= F_ISSUE; /* invert flag "show /etc/issue" */ + debug("after getopt\n"); + + /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */ + op->tty = argv[0]; /* tty name */ + ts = argv[1]; /* baud rate(s) */ + if (isdigit(argv[0][0])) { + /* a number first, assume it's a speed (BSD style) */ + op->tty = ts; /* tty name is in argv[1] */ + ts = argv[0]; /* baud rate(s) */ + } + parse_speeds(op, ts); + +// TODO: if applet_name is set to "getty: TTY", bb_error_msg's get simpler! +// grep for "%s:" + + if (argv[2]) + xsetenv("TERM", argv[2]); + + debug("exiting parse_args\n"); +} + +/* open_tty - set up tty as standard { input, output, error } */ +static void open_tty(const char *tty) +{ + /* Set up new standard input, unless we are given an already opened port. */ + if (NOT_LONE_DASH(tty)) { +// struct stat st; +// int cur_dir_fd; +// int fd; + + /* Sanity checks... */ +// cur_dir_fd = xopen(".", O_DIRECTORY | O_NONBLOCK); +// xchdir("/dev"); +// xstat(tty, &st); +// if ((st.st_mode & S_IFMT) != S_IFCHR) +// bb_error_msg_and_die("%s: not a character device", tty); + + if (tty[0] != '/') + tty = xasprintf("/dev/%s", tty); /* will leak it */ + + /* Open the tty as standard input. */ + debug("open(2)\n"); + close(0); + /*fd =*/ xopen(tty, O_RDWR | O_NONBLOCK); /* uses fd 0 */ + +// /* Restore current directory */ +// fchdir(cur_dir_fd); + + /* Open the tty as standard input, continued */ +// xmove_fd(fd, 0); +// /* fd is >= cur_dir_fd, and cur_dir_fd gets closed too here: */ +// while (fd > 2) +// close(fd--); + + /* Set proper protections and ownership. */ + fchown(0, 0, 0); /* 0:0 */ + fchmod(0, 0620); /* crw--w---- */ + } else { + /* + * Standard input should already be connected to an open port. Make + * sure it is open for read/write. + */ + if ((fcntl(0, F_GETFL) & O_RDWR) != O_RDWR) + bb_error_msg_and_die("stdin is not open for read/write"); + } +} + +/* termios_init - initialize termios settings */ +static void termios_init(struct termios *tp, int speed, struct options *op) +{ + /* + * Initial termios settings: 8-bit characters, raw-mode, blocking i/o. + * Special characters are set after we have read the login name; all + * reads will be done in raw mode anyway. Errors will be dealt with + * later on. + */ +#ifdef __linux__ + /* flush input and output queues, important for modems! */ + ioctl(0, TCFLSH, TCIOFLUSH); /* tcflush(0, TCIOFLUSH)? - same */ +#endif + + tp->c_cflag = CS8 | HUPCL | CREAD | speed; + if (op->flags & F_LOCAL) + tp->c_cflag |= CLOCAL; + + tp->c_iflag = tp->c_lflag = tp->c_line = 0; + tp->c_oflag = OPOST | ONLCR; + tp->c_cc[VMIN] = 1; + tp->c_cc[VTIME] = 0; + + /* Optionally enable hardware flow control */ +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + tcsetattr_stdin_TCSANOW(tp); + + debug("term_io 2\n"); +} + +/* auto_baud - extract baud rate from modem status message */ +static void auto_baud(char *buf, unsigned size_buf, struct termios *tp) +{ + int speed; + int vmin; + unsigned iflag; + char *bp; + int nread; + + /* + * This works only if the modem produces its status code AFTER raising + * the DCD line, and if the computer is fast enough to set the proper + * baud rate before the message has gone by. We expect a message of the + * following format: + * + * + * + * The number is interpreted as the baud rate of the incoming call. If the + * modem does not tell us the baud rate within one second, we will keep + * using the current baud rate. It is advisable to enable BREAK + * processing (comma-separated list of baud rates) if the processing of + * modem status messages is enabled. + */ + + /* + * Use 7-bit characters, don't block if input queue is empty. Errors will + * be dealt with later on. + */ + iflag = tp->c_iflag; + tp->c_iflag |= ISTRIP; /* enable 8th-bit stripping */ + vmin = tp->c_cc[VMIN]; + tp->c_cc[VMIN] = 0; /* don't block if queue empty */ + tcsetattr_stdin_TCSANOW(tp); + + /* + * Wait for a while, then read everything the modem has said so far and + * try to extract the speed of the dial-in call. + */ + sleep(1); + nread = safe_read(STDIN_FILENO, buf, size_buf - 1); + if (nread > 0) { + buf[nread] = '\0'; + for (bp = buf; bp < buf + nread; bp++) { + if (isdigit(*bp)) { + speed = bcode(bp); + if (speed > 0) { + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= speed; + } + break; + } + } + } + + /* Restore terminal settings. Errors will be dealt with later on. */ + tp->c_iflag = iflag; + tp->c_cc[VMIN] = vmin; + tcsetattr_stdin_TCSANOW(tp); +} + +/* do_prompt - show login prompt, optionally preceded by /etc/issue contents */ +static void do_prompt(struct options *op) +{ +#ifdef ISSUE + print_login_issue(op->issue, op->tty); +#endif + print_login_prompt(); +} + +#ifdef HANDLE_ALLCAPS +/* all_is_upcase - string contains upper case without lower case */ +/* returns 1 if true, 0 if false */ +static int all_is_upcase(const char *s) +{ + while (*s) + if (islower(*s++)) + return 0; + return 1; +} +#endif + +/* get_logname - get user name, establish parity, speed, erase, kill, eol; + * return NULL on BREAK, logname on success */ +static char *get_logname(char *logname, unsigned size_logname, + struct options *op, struct chardata *cp) +{ + char *bp; + char c; /* input character, full eight bits */ + char ascval; /* low 7 bits of input character */ + int bits; /* # of "1" bits per character */ + int mask; /* mask with 1 bit up */ + static const char erase[][3] = {/* backspace-space-backspace */ + "\010\040\010", /* space parity */ + "\010\040\010", /* odd parity */ + "\210\240\210", /* even parity */ + "\010\040\010", /* 8 bit no parity */ + }; + + /* NB: *cp is pre-initialized with init_chardata */ + + /* Flush pending input (esp. after parsing or switching the baud rate). */ + sleep(1); + ioctl(0, TCFLSH, TCIFLUSH); /* tcflush(0, TCIOFLUSH)? - same */ + + /* Prompt for and read a login name. */ + logname[0] = '\0'; + while (!logname[0]) { + /* Write issue file and prompt, with "parity" bit == 0. */ + do_prompt(op); + + /* Read name, watch for break, parity, erase, kill, end-of-line. */ + bp = logname; + cp->eol = '\0'; + while (cp->eol == '\0') { + + /* Do not report trivial EINTR/EIO errors. */ + if (read(STDIN_FILENO, &c, 1) < 1) { + if (errno == EINTR || errno == EIO) + exit(EXIT_SUCCESS); + bb_perror_msg_and_die("%s: read", op->tty); + } + + /* BREAK. If we have speeds to try, + * return NULL (will switch speeds and return here) */ + if (c == '\0' && op->numspeed > 1) + return NULL; + + /* Do parity bit handling. */ + if (!(op->flags & F_LOCAL) && (c & 0x80)) { /* "parity" bit on? */ + bits = 1; + mask = 1; + while (mask & 0x7f) { + if (mask & c) + bits++; /* count "1" bits */ + mask <<= 1; + } + /* ... |= 2 - even, 1 - odd */ + cp->parity |= 2 - (bits & 1); + } + + /* Do erase, kill and end-of-line processing. */ + ascval = c & 0x7f; + switch (ascval) { + case CR: + case NL: + *bp = '\0'; /* terminate logname */ + cp->eol = ascval; /* set end-of-line char */ + break; + case BS: + case DEL: +#ifdef ANCIENT_BS_KILL_CHARS + case '#': +#endif + cp->erase = ascval; /* set erase character */ + if (bp > logname) { + full_write(STDOUT_FILENO, erase[cp->parity], 3); + bp--; + } + break; + case CTL('U'): +#ifdef ANCIENT_BS_KILL_CHARS + case '@': +#endif + cp->kill = ascval; /* set kill character */ + while (bp > logname) { + full_write(STDOUT_FILENO, erase[cp->parity], 3); + bp--; + } + break; + case CTL('D'): + exit(EXIT_SUCCESS); + default: + if (!isascii(ascval) || !isprint(ascval)) { + /* ignore garbage characters */ + } else if ((int)(bp - logname) >= size_logname - 1) { + bb_error_msg_and_die("%s: input overrun", op->tty); + } else { + full_write(STDOUT_FILENO, &c, 1); /* echo the character */ + *bp++ = ascval; /* and store it */ + } + break; + } + } + } + /* Handle names with upper case and no lower case. */ + +#ifdef HANDLE_ALLCAPS + cp->capslock = all_is_upcase(logname); + if (cp->capslock) { + for (bp = logname; *bp; bp++) + if (isupper(*bp)) + *bp = tolower(*bp); /* map name to lower case */ + } +#endif + return logname; +} + +/* termios_final - set the final tty mode bits */ +static void termios_final(struct options *op, struct termios *tp, struct chardata *cp) +{ + /* General terminal-independent stuff. */ + tp->c_iflag |= IXON | IXOFF; /* 2-way flow control */ + tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE; + /* no longer| ECHOCTL | ECHOPRT */ + tp->c_oflag |= OPOST; + /* tp->c_cflag = 0; */ + tp->c_cc[VINTR] = DEF_INTR; /* default interrupt */ + tp->c_cc[VQUIT] = DEF_QUIT; /* default quit */ + tp->c_cc[VEOF] = DEF_EOF; /* default EOF character */ + tp->c_cc[VEOL] = DEF_EOL; + tp->c_cc[VSWTC] = DEF_SWITCH; /* default switch character */ + + /* Account for special characters seen in input. */ + if (cp->eol == CR) { + tp->c_iflag |= ICRNL; /* map CR in input to NL */ + tp->c_oflag |= ONLCR; /* map NL in output to CR-NL */ + } + tp->c_cc[VERASE] = cp->erase; /* set erase character */ + tp->c_cc[VKILL] = cp->kill; /* set kill character */ + + /* Account for the presence or absence of parity bits in input. */ + switch (cp->parity) { + case 0: /* space (always 0) parity */ +// I bet most people go here - they use only 7-bit chars in usernames.... + break; + case 1: /* odd parity */ + tp->c_cflag |= PARODD; + /* FALLTHROUGH */ + case 2: /* even parity */ + tp->c_cflag |= PARENB; + tp->c_iflag |= INPCK | ISTRIP; + /* FALLTHROUGH */ + case (1 | 2): /* no parity bit */ + tp->c_cflag &= ~CSIZE; + tp->c_cflag |= CS7; +// FIXME: wtf? case 3: we saw both even and odd 8-bit bytes - +// it's probably some umlauts etc, but definitely NOT 7-bit!!! +// Entire parity detection madness here just begs for deletion... + break; + } + + /* Account for upper case without lower case. */ +#ifdef HANDLE_ALLCAPS + if (cp->capslock) { + tp->c_iflag |= IUCLC; + tp->c_lflag |= XCASE; + tp->c_oflag |= OLCUC; + } +#endif + /* Optionally enable hardware flow control */ +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + /* Finally, make the new settings effective */ + /* It's tcsetattr_stdin_TCSANOW() + error check */ + ioctl_or_perror_and_die(0, TCSETS, tp, "%s: TCSETS", op->tty); +} + +#if ENABLE_FEATURE_UTMP +static void touch(const char *filename) +{ + if (access(filename, R_OK | W_OK) == -1) + close(open(filename, O_WRONLY | O_CREAT, 0664)); +} + +/* update_utmp - update our utmp entry */ +static void update_utmp(const char *line, char *fakehost) +{ + struct utmp ut; + struct utmp *utp; + int mypid = getpid(); + + /* In case we won't find an entry below... */ + memset(&ut, 0, sizeof(ut)); + safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); + + /* + * The utmp file holds miscellaneous information about things started by + * /sbin/init and other system-related events. Our purpose is to update + * the utmp entry for the current process, in particular the process type + * and the tty line we are listening to. Return successfully only if the + * utmp file can be opened for update, and if we are able to find our + * entry in the utmp file. + */ + touch(_PATH_UTMP); + + utmpname(_PATH_UTMP); + setutent(); + while ((utp = getutent()) != NULL) { + if (utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid) { + memcpy(&ut, utp, sizeof(ut)); + break; + } + } + + strcpy(ut.ut_user, "LOGIN"); + safe_strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + if (fakehost) + safe_strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host)); + ut.ut_tv.tv_sec = time(NULL); + ut.ut_type = LOGIN_PROCESS; + ut.ut_pid = mypid; + + pututline(&ut); + endutent(); + +#if ENABLE_FEATURE_WTMP + touch(bb_path_wtmp_file); + updwtmp(bb_path_wtmp_file, &ut); +#endif +} +#endif /* CONFIG_FEATURE_UTMP */ + +int getty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int getty_main(int argc UNUSED_PARAM, char **argv) +{ + int n; + char *fakehost = NULL; /* Fake hostname for ut_host */ + char *logname; /* login name, given to /bin/login */ + /* Merging these into "struct local" may _seem_ to reduce + * parameter passing, but today's gcc will inline + * statics which are called once anyway, so don't do that */ + struct chardata chardata; /* set by get_logname() */ + struct termios termios; /* terminal mode bits */ + struct options options; + + chardata = init_chardata; + + memset(&options, 0, sizeof(options)); + options.login = _PATH_LOGIN; /* default login program */ + options.tty = "tty1"; /* default tty line */ + options.initstring = ""; /* modem init string */ +#ifdef ISSUE + options.issue = ISSUE; /* default issue file */ +#endif + + /* Parse command-line arguments. */ + parse_args(argv, &options, &fakehost); + + logmode = LOGMODE_NONE; + + /* Create new session, lose controlling tty, if any */ + /* docs/ctty.htm says: + * "This is allowed only when the current process + * is not a process group leader" - is this a problem? */ + setsid(); + /* close stdio, and stray descriptors, just in case */ + n = xopen(bb_dev_null, O_RDWR); + /* dup2(n, 0); - no, we need to handle "getty - 9600" too */ + xdup2(n, 1); + xdup2(n, 2); + while (n > 2) + close(n--); + + /* Logging. We want special flavor of error_msg_and_die */ + die_sleep = 10; + msg_eol = "\r\n"; + /* most likely will internally use fd #3 in CLOEXEC mode: */ + openlog(applet_name, LOG_PID, LOG_AUTH); + logmode = LOGMODE_BOTH; + +#ifdef DEBUGGING + dbf = xfopen_for_write(DEBUGTERM); + for (n = 1; argv[n]; n++) { + debug(argv[n]); + debug("\n"); + } +#endif + + /* Open the tty as standard input, if it is not "-" */ + /* If it's not "-" and not taken yet, it will become our ctty */ + debug("calling open_tty\n"); + open_tty(options.tty); + ndelay_off(0); + debug("duping\n"); + xdup2(0, 1); + xdup2(0, 2); + + /* + * The following ioctl will fail if stdin is not a tty, but also when + * there is noise on the modem control lines. In the latter case, the + * common course of action is (1) fix your cables (2) give the modem more + * time to properly reset after hanging up. SunOS users can achieve (2) + * by patching the SunOS kernel variable "zsadtrlow" to a larger value; + * 5 seconds seems to be a good value. + */ + /* tcgetattr() + error check */ + ioctl_or_perror_and_die(0, TCGETS, &termios, "%s: TCGETS", options.tty); + +#ifdef __linux__ +// FIXME: do we need this? Otherwise "-" case seems to be broken... + // /* Forcibly make fd 0 our controlling tty, even if another session + // * has it as a ctty. (Another session loses ctty). */ + // ioctl(0, TIOCSCTTY, (void*)1); + /* Make ourself a foreground process group within our session */ + tcsetpgrp(0, getpid()); +#endif + +#if ENABLE_FEATURE_UTMP + /* Update the utmp file. This tty is ours now! */ + update_utmp(options.tty, fakehost); +#endif + + /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */ + debug("calling termios_init\n"); + termios_init(&termios, options.speeds[0], &options); + + /* Write the modem init string and DON'T flush the buffers */ + if (options.flags & F_INITSTRING) { + debug("writing init string\n"); + full_write(STDOUT_FILENO, options.initstring, strlen(options.initstring)); + } + + /* Optionally detect the baud rate from the modem status message */ + debug("before autobaud\n"); + if (options.flags & F_PARSE) + auto_baud(line_buf, sizeof(line_buf), &termios); + + /* Set the optional timer */ + alarm(options.timeout); /* if 0, alarm is not set */ + + /* Optionally wait for CR or LF before writing /etc/issue */ + if (options.flags & F_WAITCRLF) { + char ch; + + debug("waiting for cr-lf\n"); + while (safe_read(STDIN_FILENO, &ch, 1) == 1) { + debug("read %x\n", (unsigned char)ch); + ch &= 0x7f; /* strip "parity bit" */ + if (ch == '\n' || ch == '\r') + break; + } + } + + logname = NULL; + if (!(options.flags & F_NOPROMPT)) { + /* NB:termios_init already set line speed + * to options.speeds[0] */ + int baud_index = 0; + + while (1) { + /* Read the login name. */ + debug("reading login name\n"); + logname = get_logname(line_buf, sizeof(line_buf), + &options, &chardata); + if (logname) + break; + /* we are here only if options.numspeed > 1 */ + baud_index = (baud_index + 1) % options.numspeed; + termios.c_cflag &= ~CBAUD; + termios.c_cflag |= options.speeds[baud_index]; + tcsetattr_stdin_TCSANOW(&termios); + } + } + + /* Disable timer. */ + alarm(0); + + /* Finalize the termios settings. */ + termios_final(&options, &termios, &chardata); + + /* Now the newline character should be properly written. */ + full_write(STDOUT_FILENO, "\n", 1); + + /* Let the login program take care of password validation. */ + /* We use PATH because we trust that root doesn't set "bad" PATH, + * and getty is not suid-root applet. */ + /* With -n, logname == NULL, and login will ask for username instead */ + BB_EXECLP(options.login, options.login, "--", logname, NULL); + bb_error_msg_and_die("%s: can't exec %s", options.tty, options.login); +} diff --git a/loginutils/login.c b/loginutils/login.c new file mode 100644 index 0000000..a18b4d5 --- /dev/null +++ b/loginutils/login.c @@ -0,0 +1,504 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include +#include +#include + +#if ENABLE_SELINUX +#include /* for is_selinux_enabled() */ +#include /* for get_default_context() */ +#include /* for security class definitions */ +#endif + +#if ENABLE_PAM +/* PAM may include . We may need to undefine bbox's stub define: */ +#undef setlocale +/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx. + * Apparently they like to confuse people. */ +#include +#include +static const struct pam_conv conv = { + misc_conv, + NULL +}; +#endif + +enum { + TIMEOUT = 60, + EMPTY_USERNAME_COUNT = 10, + USERNAME_SIZE = 32, + TTYNAME_SIZE = 32, +}; + +static char* short_tty; + +#if ENABLE_FEATURE_UTMP +/* vv Taken from tinylogin utmp.c vv */ +/* + * read_or_build_utent - see if utmp file is correct for this process + * + * System V is very picky about the contents of the utmp file + * and requires that a slot for the current process exist. + * The utmp file is scanned for an entry with the same process + * ID. If no entry exists the process exits with a message. + * + * The "picky" flag is for network and other logins that may + * use special flags. It allows the pid checks to be overridden. + * This means that getty should never invoke login with any + * command line flags. + */ + +static void read_or_build_utent(struct utmp *utptr, int run_by_root) +{ + struct utmp *ut; + pid_t pid = getpid(); + + setutent(); + + /* First, try to find a valid utmp entry for this process. */ + /* If there is one, just use it. */ + while ((ut = getutent()) != NULL) + if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] + && (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS) + ) { + *utptr = *ut; /* struct copy */ + if (run_by_root) /* why only for root? */ + memset(utptr->ut_host, 0, sizeof(utptr->ut_host)); + return; + } + +// Why? Do we require non-root to exec login from another +// former login process (e.g. login shell)? Some login's have +// login shells as children, so it won't work... +// if (!run_by_root) +// bb_error_msg_and_die("no utmp entry found"); + + /* Otherwise create a new one. */ + memset(utptr, 0, sizeof(*utptr)); + utptr->ut_type = LOGIN_PROCESS; + utptr->ut_pid = pid; + strncpy(utptr->ut_line, short_tty, sizeof(utptr->ut_line)); + /* This one is only 4 chars wide. Try to fit something + * remotely meaningful by skipping "tty"... */ + strncpy(utptr->ut_id, short_tty + 3, sizeof(utptr->ut_id)); + strncpy(utptr->ut_user, "LOGIN", sizeof(utptr->ut_user)); + utptr->ut_tv.tv_sec = time(NULL); +} + +/* + * write_utent - put a USER_PROCESS entry in the utmp file + * + * write_utent changes the type of the current utmp entry to + * USER_PROCESS. the wtmp file will be updated as well. + */ +static void write_utent(struct utmp *utptr, const char *username) +{ + utptr->ut_type = USER_PROCESS; + strncpy(utptr->ut_user, username, sizeof(utptr->ut_user)); + utptr->ut_tv.tv_sec = time(NULL); + /* other fields already filled in by read_or_build_utent above */ + setutent(); + pututline(utptr); + endutent(); +#if ENABLE_FEATURE_WTMP + if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) { + close(creat(bb_path_wtmp_file, 0664)); + } + updwtmp(bb_path_wtmp_file, utptr); +#endif +} +#else /* !ENABLE_FEATURE_UTMP */ +#define read_or_build_utent(utptr, run_by_root) ((void)0) +#define write_utent(utptr, username) ((void)0) +#endif /* !ENABLE_FEATURE_UTMP */ + +#if ENABLE_FEATURE_NOLOGIN +static void die_if_nologin(void) +{ + FILE *fp; + int c; + int empty = 1; + + fp = fopen_for_read("/etc/nologin"); + if (!fp) /* assuming it does not exist */ + return; + + while ((c = getc(fp)) != EOF) { + if (c == '\n') + bb_putchar('\r'); + bb_putchar(c); + empty = 0; + } + if (empty) + puts("\r\nSystem closed for routine maintenance\r"); + + fclose(fp); + fflush(NULL); + /* Users say that they do need this prior to exit: */ + tcdrain(STDOUT_FILENO); + exit(EXIT_FAILURE); +} +#else +static ALWAYS_INLINE void die_if_nologin(void) {} +#endif + +#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM +static int check_securetty(void) +{ + char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */ + parser_t *parser = config_open2("/etc/securetty", fopen_for_read); + while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) { + if (strcmp(buf, short_tty) == 0) + break; + buf = NULL; + } + config_close(parser); + /* buf != NULL here if config file was not found, empty + * or line was found which equals short_tty */ + return buf != NULL; +} +#else +static ALWAYS_INLINE int check_securetty(void) { return 1; } +#endif + +static void get_username_or_die(char *buf, int size_buf) +{ + int c, cntdown; + + cntdown = EMPTY_USERNAME_COUNT; + prompt: + print_login_prompt(); + /* skip whitespace */ + do { + c = getchar(); + if (c == EOF) exit(EXIT_FAILURE); + if (c == '\n') { + if (!--cntdown) exit(EXIT_FAILURE); + goto prompt; + } + } while (isspace(c)); + + *buf++ = c; + if (!fgets(buf, size_buf-2, stdin)) + exit(EXIT_FAILURE); + if (!strchr(buf, '\n')) + exit(EXIT_FAILURE); + while (isgraph(*buf)) buf++; + *buf = '\0'; +} + +static void motd(void) +{ + int fd; + + fd = open(bb_path_motd_file, O_RDONLY); + if (fd >= 0) { + fflush(stdout); + bb_copyfd_eof(fd, STDOUT_FILENO); + close(fd); + } +} + +static void alarm_handler(int sig UNUSED_PARAM) +{ + /* This is the escape hatch! Poor serial line users and the like + * arrive here when their connection is broken. + * We don't want to block here */ + ndelay_on(1); + printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT); + fflush(stdout); + /* unix API is brain damaged regarding O_NONBLOCK, + * we should undo it, or else we can affect other processes */ + ndelay_off(1); + _exit(EXIT_SUCCESS); +} + +int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int login_main(int argc UNUSED_PARAM, char **argv) +{ + enum { + LOGIN_OPT_f = (1<<0), + LOGIN_OPT_h = (1<<1), + LOGIN_OPT_p = (1<<2), + }; + char *fromhost; + char username[USERNAME_SIZE]; + const char *tmp; + int run_by_root; + unsigned opt; + int count = 0; + struct passwd *pw; + char *opt_host = opt_host; /* for compiler */ + char *opt_user = opt_user; /* for compiler */ + char full_tty[TTYNAME_SIZE]; + USE_SELINUX(security_context_t user_sid = NULL;) + USE_FEATURE_UTMP(struct utmp utent;) +#if ENABLE_PAM + int pamret; + pam_handle_t *pamh; + const char *pamuser; + const char *failed_msg; + struct passwd pwdstruct; + char pwdbuf[256]; +#endif + + short_tty = full_tty; + username[0] = '\0'; + signal(SIGALRM, alarm_handler); + alarm(TIMEOUT); + + /* More of suid paranoia if called by non-root: */ + /* Clear dangerous stuff, set PATH */ + run_by_root = !sanitize_env_if_suid(); + + /* Mandatory paranoia for suid applet: + * ensure that fd# 0,1,2 are opened (at least to /dev/null) + * and any extra open fd's are closed. + * (The name of the function is misleading. Not daemonizing here.) */ + bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL); + + opt = getopt32(argv, "f:h:p", &opt_user, &opt_host); + if (opt & LOGIN_OPT_f) { + if (!run_by_root) + bb_error_msg_and_die("-f is for root only"); + safe_strncpy(username, opt_user, sizeof(username)); + } + argv += optind; + if (argv[0]) /* user from command line (getty) */ + safe_strncpy(username, argv[0], sizeof(username)); + + /* Let's find out and memorize our tty */ + if (!isatty(0) || !isatty(1) || !isatty(2)) + return EXIT_FAILURE; /* Must be a terminal */ + safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty)); + tmp = ttyname(0); + if (tmp) { + safe_strncpy(full_tty, tmp, sizeof(full_tty)); + if (strncmp(full_tty, "/dev/", 5) == 0) + short_tty = full_tty + 5; + } + + read_or_build_utent(&utent, run_by_root); + + if (opt & LOGIN_OPT_h) { + USE_FEATURE_UTMP( + safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host)); + ) + fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host); + } else + fromhost = xasprintf(" on '%s'", short_tty); + + /* Was breaking "login " from shell command line: */ + /*bb_setpgrp();*/ + + openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); + + while (1) { + /* flush away any type-ahead (as getty does) */ + ioctl(0, TCFLSH, TCIFLUSH); + + if (!username[0]) + get_username_or_die(username, sizeof(username)); + +#if ENABLE_PAM + pamret = pam_start("login", username, &conv, &pamh); + if (pamret != PAM_SUCCESS) { + failed_msg = "start"; + goto pam_auth_failed; + } + /* set TTY (so things like securetty work) */ + pamret = pam_set_item(pamh, PAM_TTY, short_tty); + if (pamret != PAM_SUCCESS) { + failed_msg = "set_item(TTY)"; + goto pam_auth_failed; + } + pamret = pam_authenticate(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "authenticate"; + goto pam_auth_failed; + /* TODO: or just "goto auth_failed" + * since user seems to enter wrong password + * (in this case pamret == 7) + */ + } + /* check that the account is healthy */ + pamret = pam_acct_mgmt(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "acct_mgmt"; + goto pam_auth_failed; + } + /* read user back */ + pamuser = NULL; + /* gcc: "dereferencing type-punned pointer breaks aliasing rules..." + * thus we cast to (void*) */ + if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) { + failed_msg = "get_item(USER)"; + goto pam_auth_failed; + } + if (!pamuser || !pamuser[0]) + goto auth_failed; + safe_strncpy(username, pamuser, sizeof(username)); + /* Don't use "pw = getpwnam(username);", + * PAM is said to be capable of destroying static storage + * used by getpwnam(). We are using safe(r) function */ + pw = NULL; + getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw); + if (!pw) + goto auth_failed; + pamret = pam_open_session(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "open_session"; + goto pam_auth_failed; + } + pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (pamret != PAM_SUCCESS) { + failed_msg = "setcred"; + goto pam_auth_failed; + } + break; /* success, continue login process */ + + pam_auth_failed: + bb_error_msg("pam_%s call failed: %s (%d)", failed_msg, + pam_strerror(pamh, pamret), pamret); + safe_strncpy(username, "UNKNOWN", sizeof(username)); +#else /* not PAM */ + pw = getpwnam(username); + if (!pw) { + strcpy(username, "UNKNOWN"); + goto fake_it; + } + + if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*') + goto auth_failed; + + if (opt & LOGIN_OPT_f) + break; /* -f USER: success without asking passwd */ + + if (pw->pw_uid == 0 && !check_securetty()) + goto auth_failed; + + /* Don't check the password if password entry is empty (!) */ + if (!pw->pw_passwd[0]) + break; + fake_it: + /* authorization takes place here */ + if (correct_password(pw)) + break; +#endif /* ENABLE_PAM */ + auth_failed: + opt &= ~LOGIN_OPT_f; + bb_do_delay(FAIL_DELAY); + /* TODO: doesn't sound like correct English phrase to me */ + puts("Login incorrect"); + if (++count == 3) { + syslog(LOG_WARNING, "invalid password for '%s'%s", + username, fromhost); + return EXIT_FAILURE; + } + username[0] = '\0'; + } /* while (1) */ + + alarm(0); + /* We can ignore /etc/nologin if we are logging in as root, + * it doesn't matter whether we are run by root or not */ + if (pw->pw_uid != 0) + die_if_nologin(); + + write_utent(&utent, username); + +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + security_context_t old_tty_sid, new_tty_sid; + + if (get_default_context(username, NULL, &user_sid)) { + bb_error_msg_and_die("cannot get SID for %s", + username); + } + if (getfilecon(full_tty, &old_tty_sid) < 0) { + bb_perror_msg_and_die("getfilecon(%s) failed", + full_tty); + } + if (security_compute_relabel(user_sid, old_tty_sid, + SECCLASS_CHR_FILE, &new_tty_sid) != 0) { + bb_perror_msg_and_die("security_change_sid(%s) failed", + full_tty); + } + if (setfilecon(full_tty, new_tty_sid) != 0) { + bb_perror_msg_and_die("chsid(%s, %s) failed", + full_tty, new_tty_sid); + } + } +#endif + /* Try these, but don't complain if they fail. + * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */ + fchown(0, pw->pw_uid, pw->pw_gid); + fchmod(0, 0600); + + /* We trust environment only if we run by root */ + if (ENABLE_LOGIN_SCRIPTS && run_by_root) { + char *t_argv[2]; + + t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT"); + if (t_argv[0]) { + t_argv[1] = NULL; + xsetenv("LOGIN_TTY", full_tty); + xsetenv("LOGIN_USER", pw->pw_name); + xsetenv("LOGIN_UID", utoa(pw->pw_uid)); + xsetenv("LOGIN_GID", utoa(pw->pw_gid)); + xsetenv("LOGIN_SHELL", pw->pw_shell); + spawn_and_wait(t_argv); /* NOMMU-friendly */ + unsetenv("LOGIN_TTY" ); + unsetenv("LOGIN_USER" ); + unsetenv("LOGIN_UID" ); + unsetenv("LOGIN_GID" ); + unsetenv("LOGIN_SHELL"); + } + } + + change_identity(pw); + tmp = pw->pw_shell; + if (!tmp || !*tmp) + tmp = DEFAULT_SHELL; + /* setup_environment params: shell, clear_env, change_env, pw */ + setup_environment(tmp, !(opt & LOGIN_OPT_p), 1, pw); + + motd(); + + if (pw->pw_uid == 0) + syslog(LOG_INFO, "root login%s", fromhost); +#if ENABLE_SELINUX + /* well, a simple setexeccon() here would do the job as well, + * but let's play the game for now */ + set_current_security_context(user_sid); +#endif + + // util-linux login also does: + // /* start new session */ + // setsid(); + // /* TIOCSCTTY: steal tty from other process group */ + // if (ioctl(0, TIOCSCTTY, 1)) error_msg... + // BBox login used to do this (see above): + // bb_setpgrp(); + // If this stuff is really needed, add it and explain why! + + /* Set signals to defaults */ + /* Non-ignored signals revert to SIG_DFL on exec anyway */ + /*signal(SIGALRM, SIG_DFL);*/ + + /* Is this correct? This way user can ctrl-c out of /etc/profile, + * potentially creating security breach (tested with bash 3.0). + * But without this, bash 3.0 will not enable ctrl-c either. + * Maybe bash is buggy? + * Need to find out what standards say about /bin/login - + * should we leave SIGINT etc enabled or disabled? */ + signal(SIGINT, SIG_DFL); + + /* Exec login shell with no additional parameters */ + run_shell(tmp, 1, NULL, NULL); + + /* return EXIT_FAILURE; - not reached */ +} diff --git a/loginutils/passwd.c b/loginutils/passwd.c new file mode 100644 index 0000000..99fb76e --- /dev/null +++ b/loginutils/passwd.c @@ -0,0 +1,207 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +static void nuke_str(char *str) +{ + if (str) memset(str, 0, strlen(str)); +} + +static char* new_password(const struct passwd *pw, uid_t myuid, int algo) +{ + char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */ + char *orig = (char*)""; + char *newp = NULL; + char *cp = NULL; + char *ret = NULL; /* failure so far */ + + if (myuid && pw->pw_passwd[0]) { + char *encrypted; + + orig = bb_askpass(0, "Old password:"); /* returns ptr to static */ + if (!orig) + goto err_ret; + encrypted = pw_encrypt(orig, pw->pw_passwd, 1); /* returns malloced str */ + if (strcmp(encrypted, pw->pw_passwd) != 0) { + syslog(LOG_WARNING, "incorrect password for %s", + pw->pw_name); + bb_do_delay(FAIL_DELAY); + puts("Incorrect password"); + goto err_ret; + } + if (ENABLE_FEATURE_CLEAN_UP) free(encrypted); + } + orig = xstrdup(orig); /* or else bb_askpass() will destroy it */ + newp = bb_askpass(0, "New password:"); /* returns ptr to static */ + if (!newp) + goto err_ret; + newp = xstrdup(newp); /* we are going to bb_askpass() again, so save it */ + if (ENABLE_FEATURE_PASSWD_WEAK_CHECK + && obscure(orig, newp, pw) && myuid) + goto err_ret; /* non-root is not allowed to have weak passwd */ + + cp = bb_askpass(0, "Retype password:"); + if (!cp) + goto err_ret; + if (strcmp(cp, newp)) { + puts("Passwords don't match"); + goto err_ret; + } + + crypt_make_salt(salt, 1, 0); /* des */ + if (algo) { /* MD5 */ + strcpy(salt, "$1$"); + crypt_make_salt(salt + 3, 4, 0); + } + /* pw_encrypt returns malloced str */ + ret = pw_encrypt(newp, salt, 1); + /* whee, success! */ + + err_ret: + nuke_str(orig); + if (ENABLE_FEATURE_CLEAN_UP) free(orig); + nuke_str(newp); + if (ENABLE_FEATURE_CLEAN_UP) free(newp); + nuke_str(cp); + return ret; +} + +int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int passwd_main(int argc UNUSED_PARAM, char **argv) +{ + enum { + OPT_algo = 0x1, /* -a - password algorithm */ + OPT_lock = 0x2, /* -l - lock account */ + OPT_unlock = 0x4, /* -u - unlock account */ + OPT_delete = 0x8, /* -d - delete password */ + OPT_lud = 0xe, + STATE_ALGO_md5 = 0x10, + //STATE_ALGO_des = 0x20, not needed yet + }; + unsigned opt; + int rc; + const char *opt_a = ""; + const char *filename; + char *myname; + char *name; + char *newp; + struct passwd *pw; + uid_t myuid; + struct rlimit rlimit_fsize; + char c; +#if ENABLE_FEATURE_SHADOWPASSWDS + /* Using _r function to avoid pulling in static buffers */ + struct spwd spw; + char buffer[256]; +#endif + + logmode = LOGMODE_BOTH; + openlog(applet_name, LOG_NOWAIT, LOG_AUTH); + opt = getopt32(argv, "a:lud", &opt_a); + //argc -= optind; + argv += optind; + + if (strcasecmp(opt_a, "des") != 0) /* -a */ + opt |= STATE_ALGO_md5; + //else + // opt |= STATE_ALGO_des; + myuid = getuid(); + /* -l, -u, -d require root priv and username argument */ + if ((opt & OPT_lud) && (myuid || !argv[0])) + bb_show_usage(); + + /* Will complain and die if username not found */ + myname = xstrdup(bb_getpwuid(NULL, -1, myuid)); + name = argv[0] ? argv[0] : myname; + + pw = getpwnam(name); + if (!pw) + bb_error_msg_and_die("unknown user %s", name); + if (myuid && pw->pw_uid != myuid) { + /* LOGMODE_BOTH */ + bb_error_msg_and_die("%s can't change password for %s", myname, name); + } + +#if ENABLE_FEATURE_SHADOWPASSWDS + { + /* getspnam_r may return 0 yet set result to NULL. + * At least glibc 2.4 does this. Be extra paranoid here. */ + struct spwd *result = NULL; + if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) + || !result || strcmp(result->sp_namp, pw->pw_name) != 0) { + /* LOGMODE_BOTH */ + bb_error_msg("no record of %s in %s, using %s", + name, bb_path_shadow_file, + bb_path_passwd_file); + } else { + pw->pw_passwd = result->sp_pwdp; + } + } +#endif + + /* Decide what the new password will be */ + newp = NULL; + c = pw->pw_passwd[0] - '!'; + if (!(opt & OPT_lud)) { + if (myuid && !c) { /* passwd starts with '!' */ + /* LOGMODE_BOTH */ + bb_error_msg_and_die("cannot change " + "locked password for %s", name); + } + printf("Changing password for %s\n", name); + newp = new_password(pw, myuid, opt & STATE_ALGO_md5); + if (!newp) { + logmode = LOGMODE_STDIO; + bb_error_msg_and_die("password for %s is unchanged", name); + } + } else if (opt & OPT_lock) { + if (!c) goto skip; /* passwd starts with '!' */ + newp = xasprintf("!%s", pw->pw_passwd); + } else if (opt & OPT_unlock) { + if (c) goto skip; /* not '!' */ + /* pw->pw_passwd points to static storage, + * strdup'ing to avoid nasty surprizes */ + newp = xstrdup(&pw->pw_passwd[1]); + } else if (opt & OPT_delete) { + //newp = xstrdup(""); + newp = (char*)""; + } + + rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; + setrlimit(RLIMIT_FSIZE, &rlimit_fsize); + bb_signals(0 + + (1 << SIGHUP) + + (1 << SIGINT) + + (1 << SIGQUIT) + , SIG_IGN); + umask(077); + xsetuid(0); + +#if ENABLE_FEATURE_SHADOWPASSWDS + filename = bb_path_shadow_file; + rc = update_passwd(bb_path_shadow_file, name, newp); + if (rc == 0) /* no lines updated, no errors detected */ +#endif + { + filename = bb_path_passwd_file; + rc = update_passwd(bb_path_passwd_file, name, newp); + } + /* LOGMODE_BOTH */ + if (rc < 0) + bb_error_msg_and_die("cannot update password file %s", + filename); + bb_info_msg("Password for %s changed by %s", name, myname); + + //if (ENABLE_FEATURE_CLEAN_UP) free(newp); + skip: + if (!newp) { + bb_error_msg_and_die("password for %s is already %slocked", + name, (opt & OPT_unlock) ? "un" : ""); + } + if (ENABLE_FEATURE_CLEAN_UP) free(myname); + return 0; +} diff --git a/loginutils/su.c b/loginutils/su.c new file mode 100644 index 0000000..61039d8 --- /dev/null +++ b/loginutils/su.c @@ -0,0 +1,102 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini su implementation for busybox + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include + +#define SU_OPT_mp (3) +#define SU_OPT_l (4) + +int su_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int su_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned flags; + char *opt_shell = NULL; + char *opt_command = NULL; + const char *opt_username = "root"; + struct passwd *pw; + uid_t cur_uid = getuid(); + const char *tty; + char *old_user; + + flags = getopt32(argv, "mplc:s:", &opt_command, &opt_shell); + //argc -= optind; + argv += optind; + + if (argv[0] && LONE_DASH(argv[0])) { + flags |= SU_OPT_l; + argv++; + } + + /* get user if specified */ + if (argv[0]) { + opt_username = argv[0]; + argv++; + } + + if (ENABLE_FEATURE_SU_SYSLOG) { + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. + But getlogin can fail -- usually due to lack of utmp entry. + in this case resort to getpwuid. */ + old_user = xstrdup(USE_FEATURE_UTMP(getlogin() ? : ) (pw = getpwuid(cur_uid)) ? pw->pw_name : ""); + tty = ttyname(2) ? : "none"; + openlog(applet_name, 0, LOG_AUTH); + } + + pw = getpwnam(opt_username); + if (!pw) + bb_error_msg_and_die("unknown id: %s", opt_username); + + /* Make sure pw->pw_shell is non-NULL. 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. */ + if (!pw->pw_shell || !pw->pw_shell[0]) + pw->pw_shell = (char *)DEFAULT_SHELL; + + if ((cur_uid == 0) || correct_password(pw)) { + if (ENABLE_FEATURE_SU_SYSLOG) + syslog(LOG_NOTICE, "%c %s %s:%s", + '+', tty, old_user, opt_username); + } else { + if (ENABLE_FEATURE_SU_SYSLOG) + syslog(LOG_NOTICE, "%c %s %s:%s", + '-', tty, old_user, opt_username); + bb_error_msg_and_die("incorrect password"); + } + + if (ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_SU_SYSLOG) { + closelog(); + free(old_user); + } + + if (!opt_shell && (flags & SU_OPT_mp)) + opt_shell = getenv("SHELL"); + +#if ENABLE_FEATURE_SU_CHECKS_SHELLS + if (opt_shell && cur_uid && 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. */ + bb_error_msg("using restricted shell"); + opt_shell = NULL; + } +#endif + if (!opt_shell) + opt_shell = pw->pw_shell; + + change_identity(pw); + /* setup_environment params: shell, clear_env, change_env, pw */ + setup_environment(opt_shell, flags & SU_OPT_l, !(flags & SU_OPT_mp), pw); + USE_SELINUX(set_current_security_context(NULL);) + + /* Never returns */ + run_shell(opt_shell, flags & SU_OPT_l, opt_command, (const char**)argv); + + /* return EXIT_FAILURE; - not reached */ +} diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c new file mode 100644 index 0000000..892c434 --- /dev/null +++ b/loginutils/sulogin.c @@ -0,0 +1,117 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini sulogin implementation for busybox + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +//static void catchalarm(int UNUSED_PARAM junk) +//{ +// exit(EXIT_FAILURE); +//} + + +int sulogin_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int sulogin_main(int argc UNUSED_PARAM, char **argv) +{ + char *cp; + int timeout = 0; + struct passwd *pwd; + const char *shell; +#if ENABLE_FEATURE_SHADOWPASSWDS + /* Using _r function to avoid pulling in static buffers */ + char buffer[256]; + struct spwd spw; +#endif + + logmode = LOGMODE_BOTH; + openlog(applet_name, 0, LOG_AUTH); + + opt_complementary = "t+"; /* -t N */ + getopt32(argv, "t:", &timeout); + argv += optind; + + if (argv[0]) { + close(0); + close(1); + dup(xopen(argv[0], O_RDWR)); + close(2); + dup(0); + } + + /* Malicious use like "sulogin /dev/sda"? */ + if (!isatty(0) || !isatty(1) || !isatty(2)) { + logmode = LOGMODE_SYSLOG; + bb_error_msg_and_die("not a tty"); + } + + /* Clear dangerous stuff, set PATH */ + sanitize_env_if_suid(); + +// bb_askpass() already handles this +// signal(SIGALRM, catchalarm); + + pwd = getpwuid(0); + if (!pwd) { + goto auth_error; + } + +#if ENABLE_FEATURE_SHADOWPASSWDS + { + /* getspnam_r may return 0 yet set result to NULL. + * At least glibc 2.4 does this. Be extra paranoid here. */ + struct spwd *result = NULL; + int r = getspnam_r(pwd->pw_name, &spw, buffer, sizeof(buffer), &result); + if (r || !result) { + goto auth_error; + } + pwd->pw_passwd = result->sp_pwdp; + } +#endif + + while (1) { + char *encrypted; + int r; + + /* cp points to a static buffer that is zeroed every time */ + cp = bb_askpass(timeout, + "Give root password for system maintenance\n" + "(or type Control-D for normal startup):"); + + if (!cp || !*cp) { + bb_info_msg("Normal startup"); + return 0; + } + encrypted = pw_encrypt(cp, pwd->pw_passwd, 1); + r = strcmp(encrypted, pwd->pw_passwd); + free(encrypted); + if (r == 0) { + break; + } + bb_do_delay(FAIL_DELAY); + bb_error_msg("login incorrect"); + } + memset(cp, 0, strlen(cp)); +// signal(SIGALRM, SIG_DFL); + + bb_info_msg("System Maintenance Mode"); + + USE_SELINUX(renew_current_security_context()); + + shell = getenv("SUSHELL"); + if (!shell) + shell = getenv("sushell"); + if (!shell) { + shell = "/bin/sh"; + if (pwd->pw_shell[0]) + shell = pwd->pw_shell; + } + /* Exec login shell with no additional parameters. Never returns. */ + run_shell(shell, 1, NULL, NULL); + + auth_error: + bb_error_msg_and_die("no password entry for root"); +} diff --git a/loginutils/vlock.c b/loginutils/vlock.c new file mode 100644 index 0000000..0262da5 --- /dev/null +++ b/loginutils/vlock.c @@ -0,0 +1,106 @@ +/* vi: set sw=4 ts=4: */ +/* + * vlock implementation for busybox + * + * Copyright (C) 2000 by spoon + * Written by spoon + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Shoutz to Michael K. Johnson , author of the + * original vlock. I snagged a bunch of his code to write this + * minimalistic vlock. + */ +/* Fixed by Erik Andersen to do passwords the tinylogin way... + * It now works with md5, sha1, etc passwords. */ + +#include +#include "libbb.h" + +static void release_vt(int signo UNUSED_PARAM) +{ + /* If -a, param is 0, which means: + * "no, kernel, we don't allow console switch away from us!" */ + ioctl(STDIN_FILENO, VT_RELDISP, (unsigned long) !option_mask32); +} + +static void acquire_vt(int signo UNUSED_PARAM) +{ + /* ACK to kernel that switch to console is successful */ + ioctl(STDIN_FILENO, VT_RELDISP, VT_ACKACQ); +} + +int vlock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int vlock_main(int argc UNUSED_PARAM, char **argv) +{ + struct vt_mode vtm; + struct termios term; + struct termios oterm; + struct vt_mode ovtm; + uid_t uid; + struct passwd *pw; +/* XXX: xgetpwuid */ + uid = getuid(); + pw = getpwuid(uid); + if (pw == NULL) + bb_error_msg_and_die("unknown uid %d", (int)uid); + + opt_complementary = "=0"; /* no params! */ + getopt32(argv, "a"); + + /* Ignore some signals so that we don't get killed by them */ + bb_signals(0 + + (1 << SIGTSTP) + + (1 << SIGTTIN) + + (1 << SIGTTOU) + + (1 << SIGHUP ) + + (1 << SIGCHLD) /* paranoia :) */ + + (1 << SIGQUIT) + + (1 << SIGINT ) + , SIG_IGN); + + /* We will use SIGUSRx for console switch control: */ + /* 1: set handlers */ + signal_SA_RESTART_empty_mask(SIGUSR1, release_vt); + signal_SA_RESTART_empty_mask(SIGUSR2, acquire_vt); + /* 2: unmask them */ + sig_unblock(SIGUSR1); + sig_unblock(SIGUSR2); + + /* Revert stdin/out to our controlling tty + * (or die if we have none) */ + xmove_fd(xopen(CURRENT_TTY, O_RDWR), STDIN_FILENO); + xdup2(STDIN_FILENO, STDOUT_FILENO); + + xioctl(STDIN_FILENO, VT_GETMODE, &vtm); + ovtm = vtm; + /* "console switches are controlled by us, not kernel!" */ + vtm.mode = VT_PROCESS; + vtm.relsig = SIGUSR1; + vtm.acqsig = SIGUSR2; + ioctl(STDIN_FILENO, VT_SETMODE, &vtm); + + tcgetattr(STDIN_FILENO, &oterm); + term = oterm; + term.c_iflag &= ~BRKINT; + term.c_iflag |= IGNBRK; + term.c_lflag &= ~ISIG; + term.c_lflag &= ~(ECHO | ECHOCTL); + tcsetattr_stdin_TCSANOW(&term); + + do { + printf("Virtual console%s locked by %s.\n", + option_mask32 /*o_lock_all*/ ? "s" : "", + pw->pw_name); + if (correct_password(pw)) { + break; + } + bb_do_delay(FAIL_DELAY); + puts("Password incorrect"); + } while (1); + + ioctl(STDIN_FILENO, VT_SETMODE, &ovtm); + tcsetattr_stdin_TCSANOW(&oterm); + fflush_stdout_and_exit(EXIT_SUCCESS); +} -- cgit v1.2.3