Add sources for C account creation programs

This commit is contained in:
Michael Spang 2007-12-10 00:25:14 -05:00
parent 4ec2fceaca
commit 3d20172dad
21 changed files with 1681 additions and 0 deletions

4
src/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.o
/addmember
/addclub
/config-test

20
src/Makefile Normal file
View File

@ -0,0 +1,20 @@
CFLAGS ?= -ggdb -Wall -O2
CFLAGS += -I../include
LDAP := -lldap
KADM := $(shell krb5-config --libs krb5 kadm-client)
LIBCEO := util.o common.o config.o parser.o ldap.o krb5.o kadm.o addhomedir.o
all: addmember addclub
clean:
rm -f addmember addclub config-test *.o
addmember: $(LIBCEO) addmember.o
$(CC) $(LDFLAGS) $(LDAP) $(KADM) $^ -o $@
addclub: $(LIBCEO) addclub.o
$(CC) $(LDFLAGS) $(LDAP) $(KADM) $^ -o $@
config-test: config-test.o parser.o util.o
$(CC) $(LDFLAGS) $^ -o $@

181
src/addclub.c Normal file
View File

@ -0,0 +1,181 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <libgen.h>
#include <syslog.h>
#include "util.h"
#include "common.h"
#include "config.h"
#include "ldap.h"
#include "krb5.h"
#include "kadm.h"
#include "addhomedir.h"
char *prog = NULL;
char *user = NULL;
int privileged = 0;
static int force = 0;
static int no_notify = 0;
static char *name = NULL;
static char *userid = NULL;
static struct option opts[] = {
{ "force", 0, NULL, 'f' },
{ "no-notify", 0, NULL, 'q' },
{ NULL, 0, NULL, '\0' },
};
static void usage() {
fprintf(stderr, "Usage: %s userid clubname\n", prog);
exit(2);
}
void addclub() {
int krb_ok, user_ok, group_ok, home_ok, quota_ok;
int id;
char homedir[1024];
logmsg("adding uid=%s cn=%s by %s", userid, name, user);
if (setreuid(0, 0))
fatalpe("setreuid");
if (!force && getpwnam(userid) != NULL)
deny("user %s already exists", userid);
ceo_krb5_init();
ceo_ldap_init();
ceo_kadm_init();
if (ceo_user_exists(userid))
deny("user %s already exists in LDAP", userid);
if ((id = ceo_new_uid(member_min_id, member_max_id)) <= 0)
fatal("no available uids in range [%d, %d]", member_min_id, member_max_id);
snprintf(homedir, sizeof(homedir), "%s/%s", club_home, userid);
krb_ok = ceo_del_princ(userid);
if (!krb_ok)
logmsg("successfully cleared principal for %s", userid);
user_ok = krb_ok || ceo_add_user(userid, users_base, "club", name, homedir,
club_shell, id, NULL);
if (!user_ok)
logmsg("successfully created account for %s", userid);
group_ok = user_ok || ceo_add_group(userid, groups_base, id);
if (!group_ok)
logmsg("successfully created group for %s", userid);
home_ok = user_ok || ceo_create_home(homedir, id, id);
if (!home_ok)
logmsg("successfully created home directory for %s", userid);
quota_ok = user_ok || ceo_set_quota(quota_prototype, id);
if (!quota_ok)
logmsg("successfully set quota for %s", userid);
logmsg("done uid=%s", userid);
if (!no_notify && !user_ok) {
int pid;
int hkp[2];
FILE *hkf;
int status;
if (pipe(hkp))
errorpe("pipe");
fflush(stdout);
fflush(stderr);
pid = fork();
if (!pid) {
fclose(stdout);
fclose(stderr);
close(hkp[1]);
dup2(hkp[0], 0);
exit(execl(notify_hook, notify_hook, prog, user, userid, name, NULL));
}
hkf = fdopen(hkp[1], "w");
if (group_ok)
fprintf(hkf, "failed to create group\n");
if (home_ok)
fprintf(hkf, "failed to create home directory\n");
if (quota_ok)
fprintf(hkf, "failed to set quota\n");
if (!group_ok && !home_ok && !quota_ok)
fprintf(hkf, "all failures went undetected\n");
fclose(hkf);
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status))
logmsg("hook %s exited with status %d", notify_hook, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
logmsg("hook %s killed by signal %d", notify_hook, WTERMSIG(status));
}
ceo_kadm_cleanup();
ceo_ldap_cleanup();
ceo_krb5_cleanup();
return !krb_ok && !user_ok && !group_ok && !home_ok && quota_ok;
}
int main(int argc, char *argv[]) {
int opt;
openlog(prog, 0, LOG_AUTHPRIV);
configure();
prog = basename(argv[0]);
user = ceo_get_user();
privileged = ceo_get_privileged();
while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) {
switch (opt) {
case 'f':
if (!privileged)
deny("not privileged enough to force");
force = 1;
break;
case 'q':
if (!privileged)
deny("not privileged enough to suppress notifications");
no_notify = 1;
break;
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
if (argc - optind != 2)
usage();
userid = argv[optind++];
name = argv[optind++];
addclub();
return 0;
}

133
src/addhomedir.c Normal file
View File

@ -0,0 +1,133 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <fcntl.h>
#include "addhomedir.h"
#include "util.h"
#include "config.h"
int ceo_create_home(char *homedir, uid_t uid, gid_t gid) {
int mask;
DIR *skel;
struct dirent *skelent;
mask = umask(0);
if (mkdir(homedir, homedir_mode)) {
errorpe("failed to create %s", homedir);
return -1;
}
skel = opendir(skeleton_dir);
if (!skel) {
errorpe("failed to open %s", skeleton_dir);
return -1;
}
while ((skelent = readdir(skel))) {
struct stat sb;
char src[PATH_MAX], dest[PATH_MAX];
if (!strcmp(skelent->d_name, ".") || !strcmp(skelent->d_name, ".."))
continue;
snprintf(src, sizeof(src), "%s/%s", skeleton_dir, skelent->d_name);
snprintf(dest, sizeof(dest), "%s/%s", homedir, skelent->d_name);
lstat(src, &sb);
if (sb.st_uid || sb.st_gid) {
warn("not creating %s due to ownership", dest);
continue;
}
if (S_ISREG(sb.st_mode)) {
int bytes;
char buf[4096];
int srcfd = open(src, O_RDONLY);
if (srcfd == -1) {
warnpe("open: %s", src);
continue;
}
int destfd = open(dest, O_WRONLY|O_CREAT|O_EXCL, sb.st_mode & 0777);
if (destfd == -1) {
warnpe("open: %s", dest);
close(srcfd);
continue;
}
for (;;) {
bytes = read(srcfd, buf, sizeof(buf));
if (!bytes)
break;
if (bytes < 0) {
warnpe("read");
break;
}
if (write(destfd, buf, bytes) < 0) {
warnpe("write");
break;
}
}
if (fchown(destfd, uid, gid))
errorpe("chown: %s", dest);
close(srcfd);
close(destfd);
} else if (S_ISDIR(sb.st_mode)) {
if (mkdir(dest, sb.st_mode & 0777)) {
warnpe("mkdir: %s", dest);
continue;
}
if (chown(dest, uid, gid))
errorpe("chown: %s", dest);
} else if (S_ISLNK(sb.st_mode)) {
char lnkdest[PATH_MAX];
int bytes;
bytes = readlink(src, lnkdest, sizeof(lnkdest));
lnkdest[bytes] = '\0';
if (bytes == -1) {
warnpe("readlink: %s", src);
continue;
}
if (symlink(lnkdest, dest)) {
warnpe("symlink: %s", dest);
continue;
}
if (lchown(dest, uid, gid))
errorpe("lchown: %s", dest);
} else {
warn("not creating %s", dest);
}
}
closedir(skel);
if (chown(homedir, uid, gid))
errorpe("failed to chown %s", homedir);
umask(mask);
return 0;
}
int ceo_set_quota(char *proto, int id) {
char user[128];
char *sqargs[] = { "setquota", "-a", "-p", proto, NULL, NULL };
snprintf(user, sizeof(user), "%d", id);
sqargs[4] = user;
if (spawnv("/usr/sbin/setquota", sqargs)) {
error("failed to set quota for %s", user);
return -1;
}
return 0;
}

4
src/addhomedir.h Normal file
View File

@ -0,0 +1,4 @@
#include <sys/types.h>
int ceo_create_home(char *, uid_t, gid_t);
int ceo_set_quota(char *, int);

196
src/addmember.c Normal file
View File

@ -0,0 +1,196 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <libgen.h>
#include <syslog.h>
#include "util.h"
#include "common.h"
#include "config.h"
#include "ldap.h"
#include "krb5.h"
#include "kadm.h"
#include "addhomedir.h"
char *prog = NULL;
char *user = NULL;
int privileged = 0;
static int force = 0;
static int no_notify = 0;
static int use_stdin = 0;
static char *name = NULL;
static char *userid = NULL;
static char *program = NULL;
static char password[1024];
static struct option opts[] = {
{ "force", 0, NULL, 'f' },
{ "no-notify", 0, NULL, 'q' },
{ "stdin", 0, NULL, 's' },
{ NULL, 0, NULL, '\0' },
};
static void usage() {
fprintf(stderr, "Usage: %s userid realname [program]\n", prog);
exit(2);
}
void addmember() {
int krb_ok, user_ok, group_ok, home_ok, quota_ok;
int id;
char homedir[1024];
logmsg("adding uid=%s cn=%s program=%s by %s", userid, name, program, user);
if (setreuid(0, 0))
fatalpe("setreuid");
if (!force && getpwnam(userid) != NULL)
deny("user %s already exists", userid);
if (ceo_read_password(password, sizeof(password), use_stdin))
return;
ceo_krb5_init();
ceo_ldap_init();
ceo_kadm_init();
if (ceo_user_exists(userid))
deny("user %s already exists in LDAP", userid);
if ((id = ceo_new_uid(member_min_id, member_max_id)) <= 0)
fatal("no available uids in range [%d, %d]", member_min_id, member_max_id);
snprintf(homedir, sizeof(homedir), "%s/%s", member_home, userid);
krb_ok = ceo_del_princ(userid);
krb_ok = krb_ok || ceo_add_princ(userid, password);
if (!krb_ok)
logmsg("successfully created principal for %s", userid);
user_ok = krb_ok || ceo_add_user(userid, users_base, "member", name, homedir,
member_shell, id, "program", program, NULL);
if (!user_ok)
logmsg("successfully created account for %s", userid);
group_ok = user_ok || ceo_add_group(userid, groups_base, id);
if (!group_ok)
logmsg("successfully created group for %s", userid);
home_ok = user_ok || ceo_create_home(homedir, id, id);
if (!home_ok)
logmsg("successfully created home directory for %s", userid);
quota_ok = user_ok || ceo_set_quota(quota_prototype, id);
if (!quota_ok)
logmsg("successfully set quota for %s", userid);
logmsg("done uid=%s", userid);
if (!no_notify && !user_ok) {
int pid;
int hkp[2];
FILE *hkf;
int status;
if (pipe(hkp))
errorpe("pipe");
fflush(stdout);
fflush(stderr);
pid = fork();
if (!pid) {
fclose(stdout);
fclose(stderr);
close(hkp[1]);
dup2(hkp[0], 0);
exit(execl(notify_hook, notify_hook, prog, user, userid, name, program, NULL));
}
hkf = fdopen(hkp[1], "w");
if (group_ok)
fprintf(hkf, "failed to create group\n");
if (home_ok)
fprintf(hkf, "failed to create home directory\n");
if (quota_ok)
fprintf(hkf, "failed to set quota\n");
if (!group_ok && !home_ok && !quota_ok)
fprintf(hkf, "all failures went undetected\n");
fclose(hkf);
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status))
logmsg("hook %s exited with status %d", notify_hook, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
logmsg("hook %s killed by signal %d", notify_hook, WTERMSIG(status));
}
ceo_kadm_cleanup();
ceo_ldap_cleanup();
ceo_krb5_cleanup();
return !krb_ok && !user_ok && !group_ok && !home_ok && quota_ok;
}
int main(int argc, char *argv[]) {
int opt;
openlog(prog, 0, LOG_AUTHPRIV);
configure();
prog = basename(argv[0]);
user = ceo_get_user();
privileged = ceo_get_privileged();
while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) {
switch (opt) {
case 's':
use_stdin = 1;
break;
case 'f':
if (!privileged)
deny("not privileged enough to force");
force = 1;
break;
case 'q':
if (!privileged)
deny("not privileged enough to suppress notifications");
no_notify = 1;
break;
case '?':
usage();
break;
default:
fatal("error parsing arguments");
}
}
if (argc - optind != 2 && argc - optind != 3)
usage();
userid = argv[optind++];
name = argv[optind++];
if (argc - optind)
program = argv[optind++];
addmember();
return 0;
}

