/*
 * $Id: do_author.c,v 1.13 2006/12/13 01:11:37 heas Exp $
 *
 * Copyright (c) 1995-1998 by Cisco systems, Inc.
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that this
 * copyright and permission notice appear on all copies of the
 * software and supporting documentation, the name of Cisco Systems,
 * Inc. not be used in advertising or publicity pertaining to
 * distribution of the program without specific prior permission, and
 * notice be given in supporting documentation that modification,
 * copying and distribution is by permission of Cisco Systems, Inc.
 *
 * Cisco Systems, Inc. makes no representations about the suitability
 * of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
 * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "tac_plus.h"
#include "regexp.h"

static int arg_ok(char *);
static char *assemble_args(struct author_data *);
static int authorize_cmd(char *, char *, struct author_data *);
static int authorize_exec(char *, struct author_data *);
static int authorize_svc(char *, int, char *, char *, struct author_data *);
static int get_nas_svc(struct author_data *, char **, char **, char **);
static int is_separator(char);
static int mandatory(char *);
static int match_attrs(char *, char *);
static int match_values(char *, char *);
static int optional(char *);
static void post_authorization(char *, struct author_data *);
static int ppp_lcp_allowed(int, char *, char *);
static int pre_authorization(char *, struct author_data *);
static char *value(char *);


/* Return 0 is data->status is valid */
int
do_author(struct author_data *data)
{
    char *username = data->id->username;
    int status;
    int svc;
    char *cmd, *protocol, *svcname;

    status = 0;
    protocol = NULL;

    data->status = AUTHOR_STATUS_FAIL;	/* for safety */

    data->output_args = NULL;
    data->num_out_args = 0;

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "do_author: user='%s'", username);

    /* If this user doesn't exist in our configs, do the default */
    if (!cfg_user_exists(username) && !cfg_user_exists(DEFAULT_USERNAME)) {
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "do_author: user '%s' nor 'DEFAULT' exist",
		   username);

	if (cfg_no_user_permitted()) {
	    if (debug & DEBUG_AUTHOR_FLAG)
		report(LOG_DEBUG, "user '%s' or '%s' not found, permitted by "
		       "default", username, DEFAULT_USERNAME);

	    data->status = AUTHOR_STATUS_PASS_ADD;
	    data->output_args = NULL;
	    data->num_out_args = 0;
	    return(0);
	}

	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "user '%s' or '%s' not found, denied by default",
		   username, DEFAULT_USERNAME);
	data->status = AUTHOR_STATUS_FAIL;
	return(0);
    }

    if (!cfg_user_exists(username) && cfg_user_exists(DEFAULT_USERNAME)) {
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "Authorizing user '%s' instead of '%s'",
		   DEFAULT_USERNAME, username);
	}
	username = DEFAULT_USERNAME;
    }

    /* See if there's a program defined which will do authorization for us */
    if (pre_authorization(username, data))
	return(0);

    /*
     * Decide what kind of authorization request this is. Currently
     * one of: exec, cmd, slip, arap, ppp or <string>
     *
     * If it's a command typed to the exec, return its text in cmd.
     *
     * If it's a ppp request, return the protocol name in protocol.
     */
    svc = get_nas_svc(data, &cmd, &protocol, &svcname);

    if (!svc) {
	/* if we can't identify the service in the request it's an error */
	data->status = AUTHOR_STATUS_ERROR;
	data->admin_msg =
	tac_strdup("No identifiable service/protocol in authorization request");
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "user %s %s", username, data->admin_msg);
	}
	return(0);
    }

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "user '%s' found", username);

#ifdef MAXSESS
    /* Never permit if they're going over their session limit */
    switch (svc) {
    case N_svc_arap:
    case N_svc_ppp:
    case N_svc_slip:
    case N_svc_exec:
/*    case N_svc: */
	if (maxsess_check_count(username, data)) {
	    return(0);
	}

    default:
	break;
    }
