diff options
author | Jean-Francois Brousseau <jfb@cvs.openbsd.org> | 2004-07-25 03:29:37 +0000 |
---|---|---|
committer | Jean-Francois Brousseau <jfb@cvs.openbsd.org> | 2004-07-25 03:29:37 +0000 |
commit | be169be1824b7a9864243a1100db89997b425636 (patch) | |
tree | 8f691c8397c668dedba865aa251102a8ac81b502 /usr.bin/cvs/conf.y | |
parent | 45ed71a892d6d296211def9e68018a8e3deccc89 (diff) |
* rework on the child API, still needs more functionality
* move the ACL parsing code into the more general conf.y, which handles
parsing of the whole configuration file
Diffstat (limited to 'usr.bin/cvs/conf.y')
-rw-r--r-- | usr.bin/cvs/conf.y | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/usr.bin/cvs/conf.y b/usr.bin/cvs/conf.y new file mode 100644 index 00000000000..9c932d912e0 --- /dev/null +++ b/usr.bin/cvs/conf.y @@ -0,0 +1,658 @@ +/* $OpenBSD: conf.y,v 1.1 2004/07/25 03:29:34 jfb Exp $ */ +/* + * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +%{ +/* + * Configuration parser for the CVS daemon + */ +#include <sys/types.h> +#include <sys/queue.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> + +#include "cvsd.h" +#include "cvs.h" +#include "log.h" +#include "event.h" + +#define CVS_ACL_MAXRULES 256 + +#define CVS_ACL_DENY 0 +#define CVS_ACL_ALLOW 1 + +#define CVS_ACL_LOGOPT 0x01 +#define CVS_ACL_QUICKOPT 0x02 + + +struct conf_macro { + char *cm_name; + char *cm_val; + + SIMPLEQ_ENTRY(conf_macro) cm_list; +}; + + +struct acl_user { + uid_t au_uid; + SLIST_ENTRY(acl_user) au_list; +}; + + +struct acl_rule { + u_int8_t ar_id; + u_int8_t ar_act; + u_int8_t ar_opts; + u_int8_t ar_op; + char *ar_path; + char *ar_tag; + + SLIST_HEAD(, acl_user) ar_users; + TAILQ_ENTRY(acl_rule) ar_list; +}; + + + + + +typedef struct { + union { + u_int64_t num; + char *string; + struct acl_rule *rule; + struct acl_user *user_list; + struct cvsd_addr *addr; + } v; + + int lineno; +} YYSTYPE; + + + + +int lgetc (FILE *); + + +int yyerror (const char *, ...); +int yylex (void); +int yyparse (void); +int lookup (const char *); +int kw_cmp (const void *, const void *); + +int cvs_conf_setmacro (char *, char *); +const char* cvs_conf_getmacro (const char *); + +int cvs_acl_addrule (struct acl_rule *); +u_int cvs_acl_matchuid (struct acl_rule *, uid_t); +u_int cvs_acl_matchtag (const char *, const char *); +u_int cvs_acl_matchpath (const char *, const char *); + + +static const char *conf_file; +static FILE *conf_fin; +static int conf_lineno = 1; +static int conf_errors = 0; + +static SIMPLEQ_HEAD(, conf_macro) conf_macros; + +/* ACL rules */ +static TAILQ_HEAD(, acl_rule) acl_rules; +static u_int acl_nrules = 0; +static u_int acl_defact = CVS_ACL_DENY; + +%} + +%token LISTEN CVSROOT MINCHILD MAXCHILD REQSOCK +%token ALLOW DENY LOG QUICK ON TAG FROM +%token ANY ADD CHECKOUT COMMIT DIFF HISTORY UPDATE +%token <v.string> STRING +%type <v.num> action number options operation +%type <v.rule> aclrule +%type <v.addr> address +%type <v.userlist> +%type <v.string> pathspec tagspec +%% + +conf : /* empty */ + | conf '\n' + | conf cfline '\n' + | conf error '\n' { conf_errors++; } + ; + +cfline : macro_assign + | directive + | aclrule + ; + +macro_assign : STRING '=' STRING + { + if (cvs_conf_setmacro($1, $3) < 0) { + free($1); + free($3); + YYERROR; + } + } + ; + +directive : LISTEN address { cvsd_set(CVSD_SET_ADDR, $2); } + | CVSROOT STRING + { + cvsd_set(CVSD_SET_ROOT, $2); + free($2); + } + | MINCHILD number { cvsd_set(CVSD_SET_CHMIN, $2); } + | MAXCHILD number { cvsd_set(CVSD_SET_CHMAX, $2); } + | REQSOCK STRING + { + cvsd_set(CVSD_SET_SOCK, $2); + free($2); + } + ; + +address : STRING + { + struct cvsd_addr *adp; + + adp = (struct cvsd_addr *)malloc(sizeof(*adp)); + if (adp == NULL) { + YYERROR; + } + + $$ = adp; + } + ; + +aclrule : action options operation pathspec tagspec userspec + { + struct acl_rule *arp; + + arp = (struct acl_rule *)malloc(sizeof(*arp)); + if (arp == NULL) { + free($4); + free($5); + YYERROR; + } + arp->ar_act = $1; + arp->ar_opts = $2; + arp->ar_op = $3; + SLIST_INIT(&arp->ar_users); + arp->ar_path = $4; + arp->ar_tag = $5; + + $$ = arp; + } + ; + +action : ALLOW { $$ = CVS_ACL_ALLOW; } + | DENY { $$ = CVS_ACL_DENY; } + ; + +options : /* empty */ { $$ = 0; } + | LOG { $$ = CVS_ACL_LOGOPT; } + | QUICK { $$ = CVS_ACL_QUICKOPT; } + | LOG QUICK { $$ = CVS_ACL_LOGOPT | CVS_ACL_QUICKOPT; } + ; + +operation : ADD { $$ = CVS_OP_ADD; } + | ANY { $$ = CVS_OP_ANY; } + | COMMIT { $$ = CVS_OP_COMMIT; } + | TAG { $$ = CVS_OP_TAG; } + ; + +pathspec : /* empty */ { $$ = NULL; } + | ON STRING { $$ = $2; } + ; + +tagspec : /* empty */ { $$ = NULL; } + | TAG STRING { $$ = $2; } + ; + +userspec : /* empty */ + | FROM userlist + ; + +userlist : user + | userlist ',' user + ; + +user : STRING + { + uid_t uid; + char *ep; + struct passwd *pw; + struct acl_user *aup; + + uid = (uid_t)strtol($1, &ep, 10); + if (*ep != '\0') + pw = getpwnam($1); + else + pw = getpwuid(uid); + if (pw == NULL) { + yyerror("invalid username or ID `%s'", $1); + YYERROR; + } + + aup = (struct acl_user *)malloc(sizeof(*aup)); + if (aup == NULL) { + yyerror("failed to allocate ACL user data"); + YYERROR; + } + aup->au_uid = pw->pw_uid; + } + ; + +number : STRING + { + char *ep; + long res; + res = strtol($1, &ep, 0); + if ((res == LONG_MIN) || (res == LONG_MAX)) { + yyerror("%sflow while converting number `%s'", + res == LONG_MIN ? "under" : "over", $1); + free($1); + YYERROR; + } + else if (*ep != '\0') { + yyerror("invalid number `%s'", $1); + free($1); + YYERROR; + } + + $$ = (u_int64_t)res; + free($1); + } + ; + +%% + + +struct conf_kw { + char *kw_str; + u_int kw_id; +}; + + + +static const struct conf_kw keywords[] = { + { "add", ADD }, + { "allow", ALLOW }, + { "any", ANY }, + { "commit", COMMIT }, + { "cvsroot", CVSROOT }, + { "deny", DENY }, + { "from", FROM }, + { "listen", LISTEN }, + { "log", LOG }, + { "on", ON }, + { "quick", QUICK }, + { "reqsock", REQSOCK }, + { "tag", TAG }, + +}; + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct conf_kw *)e)->kw_str)); +} + + +int +lookup(const char *tstr) +{ + int type; + const struct conf_kw *kwp; + + kwp = bsearch(tstr, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + if (kwp != NULL) + type = kwp->kw_id; + else + type = STRING; + return (type); +} + + + +int +lgetc(FILE *f) +{ + int c; + + c = getc(f); + if ((c == '\t') || (c == ' ')) { + do { + c = getc(f); + } while ((c == ' ') || (c == '\t')); + ungetc(c, f); + c = ' '; + } + else if (c == '\\') + c = getc(f); + + return (c); +} + + +int +yylex(void) +{ + int c; + char buf[1024], *bp, *ep; + + bp = buf; + ep = buf + sizeof(buf) - 1; + + yylval.lineno = conf_lineno; + + /* skip whitespace */ + while ((c = lgetc(conf_fin)) == ' ') + ; + + if (c == '#') { + do { + c = lgetc(conf_fin); + } while ((c != '\n') && (c != EOF)); + } + else if (c == EOF) + c = 0; + else if (c == '\n') + conf_lineno++; + else if (c != ',') { + do { + *bp++ = c; + if (bp == ep) { + yyerror("string too long"); + return (-1); + } + + c = lgetc(conf_fin); + if (c == EOF) + break; + } while ((c != EOF) && (c != ' ') && (c != '\n')); + ungetc(c, conf_fin); + *bp = '\0'; + c = lookup(buf); + if (c == STRING) { + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) { + cvs_log(LP_ERRNO, + "failed to copy token string"); + return (-1); + } + } + } + + return (c); +} + + + +int +yyerror(const char *fmt, ...) +{ + char *nfmt; + va_list vap; + + va_start(vap, fmt); + + if (asprintf(&nfmt, "%s:%d: %s", conf_file, yylval.lineno, fmt) == -1) { + cvs_log(LP_ERRNO, "failed to allocate message buffer"); + return (-1); + } + cvs_vlog(LP_ERR, nfmt, vap); + + free(nfmt); + va_end(vap); + return (0); + +} + + +/* + * cvs_conf_setmacro() + * + * Add an entry in the macro list for the macro whose name is <macro> and + * whose value is <val>. + * Returns 0 on success, or -1 on failure. + */ + +int +cvs_conf_setmacro(char *macro, char *val) +{ + struct conf_macro *cmp; + + cmp = (struct conf_macro *)malloc(sizeof(*cmp)); + if (cmp == NULL) { + cvs_log(LP_ERRNO, "failed to allocate macro"); + return (-1); + } + + /* these strings were already dup'ed by the lexer */ + printf("macro(%s) = `%s'\n", macro, val); + cmp->cm_name = macro; + cmp->cm_val = val; + + SIMPLEQ_INSERT_TAIL(&conf_macros, cmp, cm_list); + + return (0); +} + + +/* + * cvs_conf_getmacro() + * + * Get a macro <macro>'s associated value. Returns the value string on + * success, or NULL if no such macro exists. + */ + +const char* +cvs_conf_getmacro(const char *macro) +{ + struct conf_macro *cmp; + + SIMPLEQ_FOREACH(cmp, &conf_macros, cm_list) + if (strcmp(cmp->cm_name, macro) == 0) + return (cmp->cm_val); + + return (NULL); +} + + +/* + * cvs_conf_read() + * + * Parse the contents of the configuration file <conf>. + */ + +int +cvs_conf_read(const char *conf) +{ + struct conf_macro *cmp; + + SIMPLEQ_INIT(&conf_macros); + TAILQ_INIT(&acl_rules); + acl_nrules = 0; + + conf_file = conf; + conf_fin = fopen(conf, "r"); + if (conf_fin == NULL) { + cvs_log(LP_ERRNO, "failed to open configuration `%s'", conf); + return (-1); + } + + if (yyparse() != 0) + conf_lineno = -1; + + (void)fclose(conf_fin); + + /* we can get rid of macros now */ + while ((cmp = SIMPLEQ_FIRST(&conf_macros)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&conf_macros, cm_list); + free(cmp->cm_name); + free(cmp->cm_val); + free(cmp); + } + + cvs_log(LP_INFO, "config %s parsed successfully", conf); + + return (conf_lineno); +} + + +/* + * cvs_acl_addrule() + * + * Add a rule to the currently loaded ACL rules. + */ + +int +cvs_acl_addrule(struct acl_rule *rule) +{ + if (acl_nrules == CVS_ACL_MAXRULES) { + cvs_log(LP_ERR, "failed to add ACL rule: Ruleset full"); + return (-1); + } + + TAILQ_INSERT_TAIL(&acl_rules, rule, ar_list); + return (0); +} + + +/* + * cvs_acl_eval() + * + * Evaluate a thingamajimmie against the currently loaded ACL ruleset. + * Returns CVS_ACL_ALLOW if the operation is permitted, CVS_ACL_DENY otherwise. + */ + +u_int +cvs_acl_eval(struct cvs_op *op) +{ + u_int res; + struct acl_rule *arp; + + /* deny by default */ + res = acl_defact; + + TAILQ_FOREACH(arp, &acl_rules, ar_list) { + if (((op->co_op != CVS_OP_ANY) && (op->co_op != arp->ar_op)) || + !cvs_acl_matchuid(arp, op->co_uid) || + !cvs_acl_matchtag(op->co_tag, arp->ar_tag) || + !cvs_acl_matchpath(op->co_path, arp->ar_path)) + continue; + + res = arp->ar_act; + + if (arp->ar_opts & CVS_ACL_LOGOPT) + cvs_log(LP_WARN, "act=%u, path=%s, tag=%s, uid=%u", + op->co_op, op->co_path, op->co_tag, op->co_uid); + if (arp->ar_opts & CVS_ACL_QUICKOPT) + break; + } + + return (res); +} + + +/* + * cvs_acl_matchuid() + * + * Check if an ACL rule has a UID matching <uid>. If no user is specified + * for a given rule, any UID will match. + * Returns 1 if this is the case, 0 otherwise. + */ + +u_int +cvs_acl_matchuid(struct acl_rule *rule, uid_t uid) +{ + struct acl_user *aup; + + if (SLIST_EMPTY(&(rule->ar_users))) + return (1); + + SLIST_FOREACH(aup, &(rule->ar_users), au_list) + if (aup->au_uid == uid) + return (1); + return (0); +} + + +/* + * cvs_acl_matchtag() + * + * Returns 1 if this is the case, 0 otherwise. + */ + +u_int +cvs_acl_matchtag(const char *tag1, const char *tag2) +{ + if ((tag1 == NULL) && (tag2 == NULL)) /* HEAD */ + return (1); + + if ((tag1 != NULL) && (tag2 != NULL) && + (strcmp(tag1, tag2) == 0)) + return (1); + + return (0); +} + + +/* + * cvs_acl_matchpath() + * + * Check if the path <op_path> is a subpath of <acl_path>. + * Returns 1 if this is the case, 0 otherwise. + */ + +u_int +cvs_acl_matchpath(const char *op_path, const char *acl_path) +{ + size_t len; + char rop_path[MAXPATHLEN]; + + /* if the ACL path is NULL, apply on all paths */ + if (acl_path == NULL) + return (1); + + if (realpath(op_path, rop_path) == NULL) { + cvs_log(LP_ERRNO, "failed to convert `%s' to a real path", + op_path); + return (0); + } + + printf("comparing `%s' to `%s'\n", rop_path, acl_path); + len = strlen(rop_path); + + if (strncmp(rop_path, acl_path, len) == 0) + return (1); + + return (0); +} |