58
src/common.c Normal file
View File

@ -0,0 +1,58 @@
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include "common.h"
#include "util.h"
#include "config.h"
int ceo_get_privileged() {
int uid = getuid();
// root is privileged
if (!uid)
return 1;
if (privileged_group) {
struct group *privgrp = getgrnam(privileged_group);
int pgid;
gid_t grps[128];
int count, i;
if (!privgrp)
return 0;
pgid = privgrp->gr_gid;
count = getgroups(sizeof(grps), grps);
for (i = 0; i < count; i++)
if (grps[i] == pgid)
return 1;
}
return 0;
}
char *ceo_get_user() {
struct passwd *pwent = getpwuid(getuid());
if (pwent == NULL)
fatal("could not determine user");
return xstrdup(pwent->pw_name);
}
void ceo_notify_hook(int argc, ...) {
va_list args;
char **argv;
int i = 0;
va_start(args, argc);
argv = (char **)xmalloc(sizeof(char *) * (argc + 1));
while (i < argc)
argv[i++] = va_arg(args, char *);
argv[i++] = NULL;
spawnv(notify_hook, argv);
va_end(args);
}

3
src/common.h Normal file
View File

@ -0,0 +1,3 @@
int ceo_get_privileged();
char *ceo_get_user();
void ceo_notify_hook(int, ...);