#endif /* MAXSESS */

    switch(svc) {
/*  XXX
    default:
	report(LOG_ERR, "%s: Bad service type %d", session.peer, svc);
	data->status = AUTHOR_STATUS_FAIL;
	return(0);*/

    case N_svc_cmd:
	/* A command authorisation request */
	status = authorize_cmd(username, cmd, data);
	break;

    case N_svc_exec:
	if (authorize_exec(username, data))
	    return(0);
	/* FALLTHRU */

    case N_svc_arap:
    case N_svc_ppp:
    case N_svc_slip:
	status = authorize_svc(username, svc, protocol, NULL, data);
	break;

    case N_svc:
	status = authorize_svc(username, svc, protocol, svcname, data);
	break;
    }

    post_authorization(username, data);
    return(status);
}

/*
 * If an before-authorization program has been specified, call it.
 *
 * A return value of 1 means no further authorization is required
 */
static int
pre_authorization(char *username, struct author_data *data)
{
    int status;
    char **out_args;
    int out_cnt, i;
    char *cmd;
    char error_str[255];
    int error_len = 255;

    out_cnt = 0;
    out_args = NULL;

    /*
     * If a before-authorization program exists, call it to see how to
     * proceed
     */
    cmd = cfg_get_pvalue(username, TAC_IS_USER,
			 S_before, TAC_PLUS_RECURSE);
    if (!cmd)
	return(0);

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "Before authorization call: %s", cmd);

    status = call_pre_process(cmd, data, &out_args, &out_cnt, error_str,
			      error_len);

    switch (status) {
    default:
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns %d (unrecognised value)",
		   cmd, status);

	data->status = AUTHOR_STATUS_ERROR;
	data->admin_msg =
	    tac_strdup("Illegal return status from pre-authorization command");
	data->msg = tac_strdup(error_str);
	data->num_out_args = 0;
	data->output_args = NULL;
	/* throw away out_args */
	for (i = 0; i < out_cnt; i++) {
	    free(out_args[i]);
	}
	if (out_args) {
	    free(out_args);
	}
	return(1);

    case 0: /* Permit */
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns 0 (unconditional permit)", cmd);

	data->status = AUTHOR_STATUS_PASS_ADD;
	data->num_out_args = 0;
	data->output_args = NULL;

	/* throw away out_args */
	for (i = 0; i < out_cnt; i++) {
	    free(out_args[i]);
	}
	if (out_args) {
	    free(out_args);
	}
	return(1);

    case 1: /* Deny */
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns %d (unconditional deny)",
		   cmd, status);

	data->status = AUTHOR_STATUS_FAIL;
	data->msg = tac_strdup(error_str);
	data->num_out_args = 0;
	data->output_args = NULL;

	/* throw away out_args */
	for (i = 0; i < out_cnt; i++) {
	    free(out_args[i]);
	}
	if (out_args) {
	    free(out_args);
	}
	return(1);

    case 2: /* Use replacement AV pairs from program as final result */
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "cmd %s returns %d (permitted, args replaced)",
		   cmd, status);
	    for (i = 0; i < out_cnt; i++)
		report(LOG_DEBUG, "%s", out_args[i]);
	}

	/* and install the new set of AV pairs as output */
	data->output_args = out_args;
	data->num_out_args = out_cnt;
	data->status = AUTHOR_STATUS_PASS_REPL;
	return(1); /* no more processing required */

    case 3: /* deny, but return attributes and server-msg to NAS */
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "cmd %s returns %d (deny, args replaced)",
		   cmd, status);
	    for (i = 0; i < out_cnt; i++)
		report(LOG_DEBUG, "%s", out_args[i]);
	}

	/* and install the new set of AV pairs as output */
	data->output_args = out_args;
	data->num_out_args = out_cnt;
	data->msg = tac_strdup(error_str);
	data->status = AUTHOR_STATUS_FAIL;
	return(1); /* no more processing required */
    }
}

/* If an after-authorization program has been specified, call it. It
 * can rewrite the output arguments in the authorization data, or
 * change the authorization status by calling an external program.
 */
