waffle

user and group backend daemon
git clone git://git.2f30.org/waffle
Log | Files | Refs | LICENSE

commit e2c527c8fbcf4bbf859184d1e254d8ee1cfb085e
parent 977933d69c2258e703962feca52b37e1b872d762
Author: sin <sin@2f30.org>
Date:   Mon,  9 Mar 2015 17:37:03 +0000

Add initial LDAP backend

Diffstat:
MMakefile | 8+++++---
Mbackend.c | 40++++++++++++++++++++++++++++++----------
Mdummy.c | 68+++++++++++++++++---------------------------------------------------
Aldap.c | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mreq.c | 196++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mwaffle.c | 5+++--
Mwaffle.h | 2++
7 files changed, 499 insertions(+), 127 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,13 +2,15 @@ VERSION = 0.0 PREFIX = /usr/local -CPPFLAGS = -D_DEFAULT_SOURCE +CPPFLAGS = -D_DEFAULT_SOURCE -DLDAP_DEPRECATED CFLAGS = -std=c99 -Wall -LDFLAGS = -s -static +LDFLAGS = -s +LDLIBS = -lldap -llber OBJ =\ backend.o\ dummy.o\ + ldap.o\ req.o\ util.o\ waffle.o @@ -18,7 +20,7 @@ BIN = waffle all: $(BIN) $(BIN): $(OBJ) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) $(OBJ): arg.h proto.h waffle.h diff --git a/backend.c b/backend.c @@ -5,20 +5,26 @@ static struct { char *name; + int enabled; struct backend_ops *ops; } backends[] = { - { .name = "dummy", .ops = &dummy_ops }, + { .name = "dummy", .enabled = 0, .ops = &dummy_ops }, + { .name = "ldap", .enabled = 0, .ops = &ldap_ops }, }; int backends_init(void) { size_t i; + int r = -1; - for (i = 0; i < LEN(backends); i++) - if (backends[i].ops->init() < 0) - return -1; - return 0; + for (i = 0; i < LEN(backends); i++) { + if (!backends[i].ops->init()) { + backends[i].enabled = 1; + r = 0; + } + } + return r; } void @@ -26,8 +32,10 @@ backends_term(void) { size_t i; - for (i = 0; i < LEN(backends); i++) + for (i = 0; i < LEN(backends); i++) { backends[i].ops->term(); + backends[i].enabled = 0; + } } int @@ -35,9 +43,12 @@ backends_pwbyname(const char *key, struct passwd *pw) { size_t i; - for (i = 0; i < LEN(backends); i++) + for (i = 0; i < LEN(backends); i++) { + if (!backends[i].enabled) + continue; if (!backends[i].ops->pwbyname(key, pw)) return 0; + } return -1; } @@ -52,9 +63,12 @@ backends_pwbyuid(const char *key, struct passwd *pw) uid = strtol(key, &end, 10); if (*end || errno) return -1; - for (i = 0; i < LEN(backends); i++) + for (i = 0; i < LEN(backends); i++) { + if (!backends[i].enabled) + continue; if (!backends[i].ops->pwbyuid(uid, pw)) return 0; + } return -1; } @@ -63,9 +77,12 @@ backends_grbyname(const char *key, struct group *gr) { size_t i; - for (i = 0; i < LEN(backends); i++) + for (i = 0; i < LEN(backends); i++) { + if (!backends[i].enabled) + continue; if (!backends[i].ops->grbyname(key, gr)) return 0; + } return -1; } @@ -80,8 +97,11 @@ backends_grbygid(const char *key, struct group *gr) gid = strtol(key, &end, 10); if (*end || errno) return -1; - for (i = 0; i < LEN(backends); i++) + for (i = 0; i < LEN(backends); i++) { + if (!backends[i].enabled) + continue; if (!backends[i].ops->grbygid(gid, gr)) return 0; + } return -1; } diff --git a/dummy.c b/dummy.c @@ -2,79 +2,45 @@ #include "waffle.h" static int -dummy_init(void) +init(void) { - return 0; + return -1; } static void -dummy_term(void) +term(void) { } static int -dummy_pwbyname(const char *name, struct passwd *pw) +pwbyname(const char *name, struct passwd *pw) { - if (strcmp(name, "dummy")) - return -1; - pw->pw_name = "dummy"; - pw->pw_passwd = "passwd"; - pw->pw_uid = 1337; - pw->pw_gid = 1337; - pw->pw_gecos = "gecos"; - pw->pw_dir = "/home/dummy"; - pw->pw_shell = "/bin/sh"; - return 0; + return -1; } static int -dummy_pwbyuid(uid_t uid, struct passwd *pw) +pwbyuid(uid_t uid, struct passwd *pw) { - if (uid != 1337) - return -1; - pw->pw_name = "dummy"; - pw->pw_passwd = "passwd"; - pw->pw_uid = 1337; - pw->pw_gid = 1337; - pw->pw_gecos = "gecos"; - pw->pw_dir = "/home/dummy"; - pw->pw_shell = "/bin/sh"; - return 0; + return -1; } static int -dummy_grbyname(const char *name, struct group *gr) +grbyname(const char *name, struct group *gr) { - static char *mem[] = { "group1", "group2", NULL }; - - if (strcmp(name, "dummy")) - return -1; - gr->gr_name = "dummy"; - gr->gr_passwd = "passwd"; - gr->gr_gid = 1337; - gr->gr_mem = mem; - return 0; + return -1; } static int -dummy_grbygid(gid_t gid, struct group *gr) +grbygid(gid_t gid, struct group *gr) { - static char *mem[] = { "group1", "group2", NULL }; - - if (gid != 1337) - return -1; - gr->gr_name = "dummy"; - gr->gr_passwd = "passwd"; - gr->gr_gid = 1337; - gr->gr_mem = mem; - return 0; + return -1; } struct backend_ops dummy_ops = { - .init = dummy_init, - .term = dummy_term, - .pwbyname = dummy_pwbyname, - .pwbyuid = dummy_pwbyuid, - .grbyname = dummy_grbyname, - .grbygid = dummy_grbygid, + .init = init, + .term = term, + .pwbyname = pwbyname, + .pwbyuid = pwbyuid, + .grbyname = grbyname, + .grbygid = grbygid, }; diff --git a/ldap.c b/ldap.c @@ -0,0 +1,307 @@ +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ldap.h> +#include "waffle.h" + +struct ldap_config { + char *host; + int port; + int version; + char *uri; + char *rootbinddn; + char *binddn; + char *bindpw; + char *base; + int debug; +}; + +static LDAP *ld; +static struct ldap_config ldap_conf; + +static void +free_config(void) +{ + free(ldap_conf.host); + free(ldap_conf.uri); + free(ldap_conf.rootbinddn); + free(ldap_conf.binddn); + free(ldap_conf.bindpw); + free(ldap_conf.base); + memset(&ldap_conf, 0, sizeof(ldap_conf)); +} + +static int +parse_config(void) +{ + FILE *fp; + char buf[BUFSIZ], *p; + char *key, *val; + char *end; + + fp = fopen("/etc/ldap.conf", "r"); + while (fp && fgets(buf, sizeof(buf), fp)) { + p = buf; + if (*p == '#' || *p == '\n' || !*p) + continue; + + /* strip leading whitespace before key */ + while(isspace(*p)) + p++; + key = p; + + /* null-terminate key */ + while (*p && !isspace(*p)) p++; + if (*p) + *p++ = '\0'; + + /* strip leading whitespace before val */ + while (isspace(*p)) + p++; + val = p; + + /* strip trailing whitespace after val */ + while (*p) + p++; + while (--p > val && isspace(*p)) + *p = '\0'; + + if (!strcmp(key, "host")) { + ldap_conf.host = strdup(val); + if (!ldap_conf.host) + goto out; + } else if (!strcmp(key, "port")) { + errno = 0; + ldap_conf.port = strtol(val, &end, 10); + if (*end || errno) + goto out; + } else if (!strcmp(key, "ldap_version")) { + errno = 0; + ldap_conf.version = strtol(val, &end, 10); + if (*end || errno) + goto out; + } else if (!strcmp(key, "uri")) { + ldap_conf.uri = strdup(val); + if (!ldap_conf.uri) + goto out; + } else if (!strcmp(key, "rootbinddn")) { + ldap_conf.rootbinddn = strdup(val); + if (!ldap_conf.rootbinddn) + goto out; + } else if (!strcmp(key, "binddn")) { + ldap_conf.binddn = strdup(val); + if (!ldap_conf.binddn) + goto out; + } else if (!strcmp(key, "bindpw")) { + ldap_conf.bindpw = strdup(val); + if (!ldap_conf.bindpw) + goto out; + } else if (!strcmp(key, "base")) { + ldap_conf.base = strdup(val); + if (!ldap_conf.base) + goto out; + } else if (!strcmp(key, "debug")) { + errno = 0; + ldap_conf.debug = strtol(val, &end, 10); + if (*end || errno) + goto out; + } else { + logwarn("unknown keyword in ldap.conf: %s\n", + key); + } + } + if (fp) + fclose(fp); + if (!ldap_conf.host) + ldap_conf.host = strdup("localhost"); + if (!ldap_conf.port) + ldap_conf.port = 389; + if (!ldap_conf.version) + ldap_conf.version = 3; + return ldap_conf.base ? 0 : -1; +out: + free_config(); + return -1; +} + +static int +init(void) +{ + int version = LDAP_VERSION3; + int r; + + if (parse_config() < 0) + return -1; + if (ldap_conf.version != 3) { + logwarn("unsupported LDAP version: %d\n", ldap_conf.version); + goto out; + } + + if (ldap_conf.uri) { + r = ldap_initialize(&ld, ldap_conf.uri); + if (r != LDAP_SUCCESS) { + logwarn("ldap_initialize: %s\n", strerror(errno)); + goto out; + } + } else if (ldap_conf.host) { + ld = ldap_init(ldap_conf.host, ldap_conf.port); + if (!ld) { + logwarn("ldap_init: %s\n", strerror(errno)); + goto out; + } + } + + r = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (r != LDAP_SUCCESS) { + logwarn("ldap_set_option: %s\n", ldap_err2string(r)); + goto out; + } + + r = ldap_bind_s(ld, ldap_conf.rootbinddn, ldap_conf.bindpw, + LDAP_AUTH_SIMPLE); + if (r != LDAP_SUCCESS) { + logwarn("ldap_simple_bind_s: %s\n", ldap_err2string(r)); + goto out; + } + + return 0; +out: + free_config(); + return -1; +} + +static void +term(void) +{ + if (ld) { + ldap_unbind(ld); + ld = NULL; + } + free_config(); +} + +static int +pw_query(const char *name, const char *filter, struct passwd *pw) +{ + LDAPMessage *result = NULL, *e = NULL; + BerElement *ber = NULL; + struct berval **bv; + char *a; + char *end; + int r; + + r = ldap_search_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, + filter, NULL, 0, &result); + if (r != LDAP_SUCCESS) { + logwarn("ldap_search_s: %s\n", ldap_err2string(r)); + return -1; + } + + e = ldap_first_entry(ld, result); + if (!e) { + ldap_msgfree(result); + return -1; + } + + for (a = ldap_first_attribute(ld, e, &ber); a; + a = ldap_next_attribute(ld, e, ber)) { + if (!(bv = ldap_get_values_len(ld, e, a))) + goto out; + if (!strcmp(a, "uid")) { + pw->pw_name = strdup(bv[0]->bv_val); + if (!pw->pw_name) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "userPassword")) { + pw->pw_passwd = strdup(bv[0]->bv_val); + if (!pw->pw_passwd) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "uidNumber")) { + errno = 0; + pw->pw_uid = strtol(bv[0]->bv_val, &end, 10); + if (*end || errno) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "gidNumber")) { + errno = 0; + pw->pw_gid = strtol(bv[0]->bv_val, &end, 10); + if (*end || errno) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "gecos")) { + pw->pw_gecos = strdup(bv[0]->bv_val); + if (!pw->pw_gecos) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "homeDirectory")) { + pw->pw_dir = strdup(bv[0]->bv_val); + if (!pw->pw_dir) { + ldap_value_free_len(bv); + goto out; + } + } else if (!strcmp(a, "loginShell")) { + pw->pw_shell = strdup(bv[0]->bv_val); + if (!pw->pw_shell) { + ldap_value_free_len(bv); + goto out; + } + } + ldap_value_free_len(bv); + } + ber_free(ber, 0); + ldap_msgfree(result); + return 0; +out: + ber_free(ber, 0); + ldap_msgfree(result); + return -1; +} + +static int +pwbyname(const char *name, struct passwd *pw) +{ + char filter[strlen("uid=") + LOGIN_NAME_MAX]; + snprintf(filter, sizeof(filter), "uid=%s", name); + return pw_query(name, filter, pw); +} + +static int +pwbyuid(uid_t uid, struct passwd *pw) +{ + char key[11]; /* max digits in 2^32 + null-terminator */ + char filter[strlen("uidNumber=") + 11]; + + snprintf(filter, sizeof(filter), "uidNumber=%d", uid); + snprintf(key, sizeof(key), "%d", uid); + return pw_query(key, filter, pw); +} + +static int +grbyname(const char *name, struct group *gr) +{ + return -1; +} + +static int +grbygid(gid_t gid, struct group *gr) +{ + return -1; +} + +struct backend_ops ldap_ops = { + .init = init, + .term = term, + .pwbyname = pwbyname, + .pwbyuid = pwbyuid, + .grbyname = grbyname, + .grbygid = grbygid, +}; diff --git a/req.c b/req.c @@ -50,46 +50,76 @@ replypw(int clifd, struct passwd *pw) size_t len, offset; ssize_t n; - header.version = NSCDVERSION; - header.found = pw ? 1 : 0; - header.namelen = pw ? strlen(pw->pw_name) + 1 : 0; - header.passwdlen = pw ? strlen(pw->pw_passwd) + 1 : 0; - header.uid = pw ? pw->pw_uid : -1; - header.gid = pw ? pw->pw_gid : -1; - header.gecoslen = pw ? strlen(pw->pw_gecos) + 1 : 0; - header.dirlen = pw ? strlen(pw->pw_dir) + 1 : 0; - header.shelllen = pw ? strlen(pw->pw_shell) + 1 : 0; - - len = sizeof(header) + - header.namelen + - header.passwdlen + - header.gecoslen + - header.dirlen + - header.shelllen; - - buf = malloc(len); - if (!buf) - return -1; - if (!pw) { + header.version = NSCDVERSION; + header.found = 0; + header.namelen = 0; + header.passwdlen = 0; + header.uid = -1; + header.gid = -1; + header.gecoslen = 0; + header.dirlen = 0; + header.shelllen = 0; + + len = sizeof(header); + buf = malloc(len); + if (!buf) + return -1; + memcpy(buf, &header, sizeof(header)); } else { + header.version = NSCDVERSION; + header.found = 1; + header.namelen = pw->pw_name ? strlen(pw->pw_name) + 1 : 1; + header.passwdlen = pw->pw_passwd ? strlen(pw->pw_passwd) + 1 : 1; + header.uid = pw->pw_uid; + header.gid = pw->pw_gid; + header.gecoslen = pw->pw_gecos ? strlen(pw->pw_gecos) + 1 : 1; + header.dirlen = pw->pw_dir ? strlen(pw->pw_dir) + 1 : 1; + header.shelllen = pw->pw_shell ? strlen(pw->pw_shell) + 1 : 1; + + len = sizeof(header) + + header.namelen + + header.passwdlen + + header.gecoslen + + header.dirlen + + header.shelllen; + + buf = malloc(len); + if (!buf) + return -1; + memcpy(buf, &header, sizeof(header)); offset = sizeof(header); - memcpy(buf + offset, pw->pw_name, header.namelen); + if (pw->pw_name) + memcpy(buf + offset, pw->pw_name, header.namelen); + else + buf[offset] = '\0'; offset += header.namelen; - memcpy(buf + offset, pw->pw_passwd, header.passwdlen); + if (pw->pw_passwd) + memcpy(buf + offset, pw->pw_passwd, header.passwdlen); + else + buf[offset] = '\0'; offset += header.passwdlen; - memcpy(buf + offset, pw->pw_gecos, header.gecoslen); + if (pw->pw_gecos) + memcpy(buf + offset, pw->pw_gecos, header.gecoslen); + else + buf[offset] = '\0'; offset += header.gecoslen; - memcpy(buf + offset, pw->pw_dir, header.dirlen); + if (pw->pw_dir) + memcpy(buf + offset, pw->pw_dir, header.dirlen); + else + buf[offset] = '\0'; offset += header.dirlen; - memcpy(buf + offset, pw->pw_shell, header.shelllen); + if (pw->pw_shell) + memcpy(buf + offset, pw->pw_shell, header.shelllen); + else + buf[offset] = '\0'; } n = write(clifd, buf, len); @@ -105,34 +135,42 @@ replygr(int clifd, struct group *gr) uint32_t *grlens; size_t memsize = 0, len, offset; ssize_t n; - int32_t i; - - header.version = NSCDVERSION; - header.found = gr ? 1 : 0; - header.namelen = gr ? strlen(gr->gr_name) + 1 : 0; - header.passwdlen = gr ? strlen(gr->gr_passwd) + 1 : 0; - header.gid = gr ? gr->gr_gid : -1; - if (gr) { - for (i = 0; gr->gr_mem[i]; i++) - memsize += strlen(gr->gr_mem[i]) + 1; - header.memcnt = i; - } else { - header.memcnt = 0; - } - - len = sizeof(header) + - header.namelen + - header.passwdlen + - header.memcnt * sizeof(uint32_t) + - memsize; - - buf = malloc(len); - if (!buf) - return -1; + int32_t i = 0; if (!gr) { + header.version = NSCDVERSION; + header.found = 0; + header.namelen = 0; + header.passwdlen = 0; + header.gid = -1; + header.memcnt = 0; + + len = sizeof(header); + buf = malloc(len); + if (!buf) + return -1; memcpy(buf, &header, sizeof(header)); } else { + header.version = NSCDVERSION; + header.found = 1; + header.namelen = gr->gr_name ? strlen(gr->gr_name) + 1 : 1; + header.passwdlen = gr->gr_passwd ? strlen(gr->gr_passwd) + 1 : 1; + header.gid = gr->gr_gid; + if (gr->gr_mem) + for (i = 0; gr->gr_mem[i]; i++) + memsize += strlen(gr->gr_mem[i]) + 1; + header.memcnt = i; + + len = sizeof(header) + + header.namelen + + header.passwdlen + + header.memcnt * sizeof(uint32_t) + + memsize; + + buf = malloc(len); + if (!buf) + return -1; + memcpy(buf, &header, sizeof(header)); offset = sizeof(header); @@ -141,10 +179,16 @@ replygr(int clifd, struct group *gr) grlens[i] = strlen(gr->gr_mem[i]) + 1; offset += header.memcnt * sizeof(uint32_t); - memcpy(buf + offset, gr->gr_name, header.namelen); + if (gr->gr_name) + memcpy(buf + offset, gr->gr_name, header.namelen); + else + buf[offset] = '\0'; offset += header.namelen; - memcpy(buf + offset, gr->gr_passwd, header.passwdlen); + if (gr->gr_passwd) + memcpy(buf + offset, gr->gr_passwd, header.passwdlen); + else + buf[offset] = '\0'; offset += header.passwdlen; for (i = 0; i < header.memcnt; i++) { @@ -164,28 +208,58 @@ process_req(int clifd, struct nscdreq *req, char *key) { struct passwd pw; struct group gr; + size_t i; + int r = -1; + + pw.pw_name = NULL; + pw.pw_passwd = NULL; + pw.pw_uid = -1; + pw.pw_gid = -1; + pw.pw_gecos = NULL; + pw.pw_dir = NULL; + pw.pw_shell = NULL; + + gr.gr_name = NULL; + gr.gr_passwd = NULL; + gr.gr_gid = -1; + gr.gr_mem = NULL; switch (req->type) { case GETPWBYNAME: if (!backends_pwbyname(key, &pw)) - return replypw(clifd, &pw); + r = replypw(clifd, &pw); else - return replypw(clifd, NULL); + r = replypw(clifd, NULL); + break; case GETPWBYUID: if (!backends_pwbyuid(key, &pw)) - return replypw(clifd, &pw); + r = replypw(clifd, &pw); else - return replypw(clifd, NULL); + r = replypw(clifd, NULL); + break; case GETGRBYNAME: if (!backends_grbyname(key, &gr)) - return replygr(clifd, &gr); + r = replygr(clifd, &gr); else - return replygr(clifd, NULL); + r = replygr(clifd, NULL); + break; case GETGRBYGID: if (!backends_grbygid(key, &gr)) - return replygr(clifd, &gr); + r = replygr(clifd, &gr); else - return replygr(clifd, NULL); + r = replygr(clifd, NULL); + break; } - return -1; + free(pw.pw_name); + free(pw.pw_passwd); + free(pw.pw_gecos); + free(pw.pw_dir); + free(pw.pw_shell); + free(gr.gr_name); + free(gr.gr_passwd); + if (gr.gr_mem) + for (i = 0; gr.gr_mem[i]; i++) + free(gr.gr_mem[i]); + free(gr.gr_mem); + return r; } diff --git a/waffle.c b/waffle.c @@ -148,7 +148,7 @@ main(int argc, char *argv[]) fdmax = sfd; if (backends_init() < 0) - logerr("failed to initialize backends\n"); + logerr("no available backends\n"); while (1) { rfds = master; @@ -175,7 +175,8 @@ main(int argc, char *argv[]) if (add_cli(clifd) < 0) continue; } else { - handle_req(i); + if (handle_req(i) < 0) + logwarn("failed to handle request from fd %d\n", i); del_cli(i); } } diff --git a/waffle.h b/waffle.h @@ -18,6 +18,8 @@ struct backend_ops { extern int daemonize; /* dummy.c */ extern struct backend_ops dummy_ops; +/* ldap.c */ +extern struct backend_ops ldap_ops; /* req.c */ int handle_req(int);