21
src/config-test.c Normal file
View File

@ -0,0 +1,21 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "parser.h"
#include "util.h"
void config_var(const char *name, const char *value) {
printf("%s = \"%s\"\n", name, value);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s filename\n\n", argv[0]);
exit(1);
}
config_parse(argv[1]);
return 0;
}

96
src/config.c Normal file
View File

@ -0,0 +1,96 @@
#include <stdlib.h>
#include <limits.h>
#include "config.h"
#include "parser.h"
#include "util.h"
#define DEF_STR NULL
#define DEF_LONG LONG_MIN
char *server_url = DEF_STR;
char *users_base = DEF_STR;
char *groups_base = DEF_STR;
char *skeleton_dir = DEF_STR;
char *quota_prototype = DEF_STR;
char *homedir_prefix = DEF_STR;
char *member_home = DEF_STR;
char *member_shell = DEF_STR;
long member_min_id = DEF_LONG;
long member_max_id = DEF_LONG;
char *club_home = DEF_STR;
char *club_shell = DEF_STR;
long club_min_id = DEF_LONG;
long club_max_id = DEF_LONG;
char *notify_hook = DEF_STR;
char *realm = DEF_STR;
char *admin_principal = DEF_STR;
char *admin_keytab = DEF_STR;
char *admin_bind_userid = DEF_STR;
char *admin_bind_keytab = DEF_STR;
char *sasl_realm = DEF_STR;
char *sasl_mech = DEF_STR;
char *privileged_group = DEF_STR;
long homedir_mode = DEF_LONG;
long homedir_min_uid = DEF_LONG;
static char *strvarnames[] = { "server_url", "users_base", "admin_principal",
"admin_keytab", "skeleton_dir", "quota_prototype", "homedir_prefix",
"member_home", "member_shell", "club_home", "club_shell", "realm",
"admin_bind_userid", "admin_bind_keytab", "groups_base",
"privileged_group", "notify_hook", "sasl_realm", "sasl_mech" };
static char **strvars[] = { &server_url, &users_base, &admin_principal,
&admin_keytab, &skeleton_dir, &quota_prototype, &homedir_prefix,
&member_home, &member_shell, &club_home, &club_shell, &realm,
&admin_bind_userid, &admin_bind_keytab, &groups_base,
&privileged_group, &notify_hook, &sasl_realm, &sasl_mech };
static char *longvarnames[] = { "member_min_id", "member_max_id",
"homedir_mode", "homedir_min_uid", "club_min_id", "club_max_id" };
static long *longvars[] = { &member_min_id, &member_max_id, &homedir_mode,
&homedir_min_uid, &club_min_id, &club_max_id };
void config_var(char *var, char *val) {
int i;
for (i = 0; i < sizeof(strvars)/sizeof(*strvars); i++) {
if (!strcmp(var, strvarnames[i])) {
if (!strvars[i])
free(strvars[i]);
*strvars[i] = xstrdup(val);
}
}
for (i = 0; i < sizeof(longvars)/sizeof(*longvars); i++) {
if (!strcmp(var, longvarnames[i])) {
*longvars[i] = config_long(var, val);
}
}
}
void configure() {
int i;
config_parse(CONFIG_FILE);
for (i = 0; i < sizeof(strvars)/sizeof(*strvars); i++) {
if (*strvars[i] == DEF_STR)
badconf("undefined string variable: %s", strvarnames[i]);
}
for (i = 0; i < sizeof(longvars)/sizeof(*longvars); i++) {
if (*longvars[i] == DEF_LONG)
badconf("undefined long variable: %s", longvarnames[i]);
}
}