static void
post_authorization(char *username, struct author_data *data)
{
    char **out_args;
    int out_cnt, i;
    int status;
    char *after = cfg_get_pvalue(username, TAC_IS_USER,
				S_after, TAC_PLUS_RECURSE);
    if (!after)
	return;

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "After authorization call: %s", after);

    status = call_post_process(after, data, &out_args, &out_cnt);

    if (status != 2) {
	/* throw away out_args */
	for (i = 0; i < out_cnt; i++) {
	    free(out_args[i]);
	}
	free(out_args);
    }

    switch (status) {
    default:
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG,
		   "cmd %s returns %d (Error)", after, status);

	data->status = AUTHOR_STATUS_ERROR;
	return;

    case 0:				/* Permit */
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns 0 (no change)", after);
	return;

    case 1:				/* Deny */
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns %d (unconditional deny)",
		   after, status);

	data->status = AUTHOR_STATUS_FAIL;
	return;

    case 2:
	/* Use replacement AV pairs from program */
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s returns 2 (replace & continue)",
		   after);

	/* Free any existing AV output pairs */
	if (data->num_out_args) {
	    for (i = 0; i < data->num_out_args; i++) {
		free(data->output_args[i]);
	    }
	    free(data->output_args);
	    data->output_args = NULL;
	}

	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "status is now AUTHOR_STATUS_PASS_REPL");
	}

	data->status = AUTHOR_STATUS_PASS_REPL;
	data->output_args = out_args;
	data->num_out_args = out_cnt;
	return;
    }
}

/* Return a pointer to the value part of an attr=value string */
static char *
value(char *s)
{
    while (*s && *s != '=' && *s != '*')
	s++;
    if (*s)
	return(++s);
    return(NULL);
}

/*
 * Reassemble the command arguments as typed by the user, out of the
 * array of args we received. Return "" if there are no arguments.
 */
static char *
assemble_args(struct author_data *data)
{
    char *buf;
    int i;
    char *nas_arg, *v;
    int len;

    len = 0;
    for (i = 0; i < data->num_in_args; i++) {
	nas_arg = data->input_args[i];
	if (strncmp(nas_arg, "cmd-arg", strlen("cmd-arg"))==0)
	    len += strlen(value(nas_arg)) + 1;
    }

    if (len <= 0) {
	return(tac_strdup(""));
    }

    buf = tac_malloc(len);
    buf[0] = '\0';

    for (i = 0; i < data->num_in_args; i++) {
	nas_arg = data->input_args[i];
	if (strncmp(nas_arg, "cmd-arg", strlen("cmd-arg")))
	    continue;

	v = value(nas_arg);
	if (!v) {
	    free(buf);
	    return(NULL);
	}
	strcat(buf, v);
	if (i < (data->num_in_args - 1))
	    strcat(buf, " ");
    }
    return(buf);
}

/* See if an exec is authorized. Either the user has explicitly
 * authorized the exec, or she has authorized some commands (which
 * implicitly authorizes an exec), or the default is permit.
 *
 * If she has explicitly authorized an exec, we need to process its
 * attribute=value pairs. We indicate this by returning zero to the
 * caller.
 *
 * Otherwise, we return 1, indicating no further processing is
 * required for this request.
 */
static int
authorize_exec(char *user, struct author_data *data)
{
    NODE *svc;

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "exec authorization request for %s", user);

    /*
     * Is an exec explicitly configured? If so, return 0 so we know to process
     * its attributes
     */
    svc = cfg_get_svc_node(user, N_svc_exec, NULL, NULL, TAC_PLUS_RECURSE);
    if (svc) {
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "exec is explicitly permitted by line %d",
		   svc->line);
	return(0);
    }

    /* No exec is configured. Are any commands configured? */
    svc = cfg_get_svc_node(user, N_svc_cmd, NULL, NULL, TAC_PLUS_RECURSE);
    if (svc) {
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "exec permitted because commands are configured");

	data->status = AUTHOR_STATUS_PASS_ADD;
	data->output_args = NULL;
	data->num_out_args = 0;
	return(1);
    }

    /* No exec or commands configured. What's the default? */
    if (cfg_user_svc_default_is_permit(user)) {

	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "exec permitted by default");

	data->status = AUTHOR_STATUS_PASS_ADD;
	data->output_args = NULL;
	data->num_out_args = 0;
	return(1);
    }

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "exec denied by default");

    data->status = AUTHOR_STATUS_FAIL;
    data->num_out_args = 0;
    return(1);
}

