root/src/system/libroot/posix/shadow.cpp
/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include <shadow.h>

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <new>

#include <OS.h>

#include <AutoDeleter.h>
#include <errno_private.h>
#include <libroot_private.h>
#include <RegistrarDefs.h>
#include <user_group.h>

#include <util/KMessage.h>


using BPrivate::UserGroupLocker;
using BPrivate::relocate_pointer;


static KMessage sShadowPwdDBReply;
static spwd** sShadowPwdEntries = NULL;
static size_t sShadowPwdEntryCount = 0;
static size_t sIterationIndex = 0;

static struct spwd sShadowPwdBuffer;
static char sShadowPwdStringBuffer[MAX_SHADOW_PWD_BUFFER_SIZE];


static status_t
init_shadow_pwd_db()
{
        if (sShadowPwdEntries != NULL)
                return B_OK;

        // ask the registrar
        KMessage message(BPrivate::B_REG_GET_SHADOW_PASSWD_DB);
        status_t error = BPrivate::send_authentication_request_to_registrar(message,
                sShadowPwdDBReply);
        if (error != B_OK)
                return error;

        // unpack the reply
        int32 count;
        spwd** entries;
        int32 numBytes;
        if ((error = sShadowPwdDBReply.FindInt32("count", &count)) != B_OK
                || (error = sShadowPwdDBReply.FindData("entries", B_RAW_TYPE,
                                (const void**)&entries, &numBytes)) != B_OK) {
                return error;
        }

        // relocate the entries
        addr_t baseAddress = (addr_t)entries;
        for (int32 i = 0; i < count; i++) {
                spwd* entry = relocate_pointer(baseAddress, entries[i]);
                relocate_pointer(baseAddress, entry->sp_namp);
                relocate_pointer(baseAddress, entry->sp_pwdp);
        }

        sShadowPwdEntries = entries;
        sShadowPwdEntryCount = count;

        return B_OK;
}


// #pragma mark -


struct spwd*
getspent(void)
{
        struct spwd* result = NULL;
        int status = getspent_r(&sShadowPwdBuffer, sShadowPwdStringBuffer,
                sizeof(sShadowPwdStringBuffer), &result);
        if (status != 0)
                __set_errno(status);
        return result;
}


int
getspent_r(struct spwd* spwd, char* buffer, size_t bufferSize,
        struct spwd** _result)
{
        UserGroupLocker _;

        int status = B_NO_MEMORY;

        *_result = NULL;

        if ((status = init_shadow_pwd_db()) == B_OK) {
                if (sIterationIndex >= sShadowPwdEntryCount)
                        return ENOENT;

                status = BPrivate::copy_shadow_pwd_to_buffer(
                        sShadowPwdEntries[sIterationIndex], spwd, buffer, bufferSize);

                if (status == B_OK) {
                        sIterationIndex++;
                        *_result = spwd;
                }
        }

        return status;
}


void
setspent(void)
{
        UserGroupLocker _;

        sIterationIndex = 0;
}


void
endspent(void)
{
        UserGroupLocker locker;

        sShadowPwdDBReply.Unset();
        sShadowPwdEntries = NULL;
        sShadowPwdEntryCount = 0;
        sIterationIndex = 0;
}


struct spwd *
getspnam(const char *name)
{
        struct spwd* result = NULL;
        int status = getspnam_r(name, &sShadowPwdBuffer, sShadowPwdStringBuffer,
                sizeof(sShadowPwdStringBuffer), &result);
        if (status != 0)
                __set_errno(status);
        return result;
}


int
getspnam_r(const char *name, struct spwd *spwd, char *buffer,
        size_t bufferSize, struct spwd **_result)
{
        *_result = NULL;

        KMessage message(BPrivate::B_REG_GET_USER);
        message.AddString("name", name);
        message.AddBool("shadow", true);

        KMessage reply;
        status_t error = BPrivate::send_authentication_request_to_registrar(message,
                reply);
        if (error != B_OK)
                return error;

        const char* password;
        int32 lastChanged;
        int32 min;
        int32 max;
        int32 warn;
        int32 inactive;
        int32 expiration;
        int32 flags;

        if ((error = reply.FindString("name", &name)) != B_OK
                || (error = reply.FindString("shadow password", &password)) != B_OK
                || (error = reply.FindInt32("last changed", &lastChanged)) != B_OK
                || (error = reply.FindInt32("min", &min)) != B_OK
                || (error = reply.FindInt32("max", &max)) != B_OK
                || (error = reply.FindInt32("warn", &warn)) != B_OK
                || (error = reply.FindInt32("inactive", &inactive)) != B_OK
                || (error = reply.FindInt32("expiration", &expiration)) != B_OK
                || (error = reply.FindInt32("flags", &flags)) != B_OK) {
                return error;
        }

        error = BPrivate::copy_shadow_pwd_to_buffer(name, password, lastChanged,
                min, max, warn, inactive, expiration, flags, spwd, buffer, bufferSize);
        if (error == B_OK)
                *_result = spwd;

        return error;
}


struct spwd*
sgetspent(const char* line)
{
        struct spwd* result = NULL;
        int status = sgetspent_r(line, &sShadowPwdBuffer, sShadowPwdStringBuffer,
                sizeof(sShadowPwdStringBuffer), &result);
        if (status != 0)
                __set_errno(status);
        return result;
}


int
sgetspent_r(const char* _line, struct spwd *spwd, char *buffer,
        size_t bufferSize, struct spwd** _result)
{
        *_result = NULL;

        if (_line == NULL)
                return B_BAD_VALUE;

        // we need a mutable copy of the line
        char* line = strdup(_line);
        if (line == NULL)
                return B_NO_MEMORY;
        MemoryDeleter _(line);

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

        status_t status = BPrivate::parse_shadow_pwd_line(line, name, password,
                lastChanged, min, max, warn, inactive, expiration, flags);
        if (status != B_OK)
                return status;

        status = BPrivate::copy_shadow_pwd_to_buffer(name, password, lastChanged,
                min, max, warn, inactive, expiration, flags, spwd, buffer, bufferSize);
        if (status != B_OK)
                return status;

        *_result = spwd;
        return 0;
}


struct spwd*
fgetspent(FILE* file)
{
        struct spwd* result = NULL;
        int status = fgetspent_r(file, &sShadowPwdBuffer, sShadowPwdStringBuffer,
                sizeof(sShadowPwdStringBuffer), &result);
        if (status != 0)
                __set_errno(status);
        return result;
}


int
fgetspent_r(FILE* file, struct spwd* spwd, char* buffer, size_t bufferSize,
        struct spwd** _result)
{
        *_result = NULL;

        // read a line
        char lineBuffer[LINE_MAX + 1];
        __set_errno(0);
        char* line = fgets(lineBuffer, sizeof(lineBuffer), file);
        if (line == NULL) {
                if (errno != 0)
                        return errno;
                return ENOENT;
        }

        return sgetspent_r(line, spwd, buffer, bufferSize, _result);
}