42
src/config.h Normal file
View File

@ -0,0 +1,42 @@
#define CONFIG_FILE "/etc/csc/accounts.cf"
extern char *server_url;
extern char *users_base;
extern char *groups_base;
extern char *admin_bind_dn;
extern char *admin_bind_pw;
extern char *skeleton_dir;
extern char *quota_prototype;
extern char *homedir_prefix;
extern char *member_home;
extern char *member_shell;
extern long member_min_id;
extern long member_max_id;
extern char *club_home;
extern char *club_shell;
extern long club_min_id;
extern long club_max_id;
extern char *notify_hook;
extern long homedir_mode;
extern long homedir_min_uid;
extern char *realm;
extern char *admin_principal;
extern char *admin_keytab;
extern char *admin_bind_userid;
extern char *admin_bind_keytab;
extern char *sasl_realm;
extern char *sasl_mech;
extern char *privileged_group;
void configure();

66
src/kadm.c Normal file
View File

@ -0,0 +1,66 @@
#include <kadm5/admin.h>
#include "kadm.h"
#include "krb5.h"
#include "util.h"
#include "config.h"
extern char *prog;
static void *handle;
void ceo_kadm_init() {
krb5_error_code retval;
kadm5_config_params params;
memset((void *) &params, 0, sizeof(params));
retval = kadm5_init_with_skey(admin_principal, admin_keytab,
KADM5_ADMIN_SERVICE, &params, KADM5_STRUCT_VERSION,
KADM5_API_VERSION_2, &handle);
if (retval) {
com_err(prog, retval, "while initializing kadm5");
exit(1);
}
}
void ceo_kadm_cleanup() {
kadm5_destroy(handle);
}
int ceo_add_princ(char *user, char *password) {
krb5_error_code retval;
kadm5_principal_ent_rec princ;
memset((void *) &princ, 0, sizeof(princ));
if ((retval = krb5_parse_name(context, user, &princ.principal))) {
com_err(prog, retval, "while parsing principal name");
return retval;
}
if ((retval = kadm5_create_principal(handle, &princ, KADM5_PRINCIPAL, password))) {
com_err(prog, retval, "while creating principal");
return retval;
}
krb5_free_principal(context, princ.principal);
return 0;
}
int ceo_del_princ(char *user) {
krb5_error_code retval;
krb5_principal princ;
if ((retval = krb5_parse_name(context, user, &princ))) {
com_err(prog, retval, "while parsing principal name");
return retval;
}
retval = kadm5_delete_principal(handle, princ);
if (retval && retval != KADM5_UNK_PRINC) {
com_err(prog, retval, "while deleting principal");
return retval;
}
krb5_free_principal(context, princ);
return 0;
}