/*
 * Is an exec command authorized per our database(s)?  Return 0 if status is
 * valid.
 */
static int
authorize_cmd(char *user, char *cmd, struct author_data *data)
{
    NODE *node;
    char *args;
    int match;

    args = assemble_args(data);

    if (!cmd) {
	data->status = AUTHOR_STATUS_ERROR;
	data->admin_msg = tac_strdup("No command found");
	report(LOG_ERR, "%s: %s %s", session.peer, cmd, data->admin_msg);
	data->num_out_args = 0;
	return(0);
    }

    if (debug & DEBUG_AUTHOR_FLAG)
	report(LOG_DEBUG, "authorize_cmd: user=%s, cmd=%s", user, cmd);

    node = cfg_get_cmd_node(user, cmd, TAC_PLUS_RECURSE);

    /* The command does not exist. Do the default */
    if (!node) {
	if (cfg_user_svc_default_is_permit(user)) {
	    if (debug & DEBUG_AUTHOR_FLAG)
		report(LOG_DEBUG, "cmd %s does not exist, permitted by default",
		       cmd);
	    data->status = AUTHOR_STATUS_PASS_ADD;
	    data->num_out_args = 0;
	    if (args)
		free(args);
	    return(0);
	}

	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "cmd %s does not exist, denied by default",
		   cmd);

	data->status = AUTHOR_STATUS_FAIL;
	data->num_out_args = 0;
	if (args)
	    free(args);
	return(0);
    }

    /* The command exists. The default if nothing matches is DENY */
    data->status = AUTHOR_STATUS_FAIL;
    data->num_out_args = 0;

    for (node = node->value1; node && args; node = node->next) {
	match = regexec((regexp *) node->value1, args);

	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_INFO, "line %d compare %s %s '%s' & '%s' %smatch",
		   node->line, cmd,
		   node->type == N_permit ? "permit" : "deny",
		   node->value, args, (match ? "" : "no "));
	}

	if (!match)
	    continue;

	switch (node->type) {
	case N_permit:
	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "%s %s permitted by line %d",
		       cmd, args, node->line);
	    }
	    data->status = AUTHOR_STATUS_PASS_ADD;
	    data->num_out_args = 0;
	    break;
	case N_deny:
	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "%s %s denied by line %d",
		       cmd, args, node->line);
	    }
	    data->status = AUTHOR_STATUS_FAIL;
	    data->num_out_args = 0;
	    break;
	default:
	    data->status = AUTHOR_STATUS_ERROR;
	    data->admin_msg = tac_strdup("Server error illegal configuration "
					 "node");
	    report(LOG_ERR, "%s: %s %s %s",
		   session.peer, cmd, args, data->admin_msg);
	    break;
	}
	if (args)
	    free(args);
	args = NULL;
	return(0);
    }
    if (args)
	free(args);
    return(0);
}

static int
is_separator(char ch)
{
    return(ch == '=' || ch == '*');
}

/* check an attr=value pair for well-formedness */
static int
arg_ok(char *arg)
{
    char *p = arg;

    /* It must contain an attribute */
    if (!*p)
	return(0);

    for (p = arg; *p; p++) {
	if (is_separator(*p)) {
	    if (p == arg) /* no attribute */
		return(0);
	    return(1);
	}
    }
    /* no separator */
    return(0);
}

/* return 1 if attrs match, 0 otherwise */
static int
match_attrs(char *nas_arg, char *server_arg)
{
    while (*nas_arg && *server_arg) {
	if (is_separator(*nas_arg) && is_separator(*server_arg)) {
	    return(1);
	}
	if (*nas_arg != *server_arg)
	    return(0);
	nas_arg++;
	server_arg++;
    }
    return(0);
}

