vitalnix user management suite 3.2.0


Module definition

Each module must define a structure struct vxdb_driver in which they set the function pointers to the respective functions. The module structure also includes space for name, author and description of the module for display with the vxdbinfo utility. A reduced example definition could look like this:

#include "drivers/static-build.h"
#include <vitalnix/libvxdb/libvxdb.h>

static struct vxdb_driver THIS_MODULE = {
    .name      = "Our sample module",
    .userinfo  = our_userinfo,
    .groupinfo = our_groupinfo,
};

REGISTER_MODULE(our, &THIS_MODULE);

The drivers/static-build.h include, the REGISTER_MODULE macro expands to extra code required for initializion.

Then of course, the module needs to provide the functions we have just specified in the sturct. They can then be called from user applications using the vxdb_*() functions and the respective instance as obtained from vxdb_load(). Note that the struct must be writable since it will be modified.

Implementable functions

->init

int (*init)(struct vxdb_state *mip, const char *config_file);

The init function gets called after the shared library has been opened through vxdb_load() from the caller program. The driver loader passes the config_file argument which indicates the location of the configuration file that is used.

In this function, the module should allocate its state and read configuration files. The allocation of a state might look like below, and must be done correctly to support reentrancy:

static int our_init(struct vxdb_state *mip, void *priv) {
    struct our_state *state;
    state = mip->state = malloc(sizeof(struct our_state));
    state->config = read_some_config("bla.conf");
    read_some_extras(priv);
    return 1;
}

This is a very basic example, and you should look at the already existing modules to see what they do, and possibly how they do it.

->open

int (*open)(struct vxdb_state *mip, long flags);

The open function should open a connection to the password database (if applicable), or do whatever is equivalent to prepare further actions. The flags parameter is explained in the libvxdb API. Some sample code:

int our_open(struct vxdb_state *mip, long flags) {
    struct our_state *state = mip->state;
    if((state->fp = fopen("/etc/passwd", "r")) == NULL)
        return -errno;
    return 1;
}

->close

void (*close)(struct vxdb_state *mip);

->exit

void (*exit)(struct vxdb_state *mip);

->modctl

long (*modctl)(struct vxdb_state *mip, long command, ...);

The driver can be controlled via the vxdb_modctl() function. (The idea is analogous to a device driver's ioctl().) There are some requests defined in libvxdb.h.

vxdb_modctl(struct vxdb_state *mp, VXDB_FLUSH);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTUID_SYS);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTUID);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTGID_SYS);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTGID);
VXDB_FLUSH Causes any changes to be committed to the underlying layer.
VXDB_NEXTUID_SYS Returns the next free auto-UID below UID_MIN
VXDB_NEXTUID Returns the next free auto-UID within UID_MIN and UID_MAX
VXDB_NEXTGID_SYS Returns the next free auto-GID below GID_MIN
VXDB_NEXTGID Returns the next free auto-GID within GID_MIN and GID_MAX

->lock

int (*lock)(struct vxdb_state *mip);

->unlock

int (*unlock)(struct vxdb_state *mip);

Description

Since there is a great variety of user databases, the Vitalnix Unified Account Database (VXDB) provides a generic API to any application. Some user databases are for example the Shadow Password System (/etc/passwd and friends). Another could be the Samba userdb in /var/lib/samba, or OpenLDAP (libldap).

Notes

Depending on how the user database is organized, you might want, or even need to read it into memory (= cache it). Be sure to keep a lock on the database and/or files when writing, if necessary, to not run into race conditions. Unprivileged access that cannot write does not need any write locking. Doing the I/O on our own (and even caching if the module author wants to) can result in a faster execution, and allows for way more control. "Pipe modules", which just transfer data, as it would be with a LDAP or MySQL database, do not need to do any direct file I/O, since the underlying DB will handle that.

It is not specified whether and how the back-end module and/or the user database can handle accounts with non-unique UIDs (e.g. to implement an old-style multi-superuser system). However, it should be handled gracefully, i.e. either allow it in a manageable way or return an appropriate error code.

The module may or may not allow opening the user database twice (the module may be opened by different applications). You should handle that case then. One, locking files, and two, checking for the lock is the usual thing for a module to be opened only once at a time.

Upon success, return >0, otherwise return 0 (and possibly set errno), or even return -errno and set errno to signalize a hard error (i.e. ENOMEM due to malloc()).

b_close() shall deinitialize the currently open session for the user database. Flush any data to disk if needed and free up all memory used for the session.

exit() is the counterpart of b_init() and is called through accdb_unload(). This function also does not necessarily need to exist either.

Traversing the user and group lists

int vxdb_usertrav(void *state, struct vxdb_user *result);
int vxdb_grouptrav(void *state, struct vxdb_group *result);

The accdb_[ug]entry structs are as follows:

struct vxdb_user {
    /* passwd part */
    char *lname;
    long uid, gid;
    char *gecos, *home, *shell, *igrp, *sgrp;