5
src/kadm.h Normal file
View File

@ -0,0 +1,5 @@
void ceo_kadm_init();
void ceo_kadm_cleanup();
int ceo_add_princ(char *, char *);
int ceo_del_princ(char *);

149
src/krb5.c Normal file
View File

@ -0,0 +1,149 @@
#include <stdio.h>
#include <krb5.h>
#include <syslog.h>
#include "krb5.h"
#include "util.h"
#include "config.h"
extern char *prog;
krb5_context context;
static void com_err_hk(const char *whoami, long code, const char *fmt, va_list args) {
char message[4096];
char *msgp = message;
msgp += snprintf(msgp, sizeof(message) - 2 - (msgp - message), "%s ", error_message(code));
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
msgp += vsnprintf(msgp, sizeof(message) - 2 - (msgp - message), fmt, args);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
*msgp++ = '\n';
*msgp++ = '\0';
syslog(LOG_ERR, "%s", message);
fprintf(stderr, "%s: %s", whoami, message);
}
void ceo_krb5_init() {
krb5_error_code retval;
set_com_err_hook(com_err_hk);
retval = krb5_init_context(&context);
if (retval) {
com_err(prog, retval, "while initializing krb5");
exit(1);
}
retval = krb5_set_default_realm(context, realm);
if (retval) {
com_err(prog, retval, "while setting default realm");
exit(1);
}
}
void ceo_krb5_auth(char *principal, char *ktname) {
krb5_error_code retval;
krb5_creds creds;
krb5_principal princ;
krb5_keytab keytab;
krb5_ccache cache;
krb5_get_init_creds_opt options;
krb5_get_init_creds_opt_init(&options);
memset(&creds, 0, sizeof(creds));
if ((retval = krb5_parse_name(context, principal, &princ))) {
com_err(prog, retval, "while resolving user %s", admin_bind_userid);
exit(1);
}
if ((retval = krb5_cc_default(context, &cache))) {
com_err(prog, retval, "while resolving credentials cache");
exit(1);
}
if ((retval = krb5_kt_resolve(context, ktname, &keytab))) {
com_err(prog, retval, "while resolving keytab %s", admin_bind_keytab);
exit(1);
}
if ((retval = krb5_get_init_creds_keytab(context, &creds, princ, keytab, 0, NULL, &options))) {
com_err(prog, retval, "while getting initial credentials");
exit(1);
}
if ((retval = krb5_cc_initialize(context, cache, princ))) {
com_err(prog, retval, "while initializing credentials cache");
exit(1);
}
if ((retval = krb5_cc_store_cred(context, cache, &creds))) {
com_err(prog, retval, "while storing credentials");
exit(1);
}
krb5_free_cred_contents(context, &creds);
krb5_kt_close(context, keytab);
krb5_free_principal(context, princ);
krb5_cc_close(context, cache);
}
void ceo_krb5_deauth() {
krb5_error_code retval;
krb5_ccache cache;
if ((retval = krb5_cc_default(context, &cache))) {
com_err(prog, retval, "while resolving credentials cache");
exit(1);
}
if ((retval = krb5_cc_destroy(context, cache))) {
com_err(prog, retval, "while destroying credentials cache");
exit(1);
}
}
void ceo_krb5_cleanup() {
krb5_free_context(context);
}
int ceo_read_password(char *password, unsigned int size, int use_stdin) {
int tries = 0;
unsigned int len;
do {
if (use_stdin) {
if (fgets(password, size, stdin) == NULL)
fatal("eof while reading password");
size = strlen(password);
if (password[size - 1] == '\n')
password[size - 1] = '\0';
} else {
len = size;
int retval = krb5_read_password(context, "New password", "Confirm password", password, &len);
if (retval == KRB5_LIBOS_PWDINTR) {
error("interrupted");
return -1;
} else if (retval == KRB5_LIBOS_BADPWDMATCH) {
fputs("Passwords do not match.\n", stderr);
} else if (!password || !*password) {
fputs("Please enter a password.\n", stderr);
}
}
} while (++tries < 3 && !*password);
if (!*password) {
error("maximum tries exceeded reading password");
return -1;
}
return 0;
}