/* return 1 if values match, 0 otherwise */
static int
match_values(char *nas_arg, char *server_arg)
{
    while (*nas_arg &&
	   *server_arg &&
	   !is_separator(*nas_arg)) {
	nas_arg++;
	server_arg++;
    }

    if (!*nas_arg)
	return(0);

    /* skip separator */
    nas_arg++;
    if (*server_arg)
	server_arg++;

    /* compare values */
    return(STREQ(nas_arg, server_arg));
}

/* Return 1 if arg is mandatory, 0 otherwise */
static int
mandatory(char *arg)
{
    char *p = arg;

    while (*p && !is_separator(*p))
	p++;

    /* if we're not at the end, this must be the separator */
    if (*p && !is_separator(*p)) {
	report(LOG_ERR, "%s: Error on arg %s cannot find separator",
	       session.peer, arg);
	return(0);
    }
    return(*p == '=');
}

static int
optional(char *arg)
{
    return(!mandatory(arg));
}

/*
 * PPP-LCP requests are a special case. If they are not explicitly configured,
 * but there are other ppp services explicitly configured, we admit (return 0)
 * any PPP-LCP request.
 */
static int
ppp_lcp_allowed(int svc, char *protocol, char *user)
{
    /* This is not a ppp/lcp request. Just Say No */
    if (!(svc == N_svc_ppp &&
	  protocol &&
	  STREQ(protocol, "lcp")))
	return(0);

    /* It is an LCP request. Are there PPP services configured */
    if (cfg_ppp_is_configured(user, TAC_PLUS_RECURSE)) {
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG,
		   "ppp/lcp request permitted (ppp is configured for %s)",
		   user);
	}
	return(1);
    }

    /* It is an LCP request but no PPP services are configured */
    if (debug & DEBUG_AUTHOR_FLAG) {
	report(LOG_DEBUG, "ppp/lcp request denied (ppp not configured for %s)",
	       user);
    }
    return(0);
}

/*
 * Return 0 means data->status is valid.
 * protocol is only valid if svc == ppp.
 */
static int
authorize_svc(char *user, int svc, char *protocol, char *svcname,
	      struct author_data *data)
{
    int max_args;
    char **out_args, **outp;
    char *nas_arg, *cfg_arg;
    int i, j;
    char **cfg_args;
    char **cfg_argp;
    int deny_by_default;
    NODE *node;

    int replaced = 0;
    int added = 0;
    int cfg_cnt;

    /* Does this service exist? */
    node = cfg_get_svc_node(user, svc, protocol, svcname, TAC_PLUS_RECURSE);

    if (!node) {
	/* Service not found. If the default is permit, or this is an
	 * PPP/LCP request and other ppp services are configured,
	 * we'll allow it. */

	if (cfg_user_svc_default_is_permit(user)) {

	    if (debug & DEBUG_AUTHOR_FLAG)
		report(LOG_DEBUG, "svc=%s protocol=%s svcname=%s not found, "
		       "permitted by default", cfg_nodestring(svc),
		       protocol ? protocol : "", svcname ? svcname : "");

	    data->status = AUTHOR_STATUS_PASS_ADD;
	    data->num_out_args = 0;
	    data->output_args = NULL;
	    return(0);
	}

	if (ppp_lcp_allowed(svc, protocol, user)) {
	    data->status = AUTHOR_STATUS_PASS_ADD;
	    data->num_out_args = 0;
	    data->output_args = NULL;
	    return(0);
	}

	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "svc=%s protocol=%s not found, denied by default",
		   cfg_nodestring(svc), protocol ? protocol : "");

