[BACK]Return to aclparse.y CVS log [TXT][DIR] Up to [local] / src / usr.bin / cvs

File: [local] / src / usr.bin / cvs / Attic / aclparse.y (download)

Revision 1.1, Tue Jul 13 22:02:40 2004 UTC (19 years, 11 months ago) by jfb
Branch: MAIN

Initial revision

/*	$OpenBSD: aclparse.y,v 1.1 2004/07/13 22:02:40 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. 
 *
 */
%{
#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_DENY         0
#define CVS_ACL_ALLOW        1

#define CVS_ACL_LOGOPT    0x01
#define CVS_ACL_QUICKOPT  0x02


struct acl_user {
	uid_t  au_uid;
	SLIST_ENTRY(acl_user) au_list;
};


struct acl_rule {
	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;
};




typedef struct {
	union {
		u_int32_t         num;
		char             *string;
		struct acl_user  *user_list;
	} v;

	int lineno;
} YYSTYPE;




int    lgetc    (FILE *);


int    yyerror  (const char *, ...);
int    yylex    (void);
int    lookup   (const char *);
int    kw_cmp   (const void *, const void *);

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 char            *acl_file;
static FILE            *acl_fin;
static int              acl_lineno = 1;
static int              acl_errors = 0;
static struct acl_rule *acl_rules;
static u_int            acl_nrules = 0;
static u_int            acl_defact = CVS_ACL_DENY;


static u_int            acl_opts = 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 i, res;
	struct acl_rule *arp;

	/* deny by default */
	res = acl_defact;

	for (i = 0; i < acl_nrules; i++) {
		arp = &(acl_rules[i]);

		if ((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);
}
%}

%token	ALLOW DENY LOG QUICK ON TAG FROM
%token  ADD CHECKOUT COMMIT DIFF HISTORY UPDATE
%token  <v.string>    STRING
%type   <v.num>       action options operation
%type   <v.userlist>
%type   <v.string>    pathspec tagspec
%%

ruleset		: /* empty */
		| ruleset '\n'
		| ruleset rule '\n'
		| ruleset error '\n'	{ acl_errors++; }
		;

rule		: action options operation pathspec tagspec userspec
		{
			void *tmp;
			struct acl_rule *arp;

			tmp = realloc(acl_rules,
			    (acl_nrules + 1) * sizeof(struct acl_rule));
			if (tmp == NULL) {
				cvs_log(LP_ERRNO, "failed to grow ACL ruleset");
				free($4);
				free($5);
				YYERROR;
			}
			acl_rules = (struct acl_rule *)tmp;
			arp = &(acl_rules[acl_nrules++]);

			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;

		}
		;

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; }
		| 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;
		}
		;
%%


struct acl_kw {
	char  *kw_str;
	u_int  kw_id;
};



static const struct acl_kw keywords[] = {
	{ "add",     ADD    },
	{ "allow",   ALLOW  },
	{ "commit",  COMMIT },
	{ "deny",    DENY   },
	{ "from",    FROM   },
	{ "log",     LOG    },
	{ "on",      ON     },
	{ "quick",   QUICK  },
	{ "tag",     TAG    },
};

int
kw_cmp(const void *k, const void *e)
{
	return (strcmp(k, ((const struct acl_kw *)e)->kw_str));
}


int
lookup(const char *tstr)
{
	int type;
	const struct acl_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 tok, c;
	char buf[1024], *bp, *ep;

	bp = buf;
	ep = buf + sizeof(buf) - 1;

	yylval.lineno = acl_lineno;

	/* skip whitespace */
	while ((c = lgetc(acl_fin)) == ' ')
		;

	if (c == '#') {
		do {
			c = lgetc(acl_fin);
		} while ((c != '\n') && (c != EOF));
	}
	else if (c == EOF)
		c = 0;
	else if (c == '\n')
		acl_lineno++;
	else if (c != ',') {
		do {
			*bp++ = c;
			if (bp == ep) {
				yyerror("string too long");
				return (-1);
			}

			c = lgetc(acl_fin);
			if (c == EOF)
				break;
		} while ((c != EOF) && (c != ' ') && (c != '\n'));
		ungetc(c, acl_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", acl_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_acl_parse()
 *
 * Parse the contents of the ACL file <file>.
 */

int
cvs_acl_parse(const char *file)
{
	acl_file = strdup(file);
	if (acl_file == NULL) {
		cvs_log(LP_ERRNO, "failed to copy ACL file path");
		return (-1);
	}

	acl_fin = fopen(file, "r");
	if (acl_fin == NULL) {
		cvs_log(LP_ERRNO, "failed to open ACL file `%s'", file);
		return (-1);
	}

	if (yyparse() != 0)
		acl_lineno = -1;

	(void)fclose(acl_fin);

	return (acl_lineno);
}