#include "k5-int.h"
#include <syslog.h>
#include <kadm5/admin.h>
#include <krb5/kadm5_auth_plugin.h>
#include "adm_proto.h"
#include <ctype.h>
#include "auth.h"
#define ACL_ADD 1
#define ACL_DELETE 2
#define ACL_MODIFY 4
#define ACL_CHANGEPW 8
#define ACL_INQUIRE 32
#define ACL_EXTRACT 64
#define ACL_LIST 128
#define ACL_SETKEY 256
#define ACL_IPROP 512
#define ACL_ALL_MASK (ACL_ADD | \
ACL_DELETE | \
ACL_MODIFY | \
ACL_CHANGEPW | \
ACL_INQUIRE | \
ACL_LIST | \
ACL_IPROP | \
ACL_SETKEY)
struct acl_op_table {
char op;
uint32_t mask;
};
struct acl_entry {
struct acl_entry *next;
krb5_principal client;
uint32_t op_allowed;
krb5_principal target;
struct kadm5_auth_restrictions *rs;
};
static const struct acl_op_table acl_op_table[] = {
{ 'a', ACL_ADD },
{ 'd', ACL_DELETE },
{ 'm', ACL_MODIFY },
{ 'c', ACL_CHANGEPW },
{ 'i', ACL_INQUIRE },
{ 'l', ACL_LIST },
{ 'p', ACL_IPROP },
{ 's', ACL_SETKEY },
{ 'x', ACL_ALL_MASK },
{ '*', ACL_ALL_MASK },
{ 'e', ACL_EXTRACT },
{ '\0', 0 }
};
struct wildstate {
int nwild;
const krb5_data *backref[9];
};
struct acl_state {
struct acl_entry *list;
};
static char *
get_line(FILE *fp, const char *fname, int *lineno, int *incr)
{
const int chunksize = 128;
struct k5buf buf;
size_t old_len;
char *p;
*lineno += *incr;
*incr = 0;
k5_buf_init_dynamic(&buf);
for (;;) {
old_len = buf.len;
p = k5_buf_get_space(&buf, chunksize);
if (p == NULL)
return NULL;
if (fgets(p, chunksize, fp) == NULL) {
k5_buf_truncate(&buf, old_len);
if (buf.len > 0 && *(char *)buf.data != '#')
return buf.data;
k5_buf_free(&buf);
return NULL;
}
k5_buf_truncate(&buf, old_len + strlen(p));
p = buf.data;
if (buf.len > 0 && p[buf.len - 1] == '\n') {
(*incr)++;
k5_buf_truncate(&buf, buf.len - 1);
if (buf.len > 0 && p[buf.len - 1] == '\\') {
k5_buf_truncate(&buf, buf.len - 1);
} else if (buf.len == 0 || *p == '#') {
*lineno += *incr;
*incr = 0;
k5_buf_truncate(&buf, 0);
} else {
return k5_buf_cstring(&buf);
}
}
}
}
static struct kadm5_auth_restrictions *
parse_restrictions(const char *str, const char *fname)
{
char *copy = NULL, *token, *arg, *save;
const char *delims = "\t\n\f\v\r ,";
krb5_deltat delta;
struct kadm5_auth_restrictions *rs;
copy = strdup(str);
if (copy == NULL)
return NULL;
rs = calloc(1, sizeof(*rs));
if (rs == NULL) {
free(copy);
return NULL;
}
rs->forbid_attrs = ~(krb5_flags)0;
for (token = strtok_r(copy, delims, &save); token != NULL;
token = strtok_r(NULL, delims, &save)) {
if (krb5_flagspec_to_mask(token, &rs->require_attrs,
&rs->forbid_attrs) == 0) {
rs->mask |= KADM5_ATTRIBUTES;
continue;
}
if (strcmp(token, "-clearpolicy") == 0) {
rs->mask |= KADM5_POLICY_CLR;
continue;
}
arg = strtok_r(NULL, delims, &save);
if (arg == NULL)
goto error;
if (strcmp(token, "-policy") == 0) {
if (rs->policy != NULL)
goto error;
rs->policy = strdup(arg);
if (rs->policy == NULL)
goto error;
rs->mask |= KADM5_POLICY;
continue;
}
if (krb5_string_to_deltat(arg, &delta) != 0)
goto error;
if (strcmp(token, "-expire") == 0) {
rs->princ_lifetime = delta;
rs->mask |= KADM5_PRINC_EXPIRE_TIME;
} else if (strcmp(token, "-pwexpire") == 0) {
rs->pw_lifetime = delta;
rs->mask |= KADM5_PW_EXPIRATION;
} else if (strcmp(token, "-maxlife") == 0) {
rs->max_life = delta;
rs->mask |= KADM5_MAX_LIFE;
} else if (strcmp(token, "-maxrenewlife") == 0) {
rs->max_renewable_life = delta;
rs->mask |= KADM5_MAX_RLIFE;
} else {
goto error;
}
}
free(copy);
return rs;
error:
krb5_klog_syslog(LOG_ERR, _("%s: invalid restrictions: %s"), fname, str);
free(copy);
free(rs->policy);
free(rs);
return NULL;
}
static void
free_acl_entry(struct acl_entry *entry)
{
krb5_free_principal(NULL, entry->client);
krb5_free_principal(NULL, entry->target);
if (entry->rs != NULL) {
free(entry->rs->policy);
free(entry->rs);
}
free(entry);
}
static struct acl_entry *
parse_entry(krb5_context context, const char *client, const char *ops,
const char *target, const char *rs, const char *line,
const char *fname)
{
struct acl_entry *entry;
const char *op;
char rop;
int t;
entry = calloc(1, sizeof(*entry));
if (entry == NULL)
return NULL;
for (op = ops; *op; op++) {
rop = isupper((unsigned char)*op) ? tolower((unsigned char)*op) : *op;
for (t = 0; acl_op_table[t].op; t++) {
if (rop == acl_op_table[t].op) {
if (rop == *op)
entry->op_allowed |= acl_op_table[t].mask;
else
entry->op_allowed &= ~acl_op_table[t].mask;
break;
}
}
if (!acl_op_table[t].op) {
krb5_klog_syslog(LOG_ERR,
_("Unrecognized ACL operation '%c' in %s"),
*op, line);
goto error;
}
}
if (strcmp(client, "*") != 0) {
if (krb5_parse_name(context, client, &entry->client) != 0) {
krb5_klog_syslog(LOG_ERR, _("Cannot parse client principal '%s'"),
client);
goto error;
}
}
if (target != NULL && strcmp(target, "*") != 0) {
if (krb5_parse_name(context, target, &entry->target) != 0) {
krb5_klog_syslog(LOG_ERR, _("Cannot parse target principal '%s'"),
target);
goto error;
}
}
if (rs != NULL) {
entry->rs = parse_restrictions(rs, fname);
if (entry->rs == NULL)
goto error;
}
return entry;
error:
free_acl_entry(entry);
return NULL;
}
static struct acl_entry *
parse_line(krb5_context context, const char *line, const char *fname)
{
struct acl_entry *entry = NULL;
char *copy;
char *client, *client_end, *ops, *ops_end, *target, *target_end, *rs, *end;
const char *ws = "\t\n\f\v\r ,";
copy = strdup(line);
if (copy == NULL)
return NULL;
end = copy + strlen(copy);
while (end > copy && isspace(end[-1]))
*--end = '\0';
client = copy + strspn(copy, ws);
client_end = client + strcspn(client, ws);
ops = client_end + strspn(client_end, ws);
ops_end = ops + strcspn(ops, ws);
target = ops_end + strspn(ops_end, ws);
target_end = target + strcspn(target, ws);
rs = target_end + strspn(target_end, ws);
*client_end = *ops_end = *target_end = '\0';
if (*target == '\0')
target = NULL;
if (*rs == '\0')
rs = NULL;
if (*client != '\0' && *ops != '\0')
entry = parse_entry(context, client, ops, target, rs, line, fname);
free(copy);
return entry;
}
static void
free_acl_entries(struct acl_state *state)
{
struct acl_entry *entry, *next;
for (entry = state->list; entry != NULL; entry = next) {
next = entry->next;
free_acl_entry(entry);
}
state->list = NULL;
}
static krb5_error_code
load_acl_file(krb5_context context, const char *fname, struct acl_state *state)
{
krb5_error_code ret;
FILE *fp;
char *line;
struct acl_entry **entry_slot;
int lineno, incr;
state->list = NULL;
fp = fopen(fname, "r");
if (fp == NULL) {
krb5_klog_syslog(LOG_ERR, _("%s while opening ACL file %s"),
error_message(errno), fname);
ret = errno;
k5_setmsg(context, errno, _("Cannot open %s: %s"), fname,
error_message(ret));
return ret;
}
set_cloexec_file(fp);
lineno = 1;
incr = 0;
entry_slot = &state->list;
while ((line = get_line(fp, fname, &lineno, &incr)) != NULL) {
*entry_slot = parse_line(context, line, fname);
if (*entry_slot == NULL) {
krb5_klog_syslog(LOG_ERR,
_("%s: syntax error at line %d <%.10s...>"),
fname, lineno, line);
k5_setmsg(context, EINVAL,
_("%s: syntax error at line %d <%.10s...>"),
fname, lineno, line);
free_acl_entries(state);
free(line);
fclose(fp);
return EINVAL;
}
entry_slot = &(*entry_slot)->next;
free(line);
}
fclose(fp);
return 0;
}
static krb5_boolean
match_data(const krb5_data *e1, const krb5_data *e2, krb5_boolean targetflag,
struct wildstate *ws)
{
int n;
if (data_eq_string(*e1, "*")) {
if (ws != NULL && !targetflag) {
if (ws->nwild < 9)
ws->backref[ws->nwild++] = e2;
}
return TRUE;
}
if (ws != NULL && targetflag && e1->length == 2 && e1->data[0] == '*' &&
e1->data[1] >= '1' && e1->data[1] <= '9') {
n = e1->data[1] - '1';
if (n >= ws->nwild)
return FALSE;
return data_eq(*e2, *ws->backref[n]);
} else {
return data_eq(*e2, *e1);
}
}
static krb5_boolean
match_princ(krb5_const_principal p1, krb5_const_principal p2,
krb5_boolean targetflag, struct wildstate *ws)
{
int i;
if (p1->length != p2->length)
return FALSE;
if (!match_data(&p1->realm, &p2->realm, targetflag, NULL))
return FALSE;
for (i = 0; i < p1->length; i++) {
if (!match_data(&p1->data[i], &p2->data[i], targetflag, ws))
return FALSE;
}
return TRUE;
}
static struct acl_entry *
find_entry(struct acl_state *state, krb5_const_principal client,
krb5_const_principal target)
{
struct acl_entry *entry;
struct wildstate ws;
for (entry = state->list; entry != NULL; entry = entry->next) {
memset(&ws, 0, sizeof(ws));
if (entry->client != NULL) {
if (!match_princ(entry->client, client, FALSE, &ws))
continue;
}
if (entry->target != NULL) {
if (target == NULL)
continue;
if (!match_princ(entry->target, target, TRUE, &ws))
continue;
}
return entry;
}
return NULL;
}
static krb5_error_code
acl_check(kadm5_auth_moddata data, uint32_t op, krb5_const_principal client,
krb5_const_principal target, struct kadm5_auth_restrictions **rs_out)
{
struct acl_entry *entry;
if (rs_out != NULL)
*rs_out = NULL;
entry = find_entry((struct acl_state *)data, client, target);
if (entry == NULL)
return KRB5_PLUGIN_NO_HANDLE;
if (!(entry->op_allowed & op))
return KRB5_PLUGIN_NO_HANDLE;
if (rs_out != NULL && entry->rs != NULL && entry->rs->mask)
*rs_out = entry->rs;
return 0;
}
static krb5_error_code
acl_init(krb5_context context, const char *acl_file,
kadm5_auth_moddata *data_out)
{
krb5_error_code ret;
struct acl_state *state;
*data_out = NULL;
if (acl_file == NULL)
return KRB5_PLUGIN_NO_HANDLE;
state = malloc(sizeof(*state));
state->list = NULL;
ret = load_acl_file(context, acl_file, state);
if (ret) {
free(state);
return ret;
}
*data_out = (kadm5_auth_moddata)state;
return 0;
}
static void
acl_fini(krb5_context context, kadm5_auth_moddata data)
{
if (data == NULL)
return;
free_acl_entries((struct acl_state *)data);
free(data);
}
static krb5_error_code
acl_addprinc(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target,
const struct _kadm5_principal_ent_t *ent, long mask,
struct kadm5_auth_restrictions **rs_out)
{
return acl_check(data, ACL_ADD, client, target, rs_out);
}
static krb5_error_code
acl_modprinc(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target,
const struct _kadm5_principal_ent_t *ent, long mask,
struct kadm5_auth_restrictions **rs_out)
{
return acl_check(data, ACL_MODIFY, client, target, rs_out);
}
static krb5_error_code
acl_setstr(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target,
const char *key, const char *value)
{
return acl_check(data, ACL_MODIFY, client, target, NULL);
}
static krb5_error_code
acl_cpw(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_CHANGEPW, client, target, NULL);
}
static krb5_error_code
acl_chrand(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_CHANGEPW, client, target, NULL);
}
static krb5_error_code
acl_setkey(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_SETKEY, client, target, NULL);
}
static krb5_error_code
acl_purgekeys(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_MODIFY, client, target, NULL);
}
static krb5_error_code
acl_delprinc(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_DELETE, client, target, NULL);
}
static krb5_error_code
acl_renprinc(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal src,
krb5_const_principal dest)
{
struct kadm5_auth_restrictions *rs;
if (acl_check(data, ACL_DELETE, client, src, NULL) == 0 &&
acl_check(data, ACL_ADD, client, dest, &rs) == 0 && rs == NULL)
return 0;
return KRB5_PLUGIN_NO_HANDLE;
}
static krb5_error_code
acl_getprinc(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_INQUIRE, client, target, NULL);
}
static krb5_error_code
acl_getstrs(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_INQUIRE, client, target, NULL);
}
static krb5_error_code
acl_extract(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal target)
{
return acl_check(data, ACL_EXTRACT, client, target, NULL);
}
static krb5_error_code
acl_listprincs(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client)
{
return acl_check(data, ACL_LIST, client, NULL, NULL);
}
static krb5_error_code
acl_addpol(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, const char *policy,
const struct _kadm5_policy_ent_t *ent, long mask)
{
return acl_check(data, ACL_ADD, client, NULL, NULL);
}
static krb5_error_code
acl_modpol(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, const char *policy,
const struct _kadm5_policy_ent_t *ent, long mask)
{
return acl_check(data, ACL_MODIFY, client, NULL, NULL);
}
static krb5_error_code
acl_delpol(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, const char *policy)
{
return acl_check(data, ACL_DELETE, client, NULL, NULL);
}
static krb5_error_code
acl_getpol(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, const char *policy,
const char *client_policy)
{
return acl_check(data, ACL_INQUIRE, client, NULL, NULL);
}
static krb5_error_code
acl_listpols(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client)
{
return acl_check(data, ACL_LIST, client, NULL, NULL);
}
static krb5_error_code
acl_iprop(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client)
{
return acl_check(data, ACL_IPROP, client, NULL, NULL);
}
static krb5_error_code
acl_addalias(krb5_context context, kadm5_auth_moddata data,
krb5_const_principal client, krb5_const_principal alias,
krb5_const_principal target)
{
struct kadm5_auth_restrictions *rs;
if (acl_check(data, ACL_ADD, client, alias, &rs) == 0 && rs == NULL &&
acl_check(data, ACL_MODIFY, client, target, &rs) == 0)
return 0;
return KRB5_PLUGIN_NO_HANDLE;
}
krb5_error_code
kadm5_auth_acl_initvt(krb5_context context, int maj_ver, int min_ver,
krb5_plugin_vtable vtable)
{
kadm5_auth_vtable vt;
if (maj_ver != 1)
return KRB5_PLUGIN_VER_NOTSUPP;
vt = (kadm5_auth_vtable)vtable;
vt->name = "acl";
vt->init = acl_init;
vt->fini = acl_fini;
vt->addprinc = acl_addprinc;
vt->modprinc = acl_modprinc;
vt->setstr = acl_setstr;
vt->cpw = acl_cpw;
vt->chrand = acl_chrand;
vt->setkey = acl_setkey;
vt->purgekeys = acl_purgekeys;
vt->delprinc = acl_delprinc;
vt->renprinc = acl_renprinc;
vt->getprinc = acl_getprinc;
vt->getstrs = acl_getstrs;
vt->extract = acl_extract;
vt->listprincs = acl_listprincs;
vt->addpol = acl_addpol;
vt->modpol = acl_modpol;
vt->delpol = acl_delpol;
vt->getpol = acl_getpol;
vt->listpols = acl_listpols;
vt->iprop = acl_iprop;
vt->addalias = acl_addalias;
return 0;
}