static const char rcsid[] = "$Id: colon.c,v 1.32 2020/08/03 17:13:04 will Exp $";

#include "super.h"
#include "version.h"

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Processes the special ":" builtin commands */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*	Return -1 if caller should give up parsing the file;
 *	return 0 on success;
 */
int
process_colon_cmds(command, continue_after_if)
char *command;
int *continue_after_if;	/* return !0 iff a :if command evaluates true,
			 * and the caller should continue processing the
			 * rest of the buffer as input text.
			 */
{
    *continue_after_if = 0;
    if ((strcmp(":global", command) == 0) ||
	(strcmp(":global_options", command) == 0) ||
	(strcmp("/", command) == 0)	/*obsolescent*/
	) {

	/* Process global options */
	return colon_global(command);

    } else if (strcmp(":define", command) == 0) {

	/* Process variable definition */
	return colon_define(command);

    } else if (strcmp(":die", command) == 0) {

	/* Force immediate exit */
	return colon_die(command);

    } else if (strcmp(":getenv", command) == 0) {

	/* Define variable from envvar */
	return colon_getenv(command);

    } else if (strcmp(":include", command) == 0) {

	/* Process include-file directive; don't allow missing files */
	return colon_include(command, 0);

    } else if (strcmp(":optinclude", command) == 0) {

	/* Process include-file directive; allow missing files */
	return colon_include(command, 1);

    } else if (strcmp(":if", command) == 0) {

	/* Process include-file directive */
	return colon_if(command, continue_after_if);
    }

    return Error(0, 0, "$$\n\tUnknown builtin command `%s'.\n", command);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :global command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_global(command)
char *command;
{
    char *wd;

    if (strcmp("/", command) == 0) {
	/* OBSOLESCENT COMMAND */
	/* Get the full path; verify that it is '/', then discard  */
	char *path = strqtokS(NULL, SEP, NULL, NULL, 1);
	if (!command || !*path || strcmp(path, "/") != 0)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
Cmd == '/' requires FullPathName == '/'.\n");
    }

    /* Some global settings need to be reset on each new :global line. */
    if (handle_option(NULL, NULL, 1) != 0)
	return -1;

    /* Now collect the options and process */
    for (strqS_qm = my_qm, strqS_cc = my_cc,
	    wd = strqtokS(NULL, SEP, NULL, NULL, 1); wd;
				wd = strqtokS(NULL, SEP, NULL, NULL, 1)) {
	if (global_arg(wd) == -1)
	    return -1;
    }


    /* Process logfile requests as soon as possible */
    if (globalinfo.log.newfile || globalinfo.log.newuid)
	if (process_logfile_opt() == -1)
	    return -1;

    return 0;
}

/* Process logfile requests */
int
process_logfile_opt()
{
    if (globalinfo.log.newuid && !globalinfo.log.newfile)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \n\t\
loguid=xxx must be used on the same :global_option line as logfile=yyy.\n");

    globalinfo.log.newfile = globalinfo.log.newuid = 0;
    opensuperlog();
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process a global arg */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
global_arg(word)
char *word;	/* opt=xxx or condition~Pattern or PermittedUserPattern */
{
    /* Return 0 on success, -1 if formatting error. */
    int invert, iscondition;
    char **glob;
    char *s;
    char *pattern;

    if (strcmp("\\", word) == 0)
	return 0;

    s = word;
    if (*s == CONDITION_SEP || *s == OPTION_SEP)
	return Error(0, 0, "$$\n\tBad super.tab syntax: <%s>\n", word);
    for (s++; *s && ((*s != CONDITION_SEP && *s != OPTION_SEP) ||
					    (*(s-1) == '\\')); s++)
	    ;
    /* s now points to one of:
     *	- the OPTION_SEP or CONTITION_SEP (if there is one),
     * or
     *	- the null character at the end of a plain PermittedUserPattern.
     */

    invert = (*word == '!');
    if (invert && *s == OPTION_SEP) {
	return Error(0, 0,
	    "$$\n\tsuper.tab syntax error: options cannot be negated: <%s>\n",
	    word);
    } else if (invert) {
	word++;
    }
    /* Note that word has been advanced past the '!' inversion character */

    if (strcmp(word, "<>") == 0) {
	/* End of globalinfo.before list.  Since we accumulate into
	 * globalinfo.after list, just move that list over to
	 * globalinfo.before.
	 */
	if (invert)
	    return Error(0, 0, "$$\n\tInvalid global condition \"!<>\"\n");

	if (globalinfo.use_after != 0)
	    return Error(0, 0, "$$\n\tMultiple use of `<>' in :global list.\n");

	globalinfo.use_after = 1;
	globalinfo.userbefore.next = globalinfo.userafter.next;
	globalinfo.userafter.next = NULL;

	globalinfo.timebefore.next = globalinfo.timeafter.next;
	globalinfo.timeafter.next = NULL;

	return 0;
    }

    iscondition = (*s != OPTION_SEP);
    if (iscondition) {
	/* Do brace globbing.  Remember that if the pattern is a plain
	 * PermittedUserPattern, then s points to the null character.
	 * Use a new pointer to point to the actual pattern.
	 */
	int i;
	if (*s == '\0')
	    pattern = word;	/* s pts to end of plain PermittedUserPattern */
	else
	    pattern = s+1;	/* s points to "~"; advance past to pattern */

	if (balancedbraces(pattern) != 0) {
	    return Error(0, 0, "$$Unbalanced braces in `%s'.\n", pattern);
	}
	if ((i=globbraces(pattern, 1, &glob)) != 0) {
	    /* Global condition */
	    return Error(0, 0, "$$Missing `%c'.\n", i);
	}
    }
    if (iscondition && STRMATCH3("user", word, s)) {
	/* It's a PermittedUser pattern */

	/* Put the (user/group/host) pattern into the after list.
	 * If we eventually see "<>", we'll move the list to
	 * before list (see above).
	 */
	if (globalinfo.user_clear) {
	    free_Simple2List(&globalinfo.userbefore);
	    free_Simple2List(&globalinfo.userafter);
	    free_SimpleList(&globalinfo.b_a_text);
	    globalinfo.user_clear = 0;
	}
	if (InsertUserList(pattern, glob,
		&globalinfo.userafter, &globalinfo.b_a_text, invert) == -1)
	    return -1;

    } else if (iscondition && STRMATCH3("time", word, s)) {
	/* It's a PermittedTime pattern.  Put it into the after list.
	 * If we eventually see "<>", we'll move the list to
	 * before list (see above).
	 */
	if (globalinfo.time_clear) {
	    free_TimeList(&globalinfo.timebefore);
	    free_TimeList(&globalinfo.timeafter);
	    globalinfo.time_clear = 0;
	}
	if (InsertTimeList(pattern, glob,
			&globalinfo.timeafter, "global", invert) == -1)
	    return -1;

    } else if (iscondition) {
	return Error(0, 0,
	"$$\n\tInternal error -- unrecognized global condition <%s>\n", word);
	
    } else {
	return handle_option(word, s+1, 1);
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :define command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_define(command)
char *command;
{
    extern char *strqS_start;
    char *varname, *varbody;
    char *s;

    varname = strqtokS(NULL, SEP, NULL, NULL, 1);
    if (!varname)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
variable name missing after ':define'.\n");

    varbody = strqS_start;
    if (!varbody)
	varbody = "";

    /* Skip leading whitespace in variable body */
    while (strchr(SEP, *varbody))
	varbody++;

    /* Delete final newline from varbody */
    s = varbody + strlen(varbody)-1;
    if (s >= varbody && *s == '\n')
	*s = '\0';

    return add_variable(varname, varbody);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :die command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_die(command)
char *command;
{
    char *text;
#ifdef COLLECT_ALL_TOKENS
    extern char *strqS_start;
#endif    

#ifndef COLLECT_ALL_TOKENS
    /* This collects next token only */
    text = strqtokS(NULL, SEP, NULL, NULL, 1);
#else
    /* This collects all tokens (but comments have been elided); however,
     * quotes remain in text.
     */
    text = strqS_start;
#endif
    Error(0, 1, "%s\n", text);

    /* shouldn't ever get here */
    return -1;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :getenv command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_getenv(command)
char *command;
{
    char *varname;
    char  *defn;

    for ( ; (varname = strqtokS(NULL, SEP, NULL, NULL, 1)) ; ) {
	defn = getenv(varname);
	if (!defn) {
	    defn = "";
	} else if (checkenv(varname, defn, "^[-/:+._a-zA-Z0-9]*$") == -1) {
	    /* Record the problem but continue. */
	    Error(0,0, "$$\n\timproper definition of envvar `%s': `%-.500s'\n",
	    			varname, defn);
	    continue;
	}
	/* Store value */
	if (add_variable(varname, defn) != 0) {
	    /* Better return -- we couldn't store a definition */
	    return -1;
	}
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :if command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_if(command, colonif_succeeded)
char *command;
int *colonif_succeeded;
{
    char *left, *op, *right;

    left = strqtokS(NULL, SEP, NULL, NULL, 1);
    if (!left)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
left operand missing after ':if'.\n");

    op = strqtokS(NULL, SEP, NULL, NULL, 1);
    if (!op)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
operator missing after ':if'.\n");

    right = strqtokS(NULL, SEP, NULL, NULL, 1);
    if (!right)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
right operand missing after ':if'.\n");

    if (strcmp(op, "==") == 0) {
	*colonif_succeeded = (strcmp(left, right) == 0);
    } else if (strcmp(op, "!=") == 0) {
	*colonif_succeeded = (strcmp(left, right) != 0);
    } else if (strcmp(op, "~") == 0) {
	*colonif_succeeded = wildmat(left, right);
    } else if (strcmp(op, "!~") == 0) {
	*colonif_succeeded = (wildmat(left, right) == 0);
    } else {
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
invalid operator `%s' in ':if'.\n", op);
    }

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process the :include command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
colon_include(command, allow_missing)
char *command;
int allow_missing;	/* don't complain if file is missing */
{
    char *filename, *wd, *s, *opt;
    extern char *strqS_start;
    struct passwd *uid_pw;
    struct group *gr;
    FileList *fl;
    char *owner = NULL;	/* xxx from owner=xxx */
    char *group = NULL;	/* xxx from group=xxx */
    uid_t uid;		/* uid of owner=xxx; -1 means no owner=xxx is set */
    gid_t gid;		/* gid of group=xxx; -1 means no group=xxx is set */

    filename = strqtokS(NULL, SEP, NULL, NULL, 1);
    if (!filename)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
filename missing after ':include'.\n");

    /* Check for owner= and group= options */
    while ((opt = strqtokS(NULL, SEP, NULL, NULL, 1))) {
	/* Check for and delete possible newline from opt */
	s = opt + strlen(opt)-1;
	if (s >= opt && *s == '\n')
	    *s = '\0';
	if (strncmp(opt, "owner=", 6) == 0) {
	    owner = opt + 6;
	    if (*owner == '\0') {
		return Error(0, 0,
			"$$\n\towner not specified in `owner=' option");
	    }
	} else if (strncmp(opt, "group=", 6) == 0) {
	    group = opt + 6;
	    if (*group == '\0') {
		return Error(0, 0,
			"$$\n\tgroup not specified in `group=' option");
	    }
	} else {
	    return Error(0, 0,
	    	"$$\n\tgarbage text <%s> after :include command", opt);
	}
    }

    if (owner) {
	/* :include line contained owner= entry */
	if (!(uid_pw = getpwentry(0, owner))) {
	    return -1;
	}
	uid = uid_pw->pw_uid;
    }
    if (group) {
	/* :include line contained owner= entry */
	if (!(gr = getgrentry(group))) {
	    return -1;
	}
	gid = gr->gr_gid;
    }

    wd = strqS_start;
    if (wd && *wd != '\n' && *wd != '\0')
	return Error(0, 0, "$$\n\tformat error in super.tab file: \n\t\
extra text <%s> after :include filename\n", wd);

    /* Check for and delete possible newline from filename */
    s = filename + strlen(filename)-1;
    if (s >= filename && *s == '\n')
	*s = '\0';

    fl = file_open(currfile, filename, allow_missing,
    				owner ? &uid : NULL, group ? &gid : NULL);
    if (!fl)
	return -1;

    currfile = fl;
    return 0;
}

