1 /*
2 * Host-Based-Access-control file support.
3 *
4 * Copyright (c) 2015 Marko Kreen
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include "bouncer.h"
20
21 #include <usual/cxextra.h>
22 #include <usual/cbtree.h>
23 #include <usual/fileutil.h>
24
25 enum RuleType {
26 RULE_LOCAL,
27 RULE_HOST,
28 RULE_HOSTSSL,
29 RULE_HOSTNOSSL,
30 };
31
32 #define NAME_ALL 1
33 #define NAME_SAMEUSER 2
34
35 struct NameSlot {
36 size_t strlen;
37 char str[];
38 };
39
40 struct HBAName {
41 unsigned int flags;
42 struct StrSet *name_set;
43 };
44
45 struct HBARule {
46 struct List node;
47 enum RuleType rule_type;
48 int rule_method;
49 int rule_af;
50 uint8_t rule_addr[16];
51 uint8_t rule_mask[16];
52 struct HBAName db_name;
53 struct HBAName user_name;
54 };
55
56 struct HBA {
57 struct List rules;
58 };
59
60 /*
61 * StrSet
62 */
63
64 struct StrSetNode {
65 unsigned int s_len;
66 char s_val[FLEX_ARRAY];
67 };
68
69 struct StrSet {
70 CxMem *pool;
71 unsigned count;
72 unsigned alloc;
73 struct StrSetNode **nodes;
74 struct CBTree *cbtree;
75 };
76
77 struct StrSet *strset_new(CxMem *cx);
78 void strset_free(struct StrSet *set);
79 bool strset_add(struct StrSet *set, const char *str, unsigned int len);
80 bool strset_contains(struct StrSet *set, const char *str, unsigned int len);
81
strset_new(CxMem * cx)82 struct StrSet *strset_new(CxMem *cx)
83 {
84 struct StrSet *set;
85 CxMem *pool;
86
87 pool = cx_new_pool(cx, 1024, 0);
88 if (!pool)
89 return NULL;
90 set = cx_alloc(pool, sizeof *set);
91 if (!set)
92 return NULL;
93 set->pool = pool;
94 set->cbtree = NULL;
95 set->count = 0;
96 set->alloc = 10;
97 set->nodes = cx_alloc0(pool, set->alloc * sizeof(struct StrSet *));
98 if (!set->nodes) {
99 cx_destroy(pool);
100 return NULL;
101 }
102 return set;
103 }
104
strset_node_key(void * ctx,void * obj,const void ** ptr_p)105 static size_t strset_node_key(void *ctx, void *obj, const void **ptr_p)
106 {
107 struct StrSetNode *node = obj;
108 *ptr_p = node->s_val;
109 return node->s_len;
110 }
111
strset_add(struct StrSet * set,const char * str,unsigned int len)112 bool strset_add(struct StrSet *set, const char *str, unsigned int len)
113 {
114 struct StrSetNode *node;
115 unsigned int i;
116 bool ok;
117
118 if (strset_contains(set, str, len))
119 return true;
120
121 node = cx_alloc(set->pool, offsetof(struct StrSetNode, s_val) + len + 1);
122 if (!node)
123 return false;
124 node->s_len = len;
125 memcpy(node->s_val, str, len);
126 node->s_val[len] = 0;
127
128 if (set->count < set->alloc) {
129 set->nodes[set->count++] = node;
130 return true;
131 }
132
133 if (!set->cbtree) {
134 set->cbtree = cbtree_create(strset_node_key, NULL, set, set->pool);
135 if (!set->cbtree)
136 return false;
137 for (i = 0; i < set->count; i++) {
138 ok = cbtree_insert(set->cbtree, set->nodes[i]);
139 if (!ok)
140 return false;
141 }
142 }
143 ok = cbtree_insert(set->cbtree, node);
144 if (!ok)
145 return false;
146 set->count++;
147 return true;
148 }
149
strset_contains(struct StrSet * set,const char * str,unsigned int len)150 bool strset_contains(struct StrSet *set, const char *str, unsigned int len)
151 {
152 unsigned int i;
153 struct StrSetNode *node;
154 if (set->cbtree)
155 return cbtree_lookup(set->cbtree, str, len) != NULL;
156 for (i = 0; i < set->count; i++) {
157 node = set->nodes[i];
158 if (node->s_len != len)
159 continue;
160 if (memcmp(node->s_val, str, len) == 0)
161 return true;
162 }
163 return false;
164 }
165
strset_free(struct StrSet * set)166 void strset_free(struct StrSet *set)
167 {
168 if (set)
169 cx_destroy(set->pool);
170 }
171
172 /*
173 * Parse HBA tokens.
174 */
175
176 enum TokType {
177 TOK_STRING,
178 TOK_IDENT,
179 TOK_COMMA,
180 TOK_FAIL,
181 TOK_EOL
182 };
183
184 struct TokParser {
185 const char *pos;
186 enum TokType cur_tok;
187 char *cur_tok_str;
188
189 char *buf;
190 size_t buflen;
191 };
192
tok_buf_check(struct TokParser * p,size_t len)193 static bool tok_buf_check(struct TokParser *p, size_t len)
194 {
195 size_t tmplen;
196 char *tmp;
197 if (p->buflen >= len)
198 return true;
199 tmplen = len*2;
200 tmp = realloc(p->buf, tmplen);
201 if (!tmp)
202 return false;
203 p->buf = tmp;
204 p->buflen = tmplen;
205 return true;
206 }
207
next_token(struct TokParser * p)208 static enum TokType next_token(struct TokParser *p)
209 {
210 const char *s, *s2;
211 char *dst;
212 if (p->cur_tok == TOK_EOL)
213 return TOK_EOL;
214 p->cur_tok_str = NULL;
215 p->cur_tok = TOK_FAIL;
216
217 while (p->pos[0] && isspace((unsigned char)p->pos[0]))
218 p->pos++;
219
220 if (p->pos[0] == '#' || p->pos[0] == '\0') {
221 p->cur_tok = TOK_EOL;
222 p->pos = NULL;
223 } else if (p->pos[0] == ',') {
224 p->cur_tok = TOK_COMMA;
225 p->pos++;
226 } else if (p->pos[0] == '"') {
227 for (s = p->pos+1; s[0]; s++) {
228 if (s[0] == '"') {
229 if (s[1] == '"')
230 s++;
231 else
232 break;
233 }
234 }
235 if (s[0] != '"' || !tok_buf_check(p, s - p->pos))
236 return TOK_FAIL;
237 dst = p->buf;
238 for (s2 = p->pos+1; s2 < s; s2++) {
239 *dst++ = *s2;
240 if (*s2 == '"') s2++;
241 }
242 *dst = 0;
243 p->pos = s + 1;
244 p->cur_tok = TOK_STRING;
245 p->cur_tok_str = p->buf;
246 } else {
247 for (s = p->pos + 1; *s; s++) {
248 if (*s == ',' || *s == '#' || *s == '"')
249 break;
250 if (isspace((unsigned char)*s))
251 break;
252 }
253 if (!tok_buf_check(p, s - p->pos + 1))
254 return TOK_FAIL;
255 memcpy(p->buf, p->pos, s - p->pos);
256 p->buf[s - p->pos] = 0;
257 p->pos = s;
258 p->cur_tok = TOK_IDENT;
259 p->cur_tok_str = p->buf;
260
261 }
262 return p->cur_tok;
263 }
264
eat(struct TokParser * p,enum TokType ttype)265 static bool eat(struct TokParser *p, enum TokType ttype)
266 {
267 if (p->cur_tok == ttype) {
268 next_token(p);
269 return true;
270 }
271 return false;
272 }
273
eat_kw(struct TokParser * p,const char * kw)274 static bool eat_kw(struct TokParser *p, const char *kw)
275 {
276 if (p->cur_tok == TOK_IDENT && strcmp(kw, p->cur_tok_str) == 0) {
277 next_token(p);
278 return true;
279 }
280 return false;
281 }
282
expect(struct TokParser * tp,enum TokType ttype,const char ** str_p)283 static bool expect(struct TokParser *tp, enum TokType ttype, const char **str_p)
284 {
285 if (tp->cur_tok == ttype) {
286 *str_p = tp->buf;
287 return true;
288 }
289 return false;
290 }
291
path_join(const char * p1,const char * p2)292 static char *path_join(const char *p1, const char *p2)
293 {
294 size_t len1, len2;
295 char *res = NULL, *pos;
296
297 if (p2[0] == '/' || p1[0] == 0 || !memcmp(p1, ".", 2))
298 return strdup(p2);
299 len1 = strlen(p1);
300 len2 = strlen(p2);
301 res = malloc(len1 + len2 + 2 + 1);
302 if (res) {
303 memcpy(res, p1, len1);
304 pos = res + len1;
305 if (pos[-1] != '/')
306 *pos++ = '/';
307 memcpy(pos, p2, len2 + 1);
308 }
309 return res;
310 }
311
path_join_dirname(const char * parent,const char * fn)312 static char *path_join_dirname(const char *parent, const char *fn)
313 {
314 char *tmp, *res;
315 const char *basedir;
316 if (fn[0] == '/')
317 return strdup(fn);
318 tmp = strdup(parent);
319 if (!tmp)
320 return NULL;
321 basedir = dirname(tmp);
322 res = path_join(basedir, fn);
323 free(tmp);
324 return res;
325 }
326
init_parser(struct TokParser * p)327 static void init_parser(struct TokParser *p)
328 {
329 memset(p, 0, sizeof(*p));
330 }
331
parse_from_string(struct TokParser * p,const char * str)332 static void parse_from_string(struct TokParser *p, const char *str)
333 {
334 p->pos = str;
335 p->cur_tok = TOK_COMMA;
336 p->cur_tok_str = NULL;
337 next_token(p);
338 }
339
free_parser(struct TokParser * p)340 static void free_parser(struct TokParser *p)
341 {
342 free(p->buf);
343 p->buf = NULL;
344 }
345
346 static bool parse_names(struct HBAName *hname, struct TokParser *p, bool is_db, const char *parent_filename);
347
parse_namefile(struct HBAName * hname,const char * fn,bool is_db)348 static bool parse_namefile(struct HBAName *hname, const char *fn, bool is_db)
349 {
350 FILE *f;
351 ssize_t len;
352 char *ln = NULL;
353 size_t buflen = 0;
354 int linenr;
355 bool ok = false;
356 struct TokParser tp;
357
358 init_parser(&tp);
359
360 f = fopen(fn, "r");
361 if (!f) {
362 return false;
363 }
364 for (linenr = 1; ; linenr++) {
365 len = getline(&ln, &buflen, f);
366 if (len < 0) {
367 ok = true;
368 break;
369 }
370 parse_from_string(&tp, ln);
371 if (!parse_names(hname, &tp, is_db, fn))
372 break;
373 }
374 free_parser(&tp);
375 free(ln);
376 fclose(f);
377 return ok;
378 }
379
parse_names(struct HBAName * hname,struct TokParser * tp,bool is_db,const char * parent_filename)380 static bool parse_names(struct HBAName *hname, struct TokParser *tp, bool is_db, const char *parent_filename)
381 {
382 const char *tok;
383 while (1) {
384 if (eat_kw(tp, "all")) {
385 hname->flags |= NAME_ALL;
386 goto eat_comma;
387 }
388 if (is_db) {
389 if (eat_kw(tp, "sameuser")) {
390 hname->flags |= NAME_SAMEUSER;
391 goto eat_comma;
392 }
393 if (eat_kw(tp, "samerole")) {
394 log_warning("samerole is not supported");
395 return false;
396 }
397 if (eat_kw(tp, "samegroup")) {
398 log_warning("samegroup is not supported");
399 return false;
400 }
401 if (eat_kw(tp, "replication")) {
402 log_warning("replication is not supported");
403 return false;
404 }
405 }
406
407 if (expect(tp, TOK_IDENT, &tok)) {
408 if (tok[0] == '+') {
409 return false;
410 }
411
412 if (tok[0] == '@') {
413 bool ok;
414 char *fn;
415 fn = path_join_dirname(parent_filename, tok + 1);
416 if (!fn)
417 return false;
418 ok = parse_namefile(hname, fn, is_db);
419 free(fn);
420 if (!ok)
421 return false;
422 next_token(tp);
423 goto eat_comma;
424 }
425 /* fallthrough */
426 } else if (expect(tp, TOK_STRING, &tok)) {
427 /* fallthrough */
428 } else {
429 return false;
430 }
431
432 /*
433 * TOK_IDENT or TOK_STRING as plain name.
434 */
435
436 if (!hname->name_set) {
437 hname->name_set = strset_new(NULL);
438 if (!hname->name_set)
439 return false;
440 }
441 if (!strset_add(hname->name_set, tok, strlen(tok)))
442 return false;
443 next_token(tp);
444 eat_comma:
445 if (!eat(tp, TOK_COMMA))
446 break;
447 }
448 return true;
449 }
450
rule_free(struct HBARule * rule)451 static void rule_free(struct HBARule *rule)
452 {
453 strset_free(rule->db_name.name_set);
454 strset_free(rule->user_name.name_set);
455 free(rule);
456 }
457
parse_addr(struct HBARule * rule,const char * addr)458 static bool parse_addr(struct HBARule *rule, const char *addr)
459 {
460 if (inet_pton(AF_INET6, addr, rule->rule_addr)) {
461 rule->rule_af = AF_INET6;
462 } else if (inet_pton(AF_INET, addr, rule->rule_addr)) {
463 rule->rule_af = AF_INET;
464 } else {
465 return false;
466 }
467 return true;
468 }
469
parse_nmask(struct HBARule * rule,const char * nmask)470 static bool parse_nmask(struct HBARule *rule, const char *nmask)
471 {
472 char *end = NULL;
473 unsigned long bits;
474 unsigned int i;
475 errno = 0;
476 bits = strtoul(nmask, &end, 10);
477 if (errno || *end) {
478 return false;
479 }
480 if (rule->rule_af == AF_INET && bits > 32) {
481 return false;
482 }
483 if (rule->rule_af == AF_INET6 && bits > 128) {
484 return false;
485 }
486 for (i = 0; i < bits/8; i++)
487 rule->rule_mask[i] = 255;
488 if (bits % 8)
489 rule->rule_mask[i] = 255 << (8 - (bits % 8));
490 return true;
491 }
492
bad_mask(struct HBARule * rule)493 static bool bad_mask(struct HBARule *rule)
494 {
495 int i, bytes = rule->rule_af == AF_INET ? 4 : 16;
496 uint8_t res = 0;
497 for (i = 0; i < bytes; i++)
498 res |= rule->rule_addr[i] & (255 ^ rule->rule_mask[i]);
499 return !!res;
500 }
501
parse_line(struct HBA * hba,struct TokParser * tp,int linenr,const char * parent_filename)502 static bool parse_line(struct HBA *hba, struct TokParser *tp, int linenr, const char *parent_filename)
503 {
504 const char *addr = NULL, *mask = NULL;
505 enum RuleType rtype;
506 char *nmask = NULL;
507 struct HBARule *rule = NULL;
508
509 if (eat_kw(tp, "local")) {
510 rtype = RULE_LOCAL;
511 } else if (eat_kw(tp, "host")) {
512 rtype = RULE_HOST;
513 } else if (eat_kw(tp, "hostssl")) {
514 rtype = RULE_HOSTSSL;
515 } else if (eat_kw(tp, "hostnossl")) {
516 rtype = RULE_HOSTNOSSL;
517 } else if (eat(tp, TOK_EOL)) {
518 return true;
519 } else {
520 log_warning("hba line %d: unknown type", linenr);
521 return false;
522 }
523
524 rule = calloc(sizeof *rule, 1);
525 if (!rule) {
526 log_warning("hba: no mem for rule");
527 return false;
528 }
529 rule->rule_type = rtype;
530
531 if (!parse_names(&rule->db_name, tp, true, parent_filename))
532 goto failed;
533 if (!parse_names(&rule->user_name, tp, true, parent_filename))
534 goto failed;
535
536 if (rtype == RULE_LOCAL) {
537 rule->rule_af = AF_UNIX;
538 } else {
539 if (!expect(tp, TOK_IDENT, &addr)) {
540 log_warning("hba line %d: did not find address - %d - '%s'", linenr, tp->cur_tok, tp->buf);
541 goto failed;
542 }
543 nmask = strchr(addr, '/');
544 if (nmask) {
545 *nmask++ = 0;
546 }
547
548 if (!parse_addr(rule, addr)) {
549 log_warning("hba line %d: failed to parse address - %s", linenr, addr);
550 goto failed;
551 }
552
553 if (nmask) {
554 if (!parse_nmask(rule, nmask)) {
555 log_warning("hba line %d: invalid mask", linenr);
556 goto failed;
557 }
558 next_token(tp);
559 } else {
560 next_token(tp);
561 if (!expect(tp, TOK_IDENT, &mask)) {
562 log_warning("hba line %d: did not find mask", linenr);
563 goto failed;
564 }
565 if (!inet_pton(rule->rule_af, mask, rule->rule_mask)) {
566 log_warning("hba line %d: failed to parse mask: %s", linenr, mask);
567 goto failed;
568 }
569 next_token(tp);
570 }
571 if (bad_mask(rule)) {
572 char buf1[128], buf2[128];
573 log_warning("address does not match mask in %s line #%d: %s / %s", parent_filename, linenr,
574 inet_ntop(rule->rule_af, rule->rule_addr, buf1, sizeof buf1),
575 inet_ntop(rule->rule_af, rule->rule_mask, buf2, sizeof buf2));
576 }
577 }
578
579 if (eat_kw(tp, "trust")) {
580 rule->rule_method = AUTH_TRUST;
581 } else if (eat_kw(tp, "reject")) {
582 rule->rule_method = AUTH_REJECT;
583 } else if (eat_kw(tp, "md5")) {
584 rule->rule_method = AUTH_MD5;
585 } else if (eat_kw(tp, "password")) {
586 rule->rule_method = AUTH_PLAIN;
587 } else if (eat_kw(tp, "peer")) {
588 rule->rule_method = AUTH_PEER;
589 } else if (eat_kw(tp, "cert")) {
590 rule->rule_method = AUTH_CERT;
591 } else if (eat_kw(tp, "scram-sha-256")) {
592 rule->rule_method = AUTH_SCRAM_SHA_256;
593 } else {
594 log_warning("hba line %d: unsupported method: buf=%s", linenr, tp->buf);
595 goto failed;
596 }
597
598 if (!eat(tp, TOK_EOL)) {
599 log_warning("hba line %d: unsupported parameters", linenr);
600 goto failed;
601 }
602
603 list_append(&hba->rules, &rule->node);
604 return true;
605 failed:
606 rule_free(rule);
607 return false;
608 }
609
hba_load_rules(const char * fn)610 struct HBA *hba_load_rules(const char *fn)
611 {
612 struct HBA *hba = NULL;
613 FILE *f = NULL;
614 char *ln = NULL;
615 size_t lnbuf = 0;
616 ssize_t len;
617 int linenr;
618 struct TokParser tp;
619
620 init_parser(&tp);
621
622 hba = malloc(sizeof *hba);
623 if (!hba)
624 goto out;
625
626 list_init(&hba->rules);
627
628 f = fopen(fn, "r");
629 if (!f) {
630 log_error("could not open hba config file %s: %s", fn, strerror(errno));
631 goto out;
632 }
633
634 for (linenr = 1; ; linenr++) {
635 len = getline(&ln, &lnbuf, f);
636 if (len < 0)
637 break;
638 parse_from_string(&tp, ln);
639 if (!parse_line(hba, &tp, linenr, fn)) {
640 /* Tell the admin where to look for the problem. */
641 log_warning("could not parse hba config line %d", linenr);
642 /* Ignore line, but parse to the end. */
643 continue;
644 }
645 }
646 out:
647 free_parser(&tp);
648 free(ln);
649 if (f)
650 fclose(f);
651 return hba;
652 }
653
hba_free(struct HBA * hba)654 void hba_free(struct HBA *hba)
655 {
656 struct List *el, *tmp;
657 struct HBARule *rule;
658 if (!hba)
659 return;
660 list_for_each_safe(el, &hba->rules, tmp) {
661 rule = container_of(el, struct HBARule, node);
662 list_del(&rule->node);
663 rule_free(rule);
664 }
665 free(hba);
666 }
667
name_match(struct HBAName * hname,const char * name,unsigned int namelen,const char * pair)668 static bool name_match(struct HBAName *hname, const char *name, unsigned int namelen, const char *pair)
669 {
670 if (hname->flags & NAME_ALL)
671 return true;
672 if ((hname->flags & NAME_SAMEUSER) && strcmp(name, pair) == 0)
673 return true;
674 if (hname->name_set)
675 return strset_contains(hname->name_set, name, namelen);
676 return false;
677 }
678
match_inet4(const struct HBARule * rule,PgAddr * addr)679 static bool match_inet4(const struct HBARule *rule, PgAddr *addr)
680 {
681 const uint32_t *src, *base, *mask;
682 if (pga_family(addr) != AF_INET)
683 return false;
684 src = (uint32_t *)&addr->sin.sin_addr.s_addr;
685 base = (uint32_t *)rule->rule_addr;
686 mask = (uint32_t *)rule->rule_mask;
687 return (src[0] & mask[0]) == base[0];
688 }
689
match_inet6(const struct HBARule * rule,PgAddr * addr)690 static bool match_inet6(const struct HBARule *rule, PgAddr *addr)
691 {
692 const uint32_t *src, *base, *mask;
693 if (pga_family(addr) != AF_INET6)
694 return false;
695 src = (uint32_t *)addr->sin6.sin6_addr.s6_addr;
696 base = (uint32_t *)rule->rule_addr;
697 mask = (uint32_t *)rule->rule_mask;
698 return (src[0] & mask[0]) == base[0] && (src[1] & mask[1]) == base[1] &&
699 (src[2] & mask[2]) == base[2] && (src[3] & mask[3]) == base[3];
700 }
701
hba_eval(struct HBA * hba,PgAddr * addr,bool is_tls,const char * dbname,const char * username)702 int hba_eval(struct HBA *hba, PgAddr *addr, bool is_tls, const char *dbname, const char *username)
703 {
704 struct List *el;
705 struct HBARule *rule;
706 unsigned int dbnamelen = strlen(dbname);
707 unsigned int unamelen = strlen(username);
708
709 if (!hba)
710 return AUTH_REJECT;
711
712 list_for_each(el, &hba->rules) {
713 rule = container_of(el, struct HBARule, node);
714
715 /* match address */
716 if (pga_is_unix(addr)) {
717 if (rule->rule_type != RULE_LOCAL)
718 continue;
719 } else if (rule->rule_type == RULE_LOCAL) {
720 continue;
721 } else if (rule->rule_type == RULE_HOSTSSL && !is_tls) {
722 continue;
723 } else if (rule->rule_type == RULE_HOSTNOSSL && is_tls) {
724 continue;
725 } else if (rule->rule_af == AF_INET) {
726 if (!match_inet4(rule, addr))
727 continue;
728 } else if (rule->rule_af == AF_INET6) {
729 if (!match_inet6(rule, addr))
730 continue;
731 } else {
732 continue;
733 }
734
735 /* match db & user */
736 if (!name_match(&rule->db_name, dbname, dbnamelen, username))
737 continue;
738 if (!name_match(&rule->user_name, username, unamelen, dbname))
739 continue;
740
741 /* rule matches */
742 return rule->rule_method;
743 }
744 return AUTH_REJECT;
745 }
746