From 519bb60aef35f6e7371dc0ccf0a9c989036ae9b4 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Wed, 18 Sep 2013 16:28:58 +0200 Subject: [PATCH] Add group{add,del,mod} --- build/jam/images/HaikuImage | 2 +- build/jam/images/HaikuImageBootstrap | 2 +- src/bin/multiuser/Jamfile | 6 + src/bin/multiuser/groupadd.cpp | 109 ++++++++ src/bin/multiuser/groupdel.cpp | 98 +++++++ src/bin/multiuser/groupmod.cpp | 159 +++++++++++ .../registrar/AuthenticationManager.cpp | 249 ++++++++++++++++-- 7 files changed, 599 insertions(+), 26 deletions(-) create mode 100644 src/bin/multiuser/groupadd.cpp create mode 100644 src/bin/multiuser/groupdel.cpp create mode 100644 src/bin/multiuser/groupmod.cpp diff --git a/build/jam/images/HaikuImage b/build/jam/images/HaikuImage index a58a7f8222..6ea1d85356 100644 --- a/build/jam/images/HaikuImage +++ b/build/jam/images/HaikuImage @@ -14,7 +14,7 @@ SYSTEM_BIN = [ FFilterByBuildFeatures echo eject env error expand expr factor false fdinfo ffm filepanel find finddir FirstBootPrompt fmt fold fortune frcode ftp ftpd funzip fwcontrol - gawk gdb@x86 getlimits groups gzip gzexe + gawk gdb@x86 getlimits groupadd groupdel groupmod groups gzip gzexe hd head hey hostname id ident ifconfig install installsound iroster isvolume ideinfo@ide idestatus@ide diff --git a/build/jam/images/HaikuImageBootstrap b/build/jam/images/HaikuImageBootstrap index 71e5cca30d..ebf7d1496a 100644 --- a/build/jam/images/HaikuImageBootstrap +++ b/build/jam/images/HaikuImageBootstrap @@ -14,7 +14,7 @@ SYSTEM_BIN = [ FFilterByBuildFeatures echo eject env error expand expr factor false fdinfo ffm filepanel find finddir fmt fold fortune frcode ftp ftpd funzip - gawk gdb@x86 getlimits groups gzip gzexe + gawk gdb@x86 getlimits groupadd groupdel groupmod groups gzip gzexe hd head hey hostname id ident ifconfig install isvolume ideinfo@ide idestatus@ide diff --git a/src/bin/multiuser/Jamfile b/src/bin/multiuser/Jamfile index f234bd747a..c79da87d3d 100644 --- a/src/bin/multiuser/Jamfile +++ b/src/bin/multiuser/Jamfile @@ -17,5 +17,11 @@ BinCommand useradd : useradd.cpp ; BinCommand userdel : userdel.cpp ; +BinCommand groupadd : groupadd.cpp ; + +BinCommand groupdel : groupdel.cpp ; + +BinCommand groupmod : groupmod.cpp : $(TARGET_LIBSTDC++) ; + # set set-uid bit on passwd MODE on passwd = 04755 ; diff --git a/src/bin/multiuser/groupadd.cpp b/src/bin/multiuser/groupadd.cpp new file mode 100644 index 0000000000..55e7beddfa --- /dev/null +++ b/src/bin/multiuser/groupadd.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#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" + "Creates a new group .\n" + "\n" + "Options:\n" + " -h, --help\n" + " Print usage info.\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) +{ + 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, "h", sLongOptions, NULL); + if (c == -1) + break; + + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + if (optind != argc - 1) + print_usage_and_exit(true); + + const char* group = argv[optind]; + + if (geteuid() != 0) { + fprintf(stderr, "Error: Only root may add groups.\n"); + exit(1); + } + + // check, if group already exists + if (getgrnam(group) != NULL) { + fprintf(stderr, "Error: Group \"%s\" already exists.\n", group); + exit(1); + } + + // find an unused GID + gid_t gid = 100; + while (getgrgid(gid) != NULL) + gid++; + + // prepare request for the registrar + KMessage message(BPrivate::B_REG_UPDATE_GROUP); + if (message.AddInt32("gid", gid) != B_OK + || message.AddString("name", group) != B_OK + || message.AddString("password", "x") != B_OK + || message.AddBool("add group", 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 group: %s\n", strerror(error)); + exit(1); + } + + return 0; +} diff --git a/src/bin/multiuser/groupdel.cpp b/src/bin/multiuser/groupdel.cpp new file mode 100644 index 0000000000..ab8d5ced3b --- /dev/null +++ b/src/bin/multiuser/groupdel.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "multiuser_utils.h" + + +extern const char *__progname; + + +static const char* kUsage = + "Usage: %s [ ] \n" + "Deletes the specified group.\n" + "\n" + "Options:\n" + " -h, --help\n" + " Print usage info.\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) +{ + 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, "h", sLongOptions, NULL); + if (c == -1) + break; + + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + if (optind != argc - 1) + print_usage_and_exit(true); + + const char* group = argv[optind]; + + if (geteuid() != 0) { + fprintf(stderr, "Error: Only root may delete groups.\n"); + exit(1); + } + + if (getgrnam(group) == NULL) { + fprintf(stderr, "Error: Group \"%s\" doesn't exists.\n", group); + exit(1); + } + + // prepare request for the registrar + KMessage message(BPrivate::B_REG_DELETE_GROUP); + if (message.AddString("name", group) != 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 delete group: %s\n", strerror(error)); + exit(1); + } + + return 0; +} diff --git a/src/bin/multiuser/groupmod.cpp b/src/bin/multiuser/groupmod.cpp new file mode 100644 index 0000000000..2fbb47d197 --- /dev/null +++ b/src/bin/multiuser/groupmod.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * 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" + "Creates a new group .\n" + "\n" + "Options:\n" + " -A, --add-user \n" + " Add the user to the group.\n" + " -h, --help\n" + " Print usage info.\n" + " -R, --remove-user \n" + " Remove the user from the group.\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) +{ + typedef std::set StringSet; + + StringSet usersToAdd; + StringSet usersToRemove; + + while (true) { + static struct option sLongOptions[] = { + { "add-user", required_argument, 0, 'A' }, + { "help", no_argument, 0, 'h' }, + { "remove-user", required_argument, 0, 'A' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "A:hR:", sLongOptions, NULL); + if (c == -1) + break; + + + switch (c) { + case 'A': + usersToAdd.insert(optarg); + break; + + case 'h': + print_usage_and_exit(false); + break; + + case 'R': + usersToRemove.insert(optarg); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + if (optind != argc - 1) + print_usage_and_exit(true); + + const char* group = argv[optind]; + + if (geteuid() != 0) { + fprintf(stderr, "Error: Only root may modify groups.\n"); + exit(1); + } + + // get the group + struct group* groupInfo = getgrnam(group); + if (groupInfo == NULL) { + fprintf(stderr, "Error: Group \"%s\" doesn't exist.\n", group); + exit(1); + } + + // check, if anything needs to be done + if (usersToAdd.empty() && usersToRemove.empty()) { + fprintf(stderr, "Error: No modification specified.\n"); + exit(1); + } + + // prepare request for the registrar + KMessage message(BPrivate::B_REG_UPDATE_GROUP); + if (message.AddInt32("gid", groupInfo->gr_gid) != B_OK + || message.AddString("name", group) != B_OK + || message.AddString("password", groupInfo->gr_passwd) != B_OK + || message.AddBool("add group", false) != B_OK) { + fprintf(stderr, "Error: Out of memory!\n"); + exit(1); + } + + for (int32 i = 0; const char* user = groupInfo->gr_mem[i]; i++) { + if (usersToRemove.erase(user) > 0) + continue; + + usersToAdd.insert(user); + } + + if (!usersToRemove.empty()) { + fprintf(stderr, "Error: \"%s\" is not a member of group \"%s\"\n", + usersToRemove.begin()->c_str(), group); + exit(1); + } + + // If the group doesn't have any more members, insert an empty string as an + // indicator for the registrar to remove all members. + if (usersToAdd.empty()) + usersToAdd.insert(""); + + for (StringSet::const_iterator it = usersToAdd.begin(); + it != usersToAdd.end(); ++it) { + if (message.AddString("members", it->c_str()) != 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 group: %s\n", strerror(error)); + exit(1); + } + + return 0; +} diff --git a/src/servers/registrar/AuthenticationManager.cpp b/src/servers/registrar/AuthenticationManager.cpp index a3fe86a973..57742db88c 100644 --- a/src/servers/registrar/AuthenticationManager.cpp +++ b/src/servers/registrar/AuthenticationManager.cpp @@ -12,9 +12,11 @@ #include #include +#include #include #include +#include #include #include @@ -30,6 +32,9 @@ using std::string; using namespace BPrivate; +typedef std::set StringSet; + + class AuthenticationManager::FlatStore { public: FlatStore() @@ -337,22 +342,29 @@ private: class AuthenticationManager::Group { public: + Group() + : + fGID(0), + fName(), + fPassword(), + fMembers() + { + } + Group(const char* name, const char* password, gid_t gid, const char* const* members, int memberCount) : fGID(gid), fName(name), fPassword(password), - fMembers(new string[memberCount]), - fMemberCount(memberCount) + fMembers() { for (int i = 0; i < memberCount; i++) - fMembers[i] = members[i]; + fMembers.insert(members[i]); } ~Group() { - delete[] fMembers; } const string& Name() const { return fName; } @@ -360,12 +372,40 @@ public: bool HasMember(const char* name) { - for (int i = 0; i < fMemberCount; i++) { - if (fMembers[i] == name) - return true; + try { + return fMembers.find(name) != fMembers.end(); + } catch (...) { + return false; } + } - return false; + bool MemberRemoved(const std::string& name) + { + return fMembers.erase(name) > 0; + } + + void UpdateFromMessage(const KMessage& message) + { + int32 intValue; + if (message.FindInt32("gid", &intValue) == B_OK) + fGID = intValue; + + const char* stringValue; + if (message.FindString("name", &stringValue) == B_OK) + fName = stringValue; + + if (message.FindString("password", &stringValue) == B_OK) + fPassword = stringValue; + + if (message.FindString("members", &stringValue) == B_OK) { + fMembers.clear(); + for (int32 i = 0; + (stringValue = message.GetString("members", i, NULL)) != NULL; + i++) { + if (stringValue != NULL && *stringValue != '\0') + fMembers.insert(stringValue); + } + } } group* WriteFlatGroup(FlatStore& store) const @@ -373,15 +413,18 @@ public: struct group group; char* members[MAX_GROUP_MEMBER_COUNT + 1]; - for (int i = 0; i < fMemberCount; i++) - members[i] = store.AppendString(fMembers[i].c_str()); - members[fMemberCount] = (char*)-1; + int32 count = 0; + for (StringSet::const_iterator it = fMembers.begin(); + it != fMembers.end(); ++it) { + members[count++] = store.AppendString(it->c_str()); + } + members[count] = (char*)-1; group.gr_gid = fGID; group.gr_name = store.AppendString(fName); group.gr_passwd = store.AppendString(fPassword); group.gr_mem = (char**)store.AppendData(members, - sizeof(char*) * (fMemberCount + 1), true); + sizeof(char*) * (count + 1), true); return store.AppendData(group); } @@ -396,22 +439,34 @@ public: return error; } - for (int i = 0; i < fMemberCount; i++) { - if ((error = message.AddString("members", fMembers[i].c_str())) - != B_OK) { + for (StringSet::const_iterator it = fMembers.begin(); + it != fMembers.end(); ++it) { + if ((error = message.AddString("members", it->c_str())) != B_OK) return error; - } } return B_OK; } + void WriteGroupLine(FILE* file) + { + fprintf(file, "%s:%s:%d:", + fName.c_str(), fPassword.c_str(), (int)fGID); + for (StringSet::const_iterator it = fMembers.begin(); + it != fMembers.end(); ++it) { + if (it == fMembers.begin()) + fprintf(file, "%s", it->c_str()); + else + fprintf(file, ",%s", it->c_str()); + } + fputs("\n", file); + } + private: - gid_t fGID; - string fName; - string fPassword; - string* fMembers; - int fMemberCount; + gid_t fGID; + string fName; + string fPassword; + StringSet fMembers; }; @@ -555,6 +610,23 @@ public: return B_OK; } + void RemoveGroup(Group* group) + { + fGroupsByID.erase(fGroupsByID.find(group->GID())); + fGroupsByName.erase(fGroupsByName.find(group->Name())); + } + + bool UserRemoved(const std::string& user) + { + bool changed = false; + for (map::const_iterator it = fGroupsByID.begin(); + it != fGroupsByID.end(); ++it) { + Group* group = it->second; + changed |= group->MemberRemoved(user); + } + return changed; + } + Group* GroupByID(gid_t gid) const { map::const_iterator it = fGroupsByID.find(gid); @@ -605,6 +677,31 @@ public: return count; } + void WriteToDisk() + { + // rename the old files + string groupBackup(kGroupFile); + groupBackup += ".old"; + + rename(kGroupFile, groupBackup.c_str()); + // Don't check errors. We can't do anything anyway. + + // open file + FILE* groupFile = fopen(kGroupFile, "w"); + if (groupFile == NULL) { + debug_printf("REG: Failed to open group file \"%s\" for " + "writing: %s\n", kGroupFile, strerror(errno)); + } + CObjectDeleter _1(groupFile, fclose); + + // write groups + for (map::const_iterator it = fGroupsByID.begin(); + it != fGroupsByID.end(); ++it) { + Group* group = it->second; + group->WriteGroupLine(groupFile); + } + } + private: map fGroupsByID; map fGroupsByName; @@ -1002,10 +1099,17 @@ AuthenticationManager::_RequestThread() // apply the change if (error == B_OK) { + std::string userName = user->Name(); + fUserDB->RemoveUser(user); fUserDB->WriteToDisk(); _InvalidatePasswdDBReply(); _InvalidateShadowPwdDBReply(); + + if (fGroupDB->UserRemoved(userName)) { + fGroupDB->WriteToDisk(); + _InvalidateGroupDBReply(); + } } // send reply @@ -1017,13 +1121,110 @@ AuthenticationManager::_RequestThread() } case B_REG_UPDATE_GROUP: - debug_printf("B_REG_UPDATE_GROUP done: currently unsupported!\n"); + { + // find group + Group* group = NULL; + int32 gid; + const char* name; + + if (message.FindInt32("gid", &gid) == B_OK) { + group = fGroupDB->GroupByID(gid); + } else if (message.FindString("name", &name) == B_OK) { + group = fGroupDB->GroupByName(name); + } else { + error = B_BAD_VALUE; + } + + // only root can change anything + if (error == B_OK && !isRoot) + error = EPERM; + + // check addGroup vs. existing group + bool addGroup = message.GetBool("add group", false); + if (error == B_OK) { + if (addGroup) { + if (group != NULL) + error = EEXIST; + } else if (group == NULL) + error = ENOENT; + } + + // apply all changes + if (error == B_OK) { + // clone the group object and update it from the message + Group* oldGroup = group; + group = NULL; + try { + group = (oldGroup != NULL ? new Group(*oldGroup) + : new Group); + group->UpdateFromMessage(message); + + // gid and name should remain the same + if (oldGroup != NULL) { + if (oldGroup->GID() != group->GID() + || oldGroup->Name() != group->Name()) { + error = B_BAD_VALUE; + } + } + + // replace the old group and write DBs to disk + if (error == B_OK) { + fGroupDB->AddGroup(group); + fGroupDB->WriteToDisk(); + _InvalidateGroupDBReply(); + } + } catch (...) { + error = B_NO_MEMORY; + } + + if (error == B_OK) + delete oldGroup; + else + delete group; + } + + // send reply + KMessage reply; + reply.SetWhat(error); + message.SendReply(&reply, -1, -1, 0, registrarTeam); + break; + } case B_REG_DELETE_GROUP: { - debug_printf( - "B_REG_DELETE_GROUP done: currently unsupported!\n"); + // find group + Group* group = NULL; + int32 gid; + const char* name; + + if (message.FindInt32("gid", &gid) == B_OK) { + group = fGroupDB->GroupByID(gid); + } else if (message.FindString("name", &name) == B_OK) { + group = fGroupDB->GroupByName(name); + } else { + error = B_BAD_VALUE; + } + + if (error == B_OK && group == NULL) + error = ENOENT; + + // only root can change anything + if (error == B_OK && !isRoot) + error = EPERM; + + // apply the change + if (error == B_OK) { + fGroupDB->RemoveGroup(group); + fGroupDB->WriteToDisk(); + _InvalidateGroupDBReply(); + } + + // send reply + KMessage reply; + reply.SetWhat(error); + message.SendReply(&reply, -1, -1, 0, registrarTeam); + break; }