#include <stdio.h>
#include <syslog.h>
#include <sys/param.h>
#include <gssapi/gssapi_generic.h>
#include "k5-int.h"
#include <kadm5/server_internal.h>
#include <kadm5/admin.h>
#include "adm_proto.h"
#include "server_acl.h"
#include <ctype.h>
typedef struct _acl_op_table {
char ao_op;
krb5_int32 ao_mask;
} aop_t;
typedef struct _acl_entry {
struct _acl_entry *ae_next;
char *ae_name;
krb5_boolean ae_name_bad;
krb5_principal ae_principal;
krb5_int32 ae_op_allowed;
char *ae_target;
krb5_boolean ae_target_bad;
krb5_principal ae_target_princ;
char *ae_restriction_string;
krb5_boolean ae_restriction_bad;
restriction_t *ae_restrictions;
} aent_t;
static const aop_t acl_op_table[] = {
{ 'a', ACL_ADD },
{ 'd', ACL_DELETE },
{ 'm', ACL_MODIFY },
{ 'c', ACL_CHANGEPW },
{ 'i', ACL_INQUIRE },
{ 'l', ACL_LIST },
{ 's', ACL_SETKEY },
{ 'x', ACL_ALL_MASK },
{ '*', ACL_ALL_MASK },
{ '\0', 0 }
};
typedef struct _wildstate {
int nwild;
krb5_data *backref[9];
} wildstate_t;
static aent_t *acl_list_head = (aent_t *) NULL;
static aent_t *acl_list_tail = (aent_t *) NULL;
static const char *acl_acl_file = (char *) NULL;
static int acl_inited = 0;
static int acl_debug_level = 0;
static const char *acl_catchall_entry = NULL;
static const char *acl_line2long_msg = "%s: line %d too long, truncated";
static const char *acl_op_bad_msg = "Unrecognized ACL operation '%c' in %s";
static const char *acl_syn_err_msg = "%s: syntax error at line %d <%10s...>";
static const char *acl_cantopen_msg = "%s while opening ACL file %s";
static char *
kadm5int_acl_get_line(fp, lnp)
FILE *fp;
int *lnp;
{
int i, domore;
static int line_incr = 0;
static char acl_buf[BUFSIZ];
*lnp += line_incr;
line_incr = 0;
for (domore = 1; domore && !feof(fp); ) {
for (i=0; ((i < sizeof acl_buf) && !feof(fp)); i++ ) {
acl_buf[i] = fgetc(fp);
if (acl_buf[i] == (char)EOF) {
if (i > 0 && acl_buf[i-1] == '\\')
i--;
break;
}
else if (acl_buf[i] == '\n') {
if (i == 0 || acl_buf[i-1] != '\\')
break;
else {
i -= 2;
line_incr++;
}
}
}
if (i == sizeof acl_buf && (i--, !feof(fp))) {
int c1 = acl_buf[i], c2;
krb5_klog_syslog(LOG_ERR, acl_line2long_msg, acl_acl_file, *lnp);
while ((c2 = fgetc(fp)) != EOF) {
if (c2 == '\n') {
if (c1 != '\\')
break;
line_incr++;
}
c1 = c2;
}
}
acl_buf[i] = '\0';
if (acl_buf[0] == (char) EOF)
acl_buf[0] = '\0';
else
line_incr++;
if ((acl_buf[0] != '#') && (acl_buf[0] != '\0'))
domore = 0;
}
if (domore || (strlen(acl_buf) == 0))
return((char *) NULL);
else
return(acl_buf);
}
static aent_t *
kadm5int_acl_parse_line(lp)
const char *lp;
{
static char acle_principal[BUFSIZ];
static char acle_ops[BUFSIZ];
static char acle_object[BUFSIZ];
static char acle_restrictions[BUFSIZ];
aent_t *acle;
char *op;
int t, found, opok, nmatch;
DPRINT(DEBUG_CALLS, acl_debug_level,
("* kadm5int_acl_parse_line(line=%20s)\n", lp));
acle = (aent_t *) NULL;
acle_object[0] = '\0';
nmatch = sscanf(lp, "%s %s %s %[^\n]", acle_principal, acle_ops,
acle_object, acle_restrictions);
if (nmatch >= 2) {
acle = (aent_t *) malloc(sizeof(aent_t));
if (acle) {
acle->ae_next = (aent_t *) NULL;
acle->ae_op_allowed = (krb5_int32) 0;
acle->ae_target =
(nmatch >= 3) ? strdup(acle_object) : (char *) NULL;
acle->ae_target_bad = 0;
acle->ae_target_princ = (krb5_principal) NULL;
opok = 1;
for (op=acle_ops; *op; op++) {
char rop;
rop = (isupper((unsigned char) *op)) ? tolower((unsigned char) *op) : *op;
found = 0;
for (t=0; acl_op_table[t].ao_op; t++) {
if (rop == acl_op_table[t].ao_op) {
found = 1;
if (rop == *op)
acle->ae_op_allowed |= acl_op_table[t].ao_mask;
else
acle->ae_op_allowed &= ~acl_op_table[t].ao_mask;
}
}
if (!found) {
krb5_klog_syslog(LOG_ERR, acl_op_bad_msg, *op, lp);
opok = 0;
}
}
if (opok) {
acle->ae_name = (char *) malloc(strlen(acle_principal)+1);
if (acle->ae_name) {
strcpy(acle->ae_name, acle_principal);
acle->ae_principal = (krb5_principal) NULL;
acle->ae_name_bad = 0;
DPRINT(DEBUG_ACL, acl_debug_level,
("A ACL entry %s -> opmask %x\n",
acle->ae_name, acle->ae_op_allowed));
}
else {
if (acle->ae_target)
free(acle->ae_target);
free(acle);
acle = (aent_t *) NULL;
}
}
else {
if (acle->ae_target)
free(acle->ae_target);
free(acle);
acle = (aent_t *) NULL;
}
if ( nmatch >= 4 ) {
char *trailing;
trailing = &acle_restrictions[strlen(acle_restrictions)-1];
while ( isspace((int) *trailing) )
trailing--;
trailing[1] = '\0';
acle->ae_restriction_string = strdup(acle_restrictions);
}
else {
acle->ae_restriction_string = (char *) NULL;
}
acle->ae_restriction_bad = 0;
acle->ae_restrictions = (restriction_t *) NULL;
}
}
DPRINT(DEBUG_CALLS, acl_debug_level,
("X kadm5int_acl_parse_line() = %x\n", (long) acle));
return(acle);
}
static krb5_error_code
kadm5int_acl_parse_restrictions(s, rpp)
char *s;
restriction_t **rpp;
{
char *sp, *tp, *ap;
static const char *delims = "\t\n\f\v\r ,";
krb5_deltat dt;
krb5_flags flag;
krb5_error_code code;
DPRINT(DEBUG_CALLS, acl_debug_level,
("* kadm5int_acl_parse_restrictions(s=%20s, rpp=0x%08x)\n", s, (long)rpp));
*rpp = (restriction_t *) NULL;
code = 0;
if (s) {
if (!(sp = strdup(s))
|| !(*rpp = (restriction_t *) malloc(sizeof(restriction_t)))) {
code = ENOMEM;
} else {
memset(*rpp, 0, sizeof(**rpp));
for (tp=strtok(sp, delims); tp; tp=strtok((char *)NULL, delims)) {
flag = 0;
if (!krb5_string_to_flags(tp, "+", "-", &flag)) {
if (flag) {
(*rpp)->require_attrs |= flag;
} else {
flag = ~0;
(void) krb5_string_to_flags(tp, "+", "-", &flag);
(*rpp)->forbid_attrs |= ~flag;
}
(*rpp)->mask |= KADM5_ATTRIBUTES;
} else if (!strcmp(tp, "-clearpolicy")) {
(*rpp)->mask |= KADM5_POLICY_CLR;
} else {
if (!(ap = strtok((char *)NULL, delims))) {
code = EINVAL;
break;
}
if (!strcmp(tp, "-policy")) {
if (!((*rpp)->policy = strdup(ap))) {
code = ENOMEM;
break;
}
(*rpp)->mask |= KADM5_POLICY;
} else {
if (krb5_string_to_deltat(ap, &dt)) {
code = EINVAL;
break;
}
if (!strcmp(tp, "-expire")) {
(*rpp)->princ_lifetime = dt;
(*rpp)->mask |= KADM5_PRINC_EXPIRE_TIME;
} else if (!strcmp(tp, "-pwexpire")) {
(*rpp)->pw_lifetime = dt;
(*rpp)->mask |= KADM5_PW_EXPIRATION;
} else if (!strcmp(tp, "-maxlife")) {
(*rpp)->max_life = dt;
(*rpp)->mask |= KADM5_MAX_LIFE;
} else if (!strcmp(tp, "-maxrenewlife")) {
(*rpp)->max_renewable_life = dt;
(*rpp)->mask |= KADM5_MAX_RLIFE;
} else {
code = EINVAL;
break;
}
}
}
}
}
}
if (sp)
free(sp);
if (*rpp && code) {
if ((*rpp)->policy)
free((*rpp)->policy);
free(*rpp);
*rpp = (restriction_t *) NULL;
}
DPRINT(DEBUG_CALLS, acl_debug_level,
("X kadm5int_acl_parse_restrictions() = %d, mask=0x%08x\n",
code, (*rpp) ? (*rpp)->mask : 0));
return code;
}
krb5_error_code
kadm5int_acl_impose_restrictions(kcontext, recp, maskp, rp)
krb5_context kcontext;
kadm5_principal_ent_rec *recp;
long *maskp;
restriction_t *rp;
{
krb5_error_code code;
krb5_int32 now;
DPRINT(DEBUG_CALLS, acl_debug_level,
("* kadm5int_acl_impose_restrictions(..., *maskp=0x%08x, rp=0x%08x)\n",
*maskp, (long)rp));
if (!rp)
return 0;
if (rp->mask & (KADM5_PRINC_EXPIRE_TIME|KADM5_PW_EXPIRATION))
if ((code = krb5_timeofday(kcontext, &now)))
return code;
if (rp->mask & KADM5_ATTRIBUTES) {
recp->attributes |= rp->require_attrs;
recp->attributes &= ~(rp->forbid_attrs);
*maskp |= KADM5_ATTRIBUTES;
}
if (rp->mask & KADM5_POLICY_CLR) {
*maskp &= ~KADM5_POLICY;
*maskp |= KADM5_POLICY_CLR;
} else if (rp->mask & KADM5_POLICY) {
if (recp->policy && strcmp(recp->policy, rp->policy)) {
free(recp->policy);
recp->policy = (char *) NULL;
}
if (!recp->policy) {
recp->policy = strdup(rp->policy);
if (!recp->policy)
return ENOMEM;
}
*maskp |= KADM5_POLICY;
}
if (rp->mask & KADM5_PRINC_EXPIRE_TIME) {
if (!(*maskp & KADM5_PRINC_EXPIRE_TIME)
|| (recp->princ_expire_time > (now + rp->princ_lifetime)))
recp->princ_expire_time = now + rp->princ_lifetime;
*maskp |= KADM5_PRINC_EXPIRE_TIME;
}
if (rp->mask & KADM5_PW_EXPIRATION) {
if (!(*maskp & KADM5_PW_EXPIRATION)
|| (recp->pw_expiration > (now + rp->pw_lifetime)))
recp->pw_expiration = now + rp->pw_lifetime;
*maskp |= KADM5_PW_EXPIRATION;
}
if (rp->mask & KADM5_MAX_LIFE) {
if (!(*maskp & KADM5_MAX_LIFE)
|| (recp->max_life > rp->max_life))
recp->max_life = rp->max_life;
*maskp |= KADM5_MAX_LIFE;
}
if (rp->mask & KADM5_MAX_RLIFE) {
if (!(*maskp & KADM5_MAX_RLIFE)
|| (recp->max_renewable_life > rp->max_renewable_life))
recp->max_renewable_life = rp->max_renewable_life;
*maskp |= KADM5_MAX_RLIFE;
}
DPRINT(DEBUG_CALLS, acl_debug_level,
("X kadm5int_acl_impose_restrictions() = 0, *maskp=0x%08x\n", *maskp));
return 0;
}
static void
kadm5int_acl_free_entries()
{
aent_t *ap;
aent_t *np;
DPRINT(DEBUG_CALLS, acl_debug_level, ("* kadm5int_acl_free_entries()\n"));
for (ap=acl_list_head; ap; ap = np) {
if (ap->ae_name)
free(ap->ae_name);
if (ap->ae_principal)
krb5_free_principal((krb5_context) NULL, ap->ae_principal);
if (ap->ae_target)
free(ap->ae_target);
if (ap->ae_target_princ)
krb5_free_principal((krb5_context) NULL, ap->ae_target_princ);
if (ap->ae_restriction_string)
free(ap->ae_restriction_string);
if (ap->ae_restrictions) {
if (ap->ae_restrictions->policy)
free(ap->ae_restrictions->policy);
free(ap->ae_restrictions);
}
np = ap->ae_next;
free(ap);
}
acl_list_head = acl_list_tail = (aent_t *) NULL;
acl_inited = 0;
DPRINT(DEBUG_CALLS, acl_debug_level, ("X kadm5int_acl_free_entries()\n"));
}
static int
kadm5int_acl_load_acl_file()
{
FILE *afp;
char *alinep;
aent_t **aentpp;
int alineno;
int retval = 1;
DPRINT(DEBUG_CALLS, acl_debug_level, ("* kadm5int_acl_load_acl_file()\n"));
afp = fopen(acl_acl_file, "r");
if (afp) {
alineno = 1;
aentpp = &acl_list_head;
while ((alinep = kadm5int_acl_get_line(afp, &alineno))) {
*aentpp = kadm5int_acl_parse_line(alinep);
if (!*aentpp) {
krb5_klog_syslog(LOG_ERR, acl_syn_err_msg,
acl_acl_file, alineno, alinep);
retval = 0;
break;
}
acl_list_tail = *aentpp;
aentpp = &(*aentpp)->ae_next;
}
fclose(afp);
if (acl_catchall_entry) {
*aentpp = kadm5int_acl_parse_line(acl_catchall_entry);
if (*aentpp) {
acl_list_tail = *aentpp;
}
else {
retval = 0;
DPRINT(DEBUG_OPERATION, acl_debug_level,
("> catchall acl entry (%s) load failed\n",
acl_catchall_entry));
}
}
}
else {
krb5_klog_syslog(LOG_ERR, acl_cantopen_msg,
error_message(errno), acl_acl_file);
if (acl_catchall_entry &&
(acl_list_head = kadm5int_acl_parse_line(acl_catchall_entry))) {
acl_list_tail = acl_list_head;
}
else {
retval = 0;
DPRINT(DEBUG_OPERATION, acl_debug_level,
("> catchall acl entry (%s) load failed\n",
acl_catchall_entry));
}
}
if (!retval) {
kadm5int_acl_free_entries();
}
DPRINT(DEBUG_CALLS, acl_debug_level,
("X kadm5int_acl_load_acl_file() = %d\n", retval));
return(retval);
}
static krb5_boolean
kadm5int_acl_match_data(e1, e2, targetflag, ws)
krb5_data *e1, *e2;
int targetflag;
wildstate_t *ws;
{
krb5_boolean retval;
DPRINT(DEBUG_CALLS, acl_debug_level,
("* acl_match_entry(%s, %s)\n", e1->data, e2->data));
retval = 0;
if (!strncmp(e1->data, "*", e1->length)) {
retval = 1;
if (ws && !targetflag) {
if (ws->nwild >= 9) {
DPRINT(DEBUG_ACL, acl_debug_level,
("Too many wildcards in ACL entry %s\n", entry->ae_name));
}
else
ws->backref[ws->nwild++] = e2;
}
}
else if (ws && targetflag && (e1->length == 2) && (e1->data[0] == '*') &&
(e1->data[1] >= '1') && (e1->data[1] <= '9')) {
int n = e1->data[1] - '1';
if (n >= ws->nwild) {
DPRINT(DEBUG_ACL, acl_debug_level,
("Too many backrefs in ACL entry %s\n", entry->ae_name));
}
else if ((ws->backref[n]->length == e2->length) &&
(!strncmp(ws->backref[n]->data, e2->data, e2->length)))
retval = 1;
}
else {
if ((e1->length == e2->length) &&
(!strncmp(e1->data, e2->data, e1->length)))
retval = 1;
}
DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_match_entry()=%d\n",retval));
return(retval);
}
static aent_t *
kadm5int_acl_find_entry(kcontext, principal, dest_princ)
krb5_context kcontext;
krb5_principal principal;
krb5_principal dest_princ;
{
aent_t *entry;
krb5_error_code kret;
int i;
int matchgood;
wildstate_t state;
DPRINT(DEBUG_CALLS, acl_debug_level, ("* kadm5int_acl_find_entry()\n"));
memset((char *)&state, 0, sizeof state);
for (entry=acl_list_head; entry; entry = entry->ae_next) {
if (entry->ae_name_bad)
continue;
if (!strcmp(entry->ae_name, "*")) {
DPRINT(DEBUG_ACL, acl_debug_level, ("A wildcard ACL match\n"));
matchgood = 1;
}
else {
if (!entry->ae_principal && !entry->ae_name_bad) {
kret = krb5_parse_name(kcontext,
entry->ae_name,
&entry->ae_principal);
if (kret)
entry->ae_name_bad = 1;
}
if (entry->ae_name_bad) {
DPRINT(DEBUG_ACL, acl_debug_level,
("Bad ACL entry %s\n", entry->ae_name));
continue;
}
matchgood = 0;
if (kadm5int_acl_match_data(&entry->ae_principal->realm,
&principal->realm, 0, (wildstate_t *)0) &&
(entry->ae_principal->length == principal->length)) {
matchgood = 1;
for (i=0; i<principal->length; i++) {
if (!kadm5int_acl_match_data(&entry->ae_principal->data[i],
&principal->data[i], 0, &state)) {
matchgood = 0;
break;
}
}
}
}
if (!matchgood)
continue;
if (entry->ae_target && strcmp(entry->ae_target, "*")) {
if (!entry->ae_target_princ && !entry->ae_target_bad) {
kret = krb5_parse_name(kcontext, entry->ae_target,
&entry->ae_target_princ);
if (kret)
entry->ae_target_bad = 1;
}
if (entry->ae_target_bad) {
DPRINT(DEBUG_ACL, acl_debug_level,
("Bad target in ACL entry for %s\n", entry->ae_name));
entry->ae_name_bad = 1;
continue;
}
if (!dest_princ)
matchgood = 0;
else if (entry->ae_target_princ && dest_princ) {
if (kadm5int_acl_match_data(&entry->ae_target_princ->realm,
&dest_princ->realm, 1, (wildstate_t *)0) &&
(entry->ae_target_princ->length == dest_princ->length)) {
for (i=0; i<dest_princ->length; i++) {
if (!kadm5int_acl_match_data(&entry->ae_target_princ->data[i],
&dest_princ->data[i], 1, &state)) {
matchgood = 0;
break;
}
}
}
else
matchgood = 0;
}
}
if (!matchgood)
continue;
if (entry->ae_restriction_string
&& !entry->ae_restriction_bad
&& !entry->ae_restrictions
&& kadm5int_acl_parse_restrictions(entry->ae_restriction_string,
&entry->ae_restrictions)) {
DPRINT(DEBUG_ACL, acl_debug_level,
("Bad restrictions in ACL entry for %s\n", entry->ae_name));
entry->ae_restriction_bad = 1;
}
if (entry->ae_restriction_bad) {
entry->ae_name_bad = 1;
continue;
}
break;
}
DPRINT(DEBUG_CALLS, acl_debug_level, ("X kadm5int_acl_find_entry()=%x\n",entry));
return(entry);
}
krb5_error_code
kadm5int_acl_init(kcontext, debug_level, acl_file)
krb5_context kcontext;
int debug_level;
char *acl_file;
{
krb5_error_code kret;
kret = 0;
acl_debug_level = debug_level;
DPRINT(DEBUG_CALLS, acl_debug_level,
("* kadm5int_acl_init(afile=%s)\n",
((acl_file) ? acl_file : "(null)")));
acl_acl_file = (acl_file) ? acl_file : (char *) KRB5_DEFAULT_ADMIN_ACL;
acl_inited = kadm5int_acl_load_acl_file();
DPRINT(DEBUG_CALLS, acl_debug_level, ("X kadm5int_acl_init() = %d\n", kret));
return(kret);
}
void
kadm5int_acl_finish(kcontext, debug_level)
krb5_context kcontext;
int debug_level;
{
DPRINT(DEBUG_CALLS, acl_debug_level, ("* kadm5int_acl_finish()\n"));
kadm5int_acl_free_entries();
DPRINT(DEBUG_CALLS, acl_debug_level, ("X kadm5int_acl_finish()\n"));
}
krb5_boolean
kadm5int_acl_check(kcontext, caller, opmask, principal, restrictions)
krb5_context kcontext;
gss_name_t caller;
krb5_int32 opmask;
krb5_principal principal;
restriction_t **restrictions;
{
krb5_boolean retval;
aent_t *aentry;
gss_buffer_desc caller_buf;
gss_OID caller_oid;
OM_uint32 emaj, emin;
krb5_error_code code;
krb5_principal caller_princ;
DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_op_permitted()\n"));
if (GSS_ERROR(emaj = gss_display_name(&emin, caller, &caller_buf,
&caller_oid)))
return(0);
code = krb5_parse_name(kcontext, (char *) caller_buf.value,
&caller_princ);
gss_release_buffer(&emin, &caller_buf);
if (code)
return(code);
retval = 0;
aentry = kadm5int_acl_find_entry(kcontext, caller_princ, principal);
if (aentry) {
if ((aentry->ae_op_allowed & opmask) == opmask) {
retval = 1;
if (restrictions) {
*restrictions =
(aentry->ae_restrictions && aentry->ae_restrictions->mask)
? aentry->ae_restrictions
: (restriction_t *) NULL;
}
}
}
krb5_free_principal(kcontext, caller_princ);
DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_op_permitted()=%d\n",
retval));
return(retval);
}
kadm5_ret_t
kadm5_get_privs(void *server_handle, long *privs)
{
CHECK_HANDLE(server_handle);
*privs = ~0;
return KADM5_OK;
}