    /* shadow part */
    char *pass, *pass_cryp;
    long last_change, keep_min, keep_max, warn_age, expire, inactive;
};

struct vxdb_group {
    long gid;
    char *gname;
};

Analogous to getpwent(), just a bit more organized, is the b_usertrav() function. It takes the usual state pointer and a struct vxdb_user * pointer. When calling b_usertrav(), it takes the next user found in the database and fills in the struct.

All the char * fields in the structs shall point to allocated memory (or memory available throughout the program), so do not use local variables or alloca(). strdup() or similar might help you. Those strings may not be changed by anything else than the driver module, they are meant to be read-only. That way, these strings can be reused anywhere.

For each successful returned user via b_usertrav(), the function shall return >0 (usually 1 suffices). The order of users is of no importance; if necessary, the application itself will need to sort it. If the end of the list is reached, return 0, or, if an error occurred, set errno and return -errno. When the end of the list is reached, b_usertrav() can be called with the result parameter set to NULL to reset the list traverser.

Similar applies to the group traversing function b_grouptrav().

The traversion pointer is not static but stored in the state.

Retrieve info about a user or group

int vxdb_userinfo(void *state, struct vxdb_user *req, struct vxdb_user *dest, size_t s);
int vxdb_groupinfo(void *state, struct vxdb_group *req, struct vxdb_group *dest, size_t s);

b_userinfo() walks down the list of users searching for matching users. The contents of req are compared with every user in the list. If a value in req is -1 or NULL (depending on the member type and meaning), it is ignored and not included in the comparison, so you could do i.e. the following searches:

any user with UID 37007 (req->lname == NULL && req->uid == 37007),
user "jengelh", any UID (strcmp(req->lname, "jengelh") == 0 && req->uid == -1)
user "jengelh" with UID 37007 (strcmp(req->lname, "jengelh") == 0 && req->uid == 37007)

b_userinfo() has three different operation cases. The first is if dest is not NULL, in which case at most s users are placed into dest, and the number of users stored (can never be >s, but may be less) is returned. Case two is that dest is NULL and s is 0, where 1 is returned as soon as the first match is found. Case three, dest is NULL and s is 1, no users are copied, but the number of matches will be returned.

Again, the complement b_groupinfo() behaves just as like.

b_userinfo() and b_groupinfo() do not interrupt the traversion with b_usertrav() / b_grouptrav(). They use their own (local) traversion pointer.

Adding a user or group

int vxdb_useradd(void *state, struct vxdb_user *user);
int vxdb_groupadd(void *state, struct vxdb_group *group);

b_useradd creates a new user in the database from the data provided in user. It duplicates all strings provided by an application and adds them to the database.

If .lname is NULL, or there is no group specification in either .gid or .igrp, errno is to be set to EINVAL and 0 is to be returned. For b_groupadd(), errno is set to EINVAL and 0 is returned if .gname is NULL. -errno is returned when something underlying b_useradd() (or b_groupadd()) failed, like writing to the database.

If the .uid or .gid field is -1, automatic GID selection has to be done. The struct needs to be updated to reflect this. (However, we can not update struct vxdb_user .group as it is a string, so the application needs to re-lookup the user with the new UID.)

Modifying a user or group

int vxdb_usermod(void *state, struct vxdb_user *user, struct vxdb_user *mask);
int vxdb_groupmod(void *state, struct vxdb_group *user, struct vxdb_group *mask);

b_usermod() searches for the next matching user/group and modifies its account information. NULL or -1 fields (respectively) mean ignore, for both search mask (user) and modification mask (mask).

You should take care that the search mask does not match multiple users, otherwise it is undefined which user that could match is modified.

Deleting a user or group

int vxdb_userdel(void *state, struct vxdb_user *user);
int vxdb_groupdel(void *state, struct vxdb_group *group);

Deletes the [first] user/group matching user/group, respectively.

Module info

Beautify the module by using the macros MODULE_NAME(string), MODLUE_DESC(string) and/or MODULE_INFO(string). This is not mandatory, and applications must handle this situation if ((struct vxdb_state *)m)->desc is NULL.

Module control interface

int vxdb_modctl(struct vxdb_state *mp, long request, ...)

The back-end can be controlled via the vxdb_modctl() call. (The idea is analogous to a device driver's ioctl().) There are some requests defined in libvxdb.h.

vxdb_modctl(struct vxdb_state *mp, VXDB_FLUSH);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTUID_SYS);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTUID);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTGID_SYS);
vxdb_modctl(struct vxdb_state *mp, VXDB_NEXTGID);
VXDB_FLUSH Causes any changes to be committed to the underlying layer. (That might not be hard disk!)
VXDB_NEXTUID_SYS Returns the next free auto-UID below UID_MIN
VXDB_NEXTUID Returns the next free auto-UID within UID_MIN and UID_MAX
VXDB_NEXTGID_SYS Returns the next free auto-GID below GID_MIN
VXDB_NEXTGID Returns the next free auto-GID within GID_MIN and GID_MAX