root/src/servers/registrar/AuthenticationManager.cpp
/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 */


#include "AuthenticationManager.h"

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/param.h>

#include <map>
#include <new>
#include <set>
#include <string>

#include <DataIO.h>
#include <StringList.h>

#include <AutoDeleter.h>
#include <AutoDeleterPosix.h>
#include <LaunchRoster.h>
#include <RegistrarDefs.h>

#include <libroot_private.h>
#include <user_group.h>
#include <util/KMessage.h>


using std::map;
using std::string;

using namespace BPrivate;


typedef std::set<std::string> StringSet;


class AuthenticationManager::FlatStore {
public:
        FlatStore()
                : fSize(0)
        {
                fBuffer.SetBlockSize(1024);
        }

        void WriteData(size_t offset, const void* data, size_t length)
        {
                ssize_t result = fBuffer.WriteAt(offset, data, length);
                if (result < 0)
                        throw status_t(result);
        }

        template<typename Type>
        void WriteData(size_t offset, const Type& data)
        {
                WriteData(&data, sizeof(Type));
        }

        size_t ReserveSpace(size_t length, bool align)
        {
                if (align)
                        fSize = _ALIGN(fSize);

                size_t pos = fSize;
                fSize += length;

                return pos;
        }

        void* AppendData(const void* data, size_t length, bool align)
        {
                size_t pos = ReserveSpace(length, align);
                WriteData(pos, data, length);
                return (void*)(addr_t)pos;
        }

        template<typename Type>
        Type* AppendData(const Type& data)
        {
                return (Type*)AppendData(&data, sizeof(Type), true);
        }

        char* AppendString(const char* string)
        {
                return (char*)AppendData(string, strlen(string) + 1, false);
        }

        char* AppendString(const string& str)
        {
                return (char*)AppendData(str.c_str(), str.length() + 1, false);
        }

        const void* Buffer() const
        {
                return fBuffer.Buffer();
        }

        size_t BufferLength() const
        {
                return fSize;
        }

private:
        BMallocIO       fBuffer;
        size_t          fSize;
};


class AuthenticationManager::User {
public:
        User()
                :
                fUID(0),
                fGID(0),
                fLastChanged(0),
                fMin(-1),
                fMax(-1),
                fWarn(-1),
                fInactive(-1),
                fExpiration(-1),
                fFlags(0)
        {
        }

        User(const char* name, const char* password, uid_t uid, gid_t gid,
                const char* home, const char* shell, const char* realName)
                :
                fUID(uid),
                fGID(gid),
                fName(name),
                fPassword(password),
                fHome(home),
                fShell(shell),
                fRealName(realName),
                fLastChanged(0),
                fMin(-1),
                fMax(-1),
                fWarn(-1),
                fInactive(-1),
                fExpiration(-1),
                fFlags(0)
        {
        }

        User(const User& other)
                :
                fUID(other.fUID),
                fGID(other.fGID),
                fName(other.fName),
                fPassword(other.fPassword),
                fHome(other.fHome),
                fShell(other.fShell),
                fRealName(other.fRealName),
                fShadowPassword(other.fShadowPassword),
                fLastChanged(other.fLastChanged),
                fMin(other.fMin),
                fMax(other.fMax),
                fWarn(other.fWarn),
                fInactive(other.fInactive),
                fExpiration(other.fExpiration),
                fFlags(other.fFlags)
        {
        }

        const string& Name() const      { return fName; }
        const uid_t UID() const         { return fUID; }

        void SetShadowInfo(const char* password, int lastChanged, int min, int max,
                int warn, int inactive, int expiration, int flags)
        {
                fShadowPassword = password;
                fLastChanged = lastChanged;
                fMin = min;
                fMax = max;
                fWarn = warn;
                fInactive = inactive;
                fExpiration = expiration;
                fFlags = flags;
        }

