Vitalnix : User Management Suite
  manual v1.90.6.63

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 itself (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 (LDAP/MySQL) 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.

Synopsis

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

#include <accdb.h>

int q_open(void **state);
int q_usertrav(void *state, struct accdb_user *result);
int q_userinfo(void *state, struct accdb_user *req, struct accdb_user *dest);
int q_useradd(void *state, struct accdb_user *user);
int q_usermod(void *state, struct accdb_user *user, struct accdb_group *mask);
int q_userdel(void *state, struct accdb_user *user);
int q_grouptrav(void *state, struct accdb_group *result);
int q_groupinfo(void *state, struct accdb_group *req, struct accdb_user *dest);
int q_groupadd(void *state, struct accdb_group *user);
int q_groupmod(void *state, struct accdb_group *user, struct accdb_group *mask);
int q_groupdel(void *state, struct accdb_group *user);
void q_close(void *state);
int p_sysctl(unsigned int req, ...);

Actually none of these functions are mandatory, libaccdb will accept NULL pointers from dlsym() too, however, then you will run into problems while dereferencing a NULL pointer. If you do not provide q_open, libaccdb will refuse to load the module. See doc/12_libaccdb.html for more details.

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

Getting opened

q_open() is passed a void ** pointer. You can allocate *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 allround bad (for re-entrancy too), do not stick to it.

The module may or may not allow opening the user database twice (i.e. if the module is opened by different applications), you must handle that 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()).

Traversing the user and group lists

The accdb_[ug]entry structs are as follows:

struct accdb_user {
  // passwd part
  char *lname;
  long uid, gid;
  char *group, *gecos, *home, *shell;

  // shadow part
  char *pass, *pass_cryp;
  long last_change, keep_min, keep_max, warn_age, expiry, disabled_since;
};

struct accdb_group {
  long gid;
  char *name;
};

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". Migration to UTF-8 is planned in libaccdb and its tools, but it could impact your old programs. (v1.90.6.34)

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 external applications in any way, they are meant to be read-only. That way, these strings can even 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. As of libHX-20031029, continuing traversal after modifying a libHX-Btree does not work, thus you will have to rewind first.

Specifically gain info about a user or group

q_userinfo() walks down the list of users searching for a match (and returns the first match). The contents of req are compared with every user in the list until a match is found. If a value in req is -1 or NULL (depending on the member type), 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)

If a match is found, the members of dest are filled if dest is not NULL. This is done, so req and dest can be different structs. If req and dest are different ones, req could be reused for another search without much effore, i.e. contents of req are only slightly modified, while dest is filled with the results. On top of that, req would be polluted with the results otherwise. Examples of exploiting this behavior can be found in the ACCDB utilities.

q_userinfo() does not need to store a traversion pointer for later reuse, so two calls to q_userinfo() may yield either the same or a different result if i.e. searching with a mask lname=NULL and uid=-1. If searching for multiple users (i.e. with that mask), q_usertrav() should be used instead by the application utilizing the module.

Return >0 for success, 0 for user not found, or -errno (and set errno) for an error.

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

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

Adding a user

q_useradd creates a new user. EINVAL -- u->lname was NULL ENOENT -- group not found,

1.90.6.52: XXX

Module info

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

Module control interface

int p_sysctl(unsigned int req, ...)

The back-end can be controlled via the p_sysctl() call. There are some requests defined in accdb.h, and the back-end may provide its own for debugging purposes beginning from 0x4000. The ones defined by accdb.h are:

p_sysctl(ACCDB_ADDFLAGS, state, flags): will add the provided flags with those present in the back-end's state.

p_sysctl(ACCDB_DELFLAGS, state, flags): will remove the provided flags from the back-end's state.

p_sysctl(ACCDB_FLUSHDB, state): causes any changes to be committed to the underlying medium.

p_sysctl(ACCDB_LOCK, state): lock the underlying medium for write accesses.

p_sysctl(ACCDB_UNLOCK, state): unlock the underlying medium.


November 08 2003