/* This file is in the public domain. */ /* * This file contains the config file engine and support routines used * by the rest of moussh to find out what the config file has to say. * It also contains the command-line parser. * * Config-file parsing proceeds line by line. Each line is classified * as `control' structure, macro definition, or variable assignment, * by an ad-hoc parser (see configfile_line); expressions, when they * occur, are parsed by a simple recursive-descent parser (whose * top-level routine is do_expr_top). I chose recursive-descent * because it's easy to code and its major weakness - error handling - * is not a big deal here, where every error is always localized to * its line anyway and floodgating is minimal (since error recovery * cannot involve discarding anything not part of the line-in-error). * * The parsing code is careful to not assume that letters, or even * digits, occur in a contiguous block of codepoints, as a defense * against the day when someone wants to use this on a non-ASCII * system. Unfortunately this restricts us to characters which occur * in ASCII, something which is most notable with letters. I need to * think more about internationalizing this code. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "hkdb.h" #include "util.h" #include "errf.h" #include "panic.h" #include "nested.h" #include "verbose.h" #include "algs-list.h" #include "stdio-util.h" #include "agent-util.h" #include "agent-client.h" #include "config.h" /* * These arguably should be somewhere else, but I don't see where would * be a better place for them. * * server_sshdir is the directory used as SSHDIR by default in server * mode when running as root. * * default_hostkeydir is just the default value of host-key-dir. */ static const char *server_sshdir = "/local/etc/moussh"; static const char *default_hostkeydir = "/local/etc/moussh/hostkeys"; /* * Variables delcared in config.h. */ unsigned int fwd = FWD_AGENT | FWD_X; unsigned int fwdonce = 0; const FWDWHAT fwdwhats[] = { { "agent", FWD_AGENT }, { "x", FWD_X }, { 0 } }; char **ports = 0; int nports = 0; #define FOO(x,t) ALGLIST algs_##x = ALGLIST_DEFINIT(&at_##t) FOO(kex,kex); FOO(hk,hk); FOO(enc_c2s,enc); FOO(enc_s2c,enc); FOO(mac_c2s,mac); FOO(mac_s2c,mac); FOO(comp_c2s,comp); FOO(comp_s2c,comp); FOO(ua,ua); #undef FOO char **authkeys = 0; int nauthkeys = 0; char **argsbegin = 0; int argscount = 0; TCPFWD **tcpfwds = 0; int ntcpfwds = 0; char *cmdline_txt = 0; /* * Variables private to this file. */ static int aports = 0; static int aauthkeys = 0; static int atcpfwds = 0; /* * Types private to this file. */ /* SETHASH is a hash value used for de-duping sets. */ typedef unsigned long long int SETHASH; /* Struct types. See the struct definitions for more. */ typedef struct config CONFIG; typedef struct cvar CVAR; typedef struct kval KVAL; typedef struct expr EXPR; typedef struct exprlist EXPRLIST; typedef struct confline CONFLINE; typedef struct xval XVAL; typedef struct cstack CSTACK; typedef struct fn FN; /* * FNTYPE is the type of a [ ] function. In Lisp terms, the difference * between FNT_EVAL and FNT_SPEC is that the former always evals its * arguments; the latter may not eval all its args. (This is not a * Lisp engine, but the terminology is useful.) */ typedef enum { FNT_EVAL, FNT_SPEC } FNTYPE; /* * The types an XVAL can take on. XVT_NONE is a transient type that * exists only briefly during value creation; XVT_UNSET is the type of * an unset variable's value. * * The same values are used for set member types. The semantics are * the same except that XVT_UNSET does not occur and XVT_NONE is the * member type of a zero-element set (all empty sets are * type-compatible). */ typedef enum { XVT_NONE = 1, XVT_UNSET, XVT_BOOL, XVT_STR, XVT_INT, XVT_ADDR, XVT_CIDR, XVT_SET } XVALTYPE; /* * These values represent operations in expressions. XO_NONE exists * only transiently during expression creation. * * Note that there are no XO_LE, XO_NE, and XO_GE. <=, !=, and >= are * converted into negations of >, ==, and < fairly early. */ typedef enum { XO_NONE = 1, XO_VALUE, /* constant */ XO_VAR, /* variable */ XO_CONCAT, /* . */ XO_LT, /* < */ XO_EQ, /* == */ XO_GT, /* > */ XO_AND, /* & */ XO_OR, /* | */ XO_NOT, /* ! */ XO_NEG, /* - (unary) */ XO_IN, /* @ */ XO_REGEX, /* ~ */ XO_ISSET, /* ? */ XO_COND, /* { : } */ XO_ADD, /* + */ XO_SUB, /* - (binary) */ XO_MUL, /* * */ XO_DIV, /* / */ XO_CALL /* [ ] */ } EXPROP; /* * The types a variable can have. * * VT_KEY variables look like string variables, except that they can * take on only certain values (keywords). VT_BOOL, VT_INT, VT_STR, * VT_ADDR are variables that can hold only the corresponding kind of * XVAL. VT_XVAL variables can hold any XVAL. VT_MACRO is the type * of a variable defined as a macro. VT_KFN never appears in a * variable's type; is used only in calls to add_var, which converts * it to VT_KEY, but gets the keywords differently from the way it * does for VT_KEY calls. VT_UNDEF is the type used for $-prefixed * convenience variables which have appeared but have not yet been * given any definition; such variables normally appear only * transiently, but they can persist for longer periods when the * affected variables appear in macro expansions (they must be given * definitions before the macro can be *used*) or as dummy variables * in calls that take dummy variables, such as [map]. * * Note that there is code that knows that "variable is of type * VT_XVAL, VT_MACRO, or VT_UNDEF" is equivalent to "variable is a * $-prefixed user convienience variable". */ typedef enum { VT_KEY = 1, VT_BOOL, VT_INT, VT_STR, VT_ADDR, VT_XVAL, VT_MACRO, VT_KFN, VT_UNDEF } CVARTYPE; /* * Config-file line types. CL_VSET represents `var = expr' lines; * CL_IF, CL_ELSE, CL_ELIF, and CL_ENDIF lines with the corresponding * conditional keyword. Macro definitions do not turn into * config-file lines in the sense that calls for a type here; they * exist only within the config-file parser, and disappear by the time * the lines executed at run time are created. */ typedef enum { CL_VSET = 1, CL_IF, CL_ELSE, CL_ELIF, CL_ENDIF, } CLTYPE; /* * The states a conditional (if/elif/elif/.../else/endif) block can be * in at run time. CS_FALSE is one that has never yet been true, * CS_TRUE one that's currently true, and CS_PAST one that has been * true but is now false (or one that should otherwise never in the * future be true, such as one which had a control expression of type * XVT_UNSET). */ typedef enum { CS_FALSE = 1, CS_TRUE, CS_PAST } CSTATE; /* * The structure representing a [ ] function definition. * * .name is of course the function's name and .type its type. * * .checkargs is called with an assembled list of arguments, to check * whether they're suitable for the function; it returns a nil pointer * if they're fine or an error string if not. * * .deps is called to add or remove dependencies. It should call its * fourth argument for every dependency. For FNT_EVAL functions this * always points to evalfn_deps, which corresponds to a dependency on * each argument function; for FNT_SPEC functions it will point to a * function which knows which arguments the value depends on, and how. * * .u.eval is valid only for type FNT_EVAL. .perform is the function * to evaluate the call given its arguments (which will already have * been evaluated). * * .u.spec is valid only for type FNT_SPEC. .eval_arg takes an * argument index, from 0 to #args-1, and returns a boolean saying * whether that arg should be evaluated. .eval_fn actually does the * evaluation; its args are arg count, a vector of chars holding * booleans saying whether args were evaled, and a vector of pointers, * which are EXPR * for unevaled arguments and XVAL * for evaled * arguments. * * .u is not a transparent union because that makes initialization * difficult. * * .namelen is strlen(.name), computed for all functions the first time * lookup_fn() is called. */ struct fn { const char *name; FNTYPE type; const char *(*checkargs)(int, EXPR **); void (*deps)(EXPR *, int, EXPR **, void (*)(EXPR *, EXPRLIST *)); union { struct { XVAL *(*perform)(int, XVAL **); } eval; struct { int (*eval_arg)(int); XVAL *(*eval_fn)(int, char *, void **); } spec; } u; int namelen; } ; /* * Macros for setting up functions. EVALDECL declares the * implementation functions for a FNT_EVAL function; EVALFN creates * the FN initializer for it. SPECDECL and SPECFN are the same thing * for FNT_SPEC functions. evalfn_deps is declared here as well, so * it's available for EVALFN expansions to use. */ #define EVALDECL(x) \ static const char *fn_##x##_check(int, EXPR **);\ static XVAL *fn_##x##_perform(int, XVAL **) #define EVALFN(x) {#x,FNT_EVAL,&fn_##x##_check,&evalfn_deps,{.eval={&fn_##x##_perform}}} #define SPECDECL(x) \ static const char *fn_##x##_check(int, EXPR **);\ static void fn_##x##_deps(EXPR *, int, EXPR **, void (*)(EXPR *, EXPRLIST *));\ static int fn_##x##_eval_arg(int);\ static XVAL *fn_##x##_eval_fn(int, char *, void **) #define SPECFN(x) {#x,FNT_SPEC,&fn_##x##_check,&fn_##x##_deps,{.spec={&fn_##x##_eval_arg,&fn_##x##_eval_fn}}} static void evalfn_deps(EXPR *, int, EXPR **, void (*)(EXPR *, EXPRLIST *)); /* * An entry in the run-time stack of conditionals. These form a LIFO * stack of CSTATEs. This could also be done with, eg, an array, but * the extra code complexity would not be worth the bother; this stack * rarely gets more than about four deep. */ struct cstack { CSTACK *link; CSTATE state; } ; /* * A config-file line, as built by the parser for run-time evaluation. * They form a linked list through .link, with .type being the type, * from above. .lno stores the line number (config.lno0). .vset * stores the variable (.var) and expression (.val) for variable * setting lines; .cond holds the condition for if and elif lines * (else and endif lines use neither). */ struct confline { CONFLINE *link; CLTYPE type; int lno; union { struct { CVAR *var; EXPR *val; } vset; EXPR *cond; } ; } ; /* * An expression value. * * .type is the type, one of the XVT_* values from above. * * .refcnt is a reference count. This is used so that multiple things * can point to the same expression rather than having to create * duplicates of a lot of expressions, most of which would be * unnecessary. It is also how expression values get freed: when * their refcounts go to zero, they're disposed of. * * The members in the transparent union hold the values for the various * types: * XVT_BOOL .bool * XVT_STR .str * XVT_INT .i * XVT_ADDR .addr * XVT_CIDR .cidr * XVT_SET .set * They are documented inline (except for the trivial .bool and .i). */ struct xval { XVALTYPE type; int refcnt; union { int bool; /* * A string. .s is the string itself. .flags holds various flags, * as commented; the other fields hold auxiliary information: * * .re holds the regex_t for strings which are used as regexes. * * .maxsub is used on strings which are regex replacement strings; * it stores the highest subexpression reference number used. * * .hash holds the hash of the string. */ struct { char *s; unsigned int flags; #define XV_STRF_V_RE 0x00000001 /* .re valid */ #define XV_STRF_V_MAXSUB 0x00000002 /* .maxsub valid */ #define XV_STRF_NOSUB 0x00000004 /* .re was compiled with REG_NOSUB */ #define XV_STRF_V_HASH 0x00000008 /* .hash valid */ regex_t *re; int maxsub; SETHASH hash; } str; int i; /* * An address. .af is AF_INET or AF_INET6; .u.v4 or .u.v6 holds * the actual address. */ struct { int af; union { struct in_addr v4; struct in6_addr v6; } u; } addr; /* * A CIDR block. .af is AF_INET or AF_INET6; .u.v4 or .u.v6 holds * the actual address, with .w holding the network-portion bit * width (the number after the slash in the canonical text form). */ struct { int af; union { struct in_addr v4; struct in6_addr v6; } u; int w; } cidr; /* * A set. .oftype holds the type of the members, or XVT_NONE if * the set has no members. .n holds the number of values, with * .vals an array holding the values themselves. .hash is the * hash of the set as a whole. The .vals array is kept sorted * according to the comparison function for the type. */ struct { XVALTYPE oftype; int n; XVAL **vals; SETHASH hash; } set; } ; } ; /* * A list of expressions. This is used for expression dependencies, so * that when a variable changes value, expressions that depend on it * can be invalidated (for recomputation on next use) without * invalidating expressions that don't. * * .v is the expressions, with .n holding their count. .a is the * number of elements .v points to (as distinct from the number of * elements with data in them, which is .n). */ struct exprlist { EXPR **v; int n; int a; } ; /* * An expression. * * .op is the operation, one of the XO_ values from above. * * .valid is true if .eval is valid, false if not. When false, .eval * may still point to a value, but if so it is stale and should be * discarded, not used. * * .deps is a list of expressions which depend on this one, for pushing * invalidation upward when variables change. * * .refcnt is a reference count, for discarding expressions when they * are no longer referred to. * * .eval is the value resulting from evaluating this expression (but * see .valid, above, for more). * * The transparent union holds operation-specific data: * XO_VALUE .val holds the value. * XO_VAR, XO_ISSET .var points to the variable. * XO_COND .ternary holds the left, middle, and * right subexpressions. * XO_CONCAT, XO_LT, XO_EQ, XO_GT, XO_AND, XO_OR, XO_IN, * XO_REGEX, XO_ADD, XO_SUB, XO_MUL, XO_DIV * .binary holds the left and right * subexpressions. * XO_NOT, XO_NEG .unary holds the argument. * XO_CALL .call holds the function and the vector * of arguments. */ struct expr { EXPROP op; char valid; EXPRLIST deps; int refcnt; XVAL *eval; union { XVAL *val; CVAR *var; struct { EXPR *lhs; EXPR *mhs; EXPR *rhs; } ternary; struct { EXPR *lhs; EXPR *rhs; } binary; struct { EXPR *arg; } unary; struct { FN *fn; int nargs; EXPR **args; } call; } ; } ; /* * A pair, used for keyword-valued variables. */ struct kval { const char *name; int val; } ; /* * A config variable. These are kept in a linked list; when a variable * is looked up, it is moved to the front of the list, in a simple * attempt to speed up access to frequently-used variables. * * .link forms the linked list. * * .name and .namelen are the variable name string and its length. * * .refcnt is a reference count, for sweeping up unused variables. * * .type describes the variable's type; it is one of the VT_ values. * * .flags holds flag bits. VF_R and VF_W describe what kind of access * the variable supports; the only impossible combination is neither * bit set. VF_SET and VF_CFG describe what kind of setting the * variable has experienced; VF_SET means that moussh has set it, * VF_CFG that the config file has set it. (VF_CFG takes precedence * if both are set; code that wants to override a config-file-set * value needs to clear VF_CFG. At this writing there is no such * code.) There are four VF_SPEC bits, which indicate that the * variable requires special handling; see the .spec description. * SETFIELD is an encapsulation of the decision of which field to use, * based on the VF_SET and VF_CFG flags; it depends on the names of * certain fields in the type-specific structures. * * .spec is a function which is called if any of the VF_SPEC_* bits are * set. It is called under various conditions. In each case, the * first arg is the variable itself and the second arg is an operation * indicator. * * - If VF_SPEC_INIT is set, when the variable is first set up. * The second arg is VSO_INIT, and there are no further * arguments. * * - If VF_SPEC_SET is set and the variable is writable, when the * variable has its value set. The second arg is VSO_SET, the * variable's value will have already been changed, and there * are no further arguments. * * - If VF_SPEC_SETANY is set and the variable is writable, when * the variable has its value set. The second arg is VSO_SET, * the variable's value is not touched by the * variable-independent machinery (neither before nor after * calling .spec), and one further argument is passed, that * being the XVAL * (which may be of any type) to which the * variable is being set. In this case, the .spec function is * considered to have been given one reference to the value, * which it must save or drop. * * - If VF_SPEC_GET is set and the variable is readable, when its * value is required. The second arg is VSO_GET and there is * one more arg, of type XVAL **; the .spec function must come * up with the value the variable evaluates to and store it * through this pointer. This pointer is treated as being one * reference to the value. The type of the resulting value does * not necessarily have to match that of the variable. * * If VF_SPEC_SET and VF_SPEC_SETANY are both set, VF_SPEC_SETANY * overrides VF_SPEC_SET (note that the same second arg is used for * each). While it does not really make sense to set VF_SPEC_SET or * VF_SPEC_SETANY on a non-VF_W variable, or VF_SPEC_GET on a non-VF_R * variable, nothing prevents this; the .spec function just won't get * called because the corresponding conditions won't occur. (Note * that if a variable is set both VF_SPEC_SETANY and VF_SPEC_GET, its * type is effectively ignored unless the .spec function chooses to * pay attention to it.) * * .deps is a list of expressions that depend on this variable. This * is just direct dependencies; indirect dependencies are handled with * an analogous element in an EXPR. * * The transparent union contains type-specific data. In all cases, * the set, cfg, and def fields hold the moussh-set, config-file-set, * and default values for the variable, as used by SETFIELD(). The * only case with more fields is .k, used for VT_KEY variables; the * .nvals and .vals elements hold the list of keywords and their * corresponding (integer, generally small-integer) values. * * For .a, used for VT_ADDR, each element is either a nil pointer or an * XVAL of type XVT_ADDR. */ struct cvar { CVAR *link; char *name; int namelen; int refcnt; CVARTYPE type; unsigned int flags; #define VF_SET 0x00000001 #define VF_R 0x00000002 #define VF_W 0x00000004 #define VF_RW (VF_R|VF_W) #define VF_CFG 0x00000008 #define VF_SPEC_INIT 0x00000010 #define VF_SPEC_SET 0x00000020 #define VF_SPEC_SETANY 0x00000040 #define VF_SPEC_GET 0x00000080 #define VF_SPEC_ALL (VF_SPEC_INIT|VF_SPEC_SET|VF_SPEC_SETANY|VF_SPEC_GET) #define SETFIELD(v,el,def) \ (((v)->flags&VF_CFG)?((v)->el.cfg):((v)->flags&VF_SET)?((v)->el.set):(def)) void (*spec)(CVAR *, int, ...); #define VSO_INIT 1 #define VSO_SET 2 #define VSO_GET 3 EXPRLIST deps; union { struct { KVAL *vals; int nvals; int set; int cfg; int def; } k; struct { int set; int cfg; int def; } b; struct { int set; int cfg; int def; } i; struct { char *set; char *cfg; char *def; } s; struct { /* each is nil or of type XVT_ADDR */ XVAL *set; XVAL *cfg; XVAL *def; } a; struct { XVAL *set; XVAL *cfg; XVAL *def; } x; EXPR *m; } ; } ; /* * A config. There is currently only one of these; it exists to * encapsulate the state involved, against the possibility of someday * wanting to handle multiple config files. Struct element doc is * inline. */ struct config { /* * .vars is a list of variables the config file supports. This * includes both moussh-provided variables and $-prefixed * convenience variables. .avars is the size of the pointed-to * block of memory, with .nvars being the number currently live. * * .lvars is the same list, kept as a linked list. */ CVAR **vars; int nvars; int avars; CVAR *lvars; /* * .file holds the name of the file this CONFIG corresponds to. */ char *file; /* * Config file lines are built up in mallocked memory. .lb points to * a buffer where the lines are built; .la is the amount of space * pointed to, with .ll the current length.. */ char *lb; int ll; int la; /* * The current line number when the config file is being read. There * are two values here because lines can be continued. The rightest * thing to do would be to tag ranges of the line with what line * they came from, so as to be able to report the actual line. But * that's hard enough that we make do with just reporting the range * of lines the continuation occupies. * * In the absence of continuations, these are equal. For a continued * line, lno0 is the first line and lno the current (or last, when * the parser is being run) line. */ int lno0; int lno; /* * Errors are handled by setting .err true (it's normally false) and * calling through .err_throw, which function is expected to throw * out to the level at which the current line is discarded and the * next read in. */ void (*err_throw)(void); int err; /* * Config lines are accumulated in a tconc structure of CONFLINEs, * with the list head in conflist and the tail in configlistt. */ CONFLINE *configlist; CONFLINE **configlistt; /* * .lp points to the current character in the input line. This is * used by the tokenizer (see token()). */ char *lp; /* * .tok points to the first character of the current token (.lp will * have been advanced past it); .tkl is the token's length. As a * special case, .tok is set nil and .tkl set zero upon encountering * a comment delimiter (NUL, #, or ;). */ char *tok; int tkl; /* * Conditional stack stuff. * * .ifdepth is the current number of nested conditionals, when the * file is being parsed; this exists so as to catch elif/else/endif * lines without matching if lines, and if lines without matching * endif lines. * * .cstack is the current conditional state stack at run time, when * the config is being executed. */ int ifdepth; CSTACK *cstack; /* * The config file can modify a number of things. Most notably, it * can modify forwarding enables, the list of ports, the list of * public-key authentication files, and the algorithm lists. In * order to maintain the invariant that config file execution starts * from the same state each time, we save all of these as they were * at config load time and restore them just before each execution * of the config file. * * Curently, algs_ua is not treated this way. I'm not sure whether I * think this is right; I need to think about it more. */ unsigned int pre_fwd; unsigned int pre_fwdonce; unsigned int pre_nports; unsigned int pre_nauthkeys; ALGLIST pre_algs_kex; ALGLIST pre_algs_hk; ALGLIST pre_algs_enc_c2s; ALGLIST pre_algs_enc_s2c; ALGLIST pre_algs_mac_c2s; ALGLIST pre_algs_mac_s2c; ALGLIST pre_algs_comp_c2s; ALGLIST pre_algs_comp_s2c; } ; /* * Work out the amount of memory needed for the text form of an IP * address. V4LEN is the max text length of a V4 address, V6LEN ditto * for V6; IPADDRTXTLEN is set to the larger of the two. */ #define V4LEN ((4*3)+3+1) /* nnn.nnn.nnn.nnn */ #define V6LEN ((8*4)+7+1) /* xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx */ #define IPADDRTXTLEN V4LEN #if V6LEN > IPADDRTXTLEN #undef IPADDRTXTLEN #define IPADDRTXTLEN V6LEN #endif /* * State variables private to this file. * * configfile is where the -config command-line switch stores its * value. This arguably should be replaced with config.file. It * needs a rethink if we want to support multiple config files. * * config is the only CONFIG there is. It exists to encapsulate state * against the day we might want to handle multiple config files. * * defer_eval is used to prevent recursive calls to maybe_eval; it is * also used to prevent maybe_eval from trying to evaluate stuff * before we're ready for it. * * setserial and evalserial are used to tell when we need to rerun * evaluation. setserial is incremented whenever a variable is set; * it is copied to evalserial when we do a re-evaulation run. Thus, * if evalserial==setserial, all values are up-to-date. */ static const char *configfile = 0; static CONFIG config; static int defer_eval = 1; static unsigned int setserial = 1; static unsigned int evalserial = 0; /* * The table of supported [ ] functions. First, declare the * implementation functions with EVALDECL and SPECDECL, then define * fns[] to hold the functions themselves and nfns to hold how many of * them there are. */ EVALDECL(lc); EVALDECL(env); EVALDECL(type); EVALDECL(matchsub); EVALDECL(bool); EVALDECL(str); EVALDECL(int); EVALDECL(addr); EVALDECL(cidr); EVALDECL(set); EVALDECL(in); EVALDECL(rdns); EVALDECL(fdns); SPECDECL(map); EVALDECL(md5); EVALDECL(sha1); static FN fns[] = { EVALFN(lc), EVALFN(env), EVALFN(type), EVALFN(matchsub), EVALFN(bool), EVALFN(str), EVALFN(int), EVALFN(addr), EVALFN(cidr), EVALFN(set), EVALFN(in), EVALFN(rdns), EVALFN(fdns), SPECFN(map), EVALFN(md5), EVALFN(sha1) }; static const int nfns = sizeof(fns) / sizeof(fns[0]); /* * Process a list of algorithm names, turning it into an ALGLIST. type * is the type of algorithm, s is the string containing the list, list * is the ALGLIST we should put the result in, gripe is a boolean * indicating whether we should print gripes about errors, and errto * is where to print gripes (not used if gripe is false). * * See algset(), below, for why gripe exists. */ static int list_of_algs(const ALGTYPE *type, const char *s, ALGLIST *list, int gripe, FILE *errto) { NESTED int foo(const void *sv, int len) { void *alg; const char *reason; alg = (*type->find_rostr)(blk_to_rostr(sv,len)); if (! alg) { if (gripe) fprintf(errto,"Unknown %s algorithm `%.*s'\n",type->tag,len,(const char *)sv); alglist_clear(list); return(-1); } reason = (*type->disabled)(alg); if (reason) { if (gripe) fprintf(errto,"Can't use %s algorithm `%.*s': %s\n",type->tag,len,(const char *)sv,reason); alglist_clear(list); return(-1); } alglist_append1(list,alg); return(0); } list->type = type; list->head = 0; list->tail = &list->head; return(comma_list(s,strlen(s),&foo)?1:0); } /* * Function to move algorithms to the end of a list. This is used to * handle the +-alg,alg,alg syntax. */ static void modlist_last(ALGLIST *list, ALGLIST *l) { ALE *e; alglist_expand_default(list); while ((e = l->head)) { l->head = e->link; alglist_dropalg(list,e->alg); alglist_append1(list,e->alg); free(e); } l->tail = &l->head; } /* * Function to move algorithms to the head of a list. This is used to * handle the +alg,alg,alg syntax. */ static void modlist_first(ALGLIST *list, ALGLIST *l) { ALE *e; alglist_expand_default(list); while ((e = l->head)) { l->head = e->link; alglist_dropalg(list,e->alg); alglist_prepend1(list,e->alg); free(e); } l->tail = &l->head; } /* * Function to drop algorithms from a list. This is used to handle the * -alg,alg,alg syntax. */ static void modlist_drop(ALGLIST *list, ALGLIST *l) { ALE *e; alglist_expand_default(list); while ((e = l->head)) { l->head = e->link; alglist_dropalg(list,e->alg); free(e); } l->tail = &l->head; } /* * Function to replace a list of algorithms. This is used to handle * the alg,alg,alg (without any prefix) syntax. */ static void modlist_set(ALGLIST *list, ALGLIST *l) { alglist_clear(list); list->flags &= ~ALF_DEFAULT; list->head = l->head; list->tail = l->head ? l->tail : &list->head; l->head = 0; l->tail = &l->head; } /* * Called to handle an algorithm-list-modifying flag, such as -kex or * -enc. list is the list to modify, name is the argument string, * gripe is a boolean saying whether to print complaints, and errto is * where to send complaints (used only if gripe is true). * * gripe exists so that command-line options that affect multiple * lists, such as -enc, can simply call this for each list without * generating duplicate gripes in case of errors. (This works only * because all the possible error conditions are independent of the * list being manipulated.) * * This code implements the ? convention and the DEFAULT and CLEAR * keywords directly, calling out to list_of_algs and one of the * modlist_* functions for the other syntaxes. */ int algset(ALGLIST *list, const char *name, int gripe, FILE *errto) { void *alg; ALGLIST l; if (! strcmp(name,"?")) { int i; if (! gripe) return(1); fprintf(errto,"Recognized %s algorithms:\n",list->type->tag); for (i=0;(alg=(*list->type->list)(i));i++) { const char *c; c = (*list->type->comment)(alg); fprintf(errto,"\t%s%s%s%s\n",(*list->type->name)(alg),c?" (":"",c?:"",c?")":""); } return(1); } if (!strcmp(name,"DEFAULT")) { alglist_clear(list); list->flags |= ALF_DEFAULT; return(0); } if (!strcmp(name,"CLEAR")) { alglist_clear(list); list->flags &= ~ALF_DEFAULT; return(0); } if ((name[0] == '+') && (name[1] == '-')) { if (list_of_algs(list->type,name+2,&l,gripe,errto)) return(1); modlist_last(list,&l); } else if (name[0] == '+') { if (list_of_algs(list->type,name+1,&l,gripe,errto)) return(1); modlist_first(list,&l); } else if (name[0] == '-') { if (list_of_algs(list->type,name+1,&l,gripe,errto)) return(1); modlist_drop(list,&l); } else { if (list_of_algs(list->type,name,&l,gripe,errto)) return(1); modlist_set(list,&l); } return(0); } /* * Compute the default SSHDIR. If it doesn't exist, try to create it, * but if we can't, don't error out here; let attempted accesses error * out instead. * * If we're in server mode and running as root, use server_sshdir, * otherwise $HOME/.moussh; if $HOME does not exist in the environment * for the latter case, return a nil pointer. Callers of * default_sshdir must be prepared to get a nil pointer back. */ static const char *default_sshdir(void) { const char *home; char *s; const char *d; d = 0; if ((config_key("opmode") == OPMODE_SERVER) && (getuid() == 0)) { d = server_sshdir; } else { home = getenv("HOME"); if (home) { asprintf(&s,"%s/.moussh",home); d = s; } } if (d && (access(d,F_OK) < 0) && (errno == ENOENT)) { mkdir(d,0700); /* Ignore errors here; let attempted accesses, if any, error out */ } return(d); } /* * Return the default config file path. If $SSH_CONFIG is set, use * that; otherwise, use "config" in SSHDIR. */ static const char *default_config(void) { static char *s = 0; if (! s) { s = getenv("SSH_CONFIG"); if (! s) { asprintf(&s,"%s/config",get_sshdir("config file")); } } return(s); } /* * Parse a forwarding spec. This is used by the -R/-L/-RR/-LL * command-line flags and by the client CLI forwarding code (the * latter being why it's not private to this file). arg and arglen * describe the argument string, f is where to put the result, and * (*invalid) is called on error. (*invalid) should be tagged * __attribute__((__format__(__printf__,1,2))), but gcc doesn't * support such attributes on functions accessed through pointers (at * least in the version of gcc I have). :( * * map exists so we don't have to copy things around to effectively * move all the arguments over, since the missing part in an A/B/C * spec is before A, not after C. */ void parse_fwd(const char *arg, int arglen, TCPFWD *f, void (*invalid)(const char *, ...)) { int start[4]; int end[4]; const char *map; int pcs; int i; int o; const char *slashp; NESTED int portnumber(int x, const char *what) { unsigned long int v; char *ep; char *t; x = map[x]; t = blk_to_cstr(arg+start[x],end[x]-start[x]); v = strtoul(t,&ep,0); if (ep != t+(end[x]-start[x])) { free(t); (*invalid)("bad %s port number",what); } free(t); if ((v < 1) || (v > 65535)) (*invalid)("out-of-range %s port number",what); return(v); } pcs = 0; i = 0; while ((i < arglen) && isspace((unsigned char)arg[i])) i ++; if (i >= arglen) (*invalid)("empty argument"); while (isspace((unsigned char)arg[arglen-1])) arglen --; o = i; while (1) { if (pcs >= 4) (*invalid)("too many slashes"); start[pcs] = i; slashp = memchr(arg+i,'/',arglen-i); if (! slashp) { end[pcs] = arglen; pcs ++; break; } i = slashp - arg; end[pcs] = i; i ++; pcs ++; } switch (pcs) { case 3: start[3] = 0; end[3] = 0; map = "\3\0\1\2"; break; case 4: map = "\0\1\2\3"; break; default: (*invalid)("too few slashes"); break; } f->listenport = portnumber(1,"listen-on"); f->connport = portnumber(3,"connect-to"); f->text = blk_to_cstr(arg+o,arglen-o); i = map[0]; f->listenhost = blk_to_cstr(arg+start[i],end[i]-start[i]); i = map[2]; f->connhost = blk_to_cstr(arg+start[i],end[i]-start[i]); } /* * Handle a command-line forwarding option (-R, etc). Just use * parse_fwd to parse it, then save the result in tcpfwds, where the * client code will pick it up once we have a connection established. * * Return value is true if an error occured, false if all went well. */ static int add_fwd(const char *arg, int flags) { __label__ reterr; TCPFWD f; TCPFWD *fp; NESTEDFWD void invalid(const char *, ...) __attribute__((__format__(__printf__,1,2))); NESTEDDEF void invalid(const char *fmt, ...) { va_list ap; fprintf(errf,"%s: invalid port forwarding spec `%s': ",__progname,arg); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\n"); goto reterr; } parse_fwd(arg,strlen(arg),&f,&invalid); f.flags = flags; if (ntcpfwds >= atcpfwds) tcpfwds = realloc(tcpfwds,(atcpfwds=ntcpfwds+8)*sizeof(TCPFWD *)); fp = malloc(sizeof(TCPFWD)); *fp = f; tcpfwds[ntcpfwds++] = fp; return(0); reterr:; return(1); } /* * Print a list of keytypes. * This implements the ? convention for -keytype. */ static void list_keytypes(void) { int i; HKALG *a; int j; fprintf(errf,"Key types (aliases in []):\n"); for (i=0;(a=(*at_hk.list)(i));i++) { fprintf(errf,"\t%s",(*at_hk.name)(a)); if (a->altnames[0]) { fprintf(errf," ["); for (j=0;a->altnames[j];j++) { fprintf(errf,"%s%s",j?" ":"",a->altnames[j]); } fprintf(errf,"]"); } fprintf(errf,"\n"); } } /* * This enables or disables forwarding for something other than TCP * connections (eg, agent forwarding). what is the argument. The * bits corresponding to the forwarding types in what are handled * according to fwdtype: * fwdtype operation * SFT_ALL Set in fwd, cleared in fwdonce * SFT_ONCE Set in fwd, set in fwdonce * SFT_NO Cleared in fwd, fwdonce unaffected * * Return value is true if an error occured, false if all went well. */ typedef enum { SFT_ALL = 1, SFT_ONCE, SFT_NO } SET_FWD_TYPE; static int set_forwarding(const char *what, SET_FWD_TYPE fwdtype, const char *opt) { int i; unsigned int b; if (! strcmp(what,"?")) { fprintf(errf,"%s: arguments for %s:",__progname,opt); for (i=0;fwdwhats[i].str;i++) fprintf(errf," %s",fwdwhats[i].str); fprintf(errf,"\n"); return(1); } do <"found"> { for (i=0;fwdwhats[i].str;i++) if (!strcmp(fwdwhats[i].str,what)) break <"found">; fprintf(errf,"%s: %s: forwarding `%s' not recognized\n",__progname,opt,what); return(1); } while (0); b = fwdwhats[i].bit; switch (fwdtype) { case SFT_ALL: fwd |= b; fwdonce &= ~b; break; case SFT_ONCE: fwd |= b; fwdonce |= b; break; case SFT_NO: fwd &= ~b; break; } return(0); } /* * Report a bad config line. Just prints a message thorugh * logmsg_fopen, then calls through the CONFIG's err_throw. */ void badline(CONFIG *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); void badline(CONFIG *c, const char *fmt, ...) { va_list ap; FILE *f; f = logmsg_fopen(LM_ERR); fprintf(f,"%s, ",c->file); if (c->lno == c->lno0) { fprintf(f,"line %d",c->lno); } else { fprintf(f,"lines %d-%d",c->lno0,c->lno); } fprintf(f,": bad line ("); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fprintf(f,"): %s",c->lb); fclose(f); (*c->err_throw)(); panic("err_throw returned"); } /* * Parser utility routine: skip past whitespace. This code assumes * '\0' is not considered whitespace; if this is not true it will walk * off the end of the string. */ static void skipspace(CONFIG *c) { while (*c->lp && isspace((unsigned char)*c->lp)) c->lp ++; } /* * Test whether a given character should be considered a letter for * purposes of config-file tokenizing. c is the character; first is a * boolean which is true for the first character of a (putative) * token, false for the second and later characters. Return value is * true if the character should be considered a letter, false if not. * * Be careful to list all characters explicitly here; don't depend on * ranges. */ static inline int token_letter(int c, int first) { switch (c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return(1); break; case '$': return(first); break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(!first); break; } return(0); } /* * Test whether a given character should be considered a digit for * purposes of config-file tokenizing, returning true if it should, * false if not. Don't depend on digits being in sequence. */ static inline int token_digit(int c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(1); break; } return(0); } /* * Return a digit's value as a small integer. Don't depend on digits * being in sequence. */ static inline int digitval(int c) { switch (c) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; } return(-1); } /* * Work out what the next token is. Set it into the .tok and .tkl * fields of the CONFIG. */ static void token(CONFIG *c) { skipspace(c); c->tok = c->lp; switch (*c->lp) { case '\0': case '#': case ';': /* Comments */ c->tok = 0; c->tkl = 0; return; break; case '"': case '\'': /* Strings */ do c->lp++; while (*c->lp && (*c->lp != *c->tok)); if (*c->lp) { c->lp ++; c->tkl = c->lp - c->tok; return; } badline(c,"unclosed %c",*c->lp); break; case '(': case ')': case '@': case '~': case '?': case '{': case '}': case ':': case '[': case ']': case ',': case '+': case '-': case '*': case '/': /* Characters which are always single-character tokens. */ c->lp++; c->tkl = 1; return; break; case '<': case '>': /* Single-character tokens, unless followed immediately by =, in which case they're double-character. '=' could be listed here rather than below; it makes little difference. */ if (*++c->lp == '=') { c->lp ++; c->tkl = 2; } else { c->tkl = 1; } return; break; case '!': /* Can stand alone or can adhere to a following =, @, or ~. */ switch (*++c->lp) { case '=': case '@': case '~': c->lp ++; c->tkl = 2; break; default: c->tkl = 1; break; } return; break; case '&': case '|': case '=': /* Can stand alone or can be doubled. */ if (*++c->lp == *c->tok) { c->lp ++; c->tkl = 2; } else { c->tkl = 1; } return; break; case '.': /* Can stand alone or can begin a Boolean constant token. */ do c->lp ++; while (token_letter(*c->lp,0)); if ((c->lp == c->tok+5) && !bcmp(c->tok,".true",5)) { c->tkl = 5; return; } else if ((c->lp == c->tok+6) && !bcmp(c->tok,".false",6)) { c->tkl = 6; return; } c->lp = c->tok + 1; c->tkl = 1; return; break; } if (token_digit(c->lp[0])) { /* A number. */ do c->lp++; while (token_digit(*c->lp)); } else if (token_letter(*c->lp,1)) { /* A variable name. */ do c->lp++; while (token_letter(*c->lp,0)); } else { /* Something invalid. */ badline(c,"invalid character `%c'",*c->tok); } /* Number or variable name - compute length. */ c->tkl = c->lp - c->tok; if (c->tkl < 1) panic("token <1 char long"); } /* * Drop a reference to an XVAL. This decrements the refcount, handling * freeing the value when the refcount goes to zero. */ static void drop_xval(XVAL *v) { int i; if (v == 0) return; v->refcnt --; if (v->refcnt > 0) return; if (v->refcnt < 0) panic("negative refcnt"); switch (v->type) { default: panic("bad type"); break; case XVT_UNSET: case XVT_BOOL: case XVT_INT: case XVT_ADDR: case XVT_CIDR: break; case XVT_STR: free(v->str.s); if (v->str.flags & XV_STRF_V_RE) { regfree(v->str.re); free(v->str.re); } break; case XVT_SET: for (i=v->set.n-1;i>=0;i--) drop_xval(v->set.vals[i]); break; } free(v); } /* * Free everything an EXPRLIST points to. We don't drop the EXPRs in * the list because this is used only for expression dependencies, and * dependencies don't count as references. (drop_expr is careful to * remove the expression being discarded from any dependency lists * it's still on.) */ static void exprlist_free(EXPRLIST *l) { free(l->v); } /* * Initialize a newly-allocated EXPRLIST. */ static void exprlist_init(EXPRLIST *l) { l->v = 0; l->n = 0; l->a = 0; } /* * Remove a dependency. This is basically saying "e no longer depends * on whatever l is the dependency list for". It is used when e is * about to be thrown away. */ static void remove_dep(EXPR *e, EXPRLIST *l) { int i; for (i=l->n-1;i>=0;i--) { if (l->v[i] == e) { l->n --; if (i < l->n) l->v[i] = l->v[l->n]; return; } } panic("not found"); } /* * drop_expr and drop_cvar are mutually recursive. Which one we * declare with a forward declaration is a matter of taste. */ static void drop_expr(EXPR *); /* * Drop a reference to a variable. This decrements the refcount, * handling freeing the variable when the refcount goes to zero. */ static void drop_cvar(CVAR *v) { v->refcnt --; if (v->refcnt > 0) return; if (v->refcnt < 0) panic("negative refcnt"); free(v->name); exprlist_free(&v->deps); switch (v->type) { default: panic("bad type"); break; case VT_UNDEF: break; case VT_XVAL: drop_xval(v->x.set); drop_xval(v->x.cfg); break; case VT_MACRO: drop_expr(v->m); break; } free(v); } /* * Drop a reference to an expression. This decrements the refcount, * handling freeing the expression when the refcount goes to zero. */ static void drop_expr(EXPR *e) { int i; if (e == 0) return; e->refcnt --; if (e->refcnt > 0) return; if (e->refcnt < 0) panic("negative refcnt"); exprlist_free(&e->deps); drop_xval(e->eval); switch (e->op) { default: panic("bad op"); break; case XO_VALUE: drop_xval(e->val); break; case XO_VAR: case XO_ISSET: remove_dep(e,&e->var->deps); drop_cvar(e->var); break; case XO_CONCAT: case XO_LT: case XO_EQ: case XO_GT: case XO_AND: case XO_OR: case XO_IN: case XO_REGEX: case XO_ADD: case XO_SUB: case XO_MUL: case XO_DIV: remove_dep(e,&e->binary.lhs->deps); remove_dep(e,&e->binary.rhs->deps); drop_expr(e->binary.lhs); drop_expr(e->binary.rhs); break; case XO_NOT: case XO_NEG: remove_dep(e,&e->unary.arg->deps); drop_expr(e->unary.arg); break; case XO_COND: remove_dep(e,&e->ternary.lhs->deps); remove_dep(e,&e->ternary.mhs->deps); remove_dep(e,&e->ternary.rhs->deps); drop_expr(e->ternary.lhs); drop_expr(e->ternary.mhs); drop_expr(e->ternary.rhs); break; case XO_CALL: (*e->call.fn->deps)(e,e->call.nargs,e->call.args,&remove_dep); for (i=e->call.nargs-1;i>=0;i--) drop_expr(e->call.args[i]); free(e->call.args); break; } free(e); } /* * Add a dependency. This is saying "e now depends on whatever l is * the dependency list for". */ static void add_dep(EXPR *e, EXPRLIST *l) { if (l->n >= l->a) l->v = realloc(l->v,(l->a=l->n+8)*sizeof(*l->v)); l->v[l->n++] = e; } /* * The dependency-processing function for FNT_EVAL functions. */ static void evalfn_deps(EXPR *e, int nargs, EXPR **args, void (*fn)(EXPR *, EXPRLIST *)) { int i; for (i=nargs-1;i>=0;i--) (*fn)(e,&args[i]->deps); } /* * Create and return a new XVAL. It is created with type XVT_NONE and * refcount 1. */ static XVAL *new_xval(void) { XVAL *v; v = malloc(sizeof(XVAL)); v->type = XVT_NONE; v->refcnt = 1; return(v); } /* * Return a new string XVAL. The argument pointer is used literally, * not copied; it must be free()able when the XVAL is discarded. */ static XVAL *new_str_xval(char *s) { XVAL *v; v = new_xval(); v->type = XVT_STR; v->str.s = s; v->str.flags = 0; return(v); } /* * Create and return a new empty set. The return value is not really a * valid set, since it has not been canonicalized, and in particular * does not have its hash value set correctly yet. */ static XVAL *new_set_xval(void) { XVAL *v; v = new_xval(); v->type = XVT_SET; v->set.oftype = XVT_NONE; v->set.n = 0; v->set.vals = 0; v->set.hash = 0; return(v); } /* * Return a new address value, in the given address family. */ static XVAL *new_addr_xval(int af) { XVAL *v; v = new_xval(); v->type = XVT_ADDR; v->addr.af = af; return(v); } /* * Return a new CIDR address-and-width value, in the given address * family. */ static XVAL *new_cidr_xval(int af) { XVAL *v; v = new_xval(); v->type = XVT_CIDR; v->cidr.af = af; v->cidr.w = 0; return(v); } /* * Create and return a new expression. It is of type XO_NONE, with one * reference and no dependencies. */ static EXPR *new_expr(void) { EXPR *e; e = malloc(sizeof(EXPR)); e->op = XO_NONE; e->refcnt = 1; e->valid = 0; exprlist_init(&e->deps); e->eval = 0; return(e); } /* * Add a reference to an expression. */ static EXPR *ref_expr(EXPR *e) { if (e) e->refcnt ++; return(e); } /* * Add a reference to a value. */ static XVAL *ref_xval(XVAL *v) { if (v) v->refcnt ++; return(v); } /* * Add a reference to a variable. */ static CVAR *ref_cvar(CVAR *c) { c->refcnt ++; return(c); } /* * Build a unary expression, given its operation and argument. */ static EXPR *build_expr_1(EXPROP op, EXPR *rhs) { EXPR *e; e = new_expr(); e->op = op; e->unary.arg = rhs; add_dep(e,&rhs->deps); return(e); } /* * Build a binary expression, given its operation and arguments. */ static EXPR *build_expr_2(EXPR *lhs, EXPROP op, EXPR *rhs) { EXPR *e; e = new_expr(); e->op = op; e->binary.lhs = lhs; e->binary.rhs = rhs; add_dep(e,&lhs->deps); add_dep(e,&rhs->deps); return(e); } /* * Build a ternary expression, given its operation and arguments. */ static EXPR *build_expr_3(EXPROP op, EXPR *lhs, EXPR *mhs, EXPR *rhs) { EXPR *e; e = new_expr(); e->op = op; e->ternary.lhs = lhs; e->ternary.mhs = mhs; e->ternary.rhs = rhs; add_dep(e,&lhs->deps); add_dep(e,&mhs->deps); add_dep(e,&rhs->deps); return(e); } /* * Return the logical NOT of an expression. This handles the ! * operator. If the argument is already the NOT of something, return * the inner expression; otherwise, just build a NOT expression. We * could try to get clever, floating NOTs inside logical operations * with de Morgan's laws, but that seems more work than it's worth, * especially since it means creating more than one new expression * unelss both arms of the boolean operation are themselves negations. */ static EXPR *complement_expr(EXPR *e) { switch (e->op) { case XO_NOT: return(ref_expr(e->unary.arg)); break; default: return(build_expr_1(XO_NOT,ref_expr(e))); break; } } /* * Return the arithmetic negation of an expression. This handles the * unary - operator. If the argument is already the negation of * something, return the inner expression; if it's a subtraction, swap * the LHS and RHS. Otherwise, just build a NEG exprsesion. */ static EXPR *negate_expr(EXPR *e) { switch (e->op) { case XO_NEG: return(ref_expr(e->unary.arg)); break; case XO_SUB: return(build_expr_2( ref_expr(e->binary.rhs), XO_SUB, ref_expr(e->binary.lhs) )); break; default: return(build_expr_1(XO_NEG,ref_expr(e))); break; } } /* * Add a new variable. This is one of the few routines that uses the * config global instead of taking a CONFIG * parameter, largely for * convenience of the long string of calls in config_init(). In most * cases, there are no optional arguments; if the type is VT_KEY, * there are arguments, alternating const char * and int values giving * key/value pairs, up to a nil key pointer; for VT_KFN, there is one * function pointer argument, which is called to obtain the keys. * If any of the VF_SPEC_* bits are set in flags, then (after the * aforementioned, if present) there is one argument, a function * pointer to be sored into the .spec field of the variable. * * The new variable is returned. */ static CVAR *add_var(const char *name, CVARTYPE type, unsigned int flags, ...) { CVAR *v; va_list ap; int i; const char *valname; int valval; KVAL (*kfn)(int); if (flags & (VF_SET|VF_CFG)) panic("bad flags"); v = malloc(sizeof(CVAR)); v->name = strdup(name); v->namelen = strlen(name); v->refcnt = 1; v->type = type; v->flags = flags; exprlist_init(&v->deps); va_start(ap,flags); switch (type) { default: panic("no type-specific support"); break; case VT_KEY: v->k.nvals = 0; while (1) { valname = va_arg(ap,const char *); if (valname == 0) break; valval = va_arg(ap,int); v->k.nvals ++; } va_end(ap); if (v->k.nvals < 1) panic("no keys for VT_KEY"); v->k.vals = malloc(v->k.nvals*sizeof(*v->k.vals)); va_start(ap,flags); for (i=0;;i++) { valname = va_arg(ap,const char *); valval = va_arg(ap,int); if (valname == 0) break; if (i >= v->k.nvals) panic("VT_KEY table overrun"); v->k.vals[i] = (KVAL){ .name = valname, .val = valval }; } if (i != v->k.nvals) panic("VT_KEY table phase error"); do <"deffound"> { for (i=v->k.nvals-1;i>=0;i--) { if (valval == v->k.vals[i].val) { v->k.def = i; break <"deffound">; } } panic("VT_KEY default value not found"); } while (0); va_end(ap); break; case VT_BOOL: v->b.def = 0; break; case VT_INT: v->i.def = 0; break; case VT_STR: v->s.set = 0; v->s.cfg = 0; v->s.def = 0; break; case VT_ADDR: v->a.set = 0; v->a.cfg = 0; v->a.def = 0; break; case VT_UNDEF: break; case VT_KFN: v->type = VT_KEY; kfn = va_arg(ap,__typeof__(kfn)); for (i=0;(*kfn)(i).name;i++) ; v->k.nvals = i; if (v->k.nvals < 1) panic("no keys for VT_KFN"); v->k.vals = malloc(v->k.nvals*sizeof(*v->k.vals)); for (i=v->k.nvals-1;i>=0;i--) v->k.vals[i] = (*kfn)(i); v->k.def = 0; break; } if (flags & VF_SPEC_ALL) v->spec = va_arg(ap,__typeof__(v->spec)); va_end(ap); if (config.nvars >= config.avars) config.vars = realloc(config.vars,(config.avars=config.nvars+8)*sizeof(*config.vars)); config.vars[config.nvars++] = v; v->link = config.lvars; config.lvars = v; return(v); } /* * Find a variable by name. name and namelen describe the name (if * namelen is negative, strlen(name) is used instead). If the * variable is not found, add is true, and the variable name begins * with a $, then add_var is called with VT_UNDEF and VF_RW to get a * variable to return; any other failure to find the name results in a * nil return. */ static CVAR *find_cvar(const char *name, int namelen, int add) { CVAR *v; CVAR **vp; if (namelen < 0) namelen = strlen(name); for (vp=&config.lvars;(v=*vp);vp=&v->link) { if ( (v->namelen == namelen) && !bcmp(name,v->name,namelen) ) { if (vp != &config.lvars) { *vp = v->link; v->link = config.lvars; config.lvars = v; } return(v); } } if (add && (namelen > 0) && (name[0] == '$')) { char *n; n = malloc(namelen+1); bcopy(name,n,namelen); n[namelen] = '\0'; return(add_var(n,VT_UNDEF,VF_RW)); free(n); } return(0); } /* * Look up a function by name, returning it, or nil if not found. */ static FN *lookup_fn(const char *name, int namelen) { int i; FN *f; if (fns[0].namelen == 0) { for (i=nfns-1;i>=0;i--) { f = &fns[i]; f->namelen = strlen(f->name); } } for (i=nfns-1;i>=0;i--) { f = &fns[i]; if ((f->namelen == namelen) && !bcmp(f->name,name,namelen)) return(f); } return(0); } /* * The low levels of the parser want to call the top level, for * handling parenthesized expressions, function aguments, and * conditional arms. So we declare do_expr_top forward. */ static EXPR *do_expr_top(CONFIG *); /* * Parse a `primary' expression. This can be a parenthesized * expression, a [ ] function call, a string literal, one of the * boolean constants .true or .false, a ?variable construct, a numeric * constant, or a bare variable. * * The variable-handling code here is where VT_MACRO variable expansion * happens: when a VT_MACRO variable is found, a reference to its * defined expression is substituted instead. (This also means that * macro expansions are effectively surrounded by parens, rather than * being subject to precedence lossages the way a purely text-based * macro facility would be.) */ static EXPR *do_expr_primary(CONFIG *c) { EXPR *e; CVAR *v; FN *fn; const char *err; if (! c->tok) return(0); e = 0; switch (c->tok[0]) { case '(': token(c); e = do_expr_top(c); if (!c->tok || (c->tok[0] != ')')) badline(c,"unclosed ("); token(c); break; case '[': { NESTED void teardown(void) { /* Can't use drop_expr here, since that tries to remove deps, and the deps haven't been added yet. So we tear down, by hand, the parts we've set up. (This code knows that it is called only when nargs is how many args are set up.) */ int i; for (i=e->call.nargs-1;i>=0;i--) drop_expr(e->call.args[i]); free(e->call.args); free(e); } token(c); if (! token_letter(c->tok[0],1)) badline(c,"bad function name"); fn = lookup_fn(c->tok,c->tkl); if (! fn) badline(c,"unknown function name"); e = new_expr(); e->op = XO_CALL; e->call.fn = fn; e->call.nargs = 0; e->call.args = 0; token(c); if (c->tok[0] != ']') { while (1) { EXPR *arg; arg = do_expr_top(c); if (arg == 0) { teardown(); return(0); } e->call.args = realloc(e->call.args,(e->call.nargs+1)*sizeof(EXPR *)); e->call.args[e->call.nargs++] = arg; if (! c->tok) { teardown(); badline(c,"unclosed [ ]"); } if (c->tok[0] == ']') break; if (c->tok[0] != ',') { teardown(); badline(c,"improperly closed [ ]"); } token(c); } } token(c); err = (*fn->checkargs)(e->call.nargs,e->call.args); if (err) { teardown(); badline(c,"%s",err); } (*e->call.fn->deps)(e,e->call.nargs,e->call.args,&add_dep); } break; case '"': case '\'': if (c->tkl < 2) panic("impossibly short string"); e = new_expr(); e->op = XO_VALUE; e->val = new_str_xval(malloc(c->tkl-1)); if (c->tkl > 2) bcopy(c->tok+1,e->val->str.s,c->tkl-2); e->val->str.s[c->tkl-2] = '\0'; token(c); break; case '.': if (c->tkl > 1) { e = new_expr(); e->op = XO_VALUE; e->val = new_xval(); e->val->type = XVT_BOOL; e->val->bool = (c->tok[1] == 't'); token(c); } break; case '?': token(c); if (! token_letter(c->tok[0],1)) badline(c,"? not followed by variable"); v = find_cvar(c->tok,c->tkl,1); if (v == 0) badline(c,"no such variable `%.*s'",c->tkl,c->tok); e = new_expr(); e->op = XO_ISSET; e->var = ref_cvar(v); add_dep(e,&v->deps); token(c); break; default: if ( ((c->tkl >= 2) && (c->tok[0] == '-') && token_digit(c->tok[1])) || token_digit(c->tok[0]) ) { char *t; t = malloc(c->tkl+1); bcopy(c->tok,t,c->tkl); t[c->tkl] = '\0'; e = new_expr(); e->op = XO_VALUE; e->val = new_xval(); e->val->type = XVT_INT; e->val->i = strtol(t,0,0); free(t); token(c); } else if (token_letter(c->tok[0],1)) { v = find_cvar(c->tok,c->tkl,1); if (v == 0) badline(c,"no such variable `%.*s'",c->tkl,c->tok); if (v->type == VT_MACRO) { e = ref_expr(v->m); } else { e = new_expr(); e->op = XO_VAR; e->var = ref_cvar(v); add_dep(e,&v->deps); } token(c); } break; } return(e); } /* * Parse a conditional. The condition must be a primary expression * (which means it needs to be parenthesized if it's not trivial); the * arms of the conditional can be anything that can go in parens. */ static EXPR *do_expr_cond(CONFIG *c) { EXPR *lhs; EXPR *mhs; EXPR *rhs; lhs = do_expr_primary(c); if (! lhs) return(0); while (1) { if (!c->tok || (c->tok[0] != '{'/*}*/)) return(lhs); token(c); mhs = do_expr_top(c); if (!mhs || !c->tok || (c->tok[0] != ':')) badline(c,"mangled { : }"); token(c); rhs = do_expr_top(c); if (!rhs || !c->tok || (c->tok[0] != /*{*/'}')) badline(c,"mangled { : }"); token(c); lhs = build_expr_3(XO_COND,lhs,mhs,rhs); } } /* * Parse multiplications and divisions. */ static EXPR *do_expr_mul(CONFIG *c) { EXPR *e; EXPR *rhs; e = do_expr_cond(c); while (e && c->tok && (c->tkl == 1) && ((c->tok[0] == '*') || (c->tok[0] == '/'))) { token(c); rhs = do_expr_cond(c); if (! rhs) badline(c,"missing RHS to `%c'",c->tok[0]); e = build_expr_2(e,(c->tok[0]=='*')?XO_MUL:XO_DIV,rhs); } return(e); } /* * Parse additions and subtractions. */ static EXPR *do_expr_add(CONFIG *c) { EXPR *e; EXPR *rhs; e = do_expr_mul(c); while (e && c->tok && (c->tkl == 1) && ((c->tok[0] == '+') || (c->tok[0] == '-'))) { token(c); rhs = do_expr_mul(c); if (! rhs) badline(c,"missing RHS to `%c'",c->tok[0]); e = build_expr_2(e,(c->tok[0]=='+')?XO_ADD:XO_SUB,rhs); } return(e); } /* * Parse . (string concatenations, or set union). */ static EXPR *do_expr_concat(CONFIG *c) { EXPR *e; EXPR *rhs; e = do_expr_add(c); while (e && c->tok && (c->tkl == 1) && (c->tok[0] == '.')) { token(c); rhs = do_expr_add(c); if (! rhs) badline(c,"missing RHS to `.'"); e = build_expr_2(e,XO_CONCAT,rhs); } return(e); } /* * Parse comparisons. Note that comparisons are non-associative; atkl == 2); if (c->tok) { switch ((c->tkl==1)?c->tok[0]:TWOCHAR(c->tok[0],c->tok[1])) { case '@': op = XO_IN; opstr = "@"; break; case '~': op = XO_REGEX; opstr = "~"; break; case '<': op = XO_LT; opstr = "<"; break; case '>': op = XO_GT; opstr = ">"; break; case TWOCHAR('<','='): op = XO_GT; opstr = "<="; break; case TWOCHAR('>','='): op = XO_LT; opstr = ">="; break; case TWOCHAR('=','='): op = XO_EQ; neg = 0; opstr = "=="; break; case TWOCHAR('!','@'): op = XO_IN; opstr = "!@"; break; case TWOCHAR('!','~'): op = XO_REGEX; opstr = "!~"; break; case TWOCHAR('!','='): op = XO_EQ; opstr = "!="; break; default: return(e); break; } token(c); rhs = do_expr_concat(c); if (! rhs) badline(c,"missing RHS to `%s'",opstr); e = build_expr_2(e,op,rhs); if (neg && e) e = build_expr_1(XO_NOT,e); } return(e); } /* * Parse unary operators. * * XXX think about where this falls in the precedence hierarchy; should * it perhaps come between do_expr_mul and do_expr_cond, or perhaps * even between do_expr_cond and do_expr_primary? */ static EXPR *do_expr_unary(CONFIG *c) { EXPR *e; int not; int neg; neg = 0; not = 0; while <"op"> (1) { if (!c->tok || (c->tkl != 1)) break; switch (c->tok[0]) { case '!': not ++; break; case '-': if (not == 0) neg ++; break; default: break <"op">; } token(c); } e = do_expr_cmp(c); if (! e) return(0); if (not & 1) { EXPR *e2; e2 = complement_expr(e); drop_expr(e); e = e2; } if (neg & 1) { EXPR *e2; e2 = negate_expr(e); drop_expr(e); e = e2; } return(e); } /* * Parse boolean ANDs. */ static EXPR *do_expr_and(CONFIG *c) { EXPR *e; EXPR *rhs; int l; e = do_expr_unary(c); while (e && c->tok && (c->tok[0] == '&')) { l = c->tkl; token(c); rhs = do_expr_unary(c); if (! rhs) badline(c,"missing RHS to `%.*s'",l,"&&"); e = build_expr_2(e,XO_AND,rhs); } return(e); } /* * Parse boolean ORs. */ static EXPR *do_expr_or(CONFIG *c) { EXPR *e; EXPR *rhs; int l; e = do_expr_and(c); while (e && c->tok && (c->tok[0] == '|')) { l = c->tkl; token(c); rhs = do_expr_and(c); if (! rhs) badline(c,"missing RHS to `%.*s'",l,"||"); e = build_expr_2(e,XO_OR,rhs); } return(e); } /* * Parser top level - just call do_expr_or. This is done rather than * actually using do_expr_or wherever do_expr_top currently is so that * if we ever change the name of the loosest-binding operator's * parsing function we have to change it only one place. */ static EXPR *do_expr_top(CONFIG *c) { return(do_expr_or(c)); } /* * Return a new CONFLINE for the given CONFIG. The new CONFLINE still * needs filling in, except that .lno will be set already. */ static CONFLINE *confline_append(CONFIG *c) { CONFLINE *cl; cl = malloc(sizeof(CONFLINE)); *c->configlistt = cl; c->configlistt = &cl->link; cl->lno = c->lno0; return(cl); } /* * A new line has been accumulated for the CONFIG. Handle it. This is * where the ad-hoc parser that identifies line types is; this is also * where macro definitions are handled, without generating CONFLINEs. */ static void configfile_line(CONFIG *c) { CONFLINE *cl; EXPR *e; c->lp = c->lb; token(c); if (c->tok == 0) return; if ((c->tkl == 2) && !bcmp(c->tok,"if",2)) { token(c); if (c->tok == 0) badline(c,"missing expression after `if'"); e = do_expr_top(c); if (! e) badline(c,"mangled expression after `if'"); skipspace(c); if (*c->lp) { drop_expr(e); badline(c,"junk after `if' expression"); } cl = confline_append(c); cl->type = CL_IF; cl->cond = e; c->ifdepth ++; if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: if at %d, depth %d\n",c->lno0,c->ifdepth); } else if ((c->tkl == 4) && !bcmp(c->tok,"else",4)) { if (c->ifdepth < 1) badline(c,"`else' without `if'"); skipspace(c); if (*c->lp) badline(c,"junk after `else'"); confline_append(c)->type = CL_ELSE; if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: else at %d, depth %d\n",c->lno0,c->ifdepth); } else if ((c->tkl == 4) && !bcmp(c->tok,"elif",4)) { if (c->ifdepth < 1) badline(c,"`elif' without `if'"); token(c); if (c->tok == 0) badline(c,"missing expression after `elif'"); e = do_expr_top(c); if (! e) badline(c,"mangled expression after `elif'"); skipspace(c); if (*c->lp) { drop_expr(e); badline(c,"junk after `elif' expression"); } cl = confline_append(c); cl->type = CL_ELIF; cl->cond = e; if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: elif at %d, depth %d\n",c->lno0,c->ifdepth); } else if ((c->tkl == 5) && !bcmp(c->tok,"endif",5)) { skipspace(c); if (*c->lp) badline(c,"junk after `endif'"); if (c->ifdepth < 1) badline(c,"`endif' without `if'"); confline_append(c)->type = CL_ENDIF; c->ifdepth --; if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: endif at %d, depth %d\n",c->lno0,c->ifdepth); } else { EXPR *varx; CVAR *var; EXPR *val; char setop; const char *what; varx = do_expr_primary(c); if ( !varx || (varx->op != XO_VAR) || !c->tok || (c->tkl != 1) || ((c->tok[0] != '=') && (c->tok[0] != ':')) ) { drop_expr(varx); badline(c,"non-conditional line must be `var = [value]' or `var : value'"); } var = varx->var; setop = c->tok[0]; switch (setop) { case '=': switch (var->type) { case VT_UNDEF: var->type = VT_XVAL; var->x.set = 0; var->x.cfg = 0; var->x.def = 0; break; case VT_MACRO: badline(c,"can't assign to macro variable"); break; default: break; } what = "variable setting"; break; case ':': switch (var->type) { case VT_UNDEF: var->type = VT_MACRO; var->m = 0; break; case VT_MACRO: break; case VT_XVAL: badline(c,"can't redefine a value variable as a macro"); break; default: badline(c,"can't use builtin variables as macros"); break; } what = "macro definition"; break; default: panic("impossible setop"); break; } token(c); if (c->tok == 0) { val = 0; } else { val = do_expr_top(c); if (! val) badline(c,"mangled %s expression",what); if (c->tok) badline(c,"junk after %s",what); } switch (var->type) { default: cl = confline_append(c); cl->type = CL_VSET; cl->vset.var = ref_cvar(var); drop_expr(varx); cl->vset.val = val; break; case VT_MACRO: drop_expr(var->m); var->m = val; break; } } } /* * Save a character in the CONFIG's line buffer, reallocating as * necessary. */ static void cflsave(CONFIG *c, int ch) { if (c->ll >= c->la) c->lb = realloc(c->lb,c->la=c->ll+16); c->lb[c->ll++] = ch; } /* * Dump out a string's value. This is for debugging output, so it * doesn't have to be perfect, as long as it's unambiguous. */ static void dump_string(FILE *to, const char *s) { const char *s0; char q; const char *pref; if (! s) { fprintf(to,"(s:nil)"); return; } if (! *s) { fprintf(to,"\"\""); return; } s0 = s; q = 0; pref = ""; while <"str"> (1) { switch (*s) { case '"': case '\'': if (q == 0) { q = '"' + '\'' - *s; } else if (q == *s) { fprintf(to,"%s%c%.*s%c",pref,q,(int)(s-s0),s0,q); s0 = s; q = '"' + '\'' - q; pref = "."; } break; case '\0': break <"str">; break; } s ++; } if (q == 0) q = '"'; if (s > s0) fprintf(to,"%s%c%.*s%c",pref,q,(int)(s-s0),s0,q); } /* * Return the string form of a boolean value. */ static const char *boolstr(int v) { return(v?".true":".false"); } /* * Dump out an XVAL. This is for debugging output, so we also add * notes for things like the str.flags field of XVT_STR values, and * print text instead of panicking for invalid types. */ static void dump_xval(FILE *to, XVAL *x) { if (! x) { fprintf(to,"(x:nil)"); return; } switch (x->type) { default: fprintf(to,"val?%d",(int)x->type); break; case XVT_UNSET: fprintf(to,"(unset)"); break; case XVT_BOOL: fprintf(to,"%s",boolstr(x->bool)); break; case XVT_STR: dump_string(to,x->str.s); if (x->str.flags & XV_STRF_V_RE) { fprintf(to,"<+re"); if (x->str.flags & XV_STRF_NOSUB) fprintf(to,"NS"); fprintf(to,">"); } if (x->str.flags & XV_STRF_V_MAXSUB) { fprintf(to,"<+maxsub%d>",x->str.maxsub); } if (x->str.flags & XV_STRF_V_HASH) { fprintf(to,"",x->str.hash); } break; case XVT_INT: fprintf(to,"%d",x->i); break; { NESTED void foo(int af, const void *a) { char buf[IPADDRTXTLEN]; const char *p; p = inet_ntop(af,a,&buf[0],IPADDRTXTLEN); if (p) { fprintf(to,"%s",p); } else { fprintf(to,"addr?af%derr%d",x->addr.af,errno); } } case XVT_ADDR: foo(x->addr.af,&x->addr.u); break; case XVT_CIDR: foo(x->cidr.af,&x->cidr.u); fprintf(to,"/%d",x->cidr.w); break; } case XVT_SET: { const char *pref; int i; fprintf(to,"[set"); pref = " "; for (i=0;iset.n;i++) { fprintf(to,"%s",pref); pref = " ,"; dump_xval(to,x->set.vals[i]); } fprintf(to,"]"); } break; } } /* * Dump out an expression. This is for debugging output, so we * annotate the output with things like type markers and printing text * rather than panicking for invalid types. */ static void dump_expr(FILE *to, EXPR *e) { int i; if (! e) { fprintf(to,"(e:nil)"); return; } if (! e->valid) fprintf(to,"*"); fprintf(to,"«"); dump_xval(to,e->eval); fprintf(to,"»"); switch (e->op) { default: fprintf(to,"expr?%d",(int)e->op); break; case XO_VALUE: fprintf(to,"val:"); dump_xval(to,e->val); break; case XO_VAR: fprintf(to,"var:%s",e->var->name); break; { const char *sep; { case XO_CONCAT: sep = " "; } if (0) { case XO_LT: sep = " < "; } if (0) { case XO_EQ: sep = " == "; } if (0) { case XO_GT: sep = " > "; } if (0) { case XO_AND: sep = " & "; } if (0) { case XO_OR: sep = " | "; } if (0) { case XO_IN: sep = " @ "; } if (0) { case XO_ADD: sep = " + "; } if (0) { case XO_SUB: sep = " - "; } if (0) { case XO_MUL: sep = " * "; } if (0) { case XO_DIV: sep = " / "; } if (0) { case XO_REGEX: sep = " ~ "; } fprintf(to,"( "); dump_expr(to,e->binary.lhs); fprintf(to,"%s",sep); dump_expr(to,e->binary.rhs); fprintf(to," )"); } break; case XO_NOT: fprintf(to,"! "); dump_expr(to,e->unary.arg); break; case XO_NEG: fprintf(to,"- "); dump_expr(to,e->unary.arg); break; case XO_ISSET: fprintf(to,"? var:%s",e->var->name); break; case XO_COND: dump_expr(to,e->ternary.lhs); fprintf(to,"{ "/*}*/); dump_expr(to,e->ternary.mhs); fprintf(to," : "); dump_expr(to,e->ternary.rhs); fprintf(to,/*{*/" }"); break; case XO_CALL: fprintf(to,"[%s",e->call.fn->name); for (i=0;icall.nargs;i++) { fprintf(to,"%s ",i?",":""); dump_expr(to,e->call.args[i]); } fprintf(to,"]"); break; } } /* * Convert an address value to string form. */ static const char *addrstr(XVAL *v) { static char buf[IPADDRTXTLEN]; static char *eb = 0; const char *t; int e; if (! v) return("(nil)"); if (v->type != XVT_ADDR) panic("not an address"); t = inet_ntop(v->addr.af,&v->addr.u,&buf[0],IPADDRTXTLEN); if (t) return(t); e = errno; free(eb); asprintf(&eb,"[%s]",strerror(e)); return(eb); } /* * Dump out a CONFIG's configuration. This means all the variables, * together with their various values, the file name, and the * CONFLINEs. */ static void dump_config(FILE *to, CONFIG *c) { int i; CVAR *v; int j; CONFLINE *cl; for (i=0;invars;i++) { v = c->vars[i]; fprintf(to,"var %d: ",i); if (v->flags & VF_R) fprintf(to,"R"); if (v->flags & VF_W) fprintf(to,"W"); if (v->flags & VF_SET) fprintf(to,"S"); if (v->flags & VF_CFG) fprintf(to,"C"); if (v->flags & VF_SPEC_ALL) { fprintf(to,"*"); if (v->flags & VF_SPEC_INIT) fprintf(to,"i"); if (v->flags & VF_SPEC_SET) fprintf(to,"s"); if (v->flags & VF_SPEC_SETANY) fprintf(to,"a"); if (v->flags & VF_SPEC_GET) fprintf(to,"g"); } fprintf(to," %s ",v->name); switch (v->type) { default: fprintf(to,"?%d",(int)v->type); break; case VT_KEY: fprintf(to,"KEY"); for (j=0;jk.nvals;j++) fprintf(to," %s",v->k.vals[j].name); if (v->flags & VF_SET) fprintf(to," S=%s",v->k.vals[v->k.set].name); if (v->flags & VF_CFG) fprintf(to," C=%s",v->k.vals[v->k.cfg].name); break; case VT_BOOL: fprintf(to,"BOOL"); if (v->flags & VF_SET) fprintf(to," S=%c","FT"[v->b.set]); if (v->flags & VF_CFG) fprintf(to," C=%c","FT"[v->b.cfg]); break; case VT_INT: fprintf(to,"INT"); if (v->flags & VF_SET) fprintf(to," S=%d",v->i.set); if (v->flags & VF_CFG) fprintf(to," C=%d",v->i.cfg); break; case VT_STR: fprintf(to,"STR"); if (v->flags & VF_SET) fprintf(to," S=%s",v->s.set); if (v->flags & VF_CFG) fprintf(to," C=%s",v->s.cfg); break; case VT_ADDR: fprintf(to,"ADDR"); if (v->flags & VF_SET) fprintf(to," S=%s",addrstr(v->a.set)); if (v->flags & VF_CFG) fprintf(to," C=%s",addrstr(v->a.cfg)); break; case VT_XVAL: fprintf(to,"XVAL"); if (v->flags & VF_SET) { fprintf(to," S="); dump_xval(to,v->x.set); } if (v->flags & VF_CFG) { fprintf(to," C="); dump_xval(to,v->x.cfg); } break; case VT_MACRO: fprintf(to,"MACRO "); dump_expr(to,v->m); break; case VT_UNDEF: fprintf(to,"UNDEF"); break; } fprintf(to,"\n"); } fprintf(to,"file: %s\n",c->file); for (cl=c->configlist;cl;cl=cl->link) { fprintf(to,"%d: ",cl->lno); switch (cl->type) { case CL_VSET: fprintf(to,"vset %p: var %s\n",(void *)cl,cl->vset.var->name); fprintf(to," val "); dump_expr(to,cl->vset.val); fprintf(to,"\n"); break; case CL_IF: fprintf(to,"if %p: cond ",(void *)cl); dump_expr(to,cl->cond); fprintf(to,"\n"); break; case CL_ELSE: fprintf(to,"else %p\n",(void *)cl); break; case CL_ELIF: fprintf(to,"elif %p: cond ",(void *)cl); dump_expr(to,cl->cond); fprintf(to,"\n"); break; case CL_ENDIF: fprintf(to,"endif %p\n",(void *)cl); break; default: fprintf(to,"?%d\n",(int)cl->type); break; } } } /* * Initialize the configuration in config. */ static void init_config(void) { config.file = 0; config.lb = 0; config.ll = 0; config.la = 0; config.err = 0; config.configlist = 0; config.configlistt = &config.configlist; alglist_init(&config.pre_algs_kex,&at_kex); alglist_init(&config.pre_algs_hk,&at_hk); alglist_init(&config.pre_algs_enc_c2s,&at_enc); alglist_init(&config.pre_algs_enc_s2c,&at_enc); alglist_init(&config.pre_algs_mac_c2s,&at_mac); alglist_init(&config.pre_algs_mac_s2c,&at_mac); alglist_init(&config.pre_algs_comp_c2s,&at_comp); alglist_init(&config.pre_algs_comp_s2c,&at_comp); } /* * Clear a CONFIG. This means freeing all the CONFLINEs, throwing away * all $-prefixed convenience variables, and resetting the pre_* stuff * to their pre_* states. It does *not* include clearing the VF_SET * and (in particular) VF_CFG bits on variables. */ static void clear_config(CONFIG *c) { int i; CVAR *v; CONFLINE *cl; free(config.file); config.file = 0; while ((cl = c->configlist)) { c->configlist = cl->link; switch (cl->type) { case CL_VSET: drop_cvar(cl->vset.var); drop_expr(cl->vset.val); break; case CL_IF: case CL_ELIF: drop_expr(cl->cond); break; case CL_ELSE: case CL_ENDIF: break; } free(cl); } c->configlistt = &c->configlist; c->lvars = 0; for (i=c->nvars-1;i>=0;i--) { v = c->vars[i]; if (v->name[0] == '$') { c->nvars --; if (i != c->nvars) c->vars[i] = c->vars[c->nvars]; drop_cvar(v); } else { v->link = c->lvars; c->lvars = v; } } c->pre_fwd = fwd; c->pre_fwdonce = fwdonce; c->pre_nports = nports; c->pre_nauthkeys = nauthkeys; alglist_replace(&c->pre_algs_kex,&algs_kex); alglist_replace(&c->pre_algs_hk,&algs_hk); alglist_replace(&c->pre_algs_enc_c2s,&algs_enc_c2s); alglist_replace(&c->pre_algs_enc_s2c,&algs_enc_s2c); alglist_replace(&c->pre_algs_mac_c2s,&algs_mac_c2s); alglist_replace(&c->pre_algs_mac_s2c,&algs_mac_s2c); alglist_replace(&c->pre_algs_comp_c2s,&algs_comp_c2s); alglist_replace(&c->pre_algs_comp_s2c,&algs_comp_s2c); } /* * Load a config file. Basically, this is just: clear out any current * config, open the file, accumuate lines, and call configfile_line * for each one. */ void load_config(const char *file) { __label__ err_catch; int c; FILE *f; NESTED void cferr_throw(void) { config.err = 1; goto err_catch; } if (file == 0) file = default_config(); clear_config(&config); if (! *file) return; f = fopen(file,"r"); if (f == 0) { if (errno == ENOENT) return; logmsg(LM_ERR,"%s: %s",file,strerror(errno)); } if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: starting parse\n"); config.file = strdup(file); config.lno = 1; config.lno0 = 1; config.err_throw = &cferr_throw; config.ifdepth = 0; config.cstack = 0; do { c = getc(f); switch (c) { case EOF: if (ferror(f)) { logmsg(LM_ERR,"%s: %s",file,strerror(errno)); config.err = 1; } /* fall through */ case '\n': if ((config.ll > 0) && (config.lb[config.ll-1] == '\\')) { config.ll --; config.lno ++; continue; } cflsave(&config,'\0'); configfile_line(&config); config.ll = 0; config.lno0 = ++config.lno; break; default: cflsave(&config,c); break; } } while (c != EOF); *config.configlistt = 0; if (VERB(CONFIGCOND)) verb(CONFIGCOND,"cond: ending parse, ifdepth %d\n",config.ifdepth); if (config.ifdepth) { logmsg(LM_ERR,"%s: conditional unclosed at EOF",file); cferr_throw(); } err_catch:; fclose(f); if (config.err) exit(1); if (VERB(CONFIGFILE)) { FILE *f; f = verb_fopen(VERBOSE_CONFIGFILE); fprintf(f,"After loading:\n"); dump_config(f,&config); fclose(f); } } /* * Add a port number. This is called when the port config-file * variable is set to a value not already present, and for the -port * command-line flag. */ static void add_port(char *s) { if (nports >= aports) ports = realloc(ports,(aports=nports+8)*sizeof(*ports)); ports[nports++] = s; } /* * Add an authentication-key file. This is called when the * auth-key-file config-file variable is set to a value not already * present, and for the -authkey command-line flag. */ static void add_authkey(char *s) { if (nauthkeys >= aauthkeys) authkeys = realloc(authkeys,(aauthkeys=nauthkeys+8)*sizeof(*authkeys)); authkeys[nauthkeys++] = s; } /* * Strip private algorithms out of one algorithm list. Called by * set_no_private. */ static void strip_private(ALGLIST *l) { ALGLIST tmp; alglist_init(&tmp,l->type); alglist_expand_default(l); alglist_map(l,({ NESTED int foo(void *alg) { if (! index((*l->type->name)(alg),'@')) { alglist_append1(&tmp,alg); } return(0); } &foo; })); alglist_replace(l,&tmp); alglist_clear(&tmp); } /* * Strip private algorithms out of the algorithms lists. This is * called whenever no-private is set true (even if it already was * true). */ static void set_no_private(void) { strip_private(&algs_kex); strip_private(&algs_hk); strip_private(&algs_enc_c2s); strip_private(&algs_enc_s2c); strip_private(&algs_mac_c2s); strip_private(&algs_mac_s2c); strip_private(&algs_comp_c2s); strip_private(&algs_comp_s2c); } /* * Handle command-line parsing. This is where argv[0] is tested to see * if it's one of the known values, and the host set if not. */ void handleargs(int ac, char **av) { int skip; int errs; int got; #define GOT_HOST 0x00000001 #define GOT_SHARING 0x00000002 #define GOT_USER 0x00000004 #define GOT_LANG_CtoS 0x00000008 #define GOT_LANG_StoC 0x00000010 #define GOT_HOSTKEYDB 0x00000020 #define GOT_RANDFILE 0x00000040 #define GOT_AUTHKEYDIR 0x00000080 #define GOT_HOSTKEYDIR 0x00000100 #define GOT_SSHDIR 0x00000200 #define GOT_KEYTYPE 0x00000400 #define GOT_KEYSIZE 0x00000800 #define GOT_KEYFILE_PRIV 0x00001000 #define GOT_KEYFILE_PUB 0x00002000 #define GOT_ESCCHAR 0x00004000 #define GOT_PASSPHRASE 0x00008000 #define GOT_COMMENT 0x00010000 char *sl; FILE *cmdf; const char *cmdsep; int i; skip = 0; errs = 0; got = 0; sl = rindex(av[0],'/'); if (sl) { sl ++; } else { sl = av[0]; } cmdf = fopen_alloc(&cmdline_txt,0); cmdsep = ""; if (*sl && strcmp(sl,"ssh") && strcmp(sl,"moussh")) { config_set_str("host",sl); config_set_key_v("host-type",HT_ARGV); got |= GOT_HOST; fprintf(cmdf,"%s",sl); cmdsep = " "; } for (ac--,av++;ac;ac--,av++) { fprintf(cmdf,"%s%s",cmdsep,*av); cmdsep = " "; if (skip > 0) { skip --; continue; } if (**av != '-') { if ( ! (got & GOT_HOST) && (config_key("opmode") == OPMODE_CLIENT) ) { config_set_str("host",*av); config_set_key_v("host-type",HT_BARE); got |= GOT_HOST; } else { argsbegin = av; argscount = ac; break; } continue; } if (0) { needarg:; fprintf(errf,"%s: %s needs another argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-cmd") || !strcmp(*av,"--")) { argsbegin = av + 1; argscount = ac - 1; break; } if (!strcmp(*av,"-import")) { config_set_key_v("opmode",OPMODE_IMPORT); continue; } if (!strcmp(*av,"-ia")) { config_set_key_v("opmode",OPMODE_INTAGENT); continue; } if (!strcmp(*av,"-export")) { config_set_key_v("opmode",OPMODE_EXPORT); continue; } if (!strcmp(*av,"-kfp") || !strcmp(*av,"-fingerprint")) { config_set_key_v("opmode",OPMODE_FPRINT); continue; } if (!strcmp(*av,"-host")) { WANTARG(); if (got & GOT_HOST) fprintf(errf,"%s: %s %s: warning: host already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("host",av[skip]); config_set_key_v("host-type",HT_FLAG); got |= GOT_HOST; continue; } if (!strcmp(*av,"-nohost")) { got &= ~GOT_HOST; config_unset("host"); config_set_key_v("host-type",HT_NONE); continue; } if (!strcmp(*av,"-port")) { WANTARG(); add_port(av[skip]); continue; } if (!strcmp(*av,"-keepalive-net") || !strcmp(*av,"-kan")) { config_set_bool("net-keepalive",1); continue; } if (!strcmp(*av,"-keepalive-ssh") || !strcmp(*av,"-kas")) { WANTARG(); #ifndef AF_TIMER fprintf(errf,"%s: warning: ssh-level keepalives not supported in this build (no AF_TIMER)\n",__progname); #else config_set_int("ssh-keepalive",atoi(av[skip])); #endif continue; } if (!strcmp(*av,"-share-server")) { WANTARG(); if (got & GOT_SHARING) fprintf(errf,"%s: %s %s: warning: sharing already specified, using later option\n",__progname,av[0],av[skip]); config_set_str("share-path",av[skip]); config_set_bool("share-server",1); got |= GOT_SHARING; continue; } if (!strcmp(*av,"-auto-share-timeout")) { WANTARG(); #ifndef AF_TIMER fprintf(errf,"%s: warning: auto-share timeouts not supported in this build (no AF_TIMER)\n",__progname); #else config_set_int("auto-share-timeout",atoi(av[skip])); #endif continue; } if (!strcmp(*av,"-share-client")) { WANTARG(); if (got & GOT_SHARING) fprintf(errf,"%s: %s %s: warning: sharing already specified, using later option\n",__progname,av[0],av[skip]); config_set_str("share-path",av[skip]); config_set_bool("share-client",1); got |= GOT_SHARING; continue; } if (!strcmp(*av,"-auto-share")) { if (got & GOT_SHARING) fprintf(errf,"%s: %s %s: warning: sharing already specified, using later option\n",__progname,av[0],av[skip]); WANTARG(); config_set_str("share-path",av[skip]); WANTARG(); config_set_str("auto-share-path",av[skip]); config_set_bool("auto-share",1); got |= GOT_SHARING; continue; } if (!strcmp(*av,"-no-share")) { config_set_bool("auto-share",0); config_set_bool("share-client",0); config_set_bool("share-server",0); got &= ~GOT_SHARING; continue; } if (!strcmp(*av,"-share-kill")) { config_set_key_v("opmode",OPMODE_SHAREKILL); continue; } if (!strcmp(*av,"-share-stop")) { config_set_key_v("opmode",OPMODE_SHARESTOP); continue; } if (!strcmp(*av,"-share-drop")) { config_set_key_v("opmode",OPMODE_SHAREDROP); continue; } if (!strcmp(*av,"-share-path")) { WANTARG(); config_set_str("share-path",av[skip]); continue; } if (!strcmp(*av,"-nothing")) { config_set_bool("do-nothing",1); continue; } if (!strcmp(*av,"-just-die")) { config_set_bool("just-die",1); continue; } if (!strcmp(*av,"-kex")) { WANTARG(); errs += algset(&algs_kex,av[skip],1,errf); continue; } if (!strcmp(*av,"-hk")) { WANTARG(); errs += algset(&algs_hk,av[skip],1,errf); continue; } if (!strcmp(*av,"-enc")) { WANTARG(); errs += algset(&algs_enc_c2s,av[skip],1,errf); errs += algset(&algs_enc_s2c,av[skip],0,errf); continue; } if (!strcmp(*av,"-enc-c2s")) { WANTARG(); errs += algset(&algs_enc_c2s,av[skip],1,errf); continue; } if (!strcmp(*av,"-enc-s2c")) { WANTARG(); errs += algset(&algs_enc_s2c,av[skip],1,errf); continue; } if (!strcmp(*av,"-mac")) { WANTARG(); errs += algset(&algs_mac_c2s,av[skip],1,errf); errs += algset(&algs_mac_s2c,av[skip],0,errf); continue; } if (!strcmp(*av,"-mac-c2s")) { WANTARG(); errs += algset(&algs_mac_c2s,av[skip],1,errf); continue; } if (!strcmp(*av,"-mac-s2c")) { WANTARG(); errs += algset(&algs_mac_s2c,av[skip],1,errf); continue; } if (!strcmp(*av,"-comp")) { WANTARG(); errs += algset(&algs_comp_c2s,av[skip],1,errf); errs += algset(&algs_comp_s2c,av[skip],0,errf); continue; } if (!strcmp(*av,"-comp-c2s")) { WANTARG(); errs += algset(&algs_comp_c2s,av[skip],1,errf); continue; } if (!strcmp(*av,"-comp-s2c")) { WANTARG(); errs += algset(&algs_comp_s2c,av[skip],1,errf); continue; } if (!strcmp(*av,"-ua")) { WANTARG(); errs += algset(&algs_ua,av[skip],1,errf); continue; } if (!strcmp(*av,"-lang-c2s")) { WANTARG(); if (got & GOT_LANG_CtoS) fprintf(errf,"%s: %s %s: warning: c2s language already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("lang-c2s",av[skip]); got |= GOT_LANG_CtoS; continue; } if (!strcmp(*av,"-lang-s2c")) { WANTARG(); if (got & GOT_LANG_StoC) fprintf(errf,"%s: %s %s: warning: s2c language already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("lang-s2c",av[skip]); got |= GOT_LANG_StoC; continue; } if (!strcmp(*av,"-lang")) { WANTARG(); if (got & (GOT_LANG_CtoS|GOT_LANG_StoC)) fprintf(errf,"%s: %s %s: warning: languages already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("lang",av[skip]); config_set_str("lang-c2s",av[skip]); config_set_str("lang-s2c",av[skip]); got |= GOT_LANG_CtoS | GOT_LANG_StoC; continue; } if (!strcmp(*av,"-l") || !strcmp(*av,"-user") || !strcmp(*av,"-remuser")) { WANTARG(); if (got & GOT_USER) fprintf(errf,"%s: %s %s: warning: user name already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("user",av[skip]); got |= GOT_USER; continue; } if (!strcmp(*av,"-sshdir")) { WANTARG(); if (got & GOT_SSHDIR) fprintf(errf,"%s: %s %s: warning: ssh directory path already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("sshdir",av[skip]); got |= GOT_SSHDIR; continue; } if (!strcmp(*av,"-hkdb")) { WANTARG(); if (got & GOT_HOSTKEYDB) fprintf(errf,"%s: %s %s: warning: host-key database already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("host-key-db",av[skip]); got |= GOT_HOSTKEYDB; continue; } if (!strcmp(*av,"-nohk")) { WANTARG(); if (config_set_key_s("no-hk-action",av[skip])) { fprintf(errf,"%s: bad -nohk option `%s'\n",__progname,av[skip]); errs ++; } continue; } if (!strcmp(*av,"-badhk")) { WANTARG(); if (config_set_key_s("bad-hk-action",av[skip])) { fprintf(errf,"%s: bad -badhk option `%s'\n",__progname,av[skip]); errs ++; } continue; } if (!strcmp(*av,"-randfile")) { WANTARG(); if (got & GOT_RANDFILE) fprintf(errf,"%s: %s %s: warning: random pool file already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("random-pool",av[skip]); got |= GOT_RANDFILE; continue; } if (!strcmp(*av,"-authkey")) { WANTARG(); add_authkey(av[skip]); continue; } if (!strcmp(*av,"-authkeydir")) { WANTARG(); if (got & GOT_AUTHKEYDIR) fprintf(errf,"%s: %s %s: warning: authentication key directory already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("auth-key-dir",av[skip]); got |= GOT_AUTHKEYDIR; continue; } if (!strcmp(*av,"-hostkeydir")) { WANTARG(); if (got & GOT_HOSTKEYDIR) fprintf(errf,"%s: %s %s: warning: host key directory already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("host-key-dir",av[skip]); got |= GOT_HOSTKEYDIR; continue; } if (!strcmp(*av,"-noagent")) { config_set_bool("no-agent",1); continue; } if (!strcmp(*av,"-setpass") || !strcmp(*av,"-setpassphrase")) { config_set_key_v("opmode",OPMODE_SETPASS); continue; } if (!strcmp(*av,"-setcomm") || !strcmp(*av,"-setcomment")) { config_set_key_v("opmode",OPMODE_SETCOMM); continue; } if (!strcmp(*av,"-keygen")) { config_set_key_v("opmode",OPMODE_KEYGEN); continue; } if (!strcmp(*av,"-keyvalues")) { config_set_bool("key-values",1); continue; } if (!strcmp(*av,"-keytype")) { WANTARG(); if (! strcmp(av[skip],"?")) { list_keytypes(); errs ++; break; } if (got & GOT_KEYTYPE) fprintf(errf,"%s: %s %s: warning: key type already specified, using later value\n",__progname,av[0],av[skip]); config_set_key_s("key-type",av[skip]); got |= GOT_KEYTYPE; continue; } if (!strcmp(*av,"-keysize")) { WANTARG(); if (got & GOT_KEYSIZE) fprintf(errf,"%s: %s %s: warning: key size already specified, using later value\n",__progname,av[0],av[skip]); config_set_int("key-size",strtol(av[skip],0,0)); got |= GOT_KEYSIZE; continue; } if (!strcmp(*av,"-keyfile-pub")) { WANTARG(); if (got & GOT_KEYFILE_PUB) fprintf(errf,"%s: %s %s: warning: public key filename already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("key-file-public",av[skip]); got |= GOT_KEYFILE_PUB; continue; } if (!strcmp(*av,"-keyfile-priv")) { WANTARG(); if (got & GOT_KEYFILE_PRIV) fprintf(errf,"%s: %s %s: warning: private key filename already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("key-file-private",av[skip]); got |= GOT_KEYFILE_PRIV; continue; } if (!strcmp(*av,"-keyfile")) { WANTARG(); if (got & (GOT_KEYFILE_PUB|GOT_KEYFILE_PRIV)) fprintf(errf,"%s: %s %s: warning: key filename already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("key-file-public",av[skip]); config_set_str("key-file-private",av[skip]); got |= GOT_KEYFILE_PUB | GOT_KEYFILE_PRIV; continue; } if (!strcmp(*av,"-passphrase")) { WANTARG(); if (got & GOT_PASSPHRASE) fprintf(errf,"%s: %s %s: warning: passphrase already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("passphrase",av[skip]); bzero(av[skip],strlen(av[skip])); got |= GOT_PASSPHRASE; continue; } if (!strcmp(*av,"-comment")) { WANTARG(); if (got & GOT_COMMENT) fprintf(errf,"%s: %s %s: warning: key comment already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("comment",av[skip]); got |= GOT_COMMENT; continue; } if (!strcmp(*av,"-e") || !strcmp(*av,"-esc")) { WANTARG(); if (got & GOT_ESCCHAR) fprintf(errf,"%s: %s %s: warning: escape character already specified, using later value\n",__progname,av[0],av[skip]); config_set_str("escape",av[skip]); got |= GOT_ESCCHAR; continue; } if (!strcmp(*av,"-agent")) { config_set_key_v("opmode",OPMODE_AGENT); continue; } if (!strcmp(*av,"-add")) { config_set_key_v("opmode",OPMODE_ADD); continue; } if (!strcmp(*av,"-list")) { config_set_key_v("opmode",OPMODE_LIST); continue; } if (!strcmp(*av,"-delete")) { config_set_key_v("opmode",OPMODE_DELETE); continue; } if (!strcmp(*av,"-delete-all")) { config_set_key_v("opmode",OPMODE_DELETE_ALL); continue; } if (!strcmp(*av,"-server")) { config_set_key_v("opmode",OPMODE_SERVER); continue; } if (!strcmp(*av,"-constraint")) { WANTARG(); errs += set_constraints(&constraints_add,av[skip],-1,errf); continue; } if (!strcmp(*av,"-oneconn")) { config_set_bool("one-conn",1); continue; } if (!strcmp(*av,"-long")) { config_set_bool("list-long",1); continue; } if (!strcmp(*av,"-tty")) { config_set_bool("request-pty",1); continue; } if (!strcmp(*av,"-no-tty")) { config_set_bool("request-pty",0); continue; } if (!strcmp(*av,"-v")) { verbosity_default(); continue; } if (!strcmp(*av,"-V")) { WANTARG(); verbosity_set(av[skip]); continue; } if (!strcmp(*av,"-n")) { config_set_bool("no-input",1); continue; } if (!strcmp(*av,"-f")) { config_set_bool("auto-bg",1); continue; } if (!strcmp(*av,"-syslog")) { config_set_bool("use-syslog",1); continue; } if (!strcmp(*av,"-nosyslog")) { config_set_bool("use-syslog",0); continue; } if (!strcmp(*av,"-L")) { WANTARG(); errs += add_fwd(av[skip],FF_DIR_LTOR); continue; } if (!strcmp(*av,"-LL")) { WANTARG(); errs += add_fwd(av[skip],FF_DIR_LTOR|FF_MUST); continue; } if (!strcmp(*av,"-R")) { WANTARG(); errs += add_fwd(av[skip],FF_DIR_RTOL); continue; } if (!strcmp(*av,"-RR")) { WANTARG(); errs += add_fwd(av[skip],FF_DIR_RTOL|FF_MUST); continue; } if (!strcmp(*av,"-fwd")) { WANTARG(); errs += set_forwarding(av[skip],SFT_ALL,"-fwd"); continue; } if (!strcmp(*av,"-fwdonce")) { WANTARG(); errs += set_forwarding(av[skip],SFT_ONCE,"-fwd"); continue; } if (!strcmp(*av,"-nofwd")) { WANTARG(); errs += set_forwarding(av[skip],SFT_NO,"-nofwd"); continue; } if (!strcmp(*av,"-neverauth")) { config_set_bool("never-auth",1); continue; } if (!strcmp(*av,"-config")) { WANTARG(); configfile = av[skip]; continue; } if (!strcmp(*av,"-noconfig")) { configfile = ""; continue; } if (!strcmp(*av,"-nointer")) { config_set_bool("no-interaction",1); continue; } if (!strcmp(*av,"-fork-pause")) { WANTARG(); config_set_int("fork-pause",atoi(av[skip])); continue; } if (!strcmp(*av,"-no-private")) { config_set_bool("no-private",1); set_no_private(); continue; } if (!strcmp(*av,"-kh")) { static struct { const char *key; KH_OP op; int takesarg; } ops[] = { { "import", KH_OP_IMPORT, 1 }, { "export", KH_OP_EXPORT, 1 }, { "clear", KH_OP_CLEAR, 1 }, { "delete", KH_OP_DELETE, 1 }, { "ports", KH_OP_PORTS, 1 }, { "find", KH_OP_FIND, 0 }, { "hosts", KH_OP_HOSTS, 0 }, { 0 } }; WANTARG(); config_set_key_v("opmode",OPMODE_KH); if (!strcmp(av[skip],"?")) { fprintf(errf,"%s: arguments for %s:\n",__progname,*av); fprintf(errf,"%s: With an argument:",__progname); for (i=0;ops[i].key;i++) if (ops[i].takesarg) fprintf(errf," %s",ops[i].key); fprintf(errf,"\n"); fprintf(errf,"%s: No argument:",__progname); for (i=0;ops[i].key;i++) if (! ops[i].takesarg) fprintf(errf," %s",ops[i].key); fprintf(errf,"\n"); exit(0); } else { do <"found"> { for (i=0;ops[i].key;i++) { if (!strcmp(av[skip],ops[i].key)) { if (ops[i].takesarg) { WANTARG(); config_set_key_v("kh-op",ops[i].op); config_set_str("kh-host",av[skip]); } else { config_set_key_v("kh-op",ops[i].op); } break <"found">; } } fprintf(errf,"%s: unrecognized -kh operation `%s'\n",__progname,av[skip]); errs ++; } while (0); } continue; } #undef WANTARG fprintf(errf,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); putc('\0',cmdf); fclose(cmdf); if (! (got & GOT_SSHDIR)) config_set_str("sshdir",default_sshdir()); alglist_expand_default(&algs_kex); alglist_expand_default(&algs_hk); alglist_expand_default(&algs_enc_c2s); alglist_expand_default(&algs_enc_s2c); alglist_expand_default(&algs_mac_c2s); alglist_expand_default(&algs_mac_s2c); alglist_expand_default(&algs_comp_c2s); alglist_expand_default(&algs_comp_s2c); alglist_expand_default(&algs_ua); init_config(); load_config(configfile); defer_eval = 0; if (errs) exit(1); verbose_startup(); } /* * Work out what SSHDIR is. The arguments are a printf format and * string giving what it's for, for error messages; the `format * string' can be given as SSHDIR_NULL if failure to get a path should * produce a nil return value instead of an error message and exit(1). */ const char sshdir_null = 0; const char *get_sshdir(const char *fmt, ...) { va_list ap; const char *s; FILE *f; s = config_str("sshdir"); if (s) return(s); if (fmt == SSHDIR_NULL) return(0); f = logmsg_fopen(LM_ERR); fprintf(f,"no $HOME, can't default "); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); exit(1); } /* * Return an XVAL for a string. This is just like new_str_xval except * that it copies its argument and converts nil pointers to ""s. */ static XVAL *str_xval(const char *s) { XVAL *v; v = new_str_xval(strdup(s?:"")); return(v); } /* * Return an XVAL with a given boolean value. */ static XVAL *bool_xval(int b) { XVAL *v; v = new_xval(); v->type = XVT_BOOL; v->bool = (b ? 1 : 0); return(v); } /* * Return an XVAL with a given integer value. */ static XVAL *int_xval(int i) { XVAL *v; v = new_xval(); v->type = XVT_INT; v->i = i; return(v); } /* * Return an XVAL with the unset value. */ static XVAL *unset_xval(void) { XVAL *v; v = new_xval(); v->type = XVT_UNSET; return(v); } /* * Return whether an XVAL should be considered true. * * The unset value is false. Booleans have the obvious truth values. * Zero-length strings are false; all other strings are true. Integer * 0 is false; all other integers are true. Addresses and CIDR blocks * are always true. Empty sets are false; nonempty sets are true. */ static int xval_true(XVAL *v) { if (v == 0) return(0); switch (v->type) { case XVT_UNSET: return(0); break; case XVT_BOOL: return(v->bool); break; case XVT_STR: return(v->str.s[0]); break; case XVT_INT: return(v->i); break; case XVT_ADDR: case XVT_CIDR: return(1); break; case XVT_SET: return(v->set.n>0); break; default: panic("bad type"); break; } panic("bad type"); } /* * Coerce an XVAL to a string. Return value is another XVAL, not a * char *. * * Booleans turn into "1" for true and "" for false (not ".true" and * ".false" because those don't convert back correctly). Strings are * returned unchanged. Integers are converted in decimal. Addresses * and CIDR blocks are converted to canonical text form. Sets convert * to "", with an error message. */ static XVAL *coerce_string(XVAL *v) { char *p; if (v == 0) return(str_xval("")); switch (v->type) { default: panic("bad type"); break; case XVT_BOOL: p = strdup(v->bool?"1":""); break; case XVT_STR: return(v); break; case XVT_INT: asprintf(&p,"%d",v->i); break; { char buf[IPADDRTXTLEN]; const char *t; case XVT_ADDR: t = inet_ntop(v->addr.af,&v->addr.u,&buf[0],IPADDRTXTLEN); if (! t) { logmsg(LM_ERR,"inet_ntop: %s",strerror(errno)); p = strdup(""); } else { p = strdup(t); } break; case XVT_CIDR: t = inet_ntop(v->cidr.af,&v->cidr.u,&buf[0],IPADDRTXTLEN); if (! t) { logmsg(LM_ERR,"inet_ntop: %s",strerror(errno)); p = strdup(""); } else { asprintf(&p,"%s/%d",t,v->cidr.w); } break; } case XVT_SET: logmsg(LM_ERR,"converting set to string"); p = strdup(""); break; } drop_xval(v); return(new_str_xval(p)); } /* * Coerce an XVAL to an integer. Return value is another XVAL, not an * int. * * Booleans turn into 1 for true and 0 for false. Strings are * converted with atoi (which means non-numeric strings turn into 0). * Integers are unchanged. Addresses and CIDR blocks convert to 0, * with error messages. Sets convert to their member counts. */ static XVAL *coerce_int(XVAL *v) { int i; if (v == 0) return(int_xval(0)); switch (v->type) { default: panic("bad type"); break; case XVT_BOOL: i = v->bool ? 1 : 0; break; case XVT_STR: i = atoi(v->str.s); break; case XVT_INT: i = v->i; break; case XVT_ADDR: logmsg(LM_ERR,"converting address to int"); i = 0; break; case XVT_CIDR: logmsg(LM_ERR,"converting CIDR network to int"); i = 0; break; case XVT_SET: i = v->set.n; break; } drop_xval(v); return(int_xval(i)); } /* * Return true if two values have the same type. This isn't quite as * simple as it looks at first blush, because if the two values are * sets, we have to look at the value types, and also handle cases of * empty sets. */ static int sametype(XVAL *a, XVAL *b) { if (a->type != b->type) return(0); if (a->type != XVT_SET) return(1); while (1) { if ((a->set.n < 1) || (b->set.n < 1)) return(1); if (a->set.oftype != b->set.oftype) return(0); if (a->set.oftype != XVT_SET) return(1); if (a->set.vals[0]->type != a->set.oftype) panic("impossible set"); if (b->set.vals[0]->type != b->set.oftype) panic("impossible set"); a = a->set.vals[0]; b = b->set.vals[0]; } } /* * cmp_set and type_cmp are mutually referential; which to declare * forward is a matter of taste. */ static int (*type_cmp(XVALTYPE))(XVAL *, XVAL *); /* * Compare two unset values. All unset values are equal. */ static int cmp_unset(XVAL *a __attribute__((__unused__)), XVAL *b __attribute__((__unused__))) { return(0); } /* * Compare two booleans. This isn't quite as trivial as it sounds, * since we don't want to depend on the .bool values being 0-or-1. */ static int cmp_bool(XVAL *a, XVAL *b) { if ((a->type != XVT_BOOL) || (b->type != XVT_BOOL)) panic("type wrong"); return( a->bool ? b->bool ? 0 : 1 : b->bool ? -1 : 0 ); } /* * Compare two strings. */ static int cmp_str(XVAL *a, XVAL *b) { if ((a->type != XVT_STR) || (b->type != XVT_STR)) panic("type wrong"); return(strcmp(a->str.s,b->str.s)); } /* * Compare two integers. We don't just subtract because that risks * returning values with the wrong signum for integer-overflow * reasons. */ static int cmp_int(XVAL *a, XVAL *b) { if ((a->type != XVT_INT) || (b->type != XVT_INT)) panic("type wrong"); return((a->i < b->i) ? -1 : (a->i > b->i) ? 1 : 0); } /* * Compare two addresses. On little-endian machines, this uses the * `wrong' byte sex, but that doesn't matter unless the config file is * silly enough to do signed comparisons of addresses. * * XXX Do we want to consider v6 mapped-v4 addresses as equal to their * v4 counterparts? For now, no. */ static int cmp_addr(XVAL *a, XVAL *b) { if ((a->type != XVT_ADDR) || (b->type != XVT_ADDR)) panic("type wrong"); if (a->addr.af != b->addr.af) return(a->addr.af-b->addr.af); switch (a->addr.af) { default: panic("bad af"); break; case AF_INET: return( (a->addr.u.v4.s_addr < b->addr.u.v4.s_addr) ? -1 : (a->addr.u.v4.s_addr > b->addr.u.v4.s_addr) ? 1 : 0 ); break; case AF_INET6: return(memcmp(&a->addr.u.v6.s6_addr,&b->addr.u.v6.s6_addr,16)); break; } } /* * Compare two CIDR blocks. On little-endian machines, this uses the * `wrong' byte sex, but that doesn't matter unless the config file is * silly enough to do signed comparisons of CIDRs. Note that the mask * width doesn't enter in unless the address portions are identical. * * XXX Do we want to consider v6 mapped-v4 addresses as equal to their * v4 counterparts? For now, no. */ static int cmp_cidr(XVAL *a, XVAL *b) { int c; if ((a->type != XVT_CIDR) || (b->type != XVT_CIDR)) panic("type wrong"); if (a->cidr.af != b->cidr.af) return(a->cidr.af-b->cidr.af); switch (a->cidr.af) { default: panic("bad af"); break; case AF_INET: if (a->cidr.u.v4.s_addr < b->cidr.u.v4.s_addr) return(-1); if (a->cidr.u.v4.s_addr > b->cidr.u.v4.s_addr) return(1); break; case AF_INET6: c = memcmp(&a->cidr.u.v6.s6_addr,&b->cidr.u.v6.s6_addr,16); if (c) return(c); break; } return(a->cidr.w-b->cidr.w); } /* * Compare two sets. Speed here is most of why we have hashes. * Getting a well-defined order out of this function is why we go to * the trouble to sort sets' members in set_canonicalize. */ static int cmp_set(XVAL *a, XVAL *b) { int i; int c; int (*subcmp)(XVAL *, XVAL *); if ((a->type != XVT_SET) || (b->type != XVT_SET)) panic("type wrong"); if (a->set.n != b->set.n) return(a->set.n-b->set.n); if (a->set.n < 1) return(0); if (a->set.oftype != b->set.oftype) return((int)a->set.oftype-(int)b->set.oftype); if (a->set.hash < b->set.hash) return(-1); if (a->set.hash > b->set.hash) return(1); subcmp = type_cmp(a->set.oftype); for (i=a->set.n-1;i>=0;i--) { if ( (a->set.vals[i]->type != a->set.oftype) || (b->set.vals[i]->type != a->set.oftype) ) panic("element type wrong"); c = (*subcmp)(a->set.vals[i],b->set.vals[i]); if (c) return(c); } return(0); } /* * Return the comparison function for a type. */ static int (*type_cmp(XVALTYPE t))(XVAL *, XVAL *) { switch (t) { default: panic("impossible type"); break; case XVT_UNSET: return(&cmp_unset); break; case XVT_BOOL: return(&cmp_bool); break; case XVT_STR: return(&cmp_str); break; case XVT_INT: return(&cmp_int); break; case XVT_ADDR: return(&cmp_addr); break; case XVT_CIDR: return(&cmp_cidr); break; case XVT_SET: return(&cmp_set); break; } } /* * Make sure a string XVAL has its hash computed. */ static void ensure_string_hash(XVAL *v) { SETHASH h; unsigned char *s; if (v->type != XVT_STR) panic("not a string"); if (! (v->str.flags & XV_STRF_V_HASH)) { int i; h = 0x1234; s = (void *) v->str.s; for (i=0;s[i];i++) { h = (h * 0x2419) + s[i]; h = (h << 3) | ((h >> 61) & 7); } v->str.hash = h; v->str.flags |= XV_STRF_V_HASH; } } /* * hash_set and type_hash are mutually referential; which to declare * forward is a matter of taste. */ static SETHASH (*type_hash(XVALTYPE))(XVAL *); /* * Return the hash value for an unset value. */ static SETHASH hash_unset(XVAL *v __attribute__((__unused__))) { return(0); } /* * Return the hash value for a boolean value. */ static SETHASH hash_bool(XVAL *v) { if (v->type != XVT_BOOL) panic("not a boolean"); return(v->bool?1:0); } /* * Return the hash value for a string value. */ static SETHASH hash_str(XVAL *v) { if (v->type != XVT_STR) panic("not a string"); ensure_string_hash(v); return(v->str.hash); } /* * Return the hash value for an integer value. */ static SETHASH hash_int(XVAL *v) { if (v->type != XVT_INT) panic("not an integer"); return(v->i); } /* * Return the hash value for an address value. */ static SETHASH hash_addr(XVAL *v) { if (v->type != XVT_ADDR) panic("not an address"); switch (v->addr.af) { default: panic("bad af"); break; case AF_INET: return(v->addr.u.v4.s_addr); break; case AF_INET6: { SETHASH h; int i; h = 0x12345; for (i=0;i<16;i++) { h = (h * 0x24d7) + v->addr.u.v6.s6_addr[i]; h = (h << 3) | ((h >> 61) & 7); } return(h); } break; } } /* * Return the hash value for a CIDR value. */ static SETHASH hash_cidr(XVAL *v) { if (v->type != XVT_CIDR) panic("not a CIDR"); switch (v->cidr.af) { default: panic("bad af"); break; case AF_INET: return(v->cidr.u.v4.s_addr|(((SETHASH)v->cidr.w)<<32)); break; case AF_INET6: { SETHASH h; int i; h = v->cidr.w; for (i=0;i<16;i++) { h = (h * 0x24d7) + v->cidr.u.v6.s6_addr[i]; h = (h << 3) | ((h >> 61) & 7); } return(h); } break; } } /* * Return the hash value for a set value. */ static SETHASH hash_set(XVAL *v) { return(v->set.hash); } /* * Return the hash function for a value type. */ static SETHASH (*type_hash(XVALTYPE t))(XVAL *) { switch (t) { default: panic("bad type"); break; case XVT_UNSET: return(&hash_unset); break; case XVT_BOOL: return(&hash_bool); break; case XVT_STR: return(&hash_str); break; case XVT_INT: return(&hash_int); break; case XVT_ADDR: return(&hash_addr); break; case XVT_CIDR: return(&hash_cidr); break; case XVT_SET: return(&hash_set); break; } } /* * Test whether two XVALs are equal (true if so, false if not). Just * checks the types, then, if they're the same, calls the type's * comparison function. */ static int equal_xval(XVAL *a, XVAL *b) { if (! sametype(a,b)) return(0); return((*type_cmp(a->type))(a,b)==0); } /* * Sort a vector of XVALs (vector pointer and length given) according * to the provided comparison function. This is used when * canonicalizing sets. */ static void sort_valvec(XVAL **v, int n, int (*cmp)(XVAL *, XVAL *)) { int i; int j; int k; int n2i; int n2j; if (n < 2) return; n2i = n / 2; n2j = n - n2i; { XVAL *v2[n2i]; bcopy(v,v2,n2i*sizeof(XVAL *)); sort_valvec(v2,n2i,cmp); sort_valvec(v+n2i,n2j,cmp); i = 0; j = n2i; k = 0; while (i < n2i) { if ((j >= n) || ((*cmp)(v2[i],v[j]) <= 0)) { v[k] = v2[i++]; } else { v[k] = v[j++]; } } } } /* * Canonicalize a set. This handles: * * - Setting the type to XVT_NONE if it has no entries. * - Sorting its members. * - Computing its hash. */ static void set_canonicalize(XVAL *s) { int (*cmp)(XVAL *, XVAL *); SETHASH (*hash)(XVAL *); int i; SETHASH h; if (s->type != XVT_SET) panic("not a set"); if (s->set.n < 1) { s->set.oftype = XVT_NONE; cmp = 0; hash = 0; } else { cmp = type_cmp(s->set.oftype); hash = type_hash(s->set.oftype); for (i=s->set.n-1;i>=0;i--) if (s->set.vals[i]->type != s->set.oftype) panic("element type wrong"); sort_valvec(s->set.vals,s->set.n,cmp); } h = 0x666; for (i=s->set.n-1;i>=0;i--) { h = (h * 0x27c5) + (*hash)(s->set.vals[i]); h = (h << 3) | ((h >> 61) & 7); } s->set.hash = h; } /* * Return the union of two sets. They must be type-compatible, or the * return value is an unset value. A new set is returned; neither * argument is affected. */ static XVAL *set_union(XVAL *a, XVAL *b) { XVAL *u; int i; int h; NESTED void add(XVAL *v) { int j; for (j=u->set.n-1;j>=0;j--) if (equal_xval(v,u->set.vals[j])) return; ref_xval(v); if (u->set.n >= h) u->set.vals = realloc(u->set.vals,(h=u->set.n+8)*sizeof(*u->set.vals)); u->set.vals[u->set.n++] = v; } if ((a->type != XVT_SET) || (b->type != XVT_SET)) panic("not a set"); if (a->set.n < 1) { drop_xval(a); return(b); } if (b->set.n < 1) { drop_xval(b); return(a); } if (! sametype(a,b)) { logmsg(LM_ERR,"combining sets of different types"); drop_xval(a); drop_xval(b); return(unset_xval()); } u = new_set_xval(); u->set.oftype = a->set.oftype; h = 0; for (i=a->set.n-1;i>=0;i--) add(a->set.vals[i]); for (i=b->set.n-1;i>=0;i--) add(b->set.vals[i]); set_canonicalize(u); drop_xval(a); drop_xval(b); return(u); } /* * Perform a . operation - string concat, or set union. */ static XVAL *perform_concat(XVAL *a, XVAL *b) { if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } if ((a->type == XVT_SET) && (b->type == XVT_SET)) { return(set_union(a,b)); } a = coerce_string(a); b = coerce_string(b); if (a->str.s[0] && b->str.s[0]) { XVAL *v; char *s; asprintf(&s,"%s%s",a->str.s,b->str.s); v = new_str_xval(s); drop_xval(a); drop_xval(b); return(v); } else if (! a->str.s[0]) { drop_xval(a); return(b); } else { drop_xval(b); return(a); } } /* * Perform a comparison. The test function takes a signum, such as is * returned by the comparison functions, and returns a true-or-false. */ static XVAL *perform_cmp(XVAL *a, XVAL *b, int (*test)(int)) { int cmp; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } if (a->type != b->type) { logmsg(LM_ERR,"comparing different types"); cmp = 0; } else { cmp = (*test)((*type_cmp(a->type))(a,b)); } drop_xval(a); drop_xval(b); return(bool_xval(cmp)); } /* * Perform a Boolean AND. Note that unset AND false is false, not * unset. */ static XVAL *perform_and(XVAL *a, XVAL *b) { XVAL *rv; if ( ((a->type != XVT_UNSET) && !xval_true(a)) || ((b->type != XVT_UNSET) && !xval_true(b)) ) { rv = bool_xval(0); } else if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { rv = unset_xval(); } else { rv = bool_xval(1); } drop_xval(a); drop_xval(b); return(rv); } /* * Perform a Boolean OR. Note that unset OR true is true, not unset. * * This could be optimized by noting that xval_true returns false for * unset values. I'd rather not depend on that. */ static XVAL *perform_or(XVAL *a, XVAL *b) { XVAL *rv; if ( ((a->type != XVT_UNSET) && xval_true(a)) || ((b->type != XVT_UNSET) && xval_true(b)) ) { rv = bool_xval(1); } else if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { rv = unset_xval(); } else { rv = bool_xval(0); } drop_xval(a); drop_xval(b); return(rv); } /* * Perform Boolean negation. */ static XVAL *perform_not(XVAL *a) { int rv; if (a->type == XVT_UNSET) return(a); rv = ! xval_true(a); drop_xval(a); return(bool_xval(rv)); } /* * Perform arithmetic negation. */ static XVAL *perform_neg(XVAL *a) { int rv; if (a->type == XVT_UNSET) return(a); a = coerce_int(a); rv = - a->i; drop_xval(a); return(int_xval(rv)); } /* * Perform an @ operation. * * If A and B are strings, A @ B is "is FQDN A under domain B?". * If A is an address and B a CIDR, A @ B is "is A in the netblock * corresponding to B?". * Anything else is an error. * * It would be reasonable to extend this to do set membership, a la the * [in] function. */ static XVAL *perform_in(XVAL *a, XVAL *b) { int rv; int w; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } if ((a->type == XVT_STR) && (b->type == XVT_STR)) { int al; int bl; al = strlen(a->str.s); bl = strlen(b->str.s); rv = (al >= bl) && !strcasecmp(a->str.s+al-bl,b->str.s); } else if ((a->type == XVT_ADDR) && (b->type == XVT_CIDR)) { if (a->addr.af != b->cidr.af) { rv = 0; } else { switch (a->addr.af) { case AF_INET: if ((w < 0) || (w > 32)) { logmsg(LM_ERR,"invalid width for IPv4"); rv = 0; } else { rv = ! ( ( ntohl(a->addr.u.v4.s_addr) ^ ntohl(b->cidr.u.v4.s_addr) ) & (w ? (~0U << (32-w)) : 0) ); } break; case AF_INET6: { unsigned char *aa; unsigned char *ba; if ((w < 0) || (w > 128)) { logmsg(LM_ERR,"invalid width for IPv6"); rv = 0; } else { aa = (void *)&a->cidr.u.v6.s6_addr[0]; ba = (void *)&b->cidr.u.v6.s6_addr[0]; rv = ((w < 8) || !bcmp(aa,ba,w>>3)) && ( !(w & 7) || !((aa[w>>3]^ba[w>>3])&(0xff00>>(w&7))&0xff) ); } } break; default: rv = 0; break; } } } else { logmsg(LM_ERR,"invalid types to @"); rv = 0; } drop_xval(a); drop_xval(b); return(bool_xval(rv)); } /* * Report a regex error. */ static void report_regex_error(int e, regex_t *preg) { int z; char *b; z = regerror(e,preg,0,0); b = malloc(z); regerror(e,preg,b,z); logmsg(LM_ERR,"%.*s",z,b); free(b); } /* * Ensure a string is in shape to be used as a regex. nosub_ok is true * if a REG_NOSUB compilation is good enough, false if not. */ static int ensure_string_re(XVAL *v, int nosub_ok) { int e; if (v->type != XVT_STR) panic("not a string"); if ( ( (v->str.flags & (XV_STRF_V_RE|XV_STRF_NOSUB)) == (XV_STRF_V_RE|XV_STRF_NOSUB) ) && !nosub_ok ) { regfree(v->str.re); free(v->str.re); v->str.flags &= ~XV_STRF_V_RE; } if (! (v->str.flags & XV_STRF_V_RE)) { v->str.re = malloc(sizeof(regex_t)); e = regcomp(v->str.re,v->str.s,REG_EXTENDED|(nosub_ok?REG_NOSUB:0)); if (nosub_ok) v->str.flags |= XV_STRF_NOSUB; else v->str.flags &= ~XV_STRF_NOSUB; if (e) { /* Do we need to regfree? May we? The manpage doesn't say. */ free(v->str.re); report_regex_error(e,0); return(1); } v->str.flags |= XV_STRF_V_RE; } return(0); } /* * Scan a string for regex replacement. s is the string. When a * string of one or more regular characters is found, (*nonesc) is * called with a pointer to the first one and a character count. When * a replacement sequence is found, (*esc) is called with the number * from the sequence. */ static void replace_scan(char *s, void (*nonesc)(char *, int), void (*esc)(int)) { int i; int i0; int v; int d; NESTED void end_run(void) { if (i0 >= 0) (*nonesc)(s+i0,i-i0); i0 = -1; } i0 = -1; i = 0; while (1) { switch (s[i]) { case '\0': end_run(); return; break; case '\\': end_run(); if (s[++i] == '(') { v = 0; i ++; while (1) { if (s[i] == ')') { (*esc)(v); i ++; break; } d = digitval(s[i]); if (d < 0) break; v = (v * 10) + d; i ++; } } else { v = digitval(s[i]); if (v >= 0) { i ++; (*esc)(v); } } break; default: if (i0 < 0) i0 = i; i ++; break; } } } /* * Ensure a string's maxsub value is set correctly. */ static void ensure_string_maxsub(XVAL *v) { int max; if (v->type != XVT_STR) panic("not a string"); if (! (v->str.flags & XV_STRF_V_MAXSUB)) { NESTED void nonesc(char *start __attribute__((__unused__)), int len __attribute__((__unused__))) { } NESTED void esc(int n) { if (n >= max) max = n; } max = -1; replace_scan(v->str.s,&nonesc,&esc); if (max < 0) max = -1; v->str.maxsub = max; v->str.flags |= XV_STRF_V_MAXSUB; } } /* * Perform a ~ operation: match a string against a regex. */ static XVAL *perform_regex(XVAL *a, XVAL *b) { int e; int rv; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } a = coerce_string(a); b = coerce_string(b); if (ensure_string_re(b,1)) { rv = 0; } else { e = regexec(b->str.re,a->str.s,0,0,0); switch (e) { case 0: rv = 1; break; case REG_NOMATCH: rv = 0; break; default: report_regex_error(e,b->str.re); rv = 0; break; } } drop_xval(a); drop_xval(b); return(bool_xval(rv)); } /* * Test function for <. */ static int test_lt(int cmp) { return(cmp<0); } /* * Test function for ==. */ static int test_eq(int cmp) { return(cmp==0); } /* * Test function for >. */ static int test_gt(int cmp) { return(cmp>0); } /* * Perform + (integer addition). It would be reasonable to extend this * to do add-member-to-set. */ static XVAL *perform_add(XVAL *a, XVAL *b) { int rv; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } a = coerce_int(a); b = coerce_int(b); if ((a->type != XVT_INT) || (b->type != XVT_INT)) panic("non-int"); rv = a->i + b->i; drop_xval(a); drop_xval(b); return(int_xval(rv)); } /* * Perform - (integer subtraction). It would be reasonable to extend * this to do remove-member-from-set. */ static XVAL *perform_sub(XVAL *a, XVAL *b) { int rv; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } a = coerce_int(a); b = coerce_int(b); if ((a->type != XVT_INT) || (b->type != XVT_INT)) panic("non-int"); rv = a->i - b->i; drop_xval(a); drop_xval(b); return(int_xval(rv)); } /* * Perform * (integer multiplication). It would be reasonable to * extend this to do string replication. */ static XVAL *perform_mul(XVAL *a, XVAL *b) { int rv; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } a = coerce_int(a); b = coerce_int(b); if ((a->type != XVT_INT) || (b->type != XVT_INT)) panic("non-int"); rv = a->i * b->i; drop_xval(a); drop_xval(b); return(int_xval(rv)); } /* * Perform / (integer division). */ static XVAL *perform_div(XVAL *a, XVAL *b) { int rv; if ((a->type == XVT_UNSET) || (b->type == XVT_UNSET)) { drop_xval(a); drop_xval(b); return(unset_xval()); } a = coerce_int(a); b = coerce_int(b); if ((a->type != XVT_INT) || (b->type != XVT_INT)) panic("non-int"); if (b->i == 0) { logmsg(LM_ERR,"division by zero"); rv = 0; } else { rv = a->i / b->i; } drop_xval(a); drop_xval(b); return(int_xval(rv)); } /* * Update an expression - make sure its value is current. This is * where most of expression evaluation happens. */ static void update_expr(EXPR *e) { int i; int j; if (!e || e->valid) return; drop_xval(e->eval); e->eval = 0; switch <"recompute"> (e->op) { default: panic("bad op"); break; case XO_VALUE: e->eval = ref_xval(e->val); break; case XO_VAR: if (! (e->var->flags & VF_R)) { logmsg(LM_ERR,"variable `%s' is not readable",e->var->name); e->eval = unset_xval(); } else if (e->var->flags & VF_SPEC_GET) { XVAL *vp; (*e->var->spec)(e->var,VSO_GET,&vp); e->eval = vp; } else { switch (e->var->type) { default: panic("bad var type"); break; case VT_KEY: e->eval = (e->var->flags & VF_CFG) ? str_xval(e->var->k.vals[e->var->k.cfg].name) : (e->var->flags & VF_SET) ? str_xval(e->var->k.vals[e->var->k.set].name) : unset_xval(); break; case VT_BOOL: e->eval = (e->var->flags & VF_CFG) ? bool_xval(e->var->b.cfg) : (e->var->flags & VF_SET) ? bool_xval(e->var->b.set) : unset_xval(); break; case VT_INT: e->eval = (e->var->flags & VF_CFG) ? int_xval(e->var->i.cfg) : (e->var->flags & VF_SET) ? int_xval(e->var->i.set) : unset_xval(); break; case VT_STR: e->eval = (e->var->flags & VF_CFG) ? str_xval(e->var->s.cfg) : (e->var->flags & VF_SET) ? str_xval(e->var->s.set) : unset_xval(); break; case VT_ADDR: e->eval = (e->var->flags & VF_CFG) ? ref_xval(e->var->a.cfg) : (e->var->flags & VF_SET) ? ref_xval(e->var->a.set) : unset_xval(); break; case VT_XVAL: e->eval = (e->var->flags & VF_CFG) ? ref_xval(e->var->x.cfg) : (e->var->flags & VF_SET) ? ref_xval(e->var->x.set) : unset_xval(); break; case VT_UNDEF: e->eval = unset_xval(); break; } } break; case XO_CONCAT: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_concat( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_LT: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_cmp( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval), &test_lt ); break; case XO_EQ: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_cmp( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval), &test_eq ); break; case XO_GT: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_cmp( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval), &test_gt ); break; case XO_AND: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_and( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_OR: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_or( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_ADD: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_add( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_SUB: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_sub( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_MUL: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_mul( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_DIV: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_div( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_NOT: update_expr(e->unary.arg); e->eval = perform_not(ref_xval(e->unary.arg->eval)); break; case XO_NEG: update_expr(e->unary.arg); e->eval = perform_neg(ref_xval(e->unary.arg->eval)); break; case XO_IN: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_in( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_REGEX: update_expr(e->binary.lhs); update_expr(e->binary.rhs); e->eval = perform_regex( ref_xval(e->binary.lhs->eval), ref_xval(e->binary.rhs->eval) ); break; case XO_ISSET: switch (e->var->type) { case VT_UNDEF: e->eval = bool_xval(0); break; case VT_MACRO: e->eval = bool_xval(1); break; default: e->eval = bool_xval(e->var->flags&(VF_SET|VF_CFG)); break; } break; case XO_COND: update_expr(e->ternary.lhs); if (e->ternary.lhs->eval->type == XVT_UNSET) { e->eval = ref_xval(e->ternary.lhs->eval); } else if (xval_true(e->ternary.lhs->eval)) { update_expr(e->ternary.mhs); e->eval = ref_xval(e->ternary.mhs->eval); } else { update_expr(e->ternary.rhs); e->eval = ref_xval(e->ternary.rhs->eval); } break; case XO_CALL: switch (e->call.fn->type) { default: panic("bad fn type"); break; case FNT_EVAL: if (e->call.nargs > 0) { XVAL *vals[e->call.nargs]; for (i=e->call.nargs-1;i>=0;i--) { update_expr(e->call.args[i]); vals[i] = ref_xval(e->call.args[i]->eval); if (vals[i]->type == XVT_UNSET) { for (j=e->call.nargs-1;j>=i;j--) drop_xval(vals[j]); e->eval = unset_xval(); break <"recompute">; } } e->eval = (*e->call.fn->u.eval.perform)(e->call.nargs,&vals[0]); } else { e->eval = (*e->call.fn->u.eval.perform)(0,0); } break; case FNT_SPEC: if (e->call.nargs > 0) { void *args[e->call.nargs]; char evaled[e->call.nargs]; XVAL *val; for (i=e->call.nargs-1;i>=0;i--) { if ((*e->call.fn->u.spec.eval_arg)(i)) { evaled[i] = 1; update_expr(e->call.args[i]); val = ref_xval(e->call.args[i]->eval); args[i] = val; if (val->type == XVT_UNSET) { for (j=e->call.nargs-1;j>=i;j--) if (evaled[j]) drop_xval(args[j]); e->eval = unset_xval(); break <"recompute">; } } else { evaled[i] = 0; args[i] = e->call.args[i]; } } e->eval = (*e->call.fn->u.spec.eval_fn)(e->call.nargs,&evaled[0],&args[0]); } else { e->eval = (*e->call.fn->u.spec.eval_fn)(0,0,0); } break; } break; } e->valid = 1; } /* * Add a value to a set. Silently drops duplicates. Does not assume * the set is canonicalized. Consumes (saves or drops) one reference * to el. */ static void add_to_set(XVAL *set, XVAL *el) { int i; if (set->type != XVT_SET) panic("not a set"); if (set->set.n == 0) { set->set.oftype = el->type; } else { if (el->type != set->set.oftype) panic("element type wrong"); } for (i=set->set.n-1;i>=0;i--) { if (equal_xval(set->set.vals[i],el)) { drop_xval(el); return; } } set->set.vals = realloc(set->set.vals,(set->set.n+1)*sizeof(XVAL *)); set->set.vals[set->set.n++] = el; } /* * Invalidate everything pointed to by the EXPRLIST, recursing as * necessary to get indirect dependencies invalidated too. (Note that * this checks the valid bits before recursing, eliminating the "walk * the whole tree N times because it depends on us by multiple routes" * problem.) */ static void invalidate(EXPRLIST *l) { int i; EXPR *e; for (i=l->n-1;i>=0;i--) { e = l->v[i]; if (e->valid) { e->valid = 0; invalidate(&e->deps); } } } /* * Hash a string with a hash algorithm, returning the hash as a hex * string. This is used by the [md5] and [sha1] functions. */ static XVAL *str_hash_fn(XVAL *arg, HASHALG *alg) { unsigned char *res; void *h; int i; static const char xdig[16] = "0123456789abcdef"; if (arg->type != XVT_STR) panic("type wrong"); res = malloc((alg->hashlen*2)+1); h = (*alg->init)(); (*alg->process)(h,arg->str.s,strlen(arg->str.s)); (*alg->done)(h,res); res[alg->hashlen*2] = '\0'; for (i=alg->hashlen-1;i>=0;i--) { res[i+i+1] = xdig[res[i]&0xf]; res[i+i] = xdig[res[i]>>4]; } return(new_str_xval(res)); } /* * Skip a DNS name. dbuf and len describe a buffer containing a DNS * response; this function assumes a DNS name starts at offset off in * that buffer and returns the offset of the octet just after the * name. Performs only minimal well-formedness checks, those * necessary to make sure it behaves sanely even in the face of * malformed input. */ static int dns_skip(const void *dbuf, int len, int off) { const unsigned char *d; int i; d = dbuf; while (1) { if (off >= len) return(-1); i = d[off]; switch (i & 0xc0) { case 0: if (i) off += i + 1; else return(off+1); break; case 0xc0: if (off+1 >= len) return(-1); return(off+2); break; default: return(-1); break; } } } /* * Perform a DNS query. name is the name to query. wanttype is the * record type desired (usually T_A, T_AAAA, or T_PTR). (*fn) is * called for each suitable record found; its args are: * - pointer to the beginning of the response packet * - pointer to the end of the response packet * - pointer to the beginning of the relevant reocrd * - length of the relevant record * Return value is one of the QIR_ values. */ static int query_it( const char *name, int wanttype, void (*fn)(const void *, const void *, const void *, int) ) /* return values */ #define QIR_FAIL 1 /* failure such as SERVAIL or can't-send */ #define QIR_NODATA 2 /* OK but zero answers */ #define QIR_NXDOM 3 /* NXDOMAIN */ #define QIR_SUCCESS 4 /* OK, nonzero answers */ { unsigned char respbuf[8192]; unsigned char compbuf[1024]; unsigned char nbuf[1024]; int rdl; int rl; int n; int i; int x; int nl; int type; int class; NESTED int exp(int inx) { return(dn_expand((const void *)&respbuf[0],(const void *)&respbuf[rl],(const void *)&respbuf[inx],(void *)&compbuf[0],sizeof(compbuf))); } bzero(&respbuf[0],12); /* res_query doesn't check response length (!!) */ rl = res_query(name,C_IN,wanttype,(void *)&respbuf[0],sizeof(respbuf)); if (rl <= 0) { switch (h_errno) { case HOST_NOT_FOUND: return(QIR_NXDOM); break; case NO_DATA: return(QIR_NODATA); break; } return(QIR_FAIL); } if (rl < 12) return(QIR_FAIL); if (respbuf[3] & 2) return(QIR_FAIL); n = (respbuf[6] * 256) + respbuf[7]; if (n == 0) return(QIR_NODATA); i = (respbuf[4] * 256) + respbuf[5]; x = 12; for (;i>0;i--) { x = dns_skip(&respbuf[0],rl,x); if (x < 0) return(QIR_FAIL); x += 4; if (x >= rl) return(QIR_FAIL); } for (i=0;i= rl) return(QIR_FAIL); rdl = (respbuf[x-2] * 256) + respbuf[x-1]; class = (respbuf[x-8] * 256) + respbuf[x-7]; if (class == C_IN) { type = (respbuf[x-10] * 256) + respbuf[x-9]; if (type == T_CNAME) { if (! strcasecmp(&compbuf[0],name)) { nl = exp(x); if (nl < 0) return(QIR_FAIL); if (nl != rdl) return(QIR_FAIL); strcpy(&nbuf[0],&compbuf[0]); name = &nbuf[0]; } } else if (type == wanttype) { if (! strcasecmp(&compbuf[0],name)) { (*fn)((const void *)&respbuf[0],(const void *)&respbuf[rl],(const void *)&respbuf[x],rdl); } } } x += rdl; } return(QIR_SUCCESS); } /* * Implementation of [lc]. */ static const char *fn_lc_check(int nargs, EXPR **args __attribute__((__unused__))) { return( (nargs == 1) ? 0 : "lc: arg count wrong" ); } static XVAL *fn_lc_perform(int nargs, XVAL **args) { XVAL *a; unsigned char *as; int i; XVAL *rv; if (nargs != 1) panic("argcount wrong"); a = coerce_string(args[0]); as = (void *) a->str.s; for (i=0;;i++) { if (as[i] != tolower(as[i])) break; if (! as[i]) return(a); } rv = new_str_xval(malloc(strlen(as)+1)); for (i=0;as[i];i++) rv->str.s[i] = tolower(as[i]); rv->str.s[i] = '\0'; drop_xval(a); return(rv); } /* * Implementation of [env]. */ static const char *fn_env_check(int nargs, EXPR **args __attribute__((__unused__))) { switch (nargs) { case 1: case 2: return(0); break; } return("env: arg count wrong"); } static XVAL *fn_env_perform(int nargs, XVAL **args) { XVAL *a; char *v; XVAL *rv; if ((nargs < 1) || (nargs > 2)) panic("argcount wrong"); a = coerce_string(args[0]); v = getenv(a->str.s); if (v == 0) { if (nargs < 2) { rv = new_str_xval(strdup("")); } else { rv = args[1]; } } else { if (nargs > 1) drop_xval(args[1]); rv = new_str_xval(strdup(v)); } drop_xval(a); return(rv); } /* * Implementation of [type]. */ static const char *fn_type_check(int nargs, EXPR **args __attribute__((__unused__))) { return( (nargs == 1) ? 0 : "type: arg count wrong" ); } static XVAL *fn_type_perform(int nargs __attribute__((__unused__)), XVAL **args) { const char *v; XVAL *rv; if (nargs != 1) panic("argcount wrong"); switch (args[0]->type) { default: panic("bad type"); break; case XVT_BOOL: v = "boolean"; break; case XVT_STR: v = "string"; break; case XVT_INT: v = "integer"; break; case XVT_ADDR: v = "address"; break; case XVT_CIDR: v = "cidr"; break; case XVT_SET: v = "set"; break; } rv = new_str_xval(strdup(v)); drop_xval(args[0]); return(rv); } /* * Implementation of [matchsub]. The perform function here is * reminiscent of the code for ~ operations, in perform_regex above, * but more complicated because we do substitution too. */ static const char *fn_matchsub_check(int nargs, EXPR **args __attribute__((__unused__))) { switch (nargs) { case 3: case 4: return(0); break; } return("matchsub: arg count wrong"); } static XVAL *fn_matchsub_perform(int nargs, XVAL **args) { XVAL *strx; XVAL *patx; XVAL *replx; XVAL *rv; int e; if ((nargs < 3) || (nargs > 4)) panic("argcount wrong"); strx = coerce_string(args[0]); patx = coerce_string(args[1]); replx = coerce_string(args[2]); ensure_string_maxsub(replx); if (replx->str.maxsub > 1024) { logmsg(LM_ERR,"[matchsub ...] replacement count too large (%d)",replx->str.maxsub); rv = unset_xval(); } else if (ensure_string_re(patx,0)) { rv = unset_xval(); } else { regmatch_t *m; int ns; ns = (replx->str.maxsub > 0) ? replx->str.maxsub+1 : 1; m = malloc(ns*sizeof(regmatch_t)); e = regexec(patx->str.re,strx->str.s,ns,m,0); switch (e) { FILE *f; char *out; int l; NESTED void nonesc(char *start, int len) { fwrite(start,1,len,f); } NESTED void esc(int n) { if ((n < 0) || (n > replx->str.maxsub)) panic("impossible esc"); fwrite(strx->str.s+m[n].rm_so,1,m[n].rm_eo-m[n].rm_so,f); } case 0: l = strlen(strx->str.s); f = fopen_alloc(&out,0); if (m[0].rm_so > 0) nonesc(strx->str.s,m[0].rm_so); replace_scan(replx->str.s,&nonesc,&esc); if (m[0].rm_eo < l) nonesc(strx->str.s+m[0].rm_eo,l-m[0].rm_eo); putc('\0',f); fclose(f); rv = new_str_xval(out); break; case REG_NOMATCH: if (nargs > 3) { rv = ref_xval(args[3]); } else { rv = ref_xval(strx); } break; default: l = regerror(e,patx->str.re,0,0); out = malloc(l); regerror(e,patx->str.re,out,l); logmsg(LM_ERR,"[matchsub ...] regexec error: %s",out); free(out); rv = unset_xval(); break; } } drop_xval(strx); drop_xval(patx); drop_xval(replx); if (nargs > 3) drop_xval(args[3]); return(rv); } /* * Implementation of [bool]. */ static const char *fn_bool_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"bool: arg count wrong"); } static XVAL *fn_bool_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); rv = bool_xval(xval_true(args[0])); drop_xval(args[0]); return(rv); } /* * Implementation of [str]. */ static const char *fn_str_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"str: arg count wrong"); } static XVAL *fn_str_perform(int nargs, XVAL **args) { if (nargs != 1) panic("argcount wrong"); return(coerce_string(args[0])); } /* * Implementation of [int]. */ static const char *fn_int_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"int: arg count wrong"); } static XVAL *fn_int_perform(int nargs, XVAL **args) { if (nargs != 1) panic("argcount wrong"); return(coerce_int(args[0])); } /* * Implementation of [addr]. The only thing of note here is the * conversion of a one-element set of address to a single address * value. */ static const char *fn_addr_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"addr: arg count wrong"); } static XVAL *fn_addr_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); switch (args[0]->type) { default: panic("bad type"); break; case XVT_UNSET: return(args[0]); break; case XVT_BOOL: logmsg(LM_ERR,"can't convert boolean to address"); rv = unset_xval(); break; case XVT_STR: { struct in_addr ia4; struct in6_addr ia6; if (inet_pton(AF_INET,args[0]->str.s,&ia4)) { rv = new_addr_xval(AF_INET); rv->addr.u.v4 = ia4; } else if (inet_pton(AF_INET6,args[0]->str.s,&ia6)) { rv = new_addr_xval(AF_INET6); rv->addr.u.v6 = ia6; } else { logmsg(LM_ERR,"invalid string form of address"); rv = unset_xval(); } } break; case XVT_INT: logmsg(LM_ERR,"can't convert integer to address"); rv = unset_xval(); break; case XVT_ADDR: return(args[0]); break; case XVT_CIDR: switch (args[0]->cidr.af) { case AF_INET: rv = new_addr_xval(AF_INET); rv->addr.u.v4 = args[0]->cidr.u.v4; break; case AF_INET6: rv = new_addr_xval(AF_INET6); rv->addr.u.v6 = args[0]->cidr.u.v6; break; default: panic("bad af"); break; } break; case XVT_SET: if (args[0]->set.n < 1) { logmsg(LM_ERR,"can't convert empty set to address"); } else if (args[0]->set.oftype == XVT_ADDR) { if (args[0]->set.n == 1) { rv = ref_xval(args[0]->set.vals[0]); } else if (args[0]->set.n > 1) { logmsg(LM_ERR,"can't convert multi-element set to address"); } } else { logmsg(LM_ERR,"can't convert set of non-address to address"); } break; } drop_xval(args[0]); return(rv); } /* * Implementation of [cidr]. The only thing of note here is the * conversion of a one-element set of CIDR to a single CIDR value. */ static const char *fn_cidr_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"cidr: arg count wrong"); } static XVAL *fn_cidr_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); switch (args[0]->type) { default: panic("bad type"); break; case XVT_UNSET: return(args[0]); break; case XVT_BOOL: logmsg(LM_ERR,"can't convert boolean to CIDR"); rv = unset_xval(); break; case XVT_STR: { struct in_addr ia4; struct in6_addr ia6; const char *slash; char *s; int l; int w; int maxw; slash = index(args[0]->str.s,'/'); if (slash) { l = slash - args[0]->str.s; s = malloc(l+1); bcopy(args[0]->str.s,s,l); s[l] = '\0'; w = atoi(slash+1); } else { s = strdup(args[0]->str.s); } if (inet_pton(AF_INET,s,&ia4)) { rv = new_cidr_xval(AF_INET); rv->cidr.u.v4 = ia4; maxw = 32; } else if (inet_pton(AF_INET6,s,&ia6)) { rv = new_cidr_xval(AF_INET6); rv->cidr.u.v6 = ia6; maxw = 128; } else { logmsg(LM_ERR,"invalid string form of CIDR"); rv = unset_xval(); } free(s); if (rv->type == XVT_CIDR) { if (slash) { if ((w < 0) || (w > maxw)) { logmsg(LM_ERR,"invalid width %d (not 0-%d) in CIDR",w,maxw); drop_xval(rv); rv = unset_xval(); } else { rv->cidr.w = w; } } else { rv->cidr.w = maxw; } } } break; case XVT_INT: logmsg(LM_ERR,"can't convert integer to CIDR"); rv = unset_xval(); break; case XVT_ADDR: switch (args[0]->addr.af) { case AF_INET: rv = new_cidr_xval(AF_INET); rv->cidr.u.v4 = args[0]->addr.u.v4; rv->cidr.w = 32; break; case AF_INET6: rv = new_cidr_xval(AF_INET6); rv->cidr.u.v6 = args[0]->addr.u.v6; rv->cidr.w = 128; break; default: panic("bad af"); break; } break; case XVT_CIDR: return(args[0]); break; case XVT_SET: if (args[0]->set.n < 1) { logmsg(LM_ERR,"can't convert empty set to CIDR"); } else if (args[0]->set.oftype == XVT_CIDR) { if (args[0]->set.n == 1) { rv = ref_xval(args[0]->set.vals[0]); } else if (args[0]->set.n > 1) { logmsg(LM_ERR,"can't convert multi-element set to CIDR"); } } else { logmsg(LM_ERR,"can't convert set of non-CIDR to CIDR"); } break; } drop_xval(args[0]); return(rv); } /* * Implementation of [set]. */ static const char *fn_set_check(int nargs __attribute__((__unused__)), EXPR **args __attribute__((__unused__))) { return(0); } static XVAL *fn_set_perform(int nargs, XVAL **args) { XVAL *rv; int i; int j; rv = new_set_xval(); do <"done"> { if (nargs > 1) { for (i=nargs-1;i>=1;i--) { if (args[i]->type != args[0]->type) { logmsg(LM_ERR,"[set ...] with different-type arguments"); break <"done">; } } } for <"i"> (i=nargs-1;i>=0;i--) { for (j=i-1;j>=0;j--) { if (equal_xval(args[i],args[j])) { drop_xval(args[i]); nargs --; if (i < nargs) args[i] = args[nargs]; continue <"i">; } } } rv->set.n = nargs; rv->set.oftype = (nargs > 0) ? args[0]->type : XVT_NONE; if (nargs > 0) rv->set.vals = malloc(nargs*sizeof(*rv->set.vals)); for (i=nargs-1;i>=0;i--) rv->set.vals[i] = ref_xval(args[i]); } while (0); for (i=nargs-1;i>=0;i--) drop_xval(args[i]); set_canonicalize(rv); return(rv); } /* * Implementation of [in]. */ static const char *fn_in_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==2)?0:"in: arg count wrong"); } static XVAL *fn_in_perform(int nargs, XVAL **args) { XVAL *rv; int i; if (nargs != 2) panic("argcount wrong"); if (args[0]->type != XVT_SET) { logmsg(LM_ERR,"first arg to [in ...] isn't a set"); rv = unset_xval(); } else if (args[0]->set.n < 1) { rv = bool_xval(0); } else if (args[1]->type != args[0]->set.oftype) { rv = bool_xval(0); } else { do <"done"> { for (i=args[0]->set.n-1;i>=0;i--) { if (equal_xval(args[1],args[0]->set.vals[i])) { rv = bool_xval(1); break <"done">; } } rv = bool_xval(0); } while (0); } for (i=nargs-1;i>=0;i--) drop_xval(args[i]); return(rv); } /* * Implementation of [rdns]. query_it does the hard work. */ static const char *fn_rdns_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"rdns: arg count wrong"); } static XVAL *fn_rdns_perform(int nargs, XVAL **args) { __label__ clear; XVAL *rv; if (nargs != 1) panic("argcount wrong"); args[0] = fn_addr_perform(1,&args[0]); if (args[0]->type != XVT_ADDR) { logmsg(LM_ERR,"thus can't convert [rdns ] arg to address"); rv = unset_xval(); } else { NESTED void save_ptr(const void *pktbase, const void *pktend, const void *rrn, int rdl) { unsigned char xbuf[1024]; int xl; XVAL *sv; xl = dn_expand(pktbase,pktend,rrn,(void *)&xbuf[0],sizeof(xbuf)); if (xl < 0) goto clear; if (xl != rdl) goto clear; sv = new_str_xval(strdup(&xbuf[0])); add_to_set(rv,sv); } rv = new_set_xval(); switch (args[0]->addr.af) { case AF_INET: { char name[3+1+3+1+3+1+3+1+7+1+4+1]; sprintf(&name[0],"%d.%d.%d.%d.in-addr.arpa", ((const unsigned char *)&args[0]->addr.u.v4)[3], ((const unsigned char *)&args[0]->addr.u.v4)[2], ((const unsigned char *)&args[0]->addr.u.v4)[1], ((const unsigned char *)&args[0]->addr.u.v4)[0]); query_it(&name[0],T_PTR,&save_ptr); } break; case AF_INET6: { char name[32*(1+1)+3+1+4+1]; sprintf(&name[0], "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "ip6.arpa", ((((const unsigned char *)&args[0]->addr.u.v6)[15] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[15] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[14] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[14] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[13] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[13] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[12] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[12] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[11] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[11] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[10] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[10] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 9] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 9] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 8] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 8] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 7] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 7] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 6] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 6] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 5] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 5] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 4] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 4] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 3] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 3] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 2] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 2] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 1] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 1] >> 4)&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 0] )&15), ((((const unsigned char *)&args[0]->addr.u.v6)[ 0] >> 4)&15)); query_it(&name[0],T_PTR,&save_ptr); } break; default: panic("bad af"); break; } } drop_xval(args[0]); return(rv); clear:; drop_xval(rv); rv = new_set_xval(); return(rv); } /* * Implementation of [fdns]. query_it does the hard work. */ static const char *fn_fdns_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"fdns: arg count wrong"); } static XVAL *fn_fdns_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); if (args[0]->type != XVT_STR) { logmsg(LM_ERR,"[fdns ...] arg isn't a string"); rv = unset_xval(); } else { int af; int alen; NESTED void save_addr(const void *pktbase __attribute__((__unused__)), const void *pktend __attribute__((__unused__)), const void *rrn, int rdl) { XVAL *sv; if (rdl != alen) return; sv = new_addr_xval(af); switch (af) { case AF_INET: bcopy(rrn,&sv->addr.u.v4,4); break; case AF_INET6: bcopy(rrn,&sv->addr.u.v6,16); break; default: panic("bad af"); break; } add_to_set(rv,sv); } rv = new_set_xval(); af = AF_INET6; alen = 16; query_it(args[0]->str.s,T_AAAA,&save_addr); af = AF_INET; alen = 4; query_it(args[0]->str.s,T_A,&save_addr); } drop_xval(args[0]); return(rv); } /* * Implementation of [map]. */ static const char *fn_map_check(int nargs, EXPR **args) { if (nargs != 5) return("map: arg count wrong"); if (args[0]->op != XO_VAR) return("map: first arg must be a $variable"); if (args[1]->op != XO_VAR) return("map: second arg must be a $variable"); switch (args[0]->var->type) { case VT_UNDEF: case VT_XVAL: break; default: return("map: first arg must be a $variable"); break; } switch (args[1]->var->type) { case VT_UNDEF: case VT_XVAL: break; default: return("map: second arg must be a $variable"); break; } return(0); } static void fn_map_deps(EXPR *e, int nargs, EXPR **args, void (*fn)(EXPR *, EXPRLIST *)) { if (nargs != 5) panic("argcount wrong"); (*fn)(e,&args[2]->deps); (*fn)(e,&args[3]->deps); (*fn)(e,&args[4]->deps); } static int fn_map_eval_arg(int argno) { return(argno>2); } /* eg: [map $x, $e, $x . [fdns $e], [set], [rdns remote-ip]] */ static XVAL *fn_map_eval_fn(int nargs, char *ev, void **args) { CVAR rvsave; CVAR evsave; EXPR *rvx; EXPR *evx; CVAR *rvv; CVAR *evv; EXPR *iter; XVAL *init; XVAL *over; int i; XVAL *rv; if (nargs != 5) panic("argcount wrong"); if (ev[0] || ev[1] || ev[2] || !ev[3] || !ev[4]) panic("evals wrong"); rvx = args[0]; if (rvx->op != XO_VAR) panic("rvx wrong"); switch (rvx->var->type) { case VT_XVAL: case VT_UNDEF: break; default: panic("rvx wrong"); break; } rvv = rvx->var; evx = args[1]; if (evx->op != XO_VAR) panic("evx wrong"); switch (evx->var->type) { case VT_XVAL: case VT_UNDEF: break; default: panic("evx wrong"); break; } evv = evx->var; iter = args[2]; init = args[3]; over = args[4]; if (over->type != XVT_SET) { logmsg(LM_ERR,"[map ...] last argument isn't a set"); drop_xval(init); drop_xval(over); return(unset_xval()); } rvsave = *rvv; evsave = *evv; rvv->type = VT_XVAL; rvv->flags = VF_RW | VF_CFG; rvv->x.set = 0; rvv->x.def = 0; evv->type = VT_XVAL; evv->flags = VF_RW | VF_CFG; evv->x.set = 0; evv->x.def = 0; rvv->x.cfg = init; invalidate(&rvv->deps); evv->x.cfg = ref_xval(init); for (i=over->set.n-1;i>=0;i--) { drop_xval(evv->x.cfg); evv->x.cfg = ref_xval(over->set.vals[i]); invalidate(&evv->deps); update_expr(iter); drop_xval(rvv->x.cfg); rvv->x.cfg = ref_xval(iter->eval); invalidate(&rvv->deps); } rv = rvv->x.cfg; drop_xval(evv->x.cfg); *rvv = rvsave; *evv = evsave; drop_xval(over); return(rv); } /* * Implementation of [md5]. */ static const char *fn_md5_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"md5: arg count wrong"); } static XVAL *fn_md5_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); if (args[0]->type != XVT_STR) { logmsg(LM_ERR,"[md5 ...] arg isn't a string"); rv = unset_xval(); } else { rv = str_hash_fn(args[0],&hashalg_md5); } drop_xval(args[0]); return(rv); } /* * Implementation of [sha1]. */ static const char *fn_sha1_check(int nargs, EXPR **args __attribute__((__unused__))) { return((nargs==1)?0:"sha1: arg count wrong"); } static XVAL *fn_sha1_perform(int nargs, XVAL **args) { XVAL *rv; if (nargs != 1) panic("argcount wrong"); if (args[0]->type != XVT_STR) { logmsg(LM_ERR,"[sha1 ...] arg isn't a string"); rv = unset_xval(); } else { rv = str_hash_fn(args[0],&hashalg_sha1); } drop_xval(args[0]); return(rv); } /* * Clear the config-file-set value from a variable. This is used when * assigning an unset value to a variable. */ static void clearcfg(CVAR *v) { if (v->flags & VF_CFG) { v->flags &= ~VF_CFG; invalidate(&v->deps); } } /* * Return true iff we should execute an assignment at the moment. This * is so if we aren't inside any ifs, or the innermost one is in state * TRUE. (This last condition works only because ifs nested inside a * false block start out in state PAST and thus never become TRUE.) */ static int if_true(CONFIG *c) { return(!c->cstack||(c->cstack->state==CS_TRUE)); } /* * Push a CSTATE on a CONFIG's cstack. */ static void push_cstack(CONFIG *c, CSTATE s) { CSTACK *cs; cs = malloc(sizeof(CSTACK)); cs->state = s; cs->link = c->cstack; c->cstack = cs; } /* * Dump the cstack to CONFIGCOND's destination. Called only if * VERB(CONFIGCOND). */ static void dump_cstack(CONFIG *c) { FILE *f; CSTACK *s; f = verb_fopen(VERBOSE_CONFIGCOND); fprintf(f,"cond: cstack:"); for (s=c->cstack;s;s=s->link) { switch (s->state) { case CS_FALSE: fprintf(f," FALSE"); break; case CS_TRUE: fprintf(f," TRUE"); break; case CS_PAST: fprintf(f," PAST"); break; default: fprintf(f," ?%d",s->state); break; } } fprintf(f,"\n"); fclose(f); } /* * Maybe do a config file re-evaluation. This is where variable * assignment actually happens, also where the run-time side of * conditionals happens. */ static void maybe_eval(void) { int i; CVAR *v; XVAL *x; CONFLINE *cl; CSTACK *cs; if ((setserial == evalserial) || defer_eval) return; defer_eval = 1; if (VERB(CONFIGFILE) || VERB(CONFIGVAL)) { pverb(VERBOSE_CONFIGFILE|VERBOSE_CONFIGVAL,"Doing eval [%d set %u eval %u]\n",(int)getpid(),setserial,evalserial); } fwd = config.pre_fwd; fwdonce = config.pre_fwdonce; while (nports > config.pre_nports) free(ports[--nports]); while (nauthkeys > config.pre_nauthkeys) free(authkeys[--nauthkeys]); alglist_replace(&algs_kex,&config.pre_algs_kex); alglist_replace(&algs_hk,&config.pre_algs_hk); alglist_replace(&algs_enc_c2s,&config.pre_algs_enc_c2s); alglist_replace(&algs_enc_s2c,&config.pre_algs_enc_s2c); alglist_replace(&algs_mac_c2s,&config.pre_algs_mac_c2s); alglist_replace(&algs_mac_s2c,&config.pre_algs_mac_s2c); alglist_replace(&algs_comp_c2s,&config.pre_algs_comp_c2s); alglist_replace(&algs_comp_s2c,&config.pre_algs_comp_s2c); for (i=config.nvars-1;i>=0;i--) { v = config.vars[i]; v->flags &= ~VF_CFG; if (v->flags & VF_SPEC_INIT) (*v->spec)(v,VSO_INIT); invalidate(&v->deps); } if (config.cstack) panic("leftover cstack"); for (cl=config.configlist;cl;cl=cl->link) { switch (cl->type) { default: panic("bad confline type"); break; case CL_VSET: if (if_true(&config)) { if (! cl->vset.val) { x = unset_xval(); } else { update_expr(cl->vset.val); x = cl->vset.val->eval; } v = cl->vset.var; if (! (v->flags & VF_W)) { logmsg(LM_ERR,"variable `%s' is not writable",v->name); } else if (v->flags & VF_SPEC_SETANY) { (*v->spec)(v,VSO_SET,ref_xval(x)); } else { switch (v->type) { default: panic("bad var type"); break; case VT_KEY: if (x->type == XVT_UNSET) { clearcfg(v); } else { x = coerce_string(ref_xval(x)); for (i=v->k.nvals-1;i>=0;i--) { if (!strcmp(x->str.s,v->k.vals[i].name)) { v->flags |= VF_CFG; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"%s <- %s [vset %p]\n",v->name,x->str.s,(void *)cl); if (v->k.cfg != i) { v->k.cfg = i; invalidate(&v->deps); } break; } } if (i < 0) { logmsg(LM_ERR,"invalid keyword `%s' for variable `%s'",x->str.s,v->name); } drop_xval(x); } break; case VT_BOOL: if (x->type == XVT_UNSET) { clearcfg(v); } else { v->flags |= VF_CFG; i = xval_true(x); if (VERB(CONFIGVAL)) verb(CONFIGVAL,"%s <- %s [vset %p]\n",v->name,boolstr(i),(void *)cl); if (v->b.cfg != i) { v->b.cfg = i; invalidate(&v->deps); } } break; case VT_INT: if (x->type == XVT_UNSET) { clearcfg(v); } else { x = coerce_int(ref_xval(x)); v->flags |= VF_CFG; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"%s <- %d [vset %p]\n",v->name,x->i,(void *)cl); if (v->i.cfg != x->i) { v->i.cfg = x->i; invalidate(&v->deps); } drop_xval(x); } break; case VT_STR: if (x->type == XVT_UNSET) { clearcfg(v); } else { x = coerce_string(ref_xval(x)); v->flags |= VF_CFG; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"%s <- %s [vset %p]\n",v->name,x->str.s,(void *)cl); if (!v->s.cfg || strcmp(v->s.cfg,x->str.s)) { free(v->s.cfg); v->s.cfg = strdup(x->str.s); invalidate(&v->deps); } drop_xval(x); } break; case VT_ADDR: if (x->type == XVT_UNSET) { clearcfg(v); } else { XVAL *val; val = ref_xval(x); x = fn_addr_perform(1,&val); if (x->type == XVT_ADDR) { v->flags |= VF_CFG; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"%s <- %s [vset %p]\n",v->name,x->str.s,(void *)cl); if (!v->a.cfg || !equal_xval(v->a.cfg,x)) { drop_xval(v->a.cfg); v->a.cfg = ref_xval(x); invalidate(&v->deps); } } drop_xval(x); } break; case VT_XVAL: if (x->type == XVT_UNSET) { clearcfg(v); } else { ref_xval(x); if (VERB(CONFIGVAL)) { FILE *f; f = verb_fopen(VERBOSE_CONFIGVAL); fprintf(f,"%s <- ",v->name); dump_xval(f,x); fprintf(f," [vset %p]\n",(void *)cl); fclose(f); } drop_xval(v->x.cfg); v->x.cfg = x; v->flags |= VF_CFG; invalidate(&v->deps); } break; } if (v->flags & VF_SPEC_SET) (*v->spec)(v,VSO_SET); } } break; case CL_IF: if (! if_true(&config)) { push_cstack(&config,CS_PAST); if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: if inside false if\n",cl->lno); } } else { update_expr(cl->cond); if (cl->cond->eval->type == XVT_UNSET) { push_cstack(&config,CS_PAST); if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: if UNSET\n",cl->lno); } } else if (xval_true(cl->cond->eval)) { push_cstack(&config,CS_TRUE); if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: if TRUE\n",cl->lno); } } else { push_cstack(&config,CS_FALSE); if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: if FALSE\n",cl->lno); } } } if (VERB(CONFIGCOND)) dump_cstack(&config); break; case CL_ELSE: if (! config.cstack) panic("else without cstack"); switch (config.cstack->state) { default: panic("bad conditional state"); break; case CS_FALSE: config.cstack->state = CS_TRUE; break; case CS_TRUE: config.cstack->state = CS_PAST; break; case CS_PAST: break; } if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: else\n",cl->lno); dump_cstack(&config); } break; case CL_ELIF: if (! config.cstack) panic("else without cstack"); switch (config.cstack->state) { default: panic("bad conditional state"); break; case CS_FALSE: update_expr(cl->cond); if (cl->cond->eval->type == XVT_UNSET) { config.cstack->state = CS_PAST; if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: elif FALSE UNSET\n",cl->lno); } break; } else if (xval_true(cl->cond->eval)) { config.cstack->state = CS_TRUE; if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: elif FALSE TRUE\n",cl->lno); } } else { if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: elif FALSE FALSE\n",cl->lno); } } break; case CS_TRUE: config.cstack->state = CS_PAST; if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: elif TRUE\n",cl->lno); } break; case CS_PAST: if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: elif PAST\n",cl->lno); } break; } if (VERB(CONFIGCOND)) dump_cstack(&config); break; case CL_ENDIF: cs = config.cstack; if (! cs) panic("else without cstack"); config.cstack = cs->link; free(cs); if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: %d: endif\n",cl->lno); dump_cstack(&config); } break; } } if (config.cstack) { if (VERB(CONFIGCOND)) { verb(CONFIGCOND,"cond: leftover cstack\n"); dump_cstack(&config); } panic("leftover cstack"); } evalserial = setserial; if (VERB(CONFIGFILE)) { FILE *f; f = verb_fopen(VERBOSE_CONFIGFILE); fprintf(f,"After evaling (%u):\n",setserial); dump_config(f,&config); fclose(f); } defer_eval = 0; } /* * Check that a variable exists. */ static void varexists(CVAR *v) { if (! v) panic("no such variable"); } /* * Check that a variable exists and is of the correct type. */ static void vartype(CVAR *v, CVARTYPE t) { if (! v) panic("no such variable"); if (v->type != t) panic("type wrong"); } /* * Check that a variable exists and is of the correct type, and arrange * everything necessary to set it. (This calls invalidate() only when * it actually changes the VF_SET flag. Invalidation for other * setting changes is handled by our caller.) */ static void varset(CVAR *v, CVARTYPE t) { if (! v) panic("no such variable"); if (v->type != t) panic("type wrong"); if (! (v->flags & VF_SET)) { v->flags |= VF_SET; invalidate(&v->deps); } setserial ++; } /* * Check that a variable exists and is of the correct type, and record * that something has been set, but don't do anything to this * variable. This is used when setting the default value of a * variable. */ static void vardef(CVAR *v, CVARTYPE t) { if (! v) panic("no such variable"); if (v->type != t) panic("type wrong"); setserial ++; } /* * Perform a keyword variable setting operation. name is the variable * name. (*chk) is called to check the variable's type, and possibly * do other stuff - it's normally varset or vardef. (*valp) returns a * pointer to the place we should bash to set the value. (*match) is * called with each KVAL in turn to find the one we want; it needs to * return true for that one and false up until that point. (The order * in which the values will be tried is unspecified.) tag is used in * debugging output. * * This handles routine invalidation, and is careful to invalidate only * when the value is actually changing. */ static int config_key_aux(const char *name, void (*chk)(CVAR *, CVARTYPE), int *(*valp)(CVAR *), int (*match)(KVAL *), const char *tag) { CVAR *v; int i; v = find_cvar(name,-1,0); (*chk)(v,VT_KEY); for (i=v->k.nvals-1;i>=0;i--) { if ((*match)(&v->k.vals[i])) { int *vp; vp = (*valp)(v); if (*vp != i) { *vp = i; invalidate(&v->deps); } if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: %s %s <- %s\n",tag,name,v->k.vals[i].name); return(0); } } return(1); } /* * These are (*valp) functions for config_key_aux for use when setting * the moussh-set (valp_set) or default (valp_def) value of the * variable. */ static int *valp_set(CVAR *v) { return(&v->k.set); } static int *valp_def(CVAR *v) { return(&v->k.def); } /* * Set a keyword variable's value by integer value. */ void config_set_key_v(const char *name, int val) { NESTED int match(KVAL *k) { return(k->val==val); } if (config_key_aux(name,&varset,&valp_set,&match,"set")) panic("VT_KEY value not found"); } /* * Set a keyword variable's value by string value. */ int config_set_key_s(const char *name, const char *val) { NESTED int match(KVAL *k) { return(!strcmp(k->name,val)); } return(config_key_aux(name,&varset,&valp_set,&match,"set")); } /* * Set a keyword variable's default value by integer value. */ void config_def_key_v(const char *name, int val) { NESTED int match(KVAL *k) { return(k->val==val); } if (config_key_aux(name,&vardef,&valp_def,&match,"def")) panic("VT_KEY value not found"); } /* * Set a keyword variable's default value by string value. */ void config_def_key_s(const char *name, const char *val) { NESTED int match(KVAL *k) { return(!strcmp(k->name,val)); } if (config_key_aux(name,&vardef,&valp_def,&match,"def")) panic("VT_KEY value not found"); } /* * Return a keyword variable's value, as an integer. */ int config_key(const char *name) { CVAR *v; v = find_cvar(name,-1,0); vartype(v,VT_KEY); maybe_eval(); if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: get %s -> %s\n",name,v->k.vals[SETFIELD(v,k,v->k.def)].name); return(v->k.vals[SETFIELD(v,k,v->k.def)].val); } /* * CONFIG_SETFN defines a `set' function. * * name is set or def, according as the function sets the moussh-set or * default value. * kind is the function-name form of the variable type, eg str or int. * argtype is the (C) type of the function's value argument. * vt is the VT_* type the variable must have. * field is the type-specific field to set. * valnorm is an expression that prepares the value to be stuffed into * `field'. This can be used to, eg, strdup strings. * chgtst tests whether a proposed new value (in "valtmp") is actually * any different from the current value (returns true iff so). * preset is any code to be executed before the setting; this can be * used to, eg, free a previous value. * pfmt is a printf format used in verbose messages. * pexpr is an expression suitable for printing with pfmt. */ #define CONFIG_SETFN(name,kind,argtype,vt,field,valnorm,chgtst,preset,pfmt,pexpr) \ void config_##name##_##kind(const char *name, argtype val) \ { \ CVAR *v; \ __typeof__((valnorm)) valtmp; \ \ v = find_cvar(name,-1,0); \ var##name(v,vt); \ valtmp = (valnorm); \ if (chgtst) \ { preset; \ v->field = valtmp; \ invalidate(&v->deps); \ } \ setserial ++; \ if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: " #name " %s <- " \ pfmt "\n",name,pexpr); \ } /* * CONFIG_GETFN defines a `get' function. * * Arguments with names the same as arguments to CONFIG_SETFN have the * same meanings. Other arguments: * rtype is the return type of the get function. * rexpr is an expression (usually involving SETFIELD()) giving the * value to be returned. */ #define CONFIG_GETFN(kind,rtype,vt,pfmt,pexpr,rexpr) \ rtype config_##kind(const char *name) \ { \ CVAR *v; \ \ v = find_cvar(name,-1,0); \ vartype(v,vt); \ maybe_eval(); \ if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: get %s -> " \ pfmt "\n",name,pexpr); \ return(rexpr); \ } CONFIG_SETFN(set,str,const char *,VT_STR,s.set,val?strdup(val):0, zstrcmp(v->s.set,valtmp),free(v->s.set),"%s",v->s.set?:"(nil)") CONFIG_SETFN(def,str,const char *,VT_STR,s.def,val?strdup(val):0, zstrcmp(v->s.def,valtmp),free(v->s.def),"%s",v->s.def?:"(nil)") CONFIG_GETFN(str,const char *,VT_STR,"%s", SETFIELD(v,s,v->s.def),SETFIELD(v,s,v->s.def)) CONFIG_SETFN(set,bool,int,VT_BOOL,b.set,val?1:0,v->b.set!=valtmp,,"%s",boolstr(v->b.set)) CONFIG_SETFN(def,bool,int,VT_BOOL,b.def,val?1:0,v->b.def!=valtmp,,"%s",boolstr(v->b.def)) CONFIG_GETFN(bool,int,VT_BOOL,"%s",boolstr(SETFIELD(v,b,v->b.def)),SETFIELD(v,b,v->b.def)) CONFIG_SETFN(set,int,int,VT_INT,i.set,val,v->i.set!=valtmp,,"%d",v->i.set) CONFIG_SETFN(def,int,int,VT_INT,i.def,val,v->i.def!=valtmp,,"%d",v->i.def) CONFIG_GETFN(int,int,VT_INT,"%d",SETFIELD(v,i,v->i.def),SETFIELD(v,b,v->i.def)) /* * Set function for address variables. This is too complex to be * expressed with CONFIG_SETFN, because of the address-family switch. */ void config_set_addr(const char *name, const void *val) { CVAR *v; XVAL *newval; v = find_cvar(name,-1,0); varset(v,VT_ADDR); switch (((const struct sockaddr *)val)->sa_family) { case AF_INET: newval = new_addr_xval(AF_INET); newval->addr.u.v4 = ((const struct sockaddr_in *)val)->sin_addr; break; case AF_INET6: newval = new_addr_xval(AF_INET6); newval->addr.u.v6 = ((const struct sockaddr_in6 *)val)->sin6_addr; break; default: panic("bad af"); break; } if (!v->a.set || !equal_xval(v->a.set,newval)) { drop_xval(v->a.set); v->a.set = newval; invalidate(&v->deps); } else { drop_xval(newval); } setserial ++; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: set %s <- %s\n",name,addrstr(v->a.set)); } /* * Function to remove any moussh-set value from a variable. */ void config_unset(const char *name) { CVAR *v; v = find_cvar(name,-1,0); varexists(v); if (v->flags & VF_SET) { v->flags &= ~VF_SET; invalidate(&v->deps); setserial ++; } } /* * Function to return true if the config pays attention to a variable. * This can be used to avoid doing something expensive to compute a * config-file variable's value when the config file doesn't use it * for anything. * * XXX should actually do something here! */ int config_needs(const char *name) { name=name; return(1); } /* * Check whether a variable is set. Returns true if it's set either by * moussh (VF_SET) or by the config file (VF_CFG). */ int config_isset(const char *name) { CVAR *v; v = find_cvar(name,-1,0); varexists(v); if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: isset %s -> %s\n",name,(v->flags&(VF_SET|VF_CFG))?"yes":"no"); return((v->flags&(VF_SET|VF_CFG))?1:0); } /* * Take the IP address and port number from a sockaddr (sa) and put * them into an address variable (name in ipvar) and an integer * variable (name in portvar). */ void config_sa_ip_port(const void *sa, const char *ipvar, const char *portvar) { CVAR *va; CVAR *vp; XVAL *addrval; int portval; va = find_cvar(ipvar,-1,0); vp = find_cvar(portvar,-1,0); varset(va,VT_ADDR); varset(vp,VT_INT); switch (((const struct sockaddr *)sa)->sa_family) { case AF_INET: addrval = new_addr_xval(AF_INET); addrval->addr.u.v4 = ((const struct sockaddr_in *)sa)->sin_addr; portval = ((const struct sockaddr_in *)sa)->sin_port; break; case AF_INET6: addrval = new_addr_xval(AF_INET6); addrval->addr.u.v6 = ((const struct sockaddr_in6 *)sa)->sin6_addr; portval = ((const struct sockaddr_in6 *)sa)->sin6_port; break; default: panic("bad af"); break; } if (!va->a.set || !equal_xval(va->a.set,addrval)) { va->a.set = addrval; invalidate(&va->deps); } else { drop_xval(addrval); } if (vp->i.set != portval) { vp->i.set = portval; invalidate(&vp->deps); } setserial ++; if (VERB(CONFIGVAL)) verb(CONFIGVAL,"config: set %s/%s <- %s/%d\n",ipvar,portvar,addrstr(va->a.set),portval); } /* * Suppress forwarding. This is used by the auto-share code, for * example, to make sure it doesn't try to estsablish forwardings for * itself (as opposed to for its clients). */ void config_nofwd(void) { fwd = 0; config.pre_fwd = 0; } /* * A VT_KFN keyword function for key-type, giving public-key type * names. */ static KVAL var_key_key_type(int inx) { int i; int j; int k; HKALG *a; i = 0; j = 0; while (1) { a = (*at_hk.list)(j); if (a == 0) return((KVAL){.name=0,.val=0}); if (inx == 0) return((KVAL){.name=a->name,.val=j}); inx --; for (k=0;a->altnames[k];k++) { if (inx == 0) return((KVAL){.name=a->altnames[k],.val=j}); inx --; } j ++; } } /* * Implementation of no-private. */ static void spec_no_private(CVAR *v __attribute__((__unused__)), int op, ...) { switch (op) { default: panic("bad op"); break; case VSO_SET: if (config_bool("no-private")) set_no_private(); break; } } /* * Implementation of lang. */ static void spec_lang(CVAR *v, int op, ...) { switch (op) { default: panic("bad op"); break; case VSO_SET: config_set_str("lang-c2s",v->s.cfg); config_set_str("lang-s2c",v->s.cfg); break; } } /* * Implementation of key-file. */ static void spec_key_file(CVAR *v, int op, ...) { switch (op) { default: panic("bad op"); break; case VSO_SET: config_set_str("key-file-public",v->s.cfg); config_set_str("key-file-private",v->s.cfg); break; } } /* * A VT_KFN function for fwd, giving +, -, and 1 versions of each * supported forwarding type. Must agree with spec_fwd on the integer * encoding. */ static KVAL var_key_fwdwhat(int inx) { char *t; char pref; int x; switch (inx % 3) { case 0: pref = '-'; break; case 1: pref = '1'; break; case 2: pref = '+'; break; } x = inx / 3; if (! fwdwhats[x].str) return((KVAL){.name=0,.val=0}); asprintf(&t,"%c%s",pref,fwdwhats[x].str); return((KVAL){.name=t,.val=inx}); } /* * Implementation of fwd. Must agree with var_key_fwd on the integer * encoding. */ static void spec_fwd(CVAR *v, int op, ...) { unsigned int bit; XVAL *set; int i; const char *pref; char *elstr; va_list ap; switch (op) { default: panic("bad op"); break; case VSO_SET: bit = fwdwhats[v->k.cfg/3].bit; switch (v->k.cfg % 3) { case 0: fwd &= ~bit; break; case 1: fwd |= bit; fwdonce |= bit; break; case 2: fwd |= bit; fwdonce &= ~bit; break; } break; case VSO_GET: set = new_set_xval(); for (i=0;fwdwhats[i].str;i++) { if (fwd & fwdwhats[i].bit) { pref = (fwdonce & fwdwhats[i].bit) ? "1" : "+"; asprintf(&elstr,"%s%s",pref,fwdwhats[i].str); add_to_set(set,new_str_xval(elstr)); } } set_canonicalize(set); va_start(ap,op); *va_arg(ap,XVAL **) = set; va_end(ap); break; } } /* * Implementation of port. */ static void spec_port(CVAR *v, int op, ...) { unsigned int i; char *str; XVAL *set; va_list ap; switch (op) { default: panic("bad op"); break; case VSO_SET: asprintf(&str,"%d",v->i.cfg); for (i=0;i=0;i--) if (!strcmp(authkeys[i],v->s.cfg)) return; add_authkey(strdup(v->s.cfg)); break; case VSO_GET: set = new_set_xval(); for (i=nauthkeys-1;i>=0;i--) { add_to_set(set,new_str_xval(strdup(authkeys[i]))); } set_canonicalize(set); va_start(ap,op); *va_arg(ap,XVAL **) = set; va_end(ap); break; } } /* * A helper function for the VF_SPEC special functions for setting * algorithms. */ static void spec_alg(CVAR *v, int op, ALGLIST *l) { switch (op) { default: panic("bad op"); break; case VSO_SET: algset(l,v->s.cfg,1,errf); alglist_expand_default(l); break; } } /* * Implementation of kex. */ static void spec_alg_kex(CVAR *v, int op, ...) { spec_alg(v,op,&algs_kex); } /* * Implementation of hk. */ static void spec_alg_hk(CVAR *v, int op) { spec_alg(v,op,&algs_hk); } /* * Implementation of enc_c2s. */ static void spec_alg_enc_c2s(CVAR *v, int op) { spec_alg(v,op,&algs_enc_c2s); } /* * Implementation of enc_s2c. */ static void spec_alg_enc_s2c(CVAR *v, int op) { spec_alg(v,op,&algs_enc_s2c); } /* * Implementation of enc. */ static void spec_alg_enc(CVAR *v, int op) { spec_alg(v,op,&algs_enc_c2s); spec_alg(v,op,&algs_enc_s2c); } /* * Implementation of mac_c2s. */ static void spec_alg_mac_c2s(CVAR *v, int op) { spec_alg(v,op,&algs_mac_c2s); } /* * Implementation of mac_s2c. */ static void spec_alg_mac_s2c(CVAR *v, int op) { spec_alg(v,op,&algs_mac_s2c); } /* * Implementation of mac. */ static void spec_alg_mac(CVAR *v, int op) { spec_alg(v,op,&algs_mac_c2s); spec_alg(v,op,&algs_mac_s2c); } /* * Implementation of comp_c2s. */ static void spec_alg_comp_c2s(CVAR *v, int op) { spec_alg(v,op,&algs_comp_c2s); } /* * Implementation of comp_s2c. */ static void spec_alg_comp_s2c(CVAR *v, int op) { spec_alg(v,op,&algs_comp_s2c); } /* * Implementation of comp. */ static void spec_alg_comp(CVAR *v, int op) { spec_alg(v,op,&algs_comp_c2s); spec_alg(v,op,&algs_comp_s2c); } /* * Implementation of ua. */ static void spec_alg_ua(CVAR *v, int op) { spec_alg(v,op,&algs_ua); } /* * A VF_SPEC special function for setting log. This arguably ought to * go away, because a single log in the config file will end up * logging it once per evaulation, which is probably a lot of times. */ static void spec_log(CVAR *v, int op) { switch (op) { default: panic("bad op"); break; case VSO_INIT: break; case VSO_SET: logmsg(LM_ERR,"log: %s",v->s.cfg); break; } } /* * Called to set up local-host-name from gethostname(). */ static void setup_hostname(void) { int n; char *buf; n = 8; while (1) { buf = malloc(n); buf[n-1] = '\0'; gethostname(buf,n); if (buf[n-1] == '\0') break; free(buf); n *= 2; } config_set_str("local-host-name",buf); free(buf); } /* * Called to initialize the config machinery. */ void config_init(void) { config.vars = 0; config.nvars = 0; config.avars = 0; /* Variables set only by moussh */ add_var("opmode",VT_KEY,VF_R, "client",OPMODE_CLIENT, "keygen",OPMODE_KEYGEN, "setpass",OPMODE_SETPASS, "setcomm",OPMODE_SETCOMM, "agent",OPMODE_AGENT, "add",OPMODE_ADD, "list",OPMODE_LIST, "delete",OPMODE_DELETE, "delete-all",OPMODE_DELETE_ALL, "server",OPMODE_SERVER, "import",OPMODE_IMPORT, "export",OPMODE_EXPORT, "fingerprint",OPMODE_FPRINT, "share-kill",OPMODE_SHAREKILL, "share-stop",OPMODE_SHARESTOP, "share-drop",OPMODE_SHAREDROP, "interactive-agent",OPMODE_INTAGENT, "known-hosts",OPMODE_KH, (const char *)0,OPMODE_CLIENT); add_var("kh-op",VT_KEY,VF_R, "unset",KH_OP_UNSET, "import",KH_OP_IMPORT, "export",KH_OP_EXPORT, "clear",KH_OP_CLEAR, "delete",KH_OP_DELETE, "ports",KH_OP_PORTS, "find",KH_OP_FIND, "hosts",KH_OP_HOSTS, (const char *)0,KH_OP_UNSET); add_var("kh-host",VT_STR,VF_R); config_set_key_v("opmode",OPMODE_CLIENT); /* needs to be set! */ add_var("host",VT_STR,VF_R); add_var("host-type",VT_KEY,VF_R, "none",HT_NONE, "argv",HT_ARGV, "bare",HT_BARE, "flag",HT_FLAG, (const char *)0,HT_NONE); add_var("local-ip",VT_ADDR,VF_R); add_var("local-port",VT_INT,VF_R); add_var("remote-ip",VT_ADDR,VF_R); add_var("remote-port",VT_INT,VF_R); add_var("remote-version-entire",VT_STR,VF_R); add_var("remote-version-proto",VT_STR,VF_R); add_var("remote-version-sw",VT_STR,VF_R); add_var("remote-version-comment",VT_STR,VF_R); add_var("local-host-name",VT_STR,VF_R); add_var("user-exists",VT_BOOL,VF_R); add_var("user-authenticated",VT_BOOL,VF_R); add_var("user-uid",VT_INT,VF_R); add_var("auth-alg",VT_STR,VF_R); add_var("auth-attempts",VT_INT,VF_R); add_var("auth-alg-attempts",VT_INT,VF_R); add_var("uid",VT_INT,VF_R); config_set_int("uid",getuid()); setup_hostname(); /* Variables set only by the config file */ add_var("connect-to",VT_STR,VF_RW); add_var("chroot",VT_BOOL,VF_RW); add_var("ignore-kill",VT_BOOL,VF_RW); add_var("ignore-stop",VT_BOOL,VF_RW); add_var("ignore-drop",VT_BOOL,VF_RW); add_var("hkdb-key",VT_STR,VF_RW); add_var("agent-dir",VT_STR,VF_RW); add_var("panic-core",VT_BOOL,VF_RW); /* Variables set by command-line flags, overridable by config file */ add_var("auto-bg",VT_BOOL,VF_RW); add_var("do-nothing",VT_BOOL,VF_RW); add_var("just-die",VT_BOOL,VF_RW); add_var("never-auth",VT_BOOL,VF_RW); add_var("no-agent",VT_BOOL,VF_RW); add_var("no-input",VT_BOOL,VF_RW); add_var("one-conn",VT_BOOL,VF_RW); add_var("use-syslog",VT_BOOL,VF_RW); add_var("share-client",VT_BOOL,VF_RW); add_var("share-server",VT_BOOL,VF_RW); add_var("auto-share-timeout",VT_INT,VF_RW); config_def_int("auto-share-timeout",-1); add_var("share-path",VT_STR,VF_RW); add_var("auto-share",VT_BOOL,VF_RW); add_var("auto-share-path",VT_STR,VF_RW); add_var("user",VT_STR,VF_RW); add_var("lang-c2s",VT_STR,VF_RW); add_var("lang-s2c",VT_STR,VF_RW); add_var("host-key-db",VT_STR,VF_RW); add_var("no-hk-action",VT_KEY,VF_RW, "prompt",HKACT_PROMPT, "yes",HKACT_YES, "no",HKACT_NO, "save",HKACT_SAVE, (const char *)0,HKACT_PROMPT); add_var("bad-hk-action",VT_KEY,VF_RW, "prompt",HKACT_PROMPT, "yes",HKACT_YES, "no",HKACT_NO, "replace",HKACT_REPLACE, "replall",HKACT_REPLALL, "add",HKACT_ADD, (const char *)0,HKACT_PROMPT); add_var("random-pool",VT_STR,VF_RW); add_var("auth-key-dir",VT_STR,VF_RW); add_var("host-key-dir",VT_STR,VF_RW); config_def_str("host-key-dir",default_hostkeydir); add_var("sshdir",VT_STR,VF_RW); add_var("key-type",VT_KFN,VF_RW,&var_key_key_type); add_var("key-size",VT_INT,VF_RW); add_var("key-file-public",VT_STR,VF_RW); add_var("key-file-private",VT_STR,VF_RW); add_var("key-values",VT_BOOL,VF_RW); add_var("escape",VT_STR,VF_RW); config_def_str("escape","~"); add_var("passphrase",VT_STR,VF_RW); add_var("comment",VT_STR,VF_RW); add_var("no-interaction",VT_BOOL,VF_RW); add_var("list-long",VT_BOOL,VF_RW); add_var("request-pty",VT_BOOL,VF_RW); add_var("net-keepalive",VT_BOOL,VF_RW); add_var("ssh-keepalive",VT_INT,VF_RW); add_var("fork-pause",VT_INT,VF_RW); add_var("no-private",VT_BOOL,VF_RW|VF_SPEC_SET,&spec_no_private); /* Variables set by moussh but not command-line flags, also settble by config file */ add_var("home",VT_STR,VF_RW); add_var("shell",VT_STR,VF_RW); /* Convenience variables which set multiple other variables */ add_var("lang",VT_STR,VF_W|VF_SPEC_SET,&spec_lang); add_var("key-file",VT_STR,VF_W|VF_SPEC_SET,&spec_key_file); /* Other variables with magic semantics */ add_var("fwd",VT_KFN,VF_RW|VF_SPEC_SET|VF_SPEC_GET,&var_key_fwdwhat,&spec_fwd); add_var("port",VT_INT,VF_RW|VF_SPEC_SET|VF_SPEC_GET,&spec_port); add_var("auth-key-file",VT_STR,VF_RW|VF_SPEC_SET|VF_SPEC_GET,&spec_auth_key_file); add_var("alg-kex",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_kex); add_var("alg-hk",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_hk); add_var("alg-enc-c2s",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_enc_c2s); add_var("alg-enc-s2c",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_enc_s2c); add_var("alg-enc",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_enc); add_var("alg-mac-c2s",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_mac_c2s); add_var("alg-mac-s2c",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_mac_s2c); add_var("alg-mac",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_mac); add_var("alg-comp-c2s",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_comp_c2s); add_var("alg-comp-s2c",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_comp_s2c); add_var("alg-comp",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_comp); add_var("alg-ua",VT_STR,VF_W|VF_SPEC_SET,&spec_alg_ua); add_var("log",VT_STR,VF_W|VF_SPEC_SET,&spec_log); /* when adding more variables here, add them to config.doc too */ setup_hostname(); }