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_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 struct
s 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); |
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); |
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); |
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); |
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_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 |