From f5e8e68924566110cb3022c20109dae60aee2c8f Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sat, 19 Apr 2008 00:15:59 +0000 Subject: [PATCH] * Added useradd and passwd tools. They allow adding new users and changing a user's password, like on other platforms (just a bit more bare-bones :-)). * Moved login from src/bin/network/login/ to src/bin/multiuser/. * login correctly checks the shadow password now. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25041 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/bin/Jamfile | 1 + src/bin/multiuser/Jamfile | 17 ++ .../{network/login => multiuser}/login.cpp | 11 +- src/bin/multiuser/multiuser_utils.cpp | 167 +++++++++++++++ src/bin/multiuser/multiuser_utils.h | 26 +++ src/bin/multiuser/passwd.cpp | 145 +++++++++++++ src/bin/multiuser/useradd.cpp | 192 ++++++++++++++++++ src/bin/network/Jamfile | 1 - src/bin/network/login/Jamfile | 5 - 9 files changed, 555 insertions(+), 10 deletions(-) create mode 100644 src/bin/multiuser/Jamfile rename src/bin/{network/login => multiuser}/login.cpp (96%) create mode 100644 src/bin/multiuser/multiuser_utils.cpp create mode 100644 src/bin/multiuser/multiuser_utils.h create mode 100644 src/bin/multiuser/passwd.cpp create mode 100644 src/bin/multiuser/useradd.cpp delete mode 100644 src/bin/network/login/Jamfile diff --git a/src/bin/Jamfile b/src/bin/Jamfile index 2b2c34015a..208ff4e625 100644 --- a/src/bin/Jamfile +++ b/src/bin/Jamfile @@ -181,6 +181,7 @@ SubInclude HAIKU_TOP src bin make ; SubInclude HAIKU_TOP src bin makebootable ; #SubInclude HAIKU_TOP src bin makeudfimage ; SubInclude HAIKU_TOP src bin mkdos ; +SubInclude HAIKU_TOP src bin multiuser ; SubInclude HAIKU_TOP src bin patch ; SubInclude HAIKU_TOP src bin pc ; SubInclude HAIKU_TOP src bin pcmcia-cs ; diff --git a/src/bin/multiuser/Jamfile b/src/bin/multiuser/Jamfile new file mode 100644 index 0000000000..f755d5768f --- /dev/null +++ b/src/bin/multiuser/Jamfile @@ -0,0 +1,17 @@ +SubDir HAIKU_TOP src bin multiuser ; + +UsePrivateKernelHeaders ; +UsePrivateHeaders app ; +UsePrivateHeaders libroot ; +UsePrivateHeaders shared ; + +StaticLibrary libmultiuser_utils.a : multiuser_utils.cpp ; + +BinCommand login : login.cpp : libmultiuser_utils.a ; + +BinCommand passwd : passwd.cpp : libmultiuser_utils.a ; + +BinCommand useradd : useradd.cpp : libmultiuser_utils.a ; + +# set set-uid bit on passwd +MODE on passwd = 04755 ; diff --git a/src/bin/network/login/login.cpp b/src/bin/multiuser/login.cpp similarity index 96% rename from src/bin/network/login/login.cpp rename to src/bin/multiuser/login.cpp index ebf5d57203..c02829bfbb 100644 --- a/src/bin/network/login/login.cpp +++ b/src/bin/multiuser/login.cpp @@ -19,6 +19,8 @@ #include #include +#include "multiuser_utils.h" + extern const char* __progname; const char* kProgramName = __progname; @@ -118,11 +120,12 @@ login(const char* user, struct passwd** _passwd) return status; struct passwd* passwd = getpwnam(user); - if (passwd == NULL || passwd->pw_passwd == NULL) - return B_ERROR; + struct spwd* spwd = getspnam(user); - // TODO: do a real password check! - if (strcmp(password, passwd->pw_passwd)) + bool ok = verify_password(passwd, spwd, password); + memset(password, 0, sizeof(password)); + + if (!ok) return B_PERMISSION_DENIED; *_passwd = passwd; diff --git a/src/bin/multiuser/multiuser_utils.cpp b/src/bin/multiuser/multiuser_utils.cpp new file mode 100644 index 0000000000..97551f4fb7 --- /dev/null +++ b/src/bin/multiuser/multiuser_utils.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +#include "multiuser_utils.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + + +status_t +read_password(const char* prompt, char* password, size_t bufferSize, + bool useStdio) +{ + FILE* in = stdin; + FILE* out = stdout; + + // open tty + FILE* tty = NULL; + if (!useStdio) { +// TODO: Open tty with O_NOCTTY! + tty = fopen("/dev/tty", "w+"); + if (tty == NULL) { + fprintf(stderr, "Error: Failed to open tty: %s\n", strerror(errno)); + return errno; + } + + in = tty; + out = tty; + } + CObjectDeleter ttyCloser(tty, fclose); + + // disable echo + int inFD = fileno(in); + struct termios termAttrs; + if (tcgetattr(inFD, &termAttrs) != 0) { + fprintf(in, "Error: Failed to get tty attributes: %s\n", + strerror(errno)); + return errno; + } + + tcflag_t localFlags = termAttrs.c_lflag; + termAttrs.c_lflag &= ~ECHO; + + if (tcsetattr(inFD, TCSANOW, &termAttrs) != 0) { + fprintf(in, "Error: Failed to set tty attributes: %s\n", + strerror(errno)); + return errno; + } + + status_t error = B_OK; + + // prompt and read pwd + fprintf(out, prompt); + fflush(out); + + if (fgets(password, bufferSize, in) == NULL) { + fprintf(out, "\nError: Failed to read from tty: %s\n", strerror(errno)); + error = errno != 0 ? errno : B_ERROR; + } else + fputc('\n', out); + + // chop off trailing newline + if (error == B_OK) { + size_t len = strlen(password); + if (len > 0 && password[len - 1] == '\n') + password[len - 1] = '\0'; + } + + // restore the terminal attributes + termAttrs.c_lflag = localFlags; + tcsetattr(inFD, TCSANOW, &termAttrs); + + return error; +} + + +bool +verify_password(passwd* passwd, spwd* spwd, const char* plainPassword) +{ + if (passwd == NULL) + return false; + + // check whether we need to check the shadow password + const char* requiredPassword = passwd->pw_passwd; + if (strcmp(requiredPassword, "x") == 0) { + if (spwd == NULL) { + // Mmh, we're suppose to check the shadow password, but we don't + // have it. Bail out. + return false; + } + + requiredPassword = spwd->sp_pwdp; + } + + // If no password is required, we're done. + if (requiredPassword == NULL || strlen(requiredPassword) == 0) + return true; + + // crypt and check it + char* encryptedPassword = crypt(plainPassword, requiredPassword); + + return (strcmp(encryptedPassword, requiredPassword) == 0); +} + + +/*! Checks whether the user needs to authenticate with a password, and, if + necessary, asks for it, and checks it. + \a passwd must always be given, \a spwd only if there exists an entry + for the user. +*/ +status_t +authenticate_user(const char* prompt, passwd* passwd, spwd* spwd, int maxTries, + bool useStdio) +{ + // check whether a password is need at all + if (verify_password(passwd, spwd, "")) + return B_OK; + + while (true) { + // prompt the user for the password + char plainPassword[MAX_SHADOW_PWD_PASSWORD_LEN]; + status_t error = read_password(prompt, plainPassword, + sizeof(plainPassword), useStdio); + if (error != B_OK) + return error; + + // check it + bool ok = verify_password(passwd, spwd, plainPassword); + memset(plainPassword, 0, sizeof(plainPassword)); + if (ok) + return B_OK; + + fprintf(stderr, "Incorrect password.\n"); + if (--maxTries <= 0) + return B_PERMISSION_DENIED; + } +} + + +status_t +authenticate_user(const char* prompt, const char* user, passwd** _passwd, + spwd** _spwd, int maxTries, bool useStdio) +{ + struct passwd* passwd = getpwnam(user); + struct spwd* spwd = getspnam(user); + + status_t error = authenticate_user(prompt, passwd, spwd, maxTries, + useStdio); + if (error == B_OK) { + if (_passwd) + *_passwd = passwd; + if (_spwd) + *_spwd = spwd; + } + + return error; +} diff --git a/src/bin/multiuser/multiuser_utils.h b/src/bin/multiuser/multiuser_utils.h new file mode 100644 index 0000000000..43db7d4f73 --- /dev/null +++ b/src/bin/multiuser/multiuser_utils.h @@ -0,0 +1,26 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef MULTIUSER_UTILS_H +#define MULTIUSER_UTILS_H + +#include +#include +#include + +#include + + +status_t read_password(const char* prompt, char* password, size_t bufferSize, + bool useStdio); + +bool verify_password(passwd* passwd, spwd* spwd, const char* plainPassword); + +status_t authenticate_user(const char* prompt, passwd* passwd, spwd* spwd, + int maxTries, bool useStdio); +status_t authenticate_user(const char* prompt, const char* user, + passwd** _passwd, spwd** _spwd, int maxTries, bool useStdio); + + +#endif // MULTIUSER_UTILS_H diff --git a/src/bin/multiuser/passwd.cpp b/src/bin/multiuser/passwd.cpp new file mode 100644 index 0000000000..ab08db6cfd --- /dev/null +++ b/src/bin/multiuser/passwd.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include "multiuser_utils.h" + + +extern const char *__progname; + + +static const char* kUsage = +"Usage: %s [ ]\n" +; + +static void +print_usage_and_exit(bool error) +{ + fprintf(error ? stderr : stdout, kUsage, __progname); + exit(error ? 1 : 0); +} + + +int +main(int argc, const char* const* argv) +{ + if (argc > 2) + print_usage_and_exit(true); + + const char* user = NULL; + if (argc == 2) + user = argv[1]; + + if (geteuid() != 0) { + fprintf(stderr, "Error: You need to be root.\n"); + exit(1); + } + + // this is a set-uid tool -- get the real UID + uid_t uid = getuid(); + + // get the passwd entry + struct passwd* passwd; + if (user != NULL) { + passwd = getpwnam(user); + if (passwd == NULL) { + fprintf(stderr, "Error: No user with name \"%s\".\n", user); + exit(1); + } + + if (uid != 0 && passwd->pw_uid != uid) { + fprintf(stderr, "Error: Only root can change the passwd for other " + "users.\n"); + exit(1); + } + } else { + passwd = getpwuid(uid); + if (passwd == NULL) { + fprintf(stderr, "Error: Ugh! Couldn't get passwd entry for uid " + "%d.\n", uid); + exit(1); + } + + user = passwd->pw_name; + } + + // if not root, the user needs to authenticate + if (uid != 0) { + if (authenticate_user("old password: ", passwd, getspnam(user), 1, + false) != B_OK) { + exit(1); + } + } + + // read new password + char password[LINE_MAX]; + if (read_password("new password: ", password, sizeof(password), false) + != B_OK) { + exit(1); + } + + if (strlen(password) >= MAX_SHADOW_PWD_PASSWORD_LEN) { + fprintf(stderr, "Error: The password is too long.\n"); + exit(1); + } + + // read password again + char repeatedPassword[LINE_MAX]; + if (read_password("repeat new password: ", repeatedPassword, + sizeof(repeatedPassword), false) != B_OK) { + exit(1); + } + + // passwords need to match + if (strcmp(password, repeatedPassword) != 0) { + fprintf(stderr, "Error: passwords don't match\n"); + exit(1); + } + + memset(repeatedPassword, 0, sizeof(repeatedPassword)); + + // crypt it + char* encryptedPassword; + if (strlen(password) > 0) { + encryptedPassword = crypt(password, user); + memset(password, 0, sizeof(password)); + } else + encryptedPassword = password; + + // prepare request for the registrar + KMessage message(BPrivate::B_REG_UPDATE_USER); + if (message.AddInt32("uid", passwd->pw_uid) != B_OK + || message.AddString("password", "x") != B_OK + || message.AddString("shadow password", encryptedPassword) != B_OK) { + fprintf(stderr, "Error: Out of memory!\n"); + exit(1); + } + + // send the request + KMessage reply; + status_t error = send_authentication_request_to_registrar(message, reply); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to create user: %s\n", strerror(error)); + exit(1); + } + + return 0; +} diff --git a/src/bin/multiuser/useradd.cpp b/src/bin/multiuser/useradd.cpp new file mode 100644 index 0000000000..aa1e0ac353 --- /dev/null +++ b/src/bin/multiuser/useradd.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "multiuser_utils.h" + + +extern const char *__progname; + + +static const char* kUsage = +"Usage: %s [ -d ] [ -e ] [ -f ] [ -g ]\n" +" [ -s ] [ -n ]\n" +; + +static void +print_usage_and_exit(bool error) +{ + fprintf(error ? stderr : stdout, kUsage, __progname); + exit(error ? 1 : 0); +} + + +int +main(int argc, const char* const* argv) +{ + const char* home = "/boot/home"; + int expiration = 99999; + int inactive = -1; + gid_t gid = 100; + const char* shell = "/bin/sh"; + const char* realName = ""; + + int min = -1; + int max = -1; + int warn = 7; + + while (true) { + static struct option sLongOptions[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "d:e:f:g:hn:s:", sLongOptions, + NULL); + if (c == -1) + break; + + + switch (c) { + case 'd': + home = optarg; + break; + + case 'e': + expiration = parsedate(optarg, time(NULL)) / (3600 * 24); + break; + + case 'f': + inactive = atoi(optarg); + break; + + case 'g': + gid = atoi(optarg); + break; + + case 'h': + print_usage_and_exit(false); + break; + + case 'n': + realName = optarg; + break; + + case 's': + shell = optarg; + break; + + default: + print_usage_and_exit(true); + break; + } + } + + if (optind != argc - 1) + print_usage_and_exit(true); + + const char* user = argv[optind]; + + if (geteuid() != 0) { + fprintf(stderr, "Error: You need to be root.\n"); + exit(1); + } + + // check, if user already exists + if (getpwnam(user) != NULL) { + fprintf(stderr, "Error: User \"%s\" already exists.\n", user); + exit(1); + } + + // read password + char password[LINE_MAX]; + if (read_password("password for user: ", password, sizeof(password), + false) != B_OK) { + exit(1); + } + + if (strlen(password) >= MAX_SHADOW_PWD_PASSWORD_LEN) { + fprintf(stderr, "Error: The password is too long.\n"); + exit(1); + } + + // read password again + char repeatedPassword[LINE_MAX]; + if (read_password("repeat password: ", repeatedPassword, + sizeof(repeatedPassword), false) != B_OK) { + exit(1); + } + + // passwords need to match + if (strcmp(password, repeatedPassword) != 0) { + fprintf(stderr, "Error: passwords don't match\n"); + exit(1); + } + + memset(repeatedPassword, 0, sizeof(repeatedPassword)); + + // crypt it + char* encryptedPassword; + if (strlen(password) > 0) { + encryptedPassword = crypt(password, user); + memset(password, 0, sizeof(password)); + } else + encryptedPassword = password; + + // find an unused UID + uid_t uid = 1000; + while (getpwuid(uid) != NULL) + uid++; + + // prepare request for the registrar + KMessage message(BPrivate::B_REG_UPDATE_USER); + if (message.AddInt32("uid", uid) != B_OK + || message.AddInt32("gid", gid) != B_OK + || message.AddString("name", user) != B_OK + || message.AddString("password", "x") != B_OK + || message.AddString("home", home) != B_OK + || message.AddString("shell", shell) != B_OK + || message.AddString("real name", realName) != B_OK + || message.AddString("shadow password", encryptedPassword) != B_OK + || message.AddInt32("min", min) != B_OK + || message.AddInt32("max", max) != B_OK + || message.AddInt32("warn", warn) != B_OK + || message.AddInt32("inactive", inactive) != B_OK + || message.AddInt32("expiration", expiration) != B_OK + || message.AddInt32("flags", 0) != B_OK + || message.AddBool("add user", true) != B_OK) { + fprintf(stderr, "Error: Out of memory!\n"); + exit(1); + } + + // send the request + KMessage reply; + status_t error = send_authentication_request_to_registrar(message, reply); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to create user: %s\n", strerror(error)); + exit(1); + } + + return 0; +} diff --git a/src/bin/network/Jamfile b/src/bin/network/Jamfile index dc5f51027c..a831062c66 100644 --- a/src/bin/network/Jamfile +++ b/src/bin/network/Jamfile @@ -5,7 +5,6 @@ SubInclude HAIKU_TOP src bin network atftpd ; SubInclude HAIKU_TOP src bin network ftp ; SubInclude HAIKU_TOP src bin network ftpd ; SubInclude HAIKU_TOP src bin network ifconfig ; -SubInclude HAIKU_TOP src bin network login ; SubInclude HAIKU_TOP src bin network mount_nfs ; SubInclude HAIKU_TOP src bin network nc ; SubInclude HAIKU_TOP src bin network netstat ; diff --git a/src/bin/network/login/Jamfile b/src/bin/network/login/Jamfile deleted file mode 100644 index f5792e585f..0000000000 --- a/src/bin/network/login/Jamfile +++ /dev/null @@ -1,5 +0,0 @@ -SubDir HAIKU_TOP src bin network login ; - -BinCommand login : - login.cpp -;