v1.0.30.0

Name

backend_api - The back-end module API, how it works, how to write a module

Description

Since there is a great variety of user databases, the Unified Account Database 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).

Depending on how the user database is organized, you might want, or even need to read it into memory. Be sure to keep a lock on the database and/or files when writing, if necessary to not run into race conditions.

One should choose wise how to access local files. (If that applies to the module, i.e. accdb_shadow.) Using libc's putpwent() or similar is a pain due to re-entrancy problems, race conditions and not being widely available. 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 I/O, since the underlying DB will handle that.

Applications that wish to use userdb modules might want to use accdb_load() from libaccdb for simplicity. However, they are not forced to do so, but then they would have to lookup the module symbols themselves.

Additional notes

Keep in mind that the back-end and the user database have to support non-unique UIDs (i.e. multiple accounts with UID 0 root privilegues), so you can not use UIDs as the sort key in any data structure which requires unique keys, such as B-trees. (Exception: You could pun the B-tree structure to do one level of redirection, i.e. object 0 is a pointer to an array of one or more users.)

Synopsis

First, include <accdb.h> to get at the accdb_[ug]entry structs. The module needs to provide following functions to the outside world:

#include <accdb.h>

int q_init(struct accdb_module *mp, const char *);
int q_open(struct accdb_module *mp);
int q_usertrav(struct accdb_module *mp, struct accdb_user *result);
int q_userinfo(struct accdb_module *mp, struct accdb_user *req, struct accdb_user *dest, size_t s);
int q_useradd(struct accdb_module *mp, struct accdb_user *user);
int q_usermod(struct accdb_module *mp, struct accdb_user *user, struct accdb_user *mask);
int q_userdel(struct accdb_module *mp, struct accdb_user *user);
int q_grouptrav(struct accdb_module *mp, struct accdb_group *result);
int q_groupinfo(struct accdb_module *mp, struct accdb_group *req, struct accdb_group *dest, size_t s);
int q_groupadd(struct accdb_module *mp, struct accdb_group *group);
int q_groupmod(struct accdb_module *mp, struct accdb_group *group, struct accdb_group *mask);
int q_groupdel(struct accdb_module *mp, struct accdb_group *group);
int q_close(struct accdb_module *mp);
long q_sysctl(unsigned int req, ...);
void q_deinit(struct accdb_module *mp);

q_init() and q_deinit() do not need to be provided by the back-end module. q_open() is not required either -- ACCDB will refuse to load the module. (See doc/accdb_api.html for more details.) All other fuctions must be present, otherwise you will get a Segmentation Fault when running an application which expected that function to be there.

If you include <accdb_int.h>, you also get the prototypes for free, besides some macros used for naming the module (see below near "Module Info").

1 They are prefixed so they do not clash with system function names.

Initialization

int q_init(struct accdb_module *mp, const char *name);
int q_open(struct accdb_module *mp);
int q_close(struct accdb_module *mp);
void q_deinit(struct accdb_module *mp);

When the module (SO/DLL) is loaded through accdb_load(), ACCDB allows you to load a sub-module. Thus, q_init() will be called with a pointer to the accdb_module struct and the name of the sub-module, if any. You do not need to provide q_init(), if your back-end module does not use sub-modules.

q_open() is passed a pointer to the accdb_module struct. You can allocate mp->state to point to a user-defined struct.That is, a pointer to a private data area you might need. That struct may hold anything you need. It could, for example, contain FILE * pointers, or ints to file descriptors (fds, sockets) connected to the user database. Using static variables is all-round bad (for re-entrancy too), do not stick to it.

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()).

To set up a sub-module specific state and a UDB state, you will need to create some encapsulation in mp->state, as for example in the accdb_perl module. (Well, that is no longer valid. Just imagine having the struct state from backends/shadow.c in another struct state.)

q_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.

q_deinit() is the counterpart of q_init(). It is called through accdb_unload() and is meant to clean up sub-module specific stuff, if any. This function also does not need to exist.

Traversing the user and group lists

int q_usertrav(void *state, struct accdb_user *result);
int q_grouptrav(void *state, struct accdb_group *result);

The accdb_[ug]entry structs are as follows:

struct accdb_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 accdb_group {
    long gid;
    char *gname;
};

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

The data put into the struct also needs to be in local ending. In most cases this will be ISO-8859-1 "Latin-1". UTF-8 is planned but most outside programs will just display strings as if they were in fixed 8-bit encoding.

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 backends module, they are meant to be read-only. That way, these strings can be reused anywhere.

For each successful returned user via q_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, q_usertrav() can be called with the result parameter set to NULL to reset the list traverser.

Similar applies to the group traversing function q_grouptrav().

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

Retrieve info about a user or group

int q_userinfo(void *state, struct accdb_user *req, struct accdb_user *dest, size_t s);
int q_groupinfo(void *state, struct accdb_group *req, struct accdb_group *dest, size_t s);

q_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)

q_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 q_groupinfo() behaves just as like.

q_userinfo() and q_groupinfo() do not interrupt the traversion with q_usertrav() / q_grouptrav(). They use their own (local) traversion pointer.

Adding a user or group

int q_useradd(void *state, struct accdb_user *user);
int q_groupadd(void *state, struct accdb_group *group);

q_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 q_groupadd(), errno is set to EINVAL and 0 is returned if .gname is NULL. -errno is returned when something underlying q_useradd() (or q_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 accdb_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 q_usermod(void *state, struct accdb_user *user, struct accdb_user *mask);
int q_groupmod(void *state, struct accdb_group *user, struct accdb_group *mask);

q_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 q_userdel(void *state, struct accdb_user *user);
int q_groupdel(void *state, struct accdb_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 accdb_module *)m)->desc is NULL.

Module control interface

int q_sysctl(unsigned int req, ...)

The back-end can be controlled via the q_sysctl() call. There are some requests defined in accdb.h; General Sysctl()s get a number between 0x1 and 0xFF, Extension Sysctl()s between 0x100 and 0xBFFF and back-end specific ones from 0xC000 (mostly debugging purposes only).

q_sysctl(ACCDB_ADDFLAGS, struct accdb_module *mp, unsigned long flagmask);
q_sysctl(ACCDB_DELFLAGS, struct accdb_module *mp, unsigned long flagmask);
q_sysctl(ACCDB_FLUSHDB, struct accdb_module *mp);
q_sysctl(ACCDB_NEXTUID_SYS, struct accdb_module *mp);
q_sysctl(ACCDB_NEXTUID, struct accdb_module *mp);
q_sysctl(ACCDB_NEXTGID_SYS, struct accdb_module *mp);
q_sysctl(ACCDB_NEXTGID, struct accdb_module *mp);
ACCDB_ADDFLAGS The provided flags are added (binary OR) to the state's flags.
ACCDB_DELFLAGS The provided flags are removed from the state's flags.
ACCDB_FLUSHDB Causes any changes to be committed to the underlying layer. (That might not be hard disk!)
ACCDB_NEXTUID_SYS Returns the next free auto-UID below UID_MIN
ACCDB_NEXTUID Returns the next free auto-UID within UID_MIN and UID_MAX
ACCDB_NEXTGID_SYS Returns the next free auto-GID below GID_MIN
ACCDB_NEXTGID Returns the next free auto-GID within GID_MIN and GID_MAX


April 25 2004 http://vitalnix.sf.net/