11
src/krb5.h Normal file
View File

@ -0,0 +1,11 @@
#include <krb5.h>
extern krb5_context context;
void ceo_krb5_init();
void ceo_krb5_cleanup();
void ceo_krb5_auth(char *, char *);
void ceo_krb5_deauth();
int ceo_read_password(char *, unsigned int, int);

286
src/ldap.c Normal file
View File

@ -0,0 +1,286 @@
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <sasl/sasl.h>
#include <ldap.h>
#include <krb5.h>
#include "ldap.h"
#include "krb5.h"
#include "config.h"
#include "util.h"
extern char *prog;
LDAP *ld;
static void ldap_fatal(char *msg) {
int errnum;
char *errstr, *detail;
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &errnum);
ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &detail);
errstr = ldap_err2string(errnum);
if (detail && *detail)
fatal("%s: %s (%d): %s", msg, errstr, errnum, detail);
else
fatal("%s: %s (%d)", msg, errstr, errnum);
}
static void ldap_err(char *msg) {
int errnum;
char *errstr, *detail;
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &errnum);
ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &detail);
errstr = ldap_err2string(errnum);
if (detail && *detail)
error("%s: %s (%d): %s", msg, errstr, errnum, detail);
else
error("%s: %s (%d)", msg, errstr, errnum);
}
int ceo_add_group(char *cn, char *basedn, int no) {
if (!cn || !basedn)
fatal("addgroup: Invalid argument");
LDAPMod *mods[8];
int i = -1;
int ret = 0;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "objectClass";
char *objectClasses[] = { "top", "group", "posixGroup", NULL };
mods[i]->mod_values = objectClasses;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "cn";
char *uids[] = { cn, NULL };
mods[i]->mod_values = uids;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "gidNumber";
char idno[16];
snprintf(idno, sizeof(idno), "%d", no);
char *gidNumbers[] = { idno, NULL };
mods[i]->mod_values = gidNumbers;
mods[++i] = NULL;
char dn[1024];
snprintf(dn, sizeof(dn), "cn=%s,%s", cn, basedn);
if (ldap_add_s(ld, dn, mods) != LDAP_SUCCESS) {
ldap_err("addgroup");
ret = -1;
}
i = 0;
while (mods[i])
free(mods[i++]);
return ret;
}
int ceo_add_user(char *uid, char *basedn, char *objclass, char *cn, char *home, char *shell, int no, ...) {
va_list args;
if (!uid || !basedn || !cn || !home || !shell)
fatal("adduser: Invalid argument");
LDAPMod *mods[16];
int i = -1;
int ret = 0;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "objectClass";
char *objectClasses[] = { "top", "account", "posixAccount", "shadowAccount", NULL, NULL };
if (objclass != NULL)
objectClasses[4] = objclass;
mods[i]->mod_values = objectClasses;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "uid";
char *uids[] = { uid, NULL };
mods[i]->mod_values = uids;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "cn";
char *cns[] = { cn, NULL };
mods[i]->mod_values = cns;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "uidNumber";
char idno[16];
snprintf(idno, sizeof(idno), "%d", no);
char *uidNumbers[] = { idno, NULL };
mods[i]->mod_values = uidNumbers;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "gidNumber";
mods[i]->mod_values = uidNumbers;
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = "homeDirectory";
char *homeDirectory[] = { home, NULL };
mods[i]->mod_values = homeDirectory;
va_start(args, no);
char *attr;
while ((attr = va_arg(args, char *))) {
char *val = va_arg(args, char *);
if (!val || !*val)
continue;
if (i == sizeof(mods) / sizeof(*mods) - 2) {
error("too many attributes");
return -1;
}
mods[++i] = xmalloc(sizeof(LDAPMod));
mods[i]->mod_op = LDAP_MOD_ADD;
mods[i]->mod_type = attr;
char *vals[] = { val, NULL };
mods[i]->mod_values = vals;
}
mods[++i] = NULL;
char dn[1024];
snprintf(dn, sizeof(dn), "uid=%s,%s", uid, basedn);
if (ldap_add_s(ld, dn, mods) != LDAP_SUCCESS) {
ldap_err("adduser");
ret = -1;
}
i = 0;
while (mods[i])
free(mods[i++]);
return ret;
}
int ceo_new_uid(int min, int max) {
char filter[64];
char *attrs[] = { LDAP_NO_ATTRS, NULL };
LDAPMessage *res;
int i;
for (i = min; i <= max; i++) {
// id taken due to passwd
if (getpwuid(i) != NULL)
continue;
// id taken due to group
if (getgrgid(i) != NULL)
continue;
snprintf(filter, sizeof(filter), "(|(uidNumber=%d)(gidNumber=%d))", i, i);
if (ldap_search_s(ld, users_base, LDAP_SCOPE_SUBTREE, filter, attrs, 1, &res) != LDAP_SUCCESS) {
ldap_err("firstuid");
return -1;
}
int count = ldap_count_entries(ld, res);
ldap_msgfree(res);
// id taken due to LDAP
if (count)
continue;
return i;
}
return -1;
}
int ceo_add_club(char *uid, char *cn) {
int id = ceo_new_uid(club_min_id, club_max_id);
if (ceo_add_user(uid, users_base, "club", cn, club_home, club_shell, id, NULL))
return -1;
if (ceo_add_group(uid, groups_base, id))
return -1;
return 0;
}
int ceo_user_exists(char *uid) {
char *attrs[] = { LDAP_NO_ATTRS, NULL };
LDAPMessage *msg = NULL;
char filter[128];
int count;
if (!uid)
fatal("null uid");
snprintf(filter, sizeof(filter), "uid=%s", uid);
if (ldap_search_s(ld, users_base, LDAP_SCOPE_SUBTREE, filter, attrs, 0, &msg) != LDAP_SUCCESS) {
ldap_err("user_exists");
return -1;
}
count = ldap_count_entries(ld, msg);
ldap_msgfree(msg);
return count > 0;
}
static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *in) {
sasl_interact_t *interact = in;
while (interact->id != SASL_CB_LIST_END) {
switch (interact->id) {
// GSSAPI doesn't require any callbacks
default:
interact->result = "";
interact->len = 0;
}
interact++;
}
return LDAP_SUCCESS;
}
void ceo_ldap_init() {
int proto = LDAP_DEFAULT_PROTOCOL;
if (ldap_initialize(&ld, server_url) != LDAP_SUCCESS)
ldap_fatal("ldap_initialize");
if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) != LDAP_OPT_SUCCESS)
ldap_fatal("ldap_set_option");
ceo_krb5_auth(admin_bind_userid, admin_bind_keytab);
if (ldap_sasl_interactive_bind_s(ld, NULL, sasl_mech, NULL, NULL,
LDAP_SASL_QUIET, &ldap_sasl_interact, NULL) != LDAP_SUCCESS)
ldap_fatal("Bind failed");
ceo_krb5_deauth();
}
void ceo_ldap_cleanup() {
ldap_unbind(ld);
}

