aboutsummaryrefslogtreecommitdiff
path: root/loginutils
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
committerBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
commit73b16af8feec390afbabd9356d6e5e83c0390838 (patch)
tree3730020ba2f9caeb9d7815a975af51830b51ce11 /loginutils
busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2busybox-1.13.3
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'loginutils')
-rw-r--r--loginutils/Config.in283
-rw-r--r--loginutils/Kbuild19
-rw-r--r--loginutils/addgroup.c183
-rw-r--r--loginutils/adduser.c178
-rw-r--r--loginutils/chpasswd.c72
-rw-r--r--loginutils/cryptpw.c58
-rw-r--r--loginutils/deluser.c125
-rw-r--r--loginutils/getty.c777
-rw-r--r--loginutils/login.c504
-rw-r--r--loginutils/passwd.c207
-rw-r--r--loginutils/su.c102
-rw-r--r--loginutils/sulogin.c117
-rw-r--r--loginutils/vlock.c106
13 files changed, 2731 insertions, 0 deletions
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 <andersen@codepoet.org>
+#
+# 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 <beppu@codepoet.org>
+ * Copyright (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * 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 <beppu@codepoet.org>
+ *
+ * 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 <virtuoso@slind.org>
+ * 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 <thomasez@zelow.no>
+ */
+
+#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 <beppu@codepoet.org>
+ * Copyright (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * 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 <poe@daimi.aau.dk>
+ * 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 <ear@usfirst.org> - 12/28/95
+ *
+ * 1999-02-22 Arkadiusz Mickiewicz <misiek@misiek.eu.org>
+ * - added Native Language Support
+ *
+ * 1999-05-05 Thorsten Kranzkowski <dl8bcu@gmx.net>
+ * - 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 <syslog.h>
+
+#if ENABLE_FEATURE_UTMP
+#include <utmp.h> /* 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 <sys/utsname.h>
+#include <time.h>
+#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:
+ *
+ * <junk><number><junk>
+ *
+ * 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 <syslog.h>
+#include <utmp.h>
+#include <sys/resource.h>
+
+#if ENABLE_SELINUX
+#include <selinux/selinux.h> /* for is_selinux_enabled() */
+#include <selinux/get_context_list.h> /* for get_default_context() */
+#include <selinux/flask.h> /* for security class definitions */
+#endif
+
+#if ENABLE_PAM
+/* PAM may include <locale.h>. 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 <security/pam_appl.h>
+#include <security/pam_misc.h>
+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 <username>" 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 <syslog.h>
+
+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 <syslog.h>
+
+#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 <syslog.h>
+
+//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 <spoon@ix.netcom.com>
+ * Written by spoon <spon@ix.netcom.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* Shoutz to Michael K. Johnson <johnsonm@redhat.com>, 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 <sys/vt.h>
+#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);
+}