	data->status = AUTHOR_STATUS_FAIL;
	data->num_out_args = 0;
	data->output_args = NULL;
	return(0);
    }

    /* Get server args configured in the config file. */
    cfg_args = cfg_get_svc_attrs(node, &deny_by_default);

    /* Check the nas args for well-formedness */
    for (i = 0; i < data->num_in_args; i++) {
	if (!arg_ok(data->input_args[i])) {
	    char buf[MAX_INPUT_LINE_LEN+50];
	    sprintf(buf, "Illegal arg %s from NAS", data->input_args[i]);
	    data->status = AUTHOR_STATUS_ERROR;
	    data->admin_msg = tac_strdup(buf);
	    report(LOG_ERR, "%s: Error %s", session.peer, buf);

	    /* free any server arguments */
	    for (cfg_argp = cfg_args; cfg_args && *cfg_argp; cfg_argp++)
		free(*cfg_argp);
	    free(cfg_args);
	    return(0);
	}
    }

    /* How many configured AV pairs are there ? */
    for (cfg_cnt = 0; cfg_args && cfg_args[cfg_cnt];)
	cfg_cnt++;

    /* Allocate space for in + out args */
    max_args = cfg_cnt + data->num_in_args;
    out_args = (char **) tac_malloc((max_args + 1) * sizeof(char *));
    outp = out_args;
    data->num_out_args = 0;

    bzero(out_args, (max_args + 1) * sizeof(char *));

    for (i = 0; i < data->num_in_args; i++) {
	nas_arg = data->input_args[i];

	/* always pass these pairs through unchanged */
	if (match_attrs(nas_arg, "service=") ||
	    match_attrs(nas_arg, "protocol=") ||
	    match_attrs(nas_arg, "cmd=")) {

	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "nas:%s (passed thru)", nas_arg);
	    }
	    *outp++ = tac_strdup(nas_arg);
	    data->num_out_args++;
	    continue;
	}

	/* NAS AV pair is mandatory */
	if (mandatory(nas_arg)) {
	    /*
	     * a). look for an exact attribute,value match in the daemon's
	     * mandatory list. If found, add the AV pair to the output
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (optional(cfg_arg))
		    continue;

		if (STREQ(nas_arg, cfg_arg)) {
		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s, svr:%s -> add %s (a)",
			       nas_arg, cfg_arg, nas_arg);
		    }
		    *outp++ = tac_strdup(nas_arg);
		    data->num_out_args++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * b). If an exact match doesn't exist, look in the daemon's
	     * optional list for the first attribute match. If found, add the
	     * NAS AV pair to the output
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (mandatory(cfg_arg))
		    continue;

		if (match_attrs(nas_arg, cfg_arg)) {
		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s, svr:%s -> add %s (b)",
			       nas_arg, cfg_arg, nas_arg);
		    }
		    *outp++ = tac_strdup(nas_arg);
		    data->num_out_args++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * c). If no attribute match exists, deny the command if the
	     * default is to deny
	     */
	    if (deny_by_default) {
		data->status = AUTHOR_STATUS_FAIL;
		if (debug & DEBUG_AUTHOR_FLAG) {
		    report(LOG_DEBUG, "nas:%s svr:absent, default=deny -> "
							"denied (c)", nas_arg);
		}
		if (out_args) {
		    for (i = 0; i < data->num_out_args; i++)
			free(out_args[i]);
		    free(out_args);
		}

		data->num_out_args = 0;
		data->output_args = NULL;

		/* free the server arguments */
		for (cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
		    free(*cfg_argp);
		free(cfg_args);
		return(0);
	    }

	    /*
	     * d). If the default is permit, add the NAS AV pair to the output
	     */
	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG,
		       "nas:%s, svr:absent, default=permit -> add %s (d)",
		       nas_arg, nas_arg);
	    }
	    *outp++ = tac_strdup(nas_arg);
	    data->num_out_args++;
	    goto next_nas_arg;

	} else {
	    /*
	     * NAS AV pair is Optional
	     *
	     * e). look for an exact attribute,value match in the mandatory
	     * list. If found, add DAEMON's AV pair to output
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (optional(cfg_arg))
		    continue;

		if (match_attrs(nas_arg, cfg_arg) &&
		    match_values(nas_arg, cfg_arg)) {

		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s "
					"(e)", nas_arg, cfg_arg, cfg_arg);
		    }
		    *outp++ = tac_strdup(cfg_arg);
		    data->num_out_args++;
		    replaced++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * f). If not found, look for the first attribute match in the
	     * mandatory list. If found, add DAEMONS's AV pair to output
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (optional(cfg_arg))
		    continue;

		if (match_attrs(nas_arg, cfg_arg)) {
		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s "
					"(f)", nas_arg, cfg_arg, cfg_arg);
		    }
		    *outp++ = tac_strdup(cfg_arg);
		    data->num_out_args++;
		    replaced++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * g). If no mandatory match exists, look for an exact
	     * attribute,value pair match among the daemon's optional AV
	     * pairs. If found add the DAEMON's matching AV pair to the
	     * output.
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (!optional(cfg_arg))
		    continue;

		if (match_attrs(nas_arg, cfg_arg) &&
		    match_values(nas_arg, cfg_arg)) {
		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s "
					"(g)", nas_arg, cfg_arg, cfg_arg);
		    }
		    *outp++ = tac_strdup(cfg_arg);
		    data->num_out_args++;
		    replaced++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * h). If no exact match exists, locate the first attribute match
	     * among the daemon's optional AV pairs. If found add the DAEMON's
	     * matching AV pair to the output
	     */
	    for (j = 0; j < cfg_cnt; j++) {
		cfg_arg = cfg_args[j];
		if (!optional(cfg_arg))
		    continue;

		if (match_attrs(nas_arg, cfg_arg)) {
		    if (debug & DEBUG_AUTHOR_FLAG) {
			report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s "
			       "(h)", nas_arg, cfg_arg, cfg_arg);
		    }
		    *outp++ = tac_strdup(cfg_arg);
		    data->num_out_args++;
		    replaced++;
		    goto next_nas_arg;
		}
	    }

	    /*
	     * i). If no match is found, delete the AV pair if default is deny
	     */
	    if (deny_by_default) {
		if (debug & DEBUG_AUTHOR_FLAG) {
		    report(LOG_DEBUG, "nas:%s svr:absent/deny -> delete %s (i)",
			   nas_arg, nas_arg);
		}
		replaced++;
		goto next_nas_arg;
	    }

	    /* j). If the default is permit add the NAS AV pair to the output */
	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "nas:%s svr:absent/permit -> add %s (j)",
		       nas_arg, nas_arg);
	    }
	    *outp++ = tac_strdup(nas_arg);
	    data->num_out_args++;
	    goto next_nas_arg;
	}
    next_nas_arg:;

    }

    /*
     * k). After all AV pairs have been processed, for each mandatory DAEMON
     * AV pair, if there is no attribute match already in the output list, add
     * the AV pair (add only one AV pair for each mandatory attribute)
     */
    for (i = 0; i < cfg_cnt; i++) {
	cfg_arg = cfg_args[i];

	if (!mandatory(cfg_arg))
	    continue;

	for (j = 0; j < data->num_out_args; j++) {
	    char *output_arg = out_args[j];

	    if (match_attrs(cfg_arg, output_arg)) {
		goto next_cfg_arg;
	    }
	}

	/* Attr is required by daemon but not present in output. Add it */
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "nas:absent, server:%s -> add %s (k)",
		   cfg_arg, cfg_arg);
	}
	added++;
	*outp++ = tac_strdup(cfg_arg);
	data->num_out_args++;

    next_cfg_arg:
	;
    }

    /*
     * If we replaced or deleted some pairs we must return the entire list we
     * have constructed
     */
    if (replaced) {
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "replaced %d args", replaced);
	}
	data->status = AUTHOR_STATUS_PASS_REPL;
	data->output_args = out_args;

	/* free the server arguments */
	for (cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
	    free(*cfg_argp);
	free(cfg_args);

	return(0);
    }

    /*
     * We added something not on the original nas list, but did not replace or
     * delete anything. We should return only the additions
     */
    if (added) {
	if (debug & DEBUG_AUTHOR_FLAG)
	    report(LOG_DEBUG, "added %d args", added);

	/* throw away output args which are just copies of the input args */
	for (i = 0; i < data->num_in_args; i++) {
	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "out_args[%d] = %s input copy discarded",
		       i, out_args[i]);
	    }
	    free(out_args[i]);
	    out_args[i] = NULL;
	}

	/*
	 * Now compact the new args added to the end of the array down to the
	 * beginning
	 */
	j = 0;
	for (i = data->num_in_args; i < data->num_out_args; i++) {
	    if (out_args[j]) /* we goofed */
		report(LOG_ERR, "%s: out_args[%d] should be NULL",
		       session.peer, j);
	    if (!out_args[i]) /* we goofed */
		report(LOG_ERR, "%s: out_args[%d] should not be NULL",
		       session.peer, i);

	    if (debug & DEBUG_AUTHOR_FLAG) {
		report(LOG_DEBUG, "out_args[%d] = %s compacted to out_args[%d]",
		       i, out_args[i], j);
	    }
	    out_args[j++] = out_args[i];
	    out_args[i] = NULL;
	}
	data->num_out_args = j;
	if (debug & DEBUG_AUTHOR_FLAG) {
	    report(LOG_DEBUG, "%d output args", data->num_out_args);
	}

	/* should/could do a realloc here but it won't matter */
	data->status = AUTHOR_STATUS_PASS_ADD;
	data->output_args = out_args;

	/* free the server arguments */
	for (cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
	    free(*cfg_argp);
	free(cfg_args);

	return(0);
    }

    /*
     * no additions or replacements. Input and output are identical. Don't
     * need to return anything
     */
    if (debug & DEBUG_AUTHOR_FLAG) {
	report(LOG_DEBUG, "added %d", added);
    }
    data->status = AUTHOR_STATUS_PASS_ADD;
    if (out_args) {
	for (i = 0; i < data->num_out_args; i++) {
	    free(out_args[i]);
	}
	free(out_args);
    }

    /* Final sanity check */
    if (data->num_out_args != data->num_in_args) {
	data->status = AUTHOR_STATUS_ERROR;
	data->admin_msg = tac_strdup("Bad output arg cnt from do_author");
	report(LOG_ERR, "%s: Error %s", session.peer, data->admin_msg);

	/* free the server arguments */
	for (cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
	    free(*cfg_argp);
	free(cfg_args);

	return(0);
    }

    data->num_out_args = 0;
    data->output_args = NULL;

    /* free the server arguments */
    for (cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
	free(*cfg_argp);
    free(cfg_args);

    return(0);
}

/*
 * Return an integer indicating which kind of service is being requested.
 *
 * Conveniently this integer is one of our node types.  If the service
 * is a command authorisation request, also return the command name in
 * cmdname.
 *
 * If this service is a ppp request, return the protocol name in protocol.
 *
 * If this service is not one of the standard, known ones, return its
 * name in svcname.
 */
static int
get_nas_svc(struct author_data *data, char **cmdname, char **protocol,
	    char **svcname)
{
    int i;
    char *nas_arg;

    *cmdname = NULL;
    *protocol = NULL;
    *svcname = NULL;

    for (i = 0; i < data->num_in_args; i++) {
	nas_arg = data->input_args[i];

	if (STREQ(nas_arg, "service=shell")) {
	    for (i = 0; i < data->num_in_args; i++) {
		nas_arg = data->input_args[i];
		if (strncmp(nas_arg, "cmd", strlen("cmd")) == 0) {
		    /* A cmd=<nothing> means we are authorising exec startup */
		    if ((int)strlen(nas_arg) <= 4)
			return(N_svc_exec);

		    /* A non-null command means we are authorising a command */
		    *cmdname = nas_arg + strlen("cmd") + 1;
		    return(N_svc_cmd);
		}
	    }
	    return(0);
	}

	if (STREQ(nas_arg, "service=slip")) {
	    return(N_svc_slip);
	}
	if (STREQ(nas_arg, "service=arap")) {
	    return(N_svc_arap);
	}
	if (STREQ(nas_arg, "service=ppp")) {
	    for (i = 0; i < data->num_in_args; i++) {
		nas_arg = data->input_args[i];
		if (strncmp(nas_arg, "protocol", strlen("protocol")) == 0) {
		    *protocol = nas_arg + strlen("protocol") + 1;
		    return(N_svc_ppp);
		}
	    }
	}

	if (strncmp(nas_arg, "service=", strlen("service=")) ==0 ) {
	    *svcname = nas_arg + strlen("service=");
	    return(N_svc);
	}
    }
    return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1