        void UpdateFromMessage(const KMessage& message)
        {
                int32 intValue;
                const char* stringValue;

                if (message.FindInt32("uid", &intValue) == B_OK)
                        fUID = intValue;

                if (message.FindInt32("gid", &intValue) == B_OK)
                        fGID = intValue;

                if (message.FindString("name", &stringValue) == B_OK)
                        fName = stringValue;

                if (message.FindString("password", &stringValue) == B_OK)
                        fPassword = stringValue;

                if (message.FindString("home", &stringValue) == B_OK)
                        fHome = stringValue;

                if (message.FindString("shell", &stringValue) == B_OK)
                        fShell = stringValue;

                if (message.FindString("real name", &stringValue) == B_OK)
                        fRealName = stringValue;

                if (message.FindString("shadow password", &stringValue) == B_OK) {
                        fShadowPassword = stringValue;
                        // TODO:
                        // fLastChanged = now;
                }

                if (message.FindInt32("last changed", &intValue) == B_OK)
                        fLastChanged = intValue;

                if (message.FindInt32("min", &intValue) == B_OK)
                        fMin = intValue;

                if (message.FindInt32("max", &intValue) == B_OK)
                        fMax = intValue;

                if (message.FindInt32("warn", &intValue) == B_OK)
                        fWarn = intValue;

                if (message.FindInt32("inactive", &intValue) == B_OK)
                        fInactive = intValue;

                if (message.FindInt32("expiration", &intValue) == B_OK)
                        fExpiration = intValue;

                if (message.FindInt32("flags", &intValue) == B_OK)
                        fFlags = intValue;
        }

        passwd* WriteFlatPasswd(FlatStore& store) const
        {
                struct passwd passwd;

                passwd.pw_uid = fUID;
                passwd.pw_gid = fGID;
                passwd.pw_name = store.AppendString(fName);
                passwd.pw_passwd = store.AppendString(fPassword);
                passwd.pw_dir = store.AppendString(fHome);
                passwd.pw_shell = store.AppendString(fShell);
                passwd.pw_gecos = store.AppendString(fRealName);

                return store.AppendData(passwd);
        }

        spwd* WriteFlatShadowPwd(FlatStore& store) const
        {
                struct spwd spwd;

                spwd.sp_namp = store.AppendString(fName);
                spwd.sp_pwdp = store.AppendString(fShadowPassword);
                spwd.sp_lstchg = fLastChanged;
                spwd.sp_min = fMin;
                spwd.sp_max = fMax;
                spwd.sp_warn = fWarn;
                spwd.sp_inact = fInactive;
                spwd.sp_expire = fExpiration;
                spwd.sp_flag = fFlags;

                return store.AppendData(spwd);
        }

        status_t WriteToMessage(KMessage& message, bool addShadowPwd)
        {
                status_t error;
                if ((error = message.AddInt32("uid", fUID)) != B_OK
                        || (error = message.AddInt32("gid", fGID)) != B_OK
                        || (error = message.AddString("name", fName.c_str())) != B_OK
                        || (error = message.AddString("password", fPassword.c_str()))
                                        != B_OK
                        || (error = message.AddString("home", fHome.c_str())) != B_OK
                        || (error = message.AddString("shell", fShell.c_str())) != B_OK
                        || (error = message.AddString("real name", fRealName.c_str()))
                                        != B_OK) {
                        return error;
                }

                if (!addShadowPwd)
                        return B_OK;

                if ((error = message.AddString("shadow password",
                                        fShadowPassword.c_str())) != B_OK
                        || (error = message.AddInt32("last changed", fLastChanged)) != B_OK
                        || (error = message.AddInt32("min", fMin)) != B_OK
                        || (error = message.AddInt32("max", fMax)) != B_OK
                        || (error = message.AddInt32("warn", fWarn)) != B_OK
                        || (error = message.AddInt32("inactive", fInactive)) != B_OK
                        || (error = message.AddInt32("expiration", fExpiration)) != B_OK
                        || (error = message.AddInt32("flags", fFlags)) != B_OK) {
                        return error;
                }

                return B_OK;
        }

