summaryrefslogtreecommitdiff
path: root/usr.bin/cvs/conf.y
diff options
context:
space:
mode:
authorJean-Francois Brousseau <jfb@cvs.openbsd.org>2004-07-25 03:29:37 +0000
committerJean-Francois Brousseau <jfb@cvs.openbsd.org>2004-07-25 03:29:37 +0000
commitbe169be1824b7a9864243a1100db89997b425636 (patch)
tree8f691c8397c668dedba865aa251102a8ac81b502 /usr.bin/cvs/conf.y
parent45ed71a892d6d296211def9e68018a8e3deccc89 (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.y658
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);
+}