diff options
Diffstat (limited to 'loginutils')
-rw-r--r-- | loginutils/Makefile | 30 | ||||
-rw-r--r-- | loginutils/Makefile.in | 45 | ||||
-rw-r--r-- | loginutils/addgroup.c | 168 | ||||
-rw-r--r-- | loginutils/adduser.c | 352 | ||||
-rw-r--r-- | loginutils/config.in | 25 | ||||
-rw-r--r-- | loginutils/deluser.c | 183 | ||||
-rw-r--r-- | loginutils/getty.c | 1157 | ||||
-rw-r--r-- | loginutils/login.c | 459 | ||||
-rw-r--r-- | loginutils/su.c | 167 | ||||
-rw-r--r-- | loginutils/tinylogin.c | 209 | ||||
-rw-r--r-- | loginutils/tinylogin.h | 10 |
11 files changed, 2805 insertions, 0 deletions
diff --git a/loginutils/Makefile b/loginutils/Makefile new file mode 100644 index 000000000..09b368afc --- /dev/null +++ b/loginutils/Makefile @@ -0,0 +1,30 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2002 Erik Andersen <andersee@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +TOPDIR:= ../ +LOGINUTILS_DIR:=./ +include $(TOPDIR).config +include $(TOPDIR)Rules.mak +include Makefile.in +all: $(libraries-y) +-include $(TOPDIR).depend + +clean: + rm -f *.o *.a $(AR_TARGET) + diff --git a/loginutils/Makefile.in b/loginutils/Makefile.in new file mode 100644 index 000000000..adee35baa --- /dev/null +++ b/loginutils/Makefile.in @@ -0,0 +1,45 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2002 Erik Andersen <andersee@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +LOGINUTILS_AR:=loginutils.a +ifndef LOGINUTILS_DIR +LOGINUTILS_DIR:=$(TOPDIR)loginutils/ +endif + +LOGINUTILS-y:= +LOGINUTILS-$(CONFIG_ADDGROUP) += addgroup.o +LOGINUTILS-$(CONFIG_ADDUSER) += adduser.o +LOGINUTILS-$(CONFIG_DELUSER) += deluser.o +LOGINUTILS-$(CONFIG_GETTY) += getty.o +LOGINUTILS-$(CONFIG_LOGIN) += login.o tinylogin.o +LOGINUTILS-$(CONFIG_SU) += su.o tinylogin.o + +libraries-y+=$(LOGINUTILS_DIR)$(LOGINUTILS_AR) + +needcrypt-y:= +needcrypt-$(CONFIG_LOGIN) := y +needcrypt-$(CONFIG_SU) := y + +ifeq ($(needcrypt-y),y) + libraries-y +=-lcrypt +endif + +$(LOGINUTILS_DIR)$(LOGINUTILS_AR): $(patsubst %,$(LOGINUTILS_DIR)%, $(LOGINUTILS-y)) + $(AR) -ro $@ $(patsubst %,$(LOGINUTILS_DIR)%, $(LOGINUTILS-y)) + diff --git a/loginutils/addgroup.c b/loginutils/addgroup.c new file mode 100644 index 000000000..e04a8d784 --- /dev/null +++ b/loginutils/addgroup.c @@ -0,0 +1,168 @@ +/* vi: set sw=4 ts=4: */ +/* + * addgroup - 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "busybox.h" +#include "pwd.h" +#include "grp.h" + +#define GROUP_FILE "/etc/group" +#define SHADOW_FILE "/etc/gshadow" + + +/* structs __________________________ */ + +/* data _____________________________ */ + +/* defaults : should this be in an external file? */ +static const char default_passwd[] = "x"; + + +/* make sure gr_name isn't taken, make sure gid is kosher + * return 1 on failure */ +static int group_study(const char *filename, struct group *g) +{ + FILE *etc_group; + gid_t desired; + + struct group *grp; + const int max = 65000; + + etc_group = xfopen(filename, "r"); + + /* make sure gr_name isn't taken, make sure gid is kosher */ + desired = g->gr_gid; + while ((grp = fgetgrent(etc_group))) { + if ((strcmp(grp->gr_name, g->gr_name)) == 0) { + error_msg_and_die("%s: group already in use\n", g->gr_name); + } + if ((desired) && grp->gr_gid == desired) { + error_msg_and_die("%d: gid has already been allocated\n", + desired); + } + if ((grp->gr_gid > g->gr_gid) && (grp->gr_gid < max)) { + g->gr_gid = grp->gr_gid; + } + } + fclose(etc_group); + + /* gid */ + if (desired) { + g->gr_gid = desired; + } else { + g->gr_gid++; + } + /* return 1; */ + return 0; +} + +/* append a new user to the passwd file */ +static int addgroup(const char *filename, char *group, gid_t gid) +{ + FILE *etc_group; + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + FILE *etc_gshadow; + char *gshadow = SHADOW_FILE; +#endif + + struct group gr; + + /* group:passwd:gid:userlist */ + static const char entryfmt[] = "%s:%s:%d:%s\n"; + + /* make sure gid and group haven't already been allocated */ + gr.gr_gid = gid; + gr.gr_name = group; + if (group_study(filename, &gr)) + return 1; + + /* add entry to group */ + etc_group = xfopen(filename, "a"); + + fprintf(etc_group, entryfmt, group, default_passwd, gr.gr_gid, ""); + fclose(etc_group); + + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + /* add entry to gshadow if necessary */ + if (access(gshadow, F_OK|W_OK) == 0) { + etc_gshadow = xfopen(gshadow, "a"); + fprintf(etc_gshadow, "%s:!::\n", group); + fclose(etc_gshadow); + } +#endif + + /* return 1; */ + return 0; +} + +/* + * addgroup will take a login_name as its first parameter. + * + * gid + * + * can be customized via command-line parameters. + * ________________________________________________________________________ */ +int addgroup_main(int argc, char **argv) +{ + int opt; + char *group; + gid_t gid = 0; + + /* get remaining args */ + while ((opt = getopt (argc, argv, "g:")) != -1) + switch (opt) { + case 'g': + gid = strtol(optarg, NULL, 10); + break; + default: + show_usage(); + break; + } + + if (optind >= argc) { + show_usage(); + } else { + group = argv[optind]; + } + + if (geteuid() != 0) { + error_msg_and_die + ("Only root may add a group to the system."); + } + + /* werk */ + return addgroup(GROUP_FILE, group, gid); +} + +/* $Id: addgroup.c,v 1.1 2002/06/04 20:45:05 sandman Exp $ */ diff --git a/loginutils/adduser.c b/loginutils/adduser.c new file mode 100644 index 000000000..66fcaa23f --- /dev/null +++ b/loginutils/adduser.c @@ -0,0 +1,352 @@ +/* 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "busybox.h" +#include "pwd.h" +#include "grp.h" + +#define PASSWD_FILE "/etc/passwd" +#define SHADOW_FILE "/etc/shadow" + + +/* structs __________________________ */ + +typedef struct { + uid_t u; + gid_t g; +} Id; + +/* data _____________________________ */ + +/* defaults : should this be in an external file? */ +static const char default_passwd[] = "x"; +static const char default_gecos[] = "Linux User,,,"; +static const char default_home_prefix[] = "/home"; +static const char default_shell[] = "/bin/sh"; + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + +#include "shadow.h" + +/* shadow in use? */ +static int shadow_enabled = 0; +#endif + +/* remix */ +/* EDR recoded such that the uid may be passed in *p */ +static int passwd_study(const char *filename, struct passwd *p) +{ + struct passwd *pw; + FILE *passwd; + + const int min = 500; + const int max = 65000; + + passwd = wfopen(filename, "r"); + if (!passwd) + return 4; + + /* EDR if uid is out of bounds, set to min */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + p->pw_uid = min; + + /* stuff to do: + * make sure login isn't taken; + * find free uid and gid; + */ + while ((pw = fgetpwent(passwd))) { + if (strcmp(pw->pw_name, p->pw_name) == 0) { + /* return 0; */ + return 1; + } + if ((pw->pw_uid >= p->pw_uid) && (pw->pw_uid < max) + && (pw->pw_uid >= min)) { + p->pw_uid = pw->pw_uid + 1; + } + } + + /* EDR check for an already existing gid */ + while (getgrgid(p->pw_uid) != NULL) + p->pw_uid++; + + /* EDR also check for an existing group definition */ + if (getgrnam(p->pw_name) != NULL) + return 3; + + /* EDR bounds check */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + return 2; + + /* EDR create new gid always = uid */ + p->pw_gid = p->pw_uid; + + /* return 1; */ + return 0; +} + +static void addgroup_wrapper(const char *login, gid_t gid) +{ + int argc = 3; + const char *argv0_save; + char group_id[8]; + char group_name[32]; + char *argv[] = { group_name, "-g", group_id }; + + argv0_save = applet_name; + applet_name = "addgroup"; + safe_strncpy(group_name, login, 32); + sprintf(group_id, "%d", gid); + addgroup_main(argc, argv); + applet_name = argv0_save; +} + +static void passwd_wrapper(const char *login) +{ + static const char prog[] = "passwd"; + execlp(prog, prog, login, NULL); + error_msg_and_die("Failed to execute 'passwd', you must set the password for '%s' manually", login); +} + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS +/* + * pwd_to_spwd - create entries for new spwd structure + * + * pwd_to_spwd() creates a new (struct spwd) containing the + * information in the pointed-to (struct passwd). + */ +#define DAY (24L*3600L) +#define WEEK (7*DAY) +#define SCALE DAY +static struct spwd *pwd_to_spwd(const struct passwd *pw) +{ + static struct spwd sp; + + /* + * Nice, easy parts first. The name and passwd map directly + * from the old password structure to the new one. + */ + sp.sp_namp = pw->pw_name; + sp.sp_pwdp = pw->pw_passwd; + + /* + * Defaults used if there is no pw_age information. + */ + sp.sp_min = 0; + sp.sp_max = (10000L * DAY) / SCALE; + sp.sp_lstchg = time((time_t *) 0) / SCALE; + + /* + * These fields have no corresponding information in the password + * file. They are set to uninitialized values. + */ + sp.sp_warn = -1; + sp.sp_expire = -1; + sp.sp_inact = -1; + sp.sp_flag = -1; + + return &sp; +} +#endif + +/* putpwent(3) remix */ +static int adduser(const char *filename, struct passwd *p) +{ + FILE *passwd; + int r; +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + FILE *shadow; + struct spwd *sp; +#endif + + /* make sure everything is kosher and setup uid && gid */ + passwd = wfopen(filename, "a"); + if (passwd == NULL) { + /* return -1; */ + return 1; + } + fseek(passwd, 0, SEEK_END); + + /* if (passwd_study(filename, p) == 0) { */ + r = passwd_study(filename, p); + if (r) { + if (r == 1) + error_msg("%s: login already in use", p->pw_name); + else if (r == 2) + error_msg("illegal uid or no uids left"); + else if (r == 3) + error_msg("group name %s already in use", p->pw_name); + else + error_msg("generic error."); + /* return -1; */ + return 1; + } + + /* add to passwd */ + if (putpwent(p, passwd) == -1) { + /* return -1; */ + return 1; + } + fclose(passwd); + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + /* add to shadow if necessary */ + if (shadow_enabled) { + shadow = wfopen(SHADOW_FILE, "a"); + if (shadow == NULL) { + /* return -1; */ + return 1; + } + fseek(shadow, 0, SEEK_END); + sp = pwd_to_spwd(p); + sp->sp_max = 99999; /* debianish */ + sp->sp_warn = 7; + fprintf(shadow, "%s:!:%ld:%ld:%ld:%ld:::\n", + sp->sp_namp, sp->sp_lstchg, sp->sp_min, sp->sp_max, + sp->sp_warn); + fclose(shadow); + } +#endif + + /* add to group */ + /* addgroup should be responsible for dealing w/ gshadow */ + addgroup_wrapper(p->pw_name, p->pw_gid); + + /* Clear the umask for this process so it doesn't + * * screw up the permissions on the mkdir and chown. */ + umask(0); + + /* mkdir */ + if (mkdir(p->pw_dir, 0755)) { + perror_msg("%s", p->pw_dir); + } + /* Set the owner and group so it is owned by the new user. */ + if (chown(p->pw_dir, p->pw_uid, p->pw_gid)) { + perror_msg("%s", p->pw_dir); + } + /* Now fix up the permissions to 2755. Can't do it before now + * since chown will clear the setgid bit */ + if (chmod(p->pw_dir, 02755)) { + perror_msg("%s", p->pw_dir); + } + /* interactively set passwd */ + passwd_wrapper(p->pw_name); + + return 0; +} + + +/* return current uid (root is always uid == 0, right?) */ +static inline uid_t i_am_not_root(void) +{ + return geteuid(); +} + +/* + * 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) +{ + int i = 0; + char opt; + const char *login; + const char *gecos; + const char *home = NULL; + const char *shell; + + struct passwd pw; + + /* init */ + if (argc < 2) { + show_usage(); + } + gecos = default_gecos; + shell = default_shell; + + /* get args */ + while ((opt = getopt (argc, argv, "h:g:s:")) != -1) + switch (opt) { + case 'h': + home = argv[++i]; + break; + case 'g': + gecos = argv[++i]; + break; + case 's': + shell = argv[++i]; + break; + default: + show_usage (); + break; + } + + /* got root? */ + if (i_am_not_root()) { + error_msg_and_die( "Only root may add a user or group to the system."); + } + + /* get login */ + if (optind >= argc) { + error_msg_and_die( "no user specified"); + } + login = argv[optind]; + + /* create string for $HOME if not specified already */ + if (!home) { + home = concat_path_file(default_home_prefix, login); + } +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + /* is /etc/shadow in use? */ + shadow_enabled = (0 == access(SHADOW_FILE, F_OK)); +#endif + + /* create a passwd struct */ + pw.pw_name = (char *)login; + pw.pw_passwd = (char *)default_passwd; + pw.pw_uid = 0; + pw.pw_gid = 0; + pw.pw_gecos = (char *)gecos; + pw.pw_dir = (char *)home; + pw.pw_shell = (char *)shell; + + /* grand finale */ + return adduser(PASSWD_FILE, &pw); +} + +/* $Id: adduser.c,v 1.1 2002/06/04 20:45:05 sandman Exp $ */ diff --git a/loginutils/config.in b/loginutils/config.in new file mode 100644 index 000000000..265d45ab4 --- /dev/null +++ b/loginutils/config.in @@ -0,0 +1,25 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +mainmenu_option next_comment +comment 'Login/Password Management Utilities' + + +bool 'addgroup' CONFIG_ADDGROUP +bool 'adduser' CONFIG_ADDUSER +bool 'deluser' CONFIG_DELUSER +bool 'delgroup' CONFIG_DELUSER +bool 'getty' CONFIG_GETTY +bool 'login' CONFIG_LOGIN +if [ "$CONFIG_LOGIN" = "y" ]; then + bool ' Support for /etc/securetty' CONFIG_FEATURE_SECURETTY +fi +bool 'su' CONFIG_SU +if [ "$CONFIG_ADDUSER" = "y" -o "$CONFIG_DELUSER" = "y" -o "$CONFIG_LOGIN" = "y" -o "$CONFIG_SU" = "y" ]; then + bool 'Support for shadow passwords' CONFIG_FEATURE_SHADOWPASSWDS +fi + +endmenu + diff --git a/loginutils/deluser.c b/loginutils/deluser.c new file mode 100644 index 000000000..481a716e7 --- /dev/null +++ b/loginutils/deluser.c @@ -0,0 +1,183 @@ +/* vi: set sw=4 ts=4: */ +/* + * deluser (remove lusers from the system ;) for TinyLogin + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "busybox.h" + +#define PASSWD_FILE "/etc/passwd" +#define GROUP_FILE "/etc/group" +#define SHADOW_FILE "/etc/shadow" +#define GSHADOW_FILE "/etc/gshadow" + + +/* where to start and stop deletion */ +typedef struct { + size_t start; + size_t stop; +} Bounds; + +/* An interesting side-effect of boundary()'s + * implementation is that the first user (typically root) + * cannot be removed. Let's call it a feature. */ +static inline Bounds boundary(const char *buffer, const char *login) +{ + char needle[256]; + char *start; + char *stop; + Bounds b; + + snprintf(needle, 256, "\n%s:", login); + needle[255] = 0; + start = strstr(buffer, needle); + if (!start) { + b.start = 0; + b.stop = 0; + return b; + } + start++; + + stop = index(start, '\n'); /* index is a BSD-ism */ + b.start = start - buffer; + b.stop = stop - buffer; + return b; +} + +/* grep -v ^login (except it only deletes the first match) */ +/* ...in fact, I think I'm going to simplify this later */ +static int del_line_matching(const char *login, const char *filename) +{ + char *buffer; + FILE *passwd; + size_t len; + Bounds b; + struct stat statbuf; + + /* load into buffer */ + passwd = fopen(filename, "r"); + if (!passwd) { + return 1; + } + stat(filename, &statbuf); + len = statbuf.st_size; + buffer = (char *) malloc(len * sizeof(char)); + + if (!buffer) { + fclose(passwd); + return 1; + } + fread(buffer, len, sizeof(char), passwd); + + fclose(passwd); + + /* find the user to remove */ + b = boundary(buffer, login); + if (b.stop == 0) { + free(buffer); + return 1; + } + + /* write the file w/o the user */ + passwd = fopen(filename, "w"); + if (!passwd) { + return 1; + } + fwrite(buffer, (b.start - 1), sizeof(char), passwd); + fwrite(&buffer[b.stop], (len - b.stop), sizeof(char), passwd); + + fclose(passwd); + + return 0; +} + +/* ________________________________________________________________________ */ +int delgroup_main(int argc, char **argv) +{ + /* int successful; */ + int failure; + + if (argc != 2) { + show_usage(); + } else { + + failure = del_line_matching(argv[1], GROUP_FILE); +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + if (access(GSHADOW_FILE, W_OK) == 0) { + /* EDR the |= works if the error is not 0, so he had it wrong */ + failure |= del_line_matching(argv[1], GSHADOW_FILE); + } +#endif /* CONFIG_FEATURE_SHADOWPASSWDS */ + /* if (!successful) { */ + if (failure) { + error_msg_and_die("%s: Group could not be removed\n", argv[1]); + } + + } + return (EXIT_SUCCESS); +} + +/* ________________________________________________________________________ */ +int deluser_main(int argc, char **argv) +{ + /* int successful; */ + int failure; + + if (argc != 2) { + show_usage(); + } else { + + failure = del_line_matching(argv[1], PASSWD_FILE); + /* if (!successful) { */ + if (failure) { + error_msg_and_die("%s: User could not be removed from %s\n", + argv[1], PASSWD_FILE); + } +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + failure = del_line_matching(argv[1], SHADOW_FILE); + /* if (!successful) { */ + if (failure) { + error_msg_and_die("%s: User could not be removed from %s\n", + argv[1], SHADOW_FILE); + } + failure = del_line_matching(argv[1], GSHADOW_FILE); + /* if (!successful) { */ + if (failure) { + error_msg_and_die("%s: User could not be removed from %s\n", + argv[1], GSHADOW_FILE); + } +#endif /* CONFIG_FEATURE_SHADOWPASSWDS */ + failure = del_line_matching(argv[1], GROUP_FILE); + /* if (!successful) { */ + if (failure) { + error_msg_and_die("%s: User could not be removed from %s\n", + argv[1], GROUP_FILE); + } + + } + return (EXIT_SUCCESS); +} + +/* $Id: deluser.c,v 1.1 2002/06/04 20:45:05 sandman Exp $ */ diff --git a/loginutils/getty.c b/loginutils/getty.c new file mode 100644 index 000000000..2144c95ff --- /dev/null +++ b/loginutils/getty.c @@ -0,0 +1,1157 @@ +/* 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. + + -f option added by Eric Rasmussen <ear@usfirst.org> - 12/28/95 + + 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> + - added Native Language Support + + 1999-05-05 Thorsten Kranzkowski <dl8bcu@gmx.net> + - enable hardware flow control before displaying /etc/issue + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/signal.h> +#include <fcntl.h> +#include <stdarg.h> +#include <ctype.h> +#include <utmp.h> +#include <getopt.h> +#include <termios.h> +#include "busybox.h" + +#define _PATH_LOGIN "/bin/login" + +#ifdef linux +#include <sys/param.h> +#define USE_SYSLOG +#endif + +extern void updwtmp(const char *filename, const struct utmp *ut); + + /* If USE_SYSLOG is undefined all diagnostics go directly to /dev/console. */ +#ifdef USE_SYSLOG +#include <syslog.h> +#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 */ +#define SYSV_STYLE /* select System V style getty */ +#endif + + /* + * Things you may want to modify. + * + * If ISSUE is not defined, agetty 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. Relevant for System V only. + * + * 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. + */ + +#ifdef SYSV_STYLE +#define ISSUE "/etc/issue" /* displayed before the login prompt */ +#include <sys/utsname.h> +#include <time.h> +#endif + +#define LOGIN " login: " /* 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 0 +#define DEF_SWITCH 0 /* default switch char */ + + /* + * SunOS 4.1.1 termio is broken. We must use the termios stuff instead, + * because the termio -> termios translation does not clear the termios + * CIBAUD bits. Therefore, the tty driver would sometimes report that input + * baud rate != output baud rate. I did not notice that problem with SunOS + * 4.1. We will use termios where available, and termio otherwise. + */ + +/* linux 0.12 termio is broken too, if we use it c_cc[VERASE] isn't set + properly, but all is well if we use termios?! */ + +#ifdef TCGETS +#undef TCGETA +#undef TCSETA +#undef TCSETAW +#define termio termios +#define TCGETA TCGETS +#define TCSETA TCSETS +#define TCSETAW TCSETSW +#endif + + /* + * This program tries to not use the standard-i/o library. This keeps the + * executable small on systems that do not have shared libraries (System V + * Release <3). + */ +#ifndef BUFSIZ +#define BUFSIZ 1024 +#endif + + /* + * When multiple baud rates are specified on the command line, the first one + * we will try is the first one specified. + */ + +#define FIRST_SPEED 0 + +/* Storage for command-line options. */ + +#define MAX_SPEED 10 /* max. nr. of baud rates */ + +struct options { + int flags; /* toggle switches, see below */ + int timeout; /* time-out period */ + char *login; /* login program */ + char *tty; /* name of tty */ + char *initstring; /* modem init string */ + char *issue; /* alternative issue file */ + int numspeed; /* number of baud rates to try */ + int speeds[MAX_SPEED]; /* baud rates to be tried */ +}; + +#define F_PARSE (1<<0) /* process modem status messages */ +#define F_ISSUE (1<<1) /* display /etc/issue */ +#define F_RTSCTS (1<<2) /* enable RTS/CTS flow control */ +#define F_LOCAL (1<<3) /* force local */ +#define F_INITSTRING (1<<4) /* initstring is set */ +#define F_WAITCRLF (1<<5) /* wait for CR or LF */ +#define F_CUSTISSUE (1<<6) /* give alternative issue file */ +#define F_NOPROMPT (1<<7) /* don't ask for login name! */ + +/* Storage for things detected while the login name was read. */ + +struct chardata { + int erase; /* erase character */ + int kill; /* kill character */ + int eol; /* end-of-line character */ + int parity; /* what parity did we see */ + int capslock; /* upper case without lower case */ +}; + +/* Initial values for the above. */ + +struct chardata init_chardata = { + DEF_ERASE, /* default erase character */ + DEF_KILL, /* default kill character */ + 13, /* default eol char */ + 0, /* space parity */ + 0, /* no capslock */ +}; + +struct Speedtab { + long speed; + int code; +}; + +static struct Speedtab speedtab[] = { + {50, B50}, + {75, B75}, + {110, B110}, + {134, B134}, + {150, B150}, + {200, B200}, + {300, B300}, + {600, B600}, + {1200, B1200}, + {1800, B1800}, + {2400, B2400}, + {4800, B4800}, + {9600, B9600}, +#ifdef B19200 + {19200, B19200}, +#endif +#ifdef B38400 + {38400, B38400}, +#endif +#ifdef EXTA + {19200, EXTA}, +#endif +#ifdef EXTB + {38400, EXTB}, +#endif +#ifdef B57600 + {57600, B57600}, +#endif +#ifdef B115200 + {115200, B115200}, +#endif +#ifdef B230400 + {230400, B230400}, +#endif + {0, 0}, +}; + +static void parse_args(int argc, char **argv, struct options *op); +static void parse_speeds(struct options *op, char *arg); +static void update_utmp(char *line); +static void open_tty(char *tty, struct termio *tp, int local); +static void termio_init(struct termio *tp, int speed, struct options *op); +static void auto_baud(struct termio *tp); +static void do_prompt(struct options *op, struct termio *tp); +static void next_speed(struct termio *tp, struct options *op); +static char *get_logname(struct options *op, struct chardata *cp, + + struct termio *tp); +static void termio_final(struct options *op, struct termio *tp, + + struct chardata *cp); +static int caps_lock(const char *s); +static int bcode(const char *s); +static void error(const char *fmt, ...); + +/* The following is used for understandable diagnostics. */ + +/* Fake hostname for ut_host specified on command line. */ +static char *fakehost = NULL; + +/* ... */ +#ifdef DEBUGGING +#define debug(s) fprintf(dbf,s); fflush(dbf) +#define DEBUGTERM "/dev/ttyp0" +FILE *dbf; +#else +#define debug(s) /* nothing */ +#endif + +int getty_main(int argc, char **argv) +{ + char *logname = NULL; /* login name, given to /bin/login */ + struct chardata chardata; /* set by get_logname() */ + struct termio termio; /* terminal mode bits */ + static struct options options = { + F_ISSUE, /* show /etc/issue (SYSV_STYLE) */ + 0, /* no timeout */ + _PATH_LOGIN, /* default login program */ + "tty1", /* default tty line */ + "", /* modem init string */ + ISSUE, /* default issue file */ + 0, /* no baud rates known yet */ + }; + +#ifdef DEBUGGING + dbf = xfopen(DEBUGTERM, "w"); + + { + int i; + + for (i = 1; i < argc; i++) { + debug(argv[i]); + debug("\n"); + } + } +#endif + + /* Parse command-line arguments. */ + + parse_args(argc, argv, &options); + +#ifdef __linux__ + setsid(); +#endif + + /* Update the utmp file. */ + +#ifdef SYSV_STYLE + update_utmp(options.tty); +#endif + + debug("calling open_tty\n"); + /* Open the tty as standard { input, output, error }. */ + open_tty(options.tty, &termio, options.flags & F_LOCAL); + +#ifdef __linux__ + { + int iv; + + iv = getpid(); + if (ioctl(0, TIOCSPGRP, &iv) < 0) + perror_msg("ioctl() TIOCSPGRP call failed"); + } +#endif + /* Initialize the termio settings (raw mode, eight-bit, blocking i/o). */ + debug("calling termio_init\n"); + termio_init(&termio, options.speeds[FIRST_SPEED], &options); + + /* write the modem init string and DON'T flush the buffers */ + if (options.flags & F_INITSTRING) { + debug("writing init string\n"); + write(1, options.initstring, strlen(options.initstring)); + } + + if (!(options.flags & F_LOCAL)) { + /* go to blocking write mode unless -L is specified */ + fcntl(1, F_SETFL, fcntl(1, F_GETFL, 0) & ~O_NONBLOCK); + } + + /* Optionally detect the baud rate from the modem status message. */ + debug("before autobaud\n"); + if (options.flags & F_PARSE) + auto_baud(&termio); + + /* Set the optional timer. */ + if (options.timeout) + (void) alarm((unsigned) options.timeout); + + /* optionally wait for CR or LF before writing /etc/issue */ + if (options.flags & F_WAITCRLF) { + char ch; + + debug("waiting for cr-lf\n"); + while (read(0, &ch, 1) == 1) { + ch &= 0x7f; /* strip "parity bit" */ +#ifdef DEBUGGING + fprintf(dbf, "read %c\n", ch); +#endif + if (ch == '\n' || ch == '\r') + break; + } + } + + chardata = init_chardata; + if (!(options.flags & F_NOPROMPT)) { + /* Read the login name. */ + debug("reading login name\n"); + /* while ((logname = get_logname(&options, &chardata, &termio)) == 0) */ + while ((logname = get_logname(&options, &chardata, &termio)) == + NULL) next_speed(&termio, &options); + } + + /* Disable timer. */ + + if (options.timeout) + (void) alarm(0); + + /* Finalize the termio settings. */ + + termio_final(&options, &termio, &chardata); + + /* Now the newline character should be properly written. */ + + (void) write(1, "\n", 1); + + /* Let the login program take care of password validation. */ + + (void) execl(options.login, options.login, "--", logname, (char *) 0); + error("%s: can't exec %s: %m", options.tty, options.login); + return (0); /* quiet GCC */ +} + +/* parse-args - parse command-line arguments */ + +static void parse_args(int argc, char **argv, struct options *op) +{ + extern char *optarg; /* getopt */ + extern int optind; /* getopt */ + int c; + + while (isascii(c = getopt(argc, argv, "I:LH:f:hil:mt:wn"))) { + switch (c) { + case 'I': + if (!(op->initstring = strdup(optarg))) { + error("can't malloc initstring"); + break; + } + { + char ch, *p, *q; + int i; + + /* copy optarg into op->initstring decoding \ddd + octal codes into chars */ + q = op->initstring; + p = optarg; + while (*p) { + if (*p == '\\') { /* know \\ means \ */ + p++; + if (*p == '\\') { + ch = '\\'; + p++; + } else { /* handle \000 - \177 */ + ch = 0; + for (i = 1; i <= 3; i++) { + if (*p >= '0' && *p <= '7') { + ch <<= 3; + ch += *p - '0'; + p++; + } else + break; + } + } + *q++ = ch; + } else { + *q++ = *p++; + } + } + *q = '\0'; + } + op->flags |= F_INITSTRING; + break; + + case 'L': /* force local */ + op->flags |= F_LOCAL; + break; + case 'H': /* fake login host */ + fakehost = optarg; + break; + case 'f': /* custom issue file */ + op->flags |= F_CUSTISSUE; + op->issue = optarg; + break; + case 'h': /* enable h/w flow control */ + op->flags |= F_RTSCTS; + break; + case 'i': /* do not show /etc/issue */ + op->flags &= ~F_ISSUE; + break; + case 'l': + op->login = optarg; /* non-default login program */ + break; + case 'm': /* parse modem status message */ + op->flags |= F_PARSE; + break; + case 'n': + op->flags |= F_NOPROMPT; + break; + case 't': /* time out */ + if ((op->timeout = atoi(optarg)) <= 0) + error("bad timeout value: %s", optarg); + break; + case 'w': + op->flags |= F_WAITCRLF; + break; + default: + show_usage(); + } + } + debug("after getopt loop\n"); + if (argc < optind + 2) /* check parameter count */ + show_usage(); + + /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */ + if ('0' <= argv[optind][0] && argv[optind][0] <= '9') { + /* a number first, assume it's a speed (BSD style) */ + parse_speeds(op, argv[optind++]); /* baud rate(s) */ + op->tty = argv[optind]; /* tty name */ + } else { + op->tty = argv[optind++]; /* tty name */ + parse_speeds(op, argv[optind]); /* baud rate(s) */ + } + + optind++; + if (argc > optind && argv[optind]) + setenv("TERM", argv[optind], 1); + + debug("exiting parseargs\n"); +} + +/* parse_speeds - parse alternate baud rates */ + +static void parse_speeds(struct options *op, char *arg) +{ + char *cp; + + debug("entered parse_speeds\n"); + for (cp = strtok(arg, ","); cp != 0; cp = strtok((char *) 0, ",")) { + if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0) + error("bad speed: %s", cp); + if (op->numspeed > MAX_SPEED) + error("too many alternate speeds"); + } + debug("exiting parsespeeds\n"); +} + +#ifdef SYSV_STYLE + +/* update_utmp - update our utmp entry */ +static void update_utmp(char *line) +{ + struct utmp ut; + struct utmp *utp; + time_t t; + int mypid = getpid(); +#if ! (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1)) + struct flock lock; +#endif + + /* + * 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. + */ + utmpname(_PATH_UTMP); + setutent(); + while ((utp = getutent()) + && !(utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid)) /* nothing */ + ; + + if (utp) { + memcpy(&ut, utp, sizeof(ut)); + } else { + /* some inits don't initialize utmp... */ + memset(&ut, 0, sizeof(ut)); + strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); + } + /*endutent(); */ + + strncpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user)); + strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + if (fakehost) + strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host)); + time(&t); + ut.ut_time = t; + ut.ut_type = LOGIN_PROCESS; + ut.ut_pid = mypid; + + pututline(&ut); + endutent(); + + { + updwtmp(_PATH_WTMP, &ut); + } +} + +#endif + +/* open_tty - set up tty as standard { input, output, error } */ +static void open_tty(char *tty, struct termio *tp, int local) +{ + /* Get rid of the present standard { output, error} if any. */ + + (void) close(1); + (void) close(2); + errno = 0; /* ignore above errors */ + + /* Set up new standard input, unless we are given an already opened port. */ + + if (strcmp(tty, "-")) { + struct stat st; + + /* Sanity checks... */ + + if (chdir("/dev")) + error("/dev: chdir() failed: %m"); + if (stat(tty, &st) < 0) + error("/dev/%s: %m", tty); + if ((st.st_mode & S_IFMT) != S_IFCHR) + error("/dev/%s: not a character device", tty); + + /* Open the tty as standard input. */ + + (void) close(0); + errno = 0; /* ignore close(2) errors */ + + debug("open(2)\n"); + if (open(tty, O_RDWR | O_NONBLOCK, 0) != 0) + error("/dev/%s: cannot open as standard input: %m", tty); + + } else { + + /* + * Standard input should already be connected to an open port. Make + * sure it is open for read/write. + */ + + if ((fcntl(0, F_GETFL, 0) & O_RDWR) != O_RDWR) + error("%s: not open for read/write", tty); + } + + /* Set up standard output and standard error file descriptors. */ + debug("duping\n"); + if (dup(0) != 1 || dup(0) != 2) /* set up stdout and stderr */ + error("%s: dup problem: %m", tty); /* we have a problem */ + + /* + * 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. + */ + + if (ioctl(0, TCGETA, tp) < 0) + error("%s: ioctl: %m", tty); + + /* + * It seems to be a terminal. Set proper protections and ownership. Mode + * 0622 is suitable for SYSV <4 because /bin/login does not change + * protections. SunOS 4 login will change the protections to 0620 (write + * access for group tty) after the login has succeeded. + */ + +#ifdef DEBIAN + { + /* tty to root.dialout 660 */ + struct group *gr; + int id; + + id = (gr = getgrnam("dialout")) ? gr->gr_gid : 0; + chown(tty, 0, id); + chmod(tty, 0660); + + /* vcs,vcsa to root.sys 600 */ + if (!strncmp(tty, "tty", 3) && isdigit(tty[3])) { + char *vcs, *vcsa; + + if (!(vcs = strdup(tty))) + error("Can't malloc for vcs"); + if (!(vcsa = malloc(strlen(tty) + 2))) + error("Can't malloc for vcsa"); + strcpy(vcs, "vcs"); + strcpy(vcs + 3, tty + 3); + strcpy(vcsa, "vcsa"); + strcpy(vcsa + 4, tty + 3); + + id = (gr = getgrnam("sys")) ? gr->gr_gid : 0; + chown(vcs, 0, id); + chmod(vcs, 0600); + chown(vcsa, 0, id); + chmod(vcs, 0600); + + free(vcs); + free(vcsa); + } + } +#else + (void) chown(tty, 0, 0); /* root, sys */ + (void) chmod(tty, 0622); /* crw--w--w- */ + errno = 0; /* ignore above errors */ +#endif +} + +/* termio_init - initialize termio settings */ + +static void termio_init(struct termio *tp, int speed, struct options *op) +{ + + /* + * Initial termio 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 + * lateron. + */ +#ifdef __linux__ + /* flush input and output queues, important for modems! */ + (void) ioctl(0, TCFLSH, TCIOFLUSH); +#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_oflag = tp->c_line = 0; + 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 + + (void) ioctl(0, TCSETA, tp); + + /* go to blocking input even in local mode */ + fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~O_NONBLOCK); + + debug("term_io 2\n"); +} + +/* auto_baud - extract baud rate from modem status message */ +static void auto_baud(struct termio *tp) +{ + int speed; + int vmin; + unsigned iflag; + char buf[BUFSIZ]; + 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 lateron. + */ + + 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 */ + (void) ioctl(0, TCSETA, 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. + */ + + (void) sleep(1); + if ((nread = read(0, buf, sizeof(buf) - 1)) > 0) { + buf[nread] = '\0'; + for (bp = buf; bp < buf + nread; bp++) { + if (isascii(*bp) && isdigit(*bp)) { + if ((speed = bcode(bp))) { + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= speed; + } + break; + } + } + } + /* Restore terminal settings. Errors will be dealt with lateron. */ + + tp->c_iflag = iflag; + tp->c_cc[VMIN] = vmin; + (void) ioctl(0, TCSETA, tp); +} + +/* do_prompt - show login prompt, optionally preceded by /etc/issue contents */ +static void do_prompt(struct options *op, struct termio *tp) +{ +#ifdef ISSUE + FILE *fd; + int oflag; + int c; + struct utsname uts; + + (void) uname(&uts); +#endif + + (void) write(1, "\r\n", 2); /* start a new line */ +#ifdef ISSUE /* optional: show /etc/issue */ + if ((op->flags & F_ISSUE) && (fd = fopen(op->issue, "r"))) { + oflag = tp->c_oflag; /* save current setting */ + tp->c_oflag |= (ONLCR | OPOST); /* map NL in output to CR-NL */ + (void) ioctl(0, TCSETAW, tp); + + + while ((c = getc(fd)) != EOF) { + if (c == '\\') { + c = getc(fd); + + switch (c) { + case 's': + (void) printf("%s", uts.sysname); + break; + + case 'n': + (void) printf("%s", uts.nodename); + break; + + case 'r': + (void) printf("%s", uts.release); + break; + + case 'v': + (void) printf("%s", uts.version); + break; + + case 'm': + (void) printf("%s", uts.machine); + break; + + case 'o': + { + char domainname[256]; + + getdomainname(domainname, sizeof(domainname)); + domainname[sizeof(domainname) - 1] = '\0'; + printf("%s", domainname); + } + break; + + case 'd': + case 't': + { + char *weekday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", + "Fri", "Sat" + }; + char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", + "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec" + }; + time_t now; + struct tm *tm; + + (void) time(&now); + tm = localtime(&now); + + if (c == 'd') + (void) printf("%s %s %d %d", + weekday[tm->tm_wday], + month[tm->tm_mon], tm->tm_mday, + tm->tm_year < + 70 ? tm->tm_year + + 2000 : tm->tm_year + 1900); + else + (void) printf("%02d:%02d:%02d", tm->tm_hour, + tm->tm_min, tm->tm_sec); + + break; + } + + case 'l': + (void) printf("%s", op->tty); + break; + + case 'b': + { + int i; + + for (i = 0; speedtab[i].speed; i++) { + if (speedtab[i].code == (tp->c_cflag & CBAUD)) { + printf("%ld", speedtab[i].speed); + break; + } + } + break; + } + case 'u': + case 'U': + { + int users = 0; + struct utmp *ut; + + setutent(); + while ((ut = getutent())) + if (ut->ut_type == USER_PROCESS) + users++; + endutent(); + printf("%d ", users); + if (c == 'U') + printf((users == 1) ? "user" : "users"); + break; + } + default: + (void) putchar(c); + } + } else + (void) putchar(c); + } + fflush(stdout); + + tp->c_oflag = oflag; /* restore settings */ + (void) ioctl(0, TCSETAW, tp); /* wait till output is gone */ + (void) fclose(fd); + } +#endif +#ifdef __linux__ + { + char hn[MAXHOSTNAMELEN + 1]; + + (void) gethostname(hn, MAXHOSTNAMELEN); + write(1, hn, strlen(hn)); + } +#endif + (void) write(1, LOGIN, sizeof(LOGIN) - 1); /* always show login prompt */ +} + +/* next_speed - select next baud rate */ +static void next_speed(struct termio *tp, struct options *op) +{ + static int baud_index = FIRST_SPEED; /* current speed index */ + + baud_index = (baud_index + 1) % op->numspeed; + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= op->speeds[baud_index]; + (void) ioctl(0, TCSETA, tp); +} + +/* get_logname - get user name, establish parity, speed, erase, kill, eol */ +/* return NULL on failure, logname on success */ +static char *get_logname(struct options *op, struct chardata *cp, struct termio *tp) +{ + static char logname[BUFSIZ]; + 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 char *erase[] = { /* backspace-space-backspace */ + "\010\040\010", /* space parity */ + "\010\040\010", /* odd parity */ + "\210\240\210", /* even parity */ + "\210\240\210", /* no parity */ + }; + + /* Initialize kill, erase, parity etc. (also after switching speeds). */ + + *cp = init_chardata; + + /* Flush pending input (esp. after parsing or switching the baud rate). */ + + (void) sleep(1); + (void) ioctl(0, TCFLSH, TCIFLUSH); + + /* Prompt for and read a login name. */ + + for (*logname = 0; *logname == 0; /* void */ ) { + + /* Write issue file and prompt, with "parity" bit == 0. */ + + do_prompt(op, tp); + + /* Read name, watch for break, parity, erase, kill, end-of-line. */ + + for (bp = logname, cp->eol = 0; cp->eol == 0; /* void */ ) { + + /* Do not report trivial EINTR/EIO errors. */ + + if (read(0, &c, 1) < 1) { + if (errno == EINTR || errno == EIO) + exit(0); + error("%s: read: %m", op->tty); + } + /* Do BREAK handling elsewhere. */ + + if ((c == 0) && op->numspeed > 1) + /* return (0); */ + return NULL; + + /* Do parity bit handling. */ + + if (c != (ascval = (c & 0177))) { /* "parity" bit on ? */ + for (bits = 1, mask = 1; mask & 0177; mask <<= 1) + if (mask & ascval) + bits++; /* count "1" bits */ + cp->parity |= ((bits & 1) ? 1 : 2); + } + /* Do erase, kill and end-of-line processing. */ + + switch (ascval) { + case CR: + case NL: + *bp = 0; /* terminate logname */ + cp->eol = ascval; /* set end-of-line char */ + break; + case BS: + case DEL: + case '#': + cp->erase = ascval; /* set erase character */ + if (bp > logname) { + (void) write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('U'): + case '@': + cp->kill = ascval; /* set kill character */ + while (bp > logname) { + (void) write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('D'): + exit(0); + default: + if (!isascii(ascval) || !isprint(ascval)) { + /* ignore garbage characters */ ; + } else if (bp - logname >= sizeof(logname) - 1) { + error("%s: input overrun", op->tty); + } else { + (void) write(1, &c, 1); /* echo the character */ + *bp++ = ascval; /* and store it */ + } + break; + } + } + } + /* Handle names with upper case and no lower case. */ + + if ((cp->capslock = caps_lock(logname))) { + for (bp = logname; *bp; bp++) + if (isupper(*bp)) + *bp = tolower(*bp); /* map name to lower case */ + } + return (logname); +} + +/* termio_final - set the final tty mode bits */ +static void termio_final(struct options *op, struct termio *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; +#ifdef __linux__ + tp->c_cc[VSWTC] = DEF_SWITCH; /* default switch character */ +#else + tp->c_cc[VSWTCH] = DEF_SWITCH; /* default switch character */ +#endif + + /* 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 */ + 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; + break; + } + /* Account for upper case without lower case. */ + + if (cp->capslock) { + tp->c_iflag |= IUCLC; + tp->c_lflag |= XCASE; + tp->c_oflag |= OLCUC; + } + /* Optionally enable hardware flow control */ + +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + /* Finally, make the new settings effective */ + + if (ioctl(0, TCSETA, tp) < 0) + error("%s: ioctl: TCSETA: %m", op->tty); +} + +/* caps_lock - string contains upper case without lower case */ +/* returns 1 if true, 0 if false */ +static int caps_lock(const char *s) +{ + int capslock; + + for (capslock = 0; *s; s++) { + if (islower(*s)) + return (0); + if (capslock == 0) + capslock = isupper(*s); + } + return (capslock); +} + +/* bcode - convert speed string to speed code; return 0 on failure */ +static int bcode(const char *s) +{ + struct Speedtab *sp; + long speed = atol(s); + + for (sp = speedtab; sp->speed; sp++) + if (sp->speed == speed) + return (sp->code); + return (0); +} + +/* error - report errors to console or syslog; only understands %s and %m */ + +#define str2cpy(b,s1,s2) strcat(strcpy(b,s1),s2) + +/* + * output error messages + */ +static void error(const char *fmt, ...) +{ + va_list va_alist; + char buf[256], *bp; + +#ifndef USE_SYSLOG + int fd; +#endif + +#ifdef USE_SYSLOG + buf[0] = '\0'; + bp = buf; +#else + strncpy(buf, applet_name, 256); + strncat(buf, ": ", 256); + buf[255] = 0; + bp = buf + strlen(buf); +#endif + + va_start(va_alist, fmt); + vsnprintf(bp, 256 - strlen(buf), fmt, va_alist); + buf[255] = 0; + va_end(va_alist); + +#ifdef USE_SYSLOG + syslog_msg(LOG_AUTH, LOG_ERR, buf); +#else + strncat(bp, "\r\n", 256 - strlen(buf)); + buf[255] = 0; + if ((fd = open("/dev/console", 1)) >= 0) { + write(fd, buf, strlen(buf)); + close(fd); + } +#endif + (void) sleep((unsigned) 10); /* be kind to init(8) */ + exit(1); +} diff --git a/loginutils/login.c b/loginutils/login.c new file mode 100644 index 000000000..4d93ece49 --- /dev/null +++ b/loginutils/login.c @@ -0,0 +1,459 @@ +/* vi: set sw=4 ts=4: */ +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <termios.h> +#include <unistd.h> +#include <utmp.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <ctype.h> +#include <time.h> +#include "busybox.h" + +#include "pwd.h" +#include "grp.h" + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS +#include "shadow.h" +#endif + +#include "tinylogin.h" + +// import from utmp.c +static void checkutmp(int picky); +static void setutmp(const char *name, const char *line); + +// import from encrypt.c +extern char *pw_encrypt(const char *clear, const char *salt); + + +// login defines +#define DEFAULT_USER "UNKNOWN" +#define DEFAULT_PWD "!" +#define DEFAULT_SHELL "/bin/sh" +#define TIMEOUT 60 +#define FAIL_DELAY 3 +#define MOTD_FILE "/etc/motd" +#define NOLOGIN_FILE "/etc/nologin" +#define SECURETTY_FILE "/etc/securetty" + +/* Stuff global to this file */ +struct utmp utent; + + +static int check_nologin ( int amroot ); + +#if defined CONFIG_FEATURE_SECURETTY +static int check_tty ( const char *tty ); + +#else +static inline int check_tty ( const char *tty ) { return 1; } + +#endif + +static int is_my_tty ( const char *tty ); +static const char *login_prompt ( void ); +static void motd ( void ); +static void set_env(int argc, char *const *argv); + + +static void alarm_handler ( int sig ) +{ + error_msg ( "\nLogin timed out after %d seconds.\n", TIMEOUT ); + exit ( EXIT_SUCCESS ); +} + + +extern int login_main(int argc, char **argv) +{ + char tty[BUFSIZ]; + char full_tty[200]; + char fromhost[512]; + char *tmp; + int amroot; + int flag; + int failed; + int count=0; + struct passwd *pw, pw_copy; + + int opt_preserve = 0; + int opt_fflag = 0; + char *opt_host = 0; + const char *username = 0; + int alarmstarted = 0; + + amroot = ( getuid ( ) == 0 ); + signal ( SIGALRM, alarm_handler ); + + if (( argc > 1 ) && ( TIMEOUT > 0 )) { + alarm ( TIMEOUT ); + alarmstarted = 1; + } + + while (( flag = getopt(argc, argv, "f:h:p")) != EOF ) { + switch ( flag ) { + case 'p': + printf ( "PRESERVE\n" ); + opt_preserve = 1; + break; + case 'f': + /* + * username must be a seperate token + * (-f root, *NOT* -froot). --marekm + */ + if ( optarg != argv[optind-1] ) + show_usage ( ); + + if ( !amroot ) /* Auth bypass only if real UID is zero */ + error_msg_and_die ( "login: -f permission denied\n" ); + + printf ( "USERNAME: %s\n", optarg ); + + username = optarg; + opt_fflag = 1; + break; + case 'h': + opt_host = optarg; + break; + default: + show_usage ( ); + } + } + + if ( optind < argc ) // got a username + username = argv [optind++]; + + if ( !isatty ( 0 ) || !isatty ( 1 ) || !isatty ( 2 )) + return EXIT_FAILURE; /* Must be a terminal */ + + checkutmp ( !amroot ); + + tmp = ttyname ( 0 ); + if ( tmp && ( strncmp ( tmp, "/dev/", 5 ) == 0 )) + safe_strncpy ( tty, tmp + 5, sizeof( tty )); + else + safe_strncpy ( tty, "UNKNOWN", sizeof( tty )); + + if ( amroot ) + memset ( utent.ut_host, 0, sizeof utent.ut_host ); + + if ( opt_host ) { + safe_strncpy ( utent.ut_host, opt_host, sizeof( utent. ut_host )); + + snprintf ( fromhost, sizeof( fromhost ) - 1, " on `%.100s' from `%.200s'", tty, opt_host ); + } + else + snprintf ( fromhost, sizeof( fromhost ) - 1, " on `%.100s'", tty ); + + openlog ( "login", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH ); + + while ( 1 ) { + failed = 0; + + if ( !username || !username[0] ) + username = login_prompt ( ); + + if ( !alarmstarted && ( TIMEOUT > 0 )) { + alarm ( TIMEOUT ); + alarmstarted = 1; + } + + if (!( pw = getpwnam ( username ))) { + pw_copy. pw_name = DEFAULT_USER; + pw_copy. pw_passwd = DEFAULT_PWD; + pw_copy. pw_shell = DEFAULT_SHELL; + opt_fflag = 0; + failed = 1; + } else + pw_copy = *pw; + + pw = &pw_copy; + + if (( pw-> pw_passwd [0] == '!' ) || ( pw-> pw_passwd[0] == '*' )) + failed = 1; + + if ( opt_fflag ) { + opt_fflag = 0; + goto auth_ok; + } + + if (( pw-> pw_uid == 0 ) && ( !check_tty ( tty ))) + failed = 1; + + /* Don't check the password if password entry is empty (!) */ + if ( !pw-> pw_passwd[0] ) + goto auth_ok; + + /* authorization takes place here */ + if ( correct_password ( pw )) + goto auth_ok; + + syslog ( LOG_WARNING, "invalid password for `%s'%s\n", pw-> pw_name, fromhost); + failed = 1; + +auth_ok: + if ( !failed) + break; + + { // delay next try + time_t start, now; + + time ( &start ); + now = start; + while ( difftime ( now, start ) < FAIL_DELAY) { + sleep ( FAIL_DELAY ); + time ( &now ); + } + } + + puts("Login incorrect"); + if ( ++count == 3 ) + return EXIT_FAILURE; + } + + alarm ( 0 ); + if ( check_nologin ( pw-> pw_uid == 0 )) + return EXIT_FAILURE; + + setutmp ( username, tty ); + if ( *tty != '/' ) + snprintf ( full_tty, sizeof( full_tty ) - 1, "/dev/%s", tty); + else + safe_strncpy ( full_tty, tty, sizeof( full_tty ) - 1 ); + + if ( !is_my_tty ( full_tty )) + syslog ( LOG_ERR, "unable to determine TTY name, got %s\n", full_tty ); + + /* Try these, but don't complain if they fail + * (for example when the root fs is read only) */ + chown ( full_tty, pw-> pw_uid, pw-> pw_gid ); + chmod ( full_tty, 0600 ); + + change_identity ( pw ); + setup_environment ( pw-> pw_shell, 1, !opt_preserve, pw ); + + motd ( ); + signal ( SIGALRM, SIG_DFL ); /* default alarm signal */ + + if ( pw-> pw_uid == 0 ) + syslog ( LOG_INFO, "root login %s\n", fromhost ); + + run_shell ( pw-> pw_shell, 1, 0, 0 ); /* exec the shell finally. */ + + return EXIT_FAILURE; +} + + + +static const char *login_prompt ( void ) +{ + char buf [1024]; + char *sp, *ep; + + gethostname ( buf, sizeof( buf )); + printf ( "\nBusyBox on %s login: ", buf ); + fflush ( stdout ); + + if ( !fgets ( buf, sizeof( buf ) - 1, stdin )) + return 0; + + if ( !strchr ( buf, '\n' )); + return 0; + + for ( sp = buf; isspace ( *sp ); sp++ ) { } + for ( ep = sp; isgraph ( *ep ); ep++ ) { } + + *ep = 0; + return sp; +} + + +static int check_nologin ( int amroot ) +{ + if ( access ( NOLOGIN_FILE, F_OK ) == 0 ) { + FILE *fp; + int c; + + if (( fp = fopen ( NOLOGIN_FILE, "r" ))) { + while (( c = getc ( fp )) != EOF ) + putchar (( c == '\n' ) ? '\r' : c ); + + fflush ( stdout ); + fclose ( fp ); + } else { + puts ( "\r\nSystem closed for routine maintenance.\r" ); + } + if ( !amroot ) + return 1; + + puts ( "\r\n[Disconnect bypassed -- root login allowed.]\r" ); + } + return 0; +} + +#ifdef CONFIG_FEATURE_SECURETTY + +static int check_tty ( const char *tty ) +{ + FILE *fp; + int i; + char buf[BUFSIZ]; + + if (( fp = fopen ( SECURETTY_FILE, "r" ))) { + while ( fgets ( buf, sizeof( buf ) - 1, fp )) { + for ( i = xstrlen( buf ) - 1; i >= 0; --i ) { + if ( !isspace ( buf[i] )) + break; + } + buf[++i] = '\0'; + if (( buf [0] == '\0' ) || ( buf [0] == '#' )) + continue; + + if ( strcmp ( buf, tty ) == 0 ) { + fclose ( fp ); + return 1; + } + } + fclose(fp); + return 0; + } + else { + syslog ( LOG_WARNING, "cannot open securetty file.\n" ); + return 1; + } +} + +#endif + +/* returns 1 if true */ +static int is_my_tty ( const char *tty ) +{ + struct stat by_name, by_fd; + + if ( stat ( tty, &by_name ) || fstat ( 0, &by_fd )) + return 0; + + if ( by_name. st_rdev != by_fd. st_rdev ) + return 0; + else + return 1; +} + + +static void motd ( ) +{ + FILE *fp; + register int c; + + if (( fp = fopen ( MOTD_FILE, "r" ))) { + while (( c = getc ( fp )) != EOF ) + putchar ( c ); + fclose ( fp ); + } +} + + +// vv Taken from tinylogin utmp.c vv + +#define _WTMP_FILE "/var/log/wtmp" + +#define NO_UTENT \ + "No utmp entry. You must exec \"login\" from the lowest level \"sh\"" +#define NO_TTY \ + "Unable to determine your tty name." + +/* + * checkutmp - 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 checkutmp(int picky) +{ + char *line; + struct utmp *ut; + pid_t pid = getpid(); + + setutent(); + + /* First, try to find a valid utmp entry for this process. */ + while ((ut = getutent())) + if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] && + (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)) + break; + + /* If there is one, just use it, otherwise create a new one. */ + if (ut) { + utent = *ut; + } else { + if (picky) { + puts(NO_UTENT); + exit(1); + } + line = ttyname(0); + if (!line) { + puts(NO_TTY); + exit(1); + } + if (strncmp(line, "/dev/", 5) == 0) + line += 5; + memset((void *) &utent, 0, sizeof utent); + utent.ut_type = LOGIN_PROCESS; + utent.ut_pid = pid; + strncpy(utent.ut_line, line, sizeof utent.ut_line); + /* XXX - assumes /dev/tty?? */ + strncpy(utent.ut_id, utent.ut_line + 3, sizeof utent.ut_id); + strncpy(utent.ut_user, "LOGIN", sizeof utent.ut_user); + time(&utent.ut_time); + } +} + +#if __GNU_LIBRARY__ < 5 +/* + * Some systems already have updwtmp() and possibly updwtmpx(). Others + * don't, so we re-implement these functions if necessary. --marekm + */ +static void updwtmp(const char *filename, const struct utmp *ut) +{ + int fd; + + fd = open(filename, O_APPEND | O_WRONLY, 0); + if (fd >= 0) { + write(fd, (const char *) ut, sizeof(*ut)); + close(fd); + } +} +#endif + +/* + * setutmp - put a USER_PROCESS entry in the utmp file + * + * setutmp changes the type of the current utmp entry to + * USER_PROCESS. the wtmp file will be updated as well. + */ + +static void setutmp(const char *name, const char *line) +{ + utent.ut_type = USER_PROCESS; + strncpy(utent.ut_user, name, sizeof utent.ut_user); + time(&utent.ut_time); + /* other fields already filled in by checkutmp above */ + setutent(); + pututline(&utent); + endutent(); + updwtmp(_WTMP_FILE, &utent); +} diff --git a/loginutils/su.c b/loginutils/su.c new file mode 100644 index 000000000..33e62e837 --- /dev/null +++ b/loginutils/su.c @@ -0,0 +1,167 @@ +/* vi: set sw=4 ts=4: */ + +#include "busybox.h" + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <termios.h> +#include <unistd.h> +#include <utmp.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <ctype.h> +#include <time.h> + +#include "pwd.h" +#include "grp.h" + +#include "tinylogin.h" + + + +/* The shell to run if none is given in the user's passwd entry. */ +#define DEFAULT_SHELL "/bin/sh" +#define DEFAULT_USER "root" + +//#define SYSLOG_SUCCESS +#define SYSLOG_FAILURE + + +#if defined( SYSLOG_SUCCESS ) || defined( SYSLOG_FAILURE ) +/* Log the fact that someone has run su to the user given by PW; + if SUCCESSFUL is nonzero, they gave the correct password, etc. */ + +static void log_su ( const struct passwd *pw, int successful ) +{ + const char *old_user, *tty; + +#if !defined( SYSLOG_SUCESS ) + if ( successful ) + return; +#endif +#if !defined( SYSLOG_FAILURE ) + if ( !successful ) + return; +#endif + + if ( pw-> pw_uid ) // not to root -> ignored + return; + + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. */ + old_user = getlogin ( ); + if ( !old_user ) { + /* getlogin can fail -- usually due to lack of utmp entry. Resort to getpwuid. */ + struct passwd *pwd = getpwuid ( getuid ( )); + old_user = ( pwd ? pwd-> pw_name : "" ); + } + + tty = ttyname ( 2 ); + + openlog ( "su", 0, LOG_AUTH ); + syslog ( LOG_NOTICE, "%s%s on %s", successful ? "" : "FAILED SU ", old_user, tty ? tty : "none" ); +} +#endif + + + +int su_main ( int argc, char **argv ) +{ + int flag; + int opt_preserve = 0; + int opt_loginshell = 0; + char *opt_shell = 0; + char *opt_command = 0; + char *opt_username = DEFAULT_USER; + char **opt_args = 0; + struct passwd *pw, pw_copy; + + + while (( flag = getopt ( argc, argv, "c:lmps:" )) != -1 ) { + switch ( flag ) { + case 'c': + opt_command = optarg; + break; + case 'm': + case 'p': + opt_preserve = 1; + break; + case 's': + opt_shell = optarg; + break; + case 'l': + opt_loginshell = 1; + break; + default: + show_usage ( ); + break; + } + } + + if (( optind < argc ) && ( argv [optind][0] == '-' ) && ( argv [optind][1] == 0 )) { + opt_loginshell = 1; + ++optind; + } + + /* get user if specified */ + if ( optind < argc ) + opt_username = argv [optind++]; + + if ( optind < argc ) + opt_args = argv + optind; + + + pw = getpwnam ( opt_username ); + if ( !pw ) + error_msg_and_die ( "user %s does not exist", 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; + + /* Make a copy of the password information and point pw at the local + copy instead. Otherwise, some systems (e.g. Linux) would clobber + the static data through the getlogin call from log_su. */ + pw_copy = *pw; + pw = &pw_copy; + pw-> pw_name = xstrdup ( pw-> pw_name ); + pw-> pw_dir = xstrdup ( pw-> pw_dir ); + pw-> pw_shell = xstrdup ( pw-> pw_shell ); + + if (( getuid ( ) == 0 ) || correct_password ( pw )) + log_su ( pw, 1 ); + else { + log_su ( pw, 0 ); + error_msg_and_die ( "incorrect password" ); + } + + if ( !opt_shell && opt_preserve ) + opt_shell = getenv ( "SHELL" ); + + if ( opt_shell && getuid ( ) && 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. */ + fputs ( "using restricted shell\n", stderr ); + opt_shell = 0; + } + + if ( !opt_shell ) + opt_shell = xstrdup ( pw-> pw_shell ); + + change_identity ( pw ); + setup_environment ( opt_shell, opt_loginshell, !opt_preserve, pw ); + run_shell ( opt_shell, opt_loginshell, opt_command, opt_args ); + + return EXIT_FAILURE; +} diff --git a/loginutils/tinylogin.c b/loginutils/tinylogin.c new file mode 100644 index 000000000..bd611fd70 --- /dev/null +++ b/loginutils/tinylogin.c @@ -0,0 +1,209 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh <jockgrrl@austin.rr.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "busybox.h" + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <syslog.h> +#include <ctype.h> + +#include "pwd.h" +#include "grp.h" + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS +#include "shadow.h" +#endif + +#define DEFAULT_LOGIN_PATH "/bin:/usr/bin" +#define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin" + + +static void xsetenv ( const char *key, const char *value ) +{ + if ( setenv ( key, value, 1 )) + error_msg_and_die ( "out of memory" ); +} + +/* Become the user and group(s) specified by PW. */ + +void change_identity ( const struct passwd *pw ) +{ + if ( initgroups ( pw-> pw_name, pw-> pw_gid ) == -1 ) + perror_msg_and_die ( "cannot set groups" ); + endgrent ( ); + + if ( setgid ( pw-> pw_gid )) + perror_msg_and_die ( "cannot set group id" ); + if ( setuid ( pw->pw_uid )) + perror_msg_and_die ( "cannot set user id" ); +} + +/* Run SHELL, or DEFAULT_SHELL if SHELL is empty. + If COMMAND is nonzero, pass it to the shell with the -c option. + If ADDITIONAL_ARGS is nonzero, pass it to the shell as more + arguments. */ + +void run_shell ( const char *shell, int loginshell, const char *command, const char **additional_args ) +{ + const char **args; + int argno = 1; + int additional_args_cnt = 0; + + for ( args = additional_args; args && *args; args++ ) + additional_args_cnt++; + + if ( additional_args ) + args = (const char **) xmalloc (sizeof (char *) * ( 4 + additional_args_cnt )); + else + args = (const char **) xmalloc (sizeof (char *) * 4 ); + + args [0] = get_last_path_component ( xstrdup ( shell )); + + if ( loginshell ) { + char *args0 = xmalloc ( xstrlen ( args [0] ) + 2 ); + args0 [0] = '-'; + strcpy ( args0 + 1, args [0] ); + args [0] = args0; + } + + if ( command ) { + args [argno++] = "-c"; + args [argno++] = command; + } + if ( additional_args ) { + for ( ; *additional_args; ++additional_args ) + args [argno++] = *additional_args; + } + args [argno] = 0; + execv ( shell, (char **) args ); + perror_msg_and_die ( "cannot run %s", shell ); +} + +/* Return 1 if SHELL is a restricted shell (one not returned by + getusershell), else 0, meaning it is a standard shell. */ + +int restricted_shell ( const char *shell ) +{ + char *line; + + setusershell ( ); + while (( line = getusershell ( ))) { + if (( *line != '#' ) && ( strcmp ( line, shell ) == 0 )) + break; + } + endusershell ( ); + return line ? 0 : 1; +} + +/* Update `environ' for the new shell based on PW, with SHELL being + the value for the SHELL environment variable. */ + +void setup_environment ( const char *shell, int loginshell, int changeenv, const struct passwd *pw ) +{ + if ( loginshell ) { + char *term; + + /* Change the current working directory to be the home directory + * of the user. It is a fatal error for this process to be unable + * to change to that directory. There is no "default" home + * directory. + * Some systems default to HOME=/ + */ + if ( chdir ( pw-> pw_dir )) { + if ( chdir ( "/" )) { + syslog ( LOG_WARNING, "unable to cd to %s' for user %s'\n", pw-> pw_dir, pw-> pw_name ); + error_msg_and_die ( "cannot cd to home directory or /" ); + } + fputs ( "warning: cannot change to home directory\n", stderr ); + } + + /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. + Unset all other environment variables. */ + term = getenv ("TERM"); + clearenv ( ); + if ( term ) + xsetenv ( "TERM", term ); + xsetenv ( "HOME", pw-> pw_dir ); + xsetenv ( "SHELL", shell ); + xsetenv ( "USER", pw-> pw_name ); + xsetenv ( "LOGNAME", pw-> pw_name ); + xsetenv ( "PATH", ( pw-> pw_uid ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH )); + } + else if ( changeenv ) { + /* Set HOME, SHELL, and if not becoming a super-user, + USER and LOGNAME. */ + xsetenv ( "HOME", pw-> pw_dir ); + xsetenv ( "SHELL", shell ); + if ( pw-> pw_uid ) { + xsetenv ( "USER", pw-> pw_name ); + xsetenv ( "LOGNAME", pw-> pw_name ); + } + } +} + +/* Ask the user for a password. + Return 1 if the user gives the correct password for entry PW, + 0 if not. Return 1 without asking for a password if run by UID 0 + or if PW has an empty password. */ + +int correct_password ( const struct passwd *pw ) +{ + char *unencrypted, *encrypted, *correct; + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + if (( strcmp ( pw-> pw_passwd, "x" ) == 0 ) || ( strcmp ( pw-> pw_passwd, "*" ) == 0 )) { + struct spwd *sp = getspnam ( pw-> pw_name ); + + if ( !sp ) + error_msg_and_die ( "no valid shadow password" ); + + correct = sp-> sp_pwdp; + } + else +#endif + correct = pw-> pw_passwd; + + if ( correct == 0 || correct[0] == '\0' ) + return 1; + + unencrypted = getpass ( "Password: " ); + if ( !unencrypted ) + { + fputs ( "getpass: cannot open /dev/tty\n", stderr ); + return 0; + } + encrypted = crypt ( unencrypted, correct ); + memset ( unencrypted, 0, xstrlen ( unencrypted )); + return ( strcmp ( encrypted, correct ) == 0 ) ? 1 : 0; +} diff --git a/loginutils/tinylogin.h b/loginutils/tinylogin.h new file mode 100644 index 000000000..5e56a2c7f --- /dev/null +++ b/loginutils/tinylogin.h @@ -0,0 +1,10 @@ +#ifndef BB_LOGINUTILS_SHELL_H +#define BB_LOGINUTILS_SHELL_H + +extern void change_identity ( const struct passwd *pw ); +extern void run_shell ( const char *shell, int loginshell, const char *command, char **additional_args ); +extern int restricted_shell ( const char *shell ); +extern void setup_environment ( const char *shell, int loginshell, int changeenv, const struct passwd *pw ); +extern int correct_password ( const struct passwd *pw ); + +#endif |