        void WritePasswdLine(FILE* file)
        {
                fprintf(file, "%s:%s:%d:%d:%s:%s:%s\n",
                        fName.c_str(), fPassword.c_str(), (int)fUID, (int)fGID,
                        fRealName.c_str(), fHome.c_str(), fShell.c_str());
        }

        void WriteShadowPwdLine(FILE* file)
        {
                fprintf(file, "%s:%s:%d:", fName.c_str(), fShadowPassword.c_str(),
                        fLastChanged);

                // The following values are supposed to be printed as empty strings,
                // if negative.
                int values[5] = { fMin, fMax, fWarn, fInactive, fExpiration };
                for (int i = 0; i < 5; i++) {
                        if (values[i] >= 0)
                                fprintf(file, "%d", values[i]);
                        fprintf(file, ":");
                }

                fprintf(file, "%d\n", fFlags);
        }

private:
        uid_t   fUID;
        gid_t   fGID;
        string  fName;
        string  fPassword;
        string  fHome;
        string  fShell;
        string  fRealName;
        string  fShadowPassword;
        int             fLastChanged;
        int             fMin;
        int             fMax;
        int             fWarn;
        int             fInactive;
        int             fExpiration;
        int             fFlags;
};


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()
        {
                for (int i = 0; i < memberCount; i++)
                        fMembers.insert(members[i]);
        }

        ~Group()
        {
        }

        const string& Name() const      { return fName; }
        const gid_t GID() const         { return fGID; }

        bool HasMember(const char* name)
        {
                try {
                        return fMembers.find(name) != fMembers.end();
                } catch (...) {
                        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
        {
                struct group group;

                char* members[MAX_GROUP_MEMBER_COUNT + 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*) * (count + 1), true);

                return store.AppendData(group);
        }

        status_t WriteToMessage(KMessage& message)
        {
                status_t error;
                if ((error = message.AddInt32("gid", fGID)) != B_OK
                        || (error = message.AddString("name", fName.c_str())) != B_OK
                        || (error = message.AddString("password", fPassword.c_str()))
                                        != B_OK) {
                        return error;
                }

                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;
        StringSet       fMembers;
};


class AuthenticationManager::UserDB {
public:
        status_t AddUser(User* user)
        {
                try {
                        fUsersByID[user->UID()] = user;
                } catch (...) {
                        return B_NO_MEMORY;
                }

                try {
                        fUsersByName[user->Name()] = user;
                } catch (...) {
                        fUsersByID.erase(fUsersByID.find(user->UID()));
                        return B_NO_MEMORY;
                }

                return B_OK;
        }

        void RemoveUser(User* user)
        {
                fUsersByID.erase(fUsersByID.find(user->UID()));
                fUsersByName.erase(fUsersByName.find(user->Name()));
        }

        User* UserByID(uid_t uid) const
        {
                map<uid_t, User*>::const_iterator it = fUsersByID.find(uid);
                return (it == fUsersByID.end() ? NULL : it->second);
        }

        User* UserByName(const char* name) const
        {
                map<string, User*>::const_iterator it = fUsersByName.find(name);
                return (it == fUsersByName.end() ? NULL : it->second);
        }

        int32 WriteFlatPasswdDB(FlatStore& store) const
        {
                int32 count = fUsersByID.size();

                size_t entriesSpace = sizeof(passwd*) * count;
                size_t offset = store.ReserveSpace(entriesSpace, true);
                passwd** entries = new passwd*[count];
                ArrayDeleter<passwd*> _(entries);

                int32 index = 0;
                for (map<uid_t, User*>::const_iterator it = fUsersByID.begin();
                         it != fUsersByID.end(); ++it) {
                        entries[index++] = it->second->WriteFlatPasswd(store);
                }

                store.WriteData(offset, entries, entriesSpace);

                return count;
        }

        int32 WriteFlatShadowDB(FlatStore& store) const
        {
                int32 count = fUsersByID.size();

                size_t entriesSpace = sizeof(spwd*) * count;
                size_t offset = store.ReserveSpace(entriesSpace, true);
                spwd** entries = new spwd*[count];
                ArrayDeleter<spwd*> _(entries);

                int32 index = 0;
                for (map<uid_t, User*>::const_iterator it = fUsersByID.begin();
                         it != fUsersByID.end(); ++it) {
                        entries[index++] = it->second->WriteFlatShadowPwd(store);
                }

                store.WriteData(offset, entries, entriesSpace);

                return count;
        }

        void WriteToDisk()
        {
                // rename the old files
                string passwdBackup(kPasswdFile);
                string shadowBackup(kShadowPwdFile);
                passwdBackup += ".old";
                shadowBackup += ".old";

                rename(kPasswdFile, passwdBackup.c_str());
                rename(kShadowPwdFile, shadowBackup.c_str());
                        // Don't check errors. We can't do anything anyway.

                // open files
                FileCloser passwdFile(fopen(kPasswdFile, "w"));
                if (!passwdFile.IsSet()) {
                        debug_printf("REG: Failed to open passwd file \"%s\" for "
                                "writing: %s\n", kPasswdFile, strerror(errno));
                }

                FileCloser shadowFile(fopen(kShadowPwdFile, "w"));
                if (!shadowFile.IsSet()) {
                        debug_printf("REG: Failed to open shadow passwd file \"%s\" for "
                                "writing: %s\n", kShadowPwdFile, strerror(errno));
                }

                // write users
                for (map<uid_t, User*>::const_iterator it = fUsersByID.begin();
                         it != fUsersByID.end(); ++it) {
                        User* user = it->second;
                        user->WritePasswdLine(passwdFile.Get());
                        user->WriteShadowPwdLine(shadowFile.Get());
                }
        }

private:
        map<uid_t, User*>       fUsersByID;
        map<string, User*>      fUsersByName;
};


class AuthenticationManager::GroupDB {
public:
        status_t AddGroup(Group* group)
        {
                try {
                        fGroupsByID[group->GID()] = group;
                } catch (...) {
                        return B_NO_MEMORY;
                }

                try {
                        fGroupsByName[group->Name()] = group;
                } catch (...) {
                        fGroupsByID.erase(fGroupsByID.find(group->GID()));
                        return B_NO_MEMORY;
                }

                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<gid_t, Group*>::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<gid_t, Group*>::const_iterator it = fGroupsByID.find(gid);
                return (it == fGroupsByID.end() ? NULL : it->second);
        }

        Group* GroupByName(const char* name) const
        {
                map<string, Group*>::const_iterator it = fGroupsByName.find(name);
                return (it == fGroupsByName.end() ? NULL : it->second);
        }

        int32 GetUserGroups(const char* name, gid_t* groups, int maxCount)
        {
                int count = 0;

                for (map<gid_t, Group*>::const_iterator it = fGroupsByID.begin();
                         it != fGroupsByID.end(); ++it) {
                        Group* group = it->second;
                        if (group->HasMember(name)) {
                                if (count < maxCount)
                                        groups[count] = group->GID();
                                count++;
                        }
                }

                return count;
        }


        int32 WriteFlatGroupDB(FlatStore& store) const
        {
                int32 count = fGroupsByID.size();

                size_t entriesSpace = sizeof(group*) * count;
                size_t offset = store.ReserveSpace(entriesSpace, true);
                group** entries = new group*[count];
                ArrayDeleter<group*> _(entries);

                int32 index = 0;
                for (map<gid_t, Group*>::const_iterator it = fGroupsByID.begin();
                         it != fGroupsByID.end(); ++it) {
                        entries[index++] = it->second->WriteFlatGroup(store);
                }

                store.WriteData(offset, entries, entriesSpace);

                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
                FileCloser groupFile(fopen(kGroupFile, "w"));
                if (!groupFile.IsSet()) {
                        debug_printf("REG: Failed to open group file \"%s\" for "
                                "writing: %s\n", kGroupFile, strerror(errno));
                }

                // write groups
                for (map<gid_t, Group*>::const_iterator it = fGroupsByID.begin();
                        it != fGroupsByID.end(); ++it) {
                        Group* group = it->second;
                        group->WriteGroupLine(groupFile.Get());
                }
        }

private:
        map<uid_t, Group*>      fGroupsByID;
        map<string, Group*>     fGroupsByName;
};


AuthenticationManager::AuthenticationManager()
        :
        fRequestPort(-1),
        fRequestThread(-1),
        fUserDB(NULL),
        fGroupDB(NULL),
        fPasswdDBReply(NULL),
        fGroupDBReply(NULL),
        fShadowPwdDBReply(NULL)
{
}


AuthenticationManager::~AuthenticationManager()
{
        // Quit the request thread and wait for it to finish
        write_port(fRequestPort, 'quit', NULL, 0);
        wait_for_thread(fRequestThread, NULL);

        delete fUserDB;
        delete fGroupDB;
        delete fPasswdDBReply;
        delete fGroupDBReply;
        delete fShadowPwdDBReply;
}


status_t
AuthenticationManager::Init()
{
        fUserDB = new(std::nothrow) UserDB;
        fGroupDB = new(std::nothrow) GroupDB;
        fPasswdDBReply = new(std::nothrow) KMessage(1);
        fGroupDBReply = new(std::nothrow) KMessage(1);
        fShadowPwdDBReply = new(std::nothrow) KMessage(1);

        if (fUserDB == NULL || fGroupDB == NULL || fPasswdDBReply == NULL
                        || fGroupDBReply == NULL || fShadowPwdDBReply == NULL) {
                return B_NO_MEMORY;
        }

        fRequestPort = BLaunchRoster().GetPort(
                B_REGISTRAR_AUTHENTICATION_PORT_NAME);
        if (fRequestPort < 0)
                return fRequestPort;

        fRequestThread = spawn_thread(&_RequestThreadEntry,
                "authentication manager", B_NORMAL_PRIORITY + 1, this);
        if (fRequestThread < 0)
                return fRequestThread;

        resume_thread(fRequestThread);

        return B_OK;
}


status_t
AuthenticationManager::_RequestThreadEntry(void* data)
{
        return ((AuthenticationManager*)data)->_RequestThread();
}


status_t
AuthenticationManager::_RequestThread()
{
        // read the DB files
        _InitPasswdDB();
        _InitGroupDB();
        _InitShadowPwdDB();

    // get our team ID
        team_id registrarTeam = -1;
        {
                thread_info info;
                if (get_thread_info(find_thread(NULL), &info) == B_OK)
                        registrarTeam = info.team;
        }

        // request loop
        while (true) {
                KMessage message;
                port_message_info messageInfo;
                status_t error = message.ReceiveFrom(fRequestPort, -1, &messageInfo);
                if (error != B_OK)
                        return B_OK;

                bool isRoot = (messageInfo.sender == 0);

                switch (message.What()) {
                        case B_REG_GET_PASSWD_DB:
                        {
                                // lazily build the reply
                                try {
                                        if (fPasswdDBReply->What() == 1) {
                                                FlatStore store;
                                                int32 count = fUserDB->WriteFlatPasswdDB(store);
                                                if (fPasswdDBReply->AddInt32("count", count) != B_OK
                                                        || fPasswdDBReply->AddData("entries", B_RAW_TYPE,
                                                                        store.Buffer(), store.BufferLength(),
                                                                        false) != B_OK) {
                                                        error = B_NO_MEMORY;
                                                }

                                                fPasswdDBReply->SetWhat(0);
                                        }
                                } catch (...) {
                                        error = B_NO_MEMORY;
                                }

                                if (error == B_OK) {
                                        message.SendReply(fPasswdDBReply, -1, -1, 0, registrarTeam);
                                } else {
                                        _InvalidatePasswdDBReply();
                                        KMessage reply(error);
                                        message.SendReply(&reply, -1, -1, 0, registrarTeam);
                                }

                                break;
                        }

                        case B_REG_GET_GROUP_DB:
                        {
                                // lazily build the reply
                                try {
                                        if (fGroupDBReply->What() == 1) {
                                                FlatStore store;
                                                int32 count = fGroupDB->WriteFlatGroupDB(store);
                                                if (fGroupDBReply->AddInt32("count", count) != B_OK
                                                        || fGroupDBReply->AddData("entries", B_RAW_TYPE,
                                                                        store.Buffer(), store.BufferLength(),
                                                                        false) != B_OK) {
                                                        error = B_NO_MEMORY;
                                                }

                                                fGroupDBReply->SetWhat(0);
                                        }
                                } catch (...) {
                                        error = B_NO_MEMORY;
                                }

                                if (error == B_OK) {
                                        message.SendReply(fGroupDBReply, -1, -1, 0, registrarTeam);
                                } else {
                                        _InvalidateGroupDBReply();
                                        KMessage reply(error);
                                        message.SendReply(&reply, -1, -1, 0, registrarTeam);
                                }

                                break;
                        }


                        case B_REG_GET_SHADOW_PASSWD_DB:
                        {
                                // only root may see the shadow passwd
                                if (!isRoot)
                                        error = EPERM;

                                // lazily build the reply
                                try {
                                        if (error == B_OK && fShadowPwdDBReply->What() == 1) {
                                                FlatStore store;
                                                int32 count = fUserDB->WriteFlatShadowDB(store);
                                                if (fShadowPwdDBReply->AddInt32("count", count) != B_OK
                                                        || fShadowPwdDBReply->AddData("entries", B_RAW_TYPE,
                                                                        store.Buffer(), store.BufferLength(),
                                                                        false) != B_OK) {
                                                        error = B_NO_MEMORY;
                                                }

                                                fShadowPwdDBReply->SetWhat(0);
                                        }
                                } catch (...) {
                                        error = B_NO_MEMORY;
                                }

                                if (error == B_OK) {
                                        message.SendReply(fShadowPwdDBReply, -1, -1, 0,
                                                registrarTeam);
                                } else {
                                        _InvalidateShadowPwdDBReply();
                                        KMessage reply(error);
                                        message.SendReply(&reply, -1, -1, 0, registrarTeam);
                                }

                                break;
                        }

                        case B_REG_GET_USER:
                        {
                                User* user = NULL;
                                int32 uid;
                                const char* name;

                                // find user
                                if (message.FindInt32("uid", &uid) == B_OK) {
                                        user = fUserDB->UserByID(uid);
                                } else if (message.FindString("name", &name) == B_OK) {
                                        user = fUserDB->UserByName(name);
                                } else {
                                        error = B_BAD_VALUE;
                                }

                                if (error == B_OK && user == NULL)
                                        error = ENOENT;

                                bool getShadowPwd = message.GetBool("shadow", false);

                                // only root may see the shadow passwd
                                if (error == B_OK && getShadowPwd && !isRoot)
                                        error = EPERM;

                                // add user to message
                                KMessage reply;
                                if (error == B_OK)
                                        error = user->WriteToMessage(reply, getShadowPwd);

                                // send reply
                                reply.SetWhat(error);
                                message.SendReply(&reply, -1, -1, 0, registrarTeam);

                                break;
                        }

                        case B_REG_GET_GROUP:
                        {
                                Group* group = NULL;
                                int32 gid;
                                const char* name;

                                // find group
                                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;

                                // add group to message
                                KMessage reply;
                                if (error == B_OK)
                                        error = group->WriteToMessage(reply);

                                // send reply
                                reply.SetWhat(error);
                                message.SendReply(&reply, -1, -1, 0, registrarTeam);

                                break;
                        }

                        case B_REG_GET_USER_GROUPS:
                        {
                                // get user name
                                const char* name;
                                int32 maxCount;
                                if (message.FindString("name", &name) != B_OK
                                        || message.FindInt32("max count", &maxCount) != B_OK
                                        || maxCount <= 0) {
                                        error = B_BAD_VALUE;
                                }

                                // get groups
                                gid_t groups[NGROUPS_MAX + 1];
                                int32 count = 0;
                                if (error == B_OK) {
                                        maxCount = min_c(maxCount, NGROUPS_MAX + 1);
                                        count = fGroupDB->GetUserGroups(name, groups, maxCount);
                                }

                                // add groups to message
                                KMessage reply;
                                if (error == B_OK) {
                                        if (reply.AddInt32("count", count) != B_OK
                                                || reply.AddData("groups", B_INT32_TYPE,
                                                                groups, min_c(maxCount, count) * sizeof(gid_t),
                                                                false) != B_OK) {
                                                error = B_NO_MEMORY;
                                        }
                                }

                                // send reply
                                reply.SetWhat(error);
                                message.SendReply(&reply, -1, -1, 0, registrarTeam);

                                break;
                        }

                        case B_REG_UPDATE_USER:
                        {
                                // find user
                                User* user = NULL;
                                int32 uid;
                                const char* name;

                                if (message.FindInt32("uid", &uid) == B_OK) {
                                        user = fUserDB->UserByID(uid);
                                } else if (message.FindString("name", &name) == B_OK) {
                                        user = fUserDB->UserByName(name);
                                } else {
                                        error = B_BAD_VALUE;
                                }

                                // only root can change anything
                                if (error == B_OK && !isRoot)
                                        error = EPERM;

                                // check addUser vs. existing user
                                bool addUser = message.GetBool("add user", false);
                                if (error == B_OK) {
                                        if (addUser) {
                                                if (user != NULL)
                                                        error = EEXIST;
                                        } else if (user == NULL)
                                                error = ENOENT;
                                }

                                // apply all changes
                                if (error == B_OK) {
                                        // clone the user object and update it from the message
                                        User* oldUser = user;
                                        user = NULL;
                                        try {
                                                user = (oldUser != NULL ? new User(*oldUser)
                                                        : new User);
                                                user->UpdateFromMessage(message);

                                                // uid and name should remain the same
                                                if (oldUser != NULL) {
                                                        if (oldUser->UID() != user->UID()
                                                                || oldUser->Name() != user->Name()) {
                                                                error = B_BAD_VALUE;
                                                        }
                                                }

                                                // replace the old user and write DBs to disk
                                                if (error == B_OK) {
                                                        fUserDB->AddUser(user);
                                                        fUserDB->WriteToDisk();
                                                        _InvalidatePasswdDBReply();
                                                        _InvalidateShadowPwdDBReply();
                                                }
                                        } catch (...) {
                                                error = B_NO_MEMORY;
                                        }

                                        if (error == B_OK)
                                                delete oldUser;
                                        else
                                                delete user;
                                }

                                // send reply
                                KMessage reply;
                                reply.SetWhat(error);
                                message.SendReply(&reply, -1, -1, 0, registrarTeam);

                                break;
                        }

                        case B_REG_DELETE_USER:
                        {
                                // find user
                                User* user = NULL;
                                int32 uid;
                                const char* name;

                                if (message.FindInt32("uid", &uid) == B_OK) {
                                        user = fUserDB->UserByID(uid);
                                } else if (message.FindString("name", &name) == B_OK) {
                                        user = fUserDB->UserByName(name);
                                } else {
                                        error = B_BAD_VALUE;
                                }

                                if (error == B_OK && user == NULL)
                                        error = ENOENT;

                                // only root can change anything
                                if (error == B_OK && !isRoot)
                                        error = EPERM;

                                // 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
                                KMessage reply;
                                reply.SetWhat(error);
                                message.SendReply(&reply, -1, -1, 0, registrarTeam);

                                break;
                        }

                        case B_REG_UPDATE_GROUP:
                        {
                                // 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:
                        {
                                // 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;
                        }

                        default:
                                debug_printf("REG: invalid message: %" B_PRIu32 "\n",
                                        message.What());
                }
        }
}


status_t
AuthenticationManager::_InitPasswdDB()
{
        FileCloser file(fopen(kPasswdFile, "r"));
        if (!file.IsSet()) {
                debug_printf("REG: Failed to open passwd DB file \"%s\": %s\n",
                        kPasswdFile, strerror(errno));
                return errno;
        }

        char lineBuffer[LINE_MAX];
        while (char* line = fgets(lineBuffer, sizeof(lineBuffer), file.Get())) {
                if (strlen(line) == 0)
                        continue;

                char* name;
                char* password;
                uid_t uid;
                gid_t gid;
                char* home;
                char* shell;
                char* realName;

                status_t error = parse_passwd_line(line, name, password, uid, gid,
                        home, shell, realName);
                if (error != B_OK) {
                        debug_printf("REG: Unparsable line in passwd DB file: \"%s\"\n",
                                strerror(errno));
                        continue;
                }

                User* user = NULL;
                try {
                        user = new User(name, password, uid, gid, home, shell, realName);
                } catch (...) {
                }

                if (user == NULL || fUserDB->AddUser(user) != B_OK) {
                        delete user;
                        debug_printf("REG: Out of memory\n");
                        return B_NO_MEMORY;
                }
        }

        return B_OK;
}


status_t
AuthenticationManager::_InitGroupDB()
{
        FileCloser file(fopen(kGroupFile, "r"));
        if (!file.IsSet()) {
                debug_printf("REG: Failed to open group DB file \"%s\": %s\n",
                        kGroupFile, strerror(errno));
                return errno;
        }

        char lineBuffer[LINE_MAX];
        while (char* line = fgets(lineBuffer, sizeof(lineBuffer), file.Get())) {
                if (strlen(line) == 0)
                        continue;

                char* name;
                char* password;
                gid_t gid;
                char* members[MAX_GROUP_MEMBER_COUNT];
                int memberCount;


                status_t error = parse_group_line(line, name, password, gid, members,
                        memberCount);
                if (error != B_OK) {
                        debug_printf("REG: Unparsable line in group DB file: \"%s\"\n",
                                strerror(errno));
                        continue;
                }

                Group* group = NULL;
                try {
                        group = new Group(name, password, gid, members, memberCount);
                } catch (...) {
                }

                if (group == NULL || fGroupDB->AddGroup(group) != B_OK) {
                        delete group;
                        debug_printf("REG: Out of memory\n");
                        return B_NO_MEMORY;
                }
        }

        return B_OK;
}


status_t
AuthenticationManager::_InitShadowPwdDB()
{
        FileCloser file(fopen(kShadowPwdFile, "r"));
        if (!file.IsSet()) {
                debug_printf("REG: Failed to open shadow passwd DB file \"%s\": %s\n",
                        kShadowPwdFile, strerror(errno));
                return errno;
        }

        char lineBuffer[LINE_MAX];
        while (char* line = fgets(lineBuffer, sizeof(lineBuffer), file.Get())) {
                if (strlen(line) == 0)
                        continue;

                char* name;
                char* password;
                int lastChanged;
                int min;
                int max;
                int warn;
                int inactive;
                int expiration;
                int flags;

                status_t error = parse_shadow_pwd_line(line, name, password,
                        lastChanged, min, max, warn, inactive, expiration, flags);
                if (error != B_OK) {
                        debug_printf("REG: Unparsable line in shadow passwd DB file: "
                                "\"%s\"\n", strerror(errno));
                        continue;
                }

                User* user = fUserDB->UserByName(name);
                if (user == NULL) {
                        debug_printf("REG: shadow pwd entry for unknown user \"%s\"\n",
                                name);
                        continue;
                }

                try {
                        user->SetShadowInfo(password, lastChanged, min, max, warn, inactive,
                                expiration, flags);
                } catch (...) {
                        debug_printf("REG: Out of memory\n");
                        return B_NO_MEMORY;
                }
        }

        return B_OK;
}


void
AuthenticationManager::_InvalidatePasswdDBReply()
{
        fPasswdDBReply->SetTo(1);
}


void
AuthenticationManager::_InvalidateGroupDBReply()
{
        fGroupDBReply->SetTo(1);
}


void
AuthenticationManager::_InvalidateShadowPwdDBReply()
{
        fShadowPwdDBReply->SetTo(1);
}