File: [local] / src / usr.bin / cvs / Attic / conf.y (download)
Revision 1.1, Sun Jul 25 03:29:34 2004 UTC (19 years, 10 months ago) by jfb
Branch: MAIN
* 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
|
/* $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);
}