Annotation of src/usr.bin/cvs/conf.y, Revision 1.5
1.1 jfb 1: /* $OpenBSD$ */
2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: *
26: */
27: /*
28: * Configuration parser for the CVS daemon
1.2 jfb 29: *
30: * Thanks should go to Henning Brauer for providing insight on some
31: * questions I had regarding my grammar.
1.1 jfb 32: */
1.2 jfb 33:
34: %{
1.1 jfb 35: #include <sys/types.h>
36: #include <sys/queue.h>
37:
38: #include <errno.h>
1.2 jfb 39: #include <ctype.h>
1.1 jfb 40: #include <stdio.h>
41: #include <stdlib.h>
42: #include <stdarg.h>
43: #include <unistd.h>
44: #include <string.h>
45:
46: #include "cvsd.h"
47: #include "cvs.h"
48: #include "log.h"
1.2 jfb 49: #include "file.h"
50:
1.1 jfb 51:
52: #define CVS_ACL_MAXRULES 256
53:
54: #define CVS_ACL_DENY 0
55: #define CVS_ACL_ALLOW 1
56:
57: #define CVS_ACL_LOGOPT 0x01
58: #define CVS_ACL_QUICKOPT 0x02
59:
60:
61: struct conf_macro {
62: char *cm_name;
63: char *cm_val;
64:
65: SIMPLEQ_ENTRY(conf_macro) cm_list;
66: };
67:
68:
69: struct acl_user {
70: uid_t au_uid;
71: SLIST_ENTRY(acl_user) au_list;
72: };
73:
74:
75: struct acl_rule {
76: u_int8_t ar_id;
77: u_int8_t ar_act;
78: u_int8_t ar_opts;
79: u_int8_t ar_op;
80: char *ar_path;
81: char *ar_tag;
82:
83: SLIST_HEAD(, acl_user) ar_users;
84: TAILQ_ENTRY(acl_rule) ar_list;
85: };
86:
87:
88:
89:
90:
91: typedef struct {
92: union {
93: u_int64_t num;
94: char *string;
95: struct acl_rule *rule;
96: struct acl_user *user_list;
97: struct cvsd_addr *addr;
98: } v;
99:
100: int lineno;
101: } YYSTYPE;
102:
103:
104:
105:
106: int lgetc (FILE *);
1.2 jfb 107: int lungetc (int, FILE *);
1.1 jfb 108:
109:
110: int yyerror (const char *, ...);
111: int yylex (void);
112: int yyparse (void);
113: int lookup (const char *);
114: int kw_cmp (const void *, const void *);
115:
116: int cvs_conf_setmacro (char *, char *);
117: const char* cvs_conf_getmacro (const char *);
118:
119: int cvs_acl_addrule (struct acl_rule *);
120: u_int cvs_acl_matchuid (struct acl_rule *, uid_t);
121: u_int cvs_acl_matchtag (const char *, const char *);
122: u_int cvs_acl_matchpath (const char *, const char *);
123:
124:
1.2 jfb 125: /* parse buffer for easier macro expansion */
126: static char *conf_pbuf = NULL;
127: static int conf_pbind = 0;
128:
129:
130:
1.1 jfb 131: static const char *conf_file;
132: static FILE *conf_fin;
133: static int conf_lineno = 1;
134: static int conf_errors = 0;
135:
136: static SIMPLEQ_HEAD(, conf_macro) conf_macros;
137:
138: /* ACL rules */
139: static TAILQ_HEAD(, acl_rule) acl_rules;
140: static u_int acl_nrules = 0;
141: static u_int acl_defact = CVS_ACL_DENY;
142:
143: %}
144:
145: %token LISTEN CVSROOT MINCHILD MAXCHILD REQSOCK
1.3 jfb 146: %token ALLOW DENY LOG QUICK ON TAG FROM USER GROUP
1.1 jfb 147: %token ANY ADD CHECKOUT COMMIT DIFF HISTORY UPDATE
148: %token <v.string> STRING
149: %type <v.num> action number options operation
150: %type <v.rule> aclrule
151: %type <v.addr> address
152: %type <v.userlist>
153: %type <v.string> pathspec tagspec
154: %%
155:
156: conf : /* empty */
157: | conf '\n'
158: | conf cfline '\n'
159: | conf error '\n' { conf_errors++; }
160: ;
161:
162: cfline : macro_assign
163: | directive
164: | aclrule
165: ;
166:
167: macro_assign : STRING '=' STRING
168: {
169: if (cvs_conf_setmacro($1, $3) < 0) {
170: free($1);
171: free($3);
172: YYERROR;
173: }
174: }
175: ;
176:
1.2 jfb 177: directive : LISTEN address
178: {
179: cvsd_set(CVSD_SET_ADDR, $2);
180: free($2);
181: }
1.1 jfb 182: | CVSROOT STRING
183: {
184: cvsd_set(CVSD_SET_ROOT, $2);
185: free($2);
186: }
1.3 jfb 187: | USER STRING
188: {
189: cvsd_set(CVSD_SET_USER, $2);
190: free($2);
191: }
192: | GROUP STRING
193: {
194: cvsd_set(CVSD_SET_GROUP, $2);
195: free($2);
196: }
1.1 jfb 197: | MINCHILD number { cvsd_set(CVSD_SET_CHMIN, $2); }
198: | MAXCHILD number { cvsd_set(CVSD_SET_CHMAX, $2); }
199: | REQSOCK STRING
200: {
201: cvsd_set(CVSD_SET_SOCK, $2);
202: free($2);
203: }
204: ;
205:
206: address : STRING
207: {
208: struct cvsd_addr *adp;
209:
210: adp = (struct cvsd_addr *)malloc(sizeof(*adp));
211: if (adp == NULL) {
212: YYERROR;
213: }
214:
215: $$ = adp;
216: }
217: ;
218:
219: aclrule : action options operation pathspec tagspec userspec
220: {
221: struct acl_rule *arp;
222:
223: arp = (struct acl_rule *)malloc(sizeof(*arp));
224: if (arp == NULL) {
225: free($4);
226: free($5);
227: YYERROR;
228: }
229: arp->ar_act = $1;
230: arp->ar_opts = $2;
231: arp->ar_op = $3;
232: SLIST_INIT(&arp->ar_users);
233: arp->ar_path = $4;
234: arp->ar_tag = $5;
235:
236: $$ = arp;
237: }
238: ;
239:
240: action : ALLOW { $$ = CVS_ACL_ALLOW; }
241: | DENY { $$ = CVS_ACL_DENY; }
242: ;
243:
244: options : /* empty */ { $$ = 0; }
245: | LOG { $$ = CVS_ACL_LOGOPT; }
246: | QUICK { $$ = CVS_ACL_QUICKOPT; }
247: | LOG QUICK { $$ = CVS_ACL_LOGOPT | CVS_ACL_QUICKOPT; }
248: ;
249:
250: operation : ADD { $$ = CVS_OP_ADD; }
251: | ANY { $$ = CVS_OP_ANY; }
252: | COMMIT { $$ = CVS_OP_COMMIT; }
253: | TAG { $$ = CVS_OP_TAG; }
254: ;
255:
256: pathspec : /* empty */ { $$ = NULL; }
257: | ON STRING { $$ = $2; }
258: ;
259:
260: tagspec : /* empty */ { $$ = NULL; }
261: | TAG STRING { $$ = $2; }
262: ;
263:
264: userspec : /* empty */
265: | FROM userlist
266: ;
267:
268: userlist : user
269: | userlist ',' user
270: ;
271:
272: user : STRING
273: {
274: uid_t uid;
275: char *ep;
276: struct passwd *pw;
277: struct acl_user *aup;
278:
279: uid = (uid_t)strtol($1, &ep, 10);
280: if (*ep != '\0')
281: pw = getpwnam($1);
282: else
283: pw = getpwuid(uid);
284: if (pw == NULL) {
285: yyerror("invalid username or ID `%s'", $1);
286: YYERROR;
287: }
288:
289: aup = (struct acl_user *)malloc(sizeof(*aup));
290: if (aup == NULL) {
291: yyerror("failed to allocate ACL user data");
292: YYERROR;
293: }
294: aup->au_uid = pw->pw_uid;
295: }
296: ;
297:
298: number : STRING
299: {
300: char *ep;
301: long res;
302: res = strtol($1, &ep, 0);
303: if ((res == LONG_MIN) || (res == LONG_MAX)) {
304: yyerror("%sflow while converting number `%s'",
305: res == LONG_MIN ? "under" : "over", $1);
306: free($1);
307: YYERROR;
308: }
309: else if (*ep != '\0') {
310: yyerror("invalid number `%s'", $1);
311: free($1);
312: YYERROR;
313: }
314:
315: $$ = (u_int64_t)res;
316: free($1);
317: }
318: ;
319:
320: %%
321:
322:
323: struct conf_kw {
324: char *kw_str;
325: u_int kw_id;
326: };
327:
328:
329:
330: static const struct conf_kw keywords[] = {
331: { "add", ADD },
332: { "allow", ALLOW },
333: { "any", ANY },
334: { "commit", COMMIT },
335: { "cvsroot", CVSROOT },
336: { "deny", DENY },
337: { "from", FROM },
1.3 jfb 338: { "group", GROUP },
1.1 jfb 339: { "listen", LISTEN },
340: { "log", LOG },
341: { "on", ON },
342: { "quick", QUICK },
343: { "reqsock", REQSOCK },
344: { "tag", TAG },
1.3 jfb 345: { "user", USER },
1.1 jfb 346:
347: };
348:
349: int
350: kw_cmp(const void *k, const void *e)
351: {
352: return (strcmp(k, ((const struct conf_kw *)e)->kw_str));
353: }
354:
355:
356: int
357: lookup(const char *tstr)
358: {
359: int type;
360: const struct conf_kw *kwp;
361:
362: kwp = bsearch(tstr, keywords, sizeof(keywords)/sizeof(keywords[0]),
363: sizeof(keywords[0]), kw_cmp);
364: if (kwp != NULL)
365: type = kwp->kw_id;
366: else
367: type = STRING;
368: return (type);
369: }
370:
371:
372:
373: int
374: lgetc(FILE *f)
375: {
376: int c;
377:
1.2 jfb 378: /* check if we've got something in the parse buffer first */
379: if (conf_pbuf != NULL) {
380: c = conf_pbuf[conf_pbind++];
381: if (c != '\0')
382: return (c);
383:
384: free(conf_pbuf);
385: conf_pbuf = NULL;
386: conf_pbind = 0;
387: }
388:
1.1 jfb 389: c = getc(f);
390: if ((c == '\t') || (c == ' ')) {
391: do {
392: c = getc(f);
393: } while ((c == ' ') || (c == '\t'));
1.2 jfb 394: lungetc(c, f);
1.1 jfb 395: c = ' ';
396: }
397: else if (c == '\\')
398: c = getc(f);
399:
400: return (c);
401: }
402:
403:
404: int
1.2 jfb 405: lungetc(int c, FILE *f)
406: {
407: if ((conf_pbuf != NULL) && (conf_pbind > 0)) {
408: conf_pbind--;
409: return (0);
410: }
411:
412: return ungetc(c, f);
413:
414:
415: }
416:
417:
418:
419:
420:
421: int
1.1 jfb 422: yylex(void)
423: {
424: int c;
425: char buf[1024], *bp, *ep;
1.2 jfb 426: const char *mval;
1.1 jfb 427:
1.2 jfb 428: lex_start:
1.1 jfb 429: bp = buf;
430: ep = buf + sizeof(buf) - 1;
431:
432: yylval.lineno = conf_lineno;
433:
434: /* skip whitespace */
435: while ((c = lgetc(conf_fin)) == ' ')
436: ;
437:
438: if (c == '#') {
439: do {
440: c = lgetc(conf_fin);
441: } while ((c != '\n') && (c != EOF));
442: }
1.2 jfb 443:
444: if (c == EOF)
1.1 jfb 445: c = 0;
446: else if (c == '\n')
1.2 jfb 447: yylval.lineno = conf_lineno++;
448: else if (c == '$') {
449: c = lgetc(conf_fin);
1.1 jfb 450: do {
1.2 jfb 451: *bp++ = (char)c;
452: if (bp == ep) {
453: yyerror("macro name too long");
454: return (-1);
455: }
456: c = lgetc(conf_fin);
457: } while (isalnum(c) || c == '_');
458: lungetc(c, conf_fin);
459: *bp = '\0';
460:
461: mval = cvs_conf_getmacro(buf);
462: if (mval == NULL) {
463: yyerror("undefined macro `%s'", buf);
464: return (-1);
465: }
466:
467: conf_pbuf = strdup(mval);
468: conf_pbind = 0;
469: goto lex_start;
470: }
471: else if ((c == '=') || (c == ','))
472: ; /* nothing */
473: else {
474: do {
475: *bp++ = (char)c;
1.1 jfb 476: if (bp == ep) {
477: yyerror("string too long");
478: return (-1);
479: }
480:
481: c = lgetc(conf_fin);
482: } while ((c != EOF) && (c != ' ') && (c != '\n'));
1.2 jfb 483: lungetc(c, conf_fin);
1.1 jfb 484: *bp = '\0';
485: c = lookup(buf);
486: if (c == STRING) {
487: yylval.v.string = strdup(buf);
488: if (yylval.v.string == NULL) {
489: cvs_log(LP_ERRNO,
490: "failed to copy token string");
491: return (-1);
492: }
493: }
494: }
495:
496: return (c);
497: }
498:
499:
500:
501: int
502: yyerror(const char *fmt, ...)
503: {
504: char *nfmt;
505: va_list vap;
506:
507: if (asprintf(&nfmt, "%s:%d: %s", conf_file, yylval.lineno, fmt) == -1) {
508: cvs_log(LP_ERRNO, "failed to allocate message buffer");
509: return (-1);
510: }
1.5 ! pat 511:
! 512: va_start(vap, fmt);
1.1 jfb 513: cvs_vlog(LP_ERR, nfmt, vap);
1.5 ! pat 514: va_end(vap);
1.1 jfb 515:
516: free(nfmt);
517: return (0);
518:
519: }
520:
521:
522: /*
523: * cvs_conf_setmacro()
524: *
525: * Add an entry in the macro list for the macro whose name is <macro> and
526: * whose value is <val>.
527: * Returns 0 on success, or -1 on failure.
528: */
529:
530: int
531: cvs_conf_setmacro(char *macro, char *val)
532: {
533: struct conf_macro *cmp;
534:
535: cmp = (struct conf_macro *)malloc(sizeof(*cmp));
536: if (cmp == NULL) {
537: cvs_log(LP_ERRNO, "failed to allocate macro");
538: return (-1);
539: }
540:
541: /* these strings were already dup'ed by the lexer */
542: cmp->cm_name = macro;
543: cmp->cm_val = val;
544:
545: SIMPLEQ_INSERT_TAIL(&conf_macros, cmp, cm_list);
546:
547: return (0);
548: }
549:
550:
551: /*
552: * cvs_conf_getmacro()
553: *
554: * Get a macro <macro>'s associated value. Returns the value string on
555: * success, or NULL if no such macro exists.
556: */
557:
558: const char*
559: cvs_conf_getmacro(const char *macro)
560: {
561: struct conf_macro *cmp;
562:
563: SIMPLEQ_FOREACH(cmp, &conf_macros, cm_list)
564: if (strcmp(cmp->cm_name, macro) == 0)
565: return (cmp->cm_val);
566:
567: return (NULL);
568: }
569:
570:
571: /*
572: * cvs_conf_read()
573: *
574: * Parse the contents of the configuration file <conf>.
575: */
576:
577: int
578: cvs_conf_read(const char *conf)
579: {
580: struct conf_macro *cmp;
581:
582: SIMPLEQ_INIT(&conf_macros);
583: TAILQ_INIT(&acl_rules);
584: acl_nrules = 0;
585:
1.3 jfb 586: cvs_log(LP_INFO, "using configuration file `%s'", conf);
1.1 jfb 587: conf_file = conf;
588: conf_fin = fopen(conf, "r");
589: if (conf_fin == NULL) {
590: cvs_log(LP_ERRNO, "failed to open configuration `%s'", conf);
591: return (-1);
592: }
593:
594: if (yyparse() != 0)
595: conf_lineno = -1;
596:
597: (void)fclose(conf_fin);
598:
599: /* we can get rid of macros now */
600: while ((cmp = SIMPLEQ_FIRST(&conf_macros)) != NULL) {
601: SIMPLEQ_REMOVE_HEAD(&conf_macros, cm_list);
602: free(cmp->cm_name);
603: free(cmp->cm_val);
604: free(cmp);
605: }
606:
607: cvs_log(LP_INFO, "config %s parsed successfully", conf);
608:
609: return (conf_lineno);
610: }
611:
612:
613: /*
614: * cvs_acl_addrule()
615: *
616: * Add a rule to the currently loaded ACL rules.
617: */
618:
619: int
620: cvs_acl_addrule(struct acl_rule *rule)
621: {
622: if (acl_nrules == CVS_ACL_MAXRULES) {
623: cvs_log(LP_ERR, "failed to add ACL rule: Ruleset full");
624: return (-1);
625: }
626:
627: TAILQ_INSERT_TAIL(&acl_rules, rule, ar_list);
628: return (0);
629: }
630:
631:
632: /*
633: * cvs_acl_eval()
634: *
635: * Evaluate a thingamajimmie against the currently loaded ACL ruleset.
636: * Returns CVS_ACL_ALLOW if the operation is permitted, CVS_ACL_DENY otherwise.
637: */
638:
639: u_int
640: cvs_acl_eval(struct cvs_op *op)
641: {
642: u_int res;
1.4 jfb 643: char fpath[MAXPATHLEN];
1.2 jfb 644: CVSFILE *cf;
645: struct acl_rule *rule;
1.1 jfb 646:
647: /* deny by default */
648: res = acl_defact;
649:
1.2 jfb 650: TAILQ_FOREACH(rule, &acl_rules, ar_list) {
651: if (((op->co_op != CVS_OP_ANY) && (op->co_op != rule->ar_op)) ||
652: !cvs_acl_matchuid(rule, op->co_uid) ||
653: !cvs_acl_matchtag(op->co_tag, rule->ar_tag))
1.1 jfb 654: continue;
655:
1.2 jfb 656: /* see if one of the files has a matching path */
1.4 jfb 657: TAILQ_FOREACH(cf, &(op->co_files), cf_list) {
658: /* XXX borked */
659: if (!cvs_acl_matchpath(fpath, rule->ar_path))
1.2 jfb 660: continue;
1.4 jfb 661: }
1.2 jfb 662:
663: res = rule->ar_act;
664:
665: if (rule->ar_opts & CVS_ACL_LOGOPT)
666: cvs_log(LP_WARN, "act=%u, tag=%s, uid=%u",
667: op->co_op, op->co_tag, op->co_uid);
668: if (rule->ar_opts & CVS_ACL_QUICKOPT)
1.1 jfb 669: break;
670: }
671:
672: return (res);
673: }
674:
675:
676: /*
677: * cvs_acl_matchuid()
678: *
679: * Check if an ACL rule has a UID matching <uid>. If no user is specified
680: * for a given rule, any UID will match.
681: * Returns 1 if this is the case, 0 otherwise.
682: */
683:
684: u_int
685: cvs_acl_matchuid(struct acl_rule *rule, uid_t uid)
686: {
687: struct acl_user *aup;
688:
689: if (SLIST_EMPTY(&(rule->ar_users)))
690: return (1);
691:
692: SLIST_FOREACH(aup, &(rule->ar_users), au_list)
693: if (aup->au_uid == uid)
694: return (1);
695: return (0);
696: }
697:
698:
699: /*
700: * cvs_acl_matchtag()
701: *
702: * Returns 1 if this is the case, 0 otherwise.
703: */
704:
705: u_int
706: cvs_acl_matchtag(const char *tag1, const char *tag2)
707: {
708: if ((tag1 == NULL) && (tag2 == NULL)) /* HEAD */
709: return (1);
710:
711: if ((tag1 != NULL) && (tag2 != NULL) &&
712: (strcmp(tag1, tag2) == 0))
713: return (1);
714:
715: return (0);
716: }
717:
718:
719: /*
720: * cvs_acl_matchpath()
721: *
722: * Check if the path <op_path> is a subpath of <acl_path>.
723: * Returns 1 if this is the case, 0 otherwise.
724: */
725:
726: u_int
727: cvs_acl_matchpath(const char *op_path, const char *acl_path)
728: {
729: size_t len;
730: char rop_path[MAXPATHLEN];
731:
732: /* if the ACL path is NULL, apply on all paths */
733: if (acl_path == NULL)
734: return (1);
735:
736: if (realpath(op_path, rop_path) == NULL) {
737: cvs_log(LP_ERRNO, "failed to convert `%s' to a real path",
738: op_path);
739: return (0);
740: }
741:
742: len = strlen(rop_path);
743:
744: if (strncmp(rop_path, acl_path, len) == 0)
745: return (1);
746:
747: return (0);
748: }