10
src/ldap.h Normal file
View File

@ -0,0 +1,10 @@
#define LDAP_DEFAULT_PROTOCOL LDAP_VERSION3
int ceo_add_user(char *, char *, char *, char *, char *, char *, int, ...);
int ceo_add_group(char *, char *, int);
int ceo_new_uid(int, int);
void ceo_ldap_init();
void ceo_ldap_cleanup();
int ceo_user_exists(char *);

214
src/parser.c Normal file
View File

@ -0,0 +1,214 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include "parser.h"
#include "util.h"
#include "config.h"
#define VAR_MAX 256
void config_var(const char *, const char *);
struct config_file {
FILE *p;
char *name;
int line;
struct config_file *parent;
int comment;
};
static void parse_config_file(char *, struct config_file *);
static void parse_error(struct config_file *file, char *msg) {
fatal("parse error on line %d of %s: %s", file->line, file->name, msg);
}
static int parse_char(struct config_file *file) {
int c = getc(file->p);
if (c == '\n')
(file->line)++;
return c;
}
static void unparse_char(struct config_file *file, int c) {
if (c == EOF)
return;
ungetc(c, file->p);
if (c == '\n')
(file->line)--;
}
static void parse_name(struct config_file *file, char *name, size_t maxlen) {
int len = 0;
int c;
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n') {
unparse_char(file, c);
break;
}
if (!isalpha(c) && c != '_' && c != '-') {
unparse_char(file, c);
break;
}
if (len == maxlen - 1)
parse_error(file, "max name length exceeded");
name[len++] = c;
}
if (len == 0)
parse_error(file, "expected name");
name[len++] = '\0';
}
static void parse_value(struct config_file *file, char *value, size_t maxlen) {
int len = 0;
int quote = 0;
int comment = 0;
int space = 0;
int c;
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n')
break;
if (c == '#')
comment = 1;
if ((isspace(c) && !quote) || comment) {
space = 1;
continue;
}
if (c == '"') {
quote = ! quote;
continue;
}
if (len == maxlen - space - 1)
parse_error(file, "max value length exceeded");
if (space && len) {
value[len++] = ' ';
}
space = 0;
value[len++] = c;
}
if (quote)
parse_error(file, "unbalanced quotes");
value[len++] = '\0';
}
static void parse_include(struct config_file *file) {
char path[PATH_MAX];
struct config_file *parent = file->parent;
parse_value(file, path, sizeof(path));
while (parent != NULL) {
if (!strcmp(file->name, parent->name))
return;
parent = parent->parent;
}
parse_config_file(path, file);
}
static void parse_config(struct config_file *file) {
int c;
int comment = 0;
char var[VAR_MAX];
char value[VAR_MAX];
for (;;) {
c = parse_char(file);
if (c == '\n') {
comment = 0;
continue;
}
if (c == EOF)
return;
if (isspace(c) | comment)
continue;
if (c == '#') {
comment = 1;
continue;
}
unparse_char(file, c);
parse_name(file, var, sizeof(var));
if (!strcmp(var, "include")) {
parse_include(file);
continue;
}
for (;;) {
c = parse_char(file);
if (c == EOF || c == '\n')
parse_error(file, "expected '=' before line end");
if (c == '=')
break;
if (!isspace(c))
parse_error(file, "expected '='");
}
parse_value(file, value, sizeof(value));
config_var(var, value);
}
}
static void parse_config_file(char *name, struct config_file *parent) {
struct config_file file;
file.p = fopen(name, "r");
file.name = name;
file.line = 1;
file.parent = parent;
if (!file.p) {
if (parent)
parse_error(parent, strerror(errno));
else
fatal("failed to open configuration file '%s': %s", name, strerror(errno));
}
parse_config(&file);
fclose(file.p);
}
long config_long(char *var, char *val) {
char *endptr;
long longval;
longval = strtol(val, &endptr, 0);
if (*val == '\0' || *endptr != '\0')
fatal("expected integer value for %s", var);
return longval;
}
void config_parse(char *filename) {
parse_config_file(filename, NULL);
}

2
src/parser.h Normal file
View File

@ -0,0 +1,2 @@
void config_parse(char *);
long config_long(char *var, char *val);

136
src/util.c Normal file
View File

@ -0,0 +1,136 @@
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <errno.h>
#include "util.h"
static char message[4096];
static void errmsg(int prio, const char *prefix, const char *fmt, va_list args) {
char *msgp = message;
msgp += snprintf(msgp, sizeof(message) - 2 - (msgp - message), "%s: ", prefix);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
msgp += vsnprintf(msgp, sizeof(message) - 2 - (msgp - message), fmt, args);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
*msgp++ = '\n';
*msgp++ = '\0';
syslog(prio, "%s", message);
fputs(message, stderr);
}
static void errmsgpe(int prio, const char *prefix, const char *fmt, va_list args) {
char *msgp = message;
msgp += snprintf(msgp, sizeof(message) - 2 - (msgp - message), "%s: ", prefix);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
msgp += vsnprintf(msgp, sizeof(message) - 2 - (msgp - message), fmt, args);
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
msgp += snprintf(msgp, sizeof(message) - 2 - (msgp - message), ": %s", strerror(errno));
if (msgp - message > sizeof(message) - 2)
fatal("error message overflowed");
*msgp++ = '\n';
*msgp++ = '\0';
syslog(prio, "%s", message);
fputs(message, stderr);
}
NORETURN static void die(int prio, const char *prefix, const char *msg, va_list args) {
errmsg(prio, prefix, msg, args);
exit(1);
}
NORETURN static void diepe(int prio, const char *prefix, const char *msg, va_list args) {
errmsgpe(prio, prefix, msg, args);
exit(1);
}
NORETURN void fatal(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_CRIT, "fatal", msg, args);
va_end(args);
}
void error(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_ERR, "error", msg, args);
va_end(args);
}
void warn(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsg(LOG_WARNING, "warning", msg, args);
va_end(args);
}
void logmsg(const char *msg, ...) {
va_list args;
va_start(args, msg);
vsyslog(LOG_ERR, msg, args);
va_end(args);
}
NORETURN void deny(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_ERR, "denied", msg, args);
va_end(args);
}
NORETURN void badconf(const char *msg, ...) {
va_list args;
va_start(args, msg);
die(LOG_CRIT, "configuration error", msg, args);
va_end(args);
}
NORETURN void fatalpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
diepe(LOG_CRIT, "fatal", msg, args);
va_end(args);
}
void errorpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsgpe(LOG_ERR, "error", msg, args);
va_end(args);
}
void warnpe(const char *msg, ...) {
va_list args;
va_start(args, msg);
errmsgpe(LOG_WARNING, "warning", msg, args);
va_end(args);
}
int spawnv(const char *path, char *argv[]) {
int pid, status;
pid = fork();
if (pid == -1)
fatalpe("fork");
else if (pid)
waitpid(pid, &status, 0);
else
exit(execv(path, argv));
return status;
}

44
src/util.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef CEO_UTIL_H
#define CEO_UTIL_H
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#else
#define NORETURN
#endif
int spawnv(const char *, char *[]);
NORETURN void fatal(const char *, ...);
NORETURN void fatalpe(const char *, ...);
NORETURN void badconf(const char *, ...);
NORETURN void deny(const char *, ...);
void error(const char *, ...);
void warn(const char *, ...);
void logmsg(const char *, ...);
void errorpe(const char *, ...);
void warnpe(const char *, ...);
static inline void *xmalloc(size_t size) {
void *alloc = malloc(size);
if (alloc == NULL)
fatal("out of memory");
return alloc;
}
static inline char *xstrdup(const char *s) {
char *dup = strdup(s);
if (dup == NULL)
fatal("out of memory");
return dup;
}
#endif