diff options
Diffstat (limited to 'contrib/ipfilter/lib/parse.c')
-rw-r--r-- | contrib/ipfilter/lib/parse.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/contrib/ipfilter/lib/parse.c b/contrib/ipfilter/lib/parse.c new file mode 100644 index 0000000..4cf69ab --- /dev/null +++ b/contrib/ipfilter/lib/parse.c @@ -0,0 +1,754 @@ +/* $NetBSD$ */ + +/* + * Copyright (C) 1993-2001 by Darren Reed. + * + * See the IPFILTER.LICENCE file for details on licencing. + * + * Id: parse.c,v 1.34.2.1 2004/12/09 19:41:21 darrenr Exp + */ +#include <ctype.h> +#include "ipf.h" +#include "opts.h" + +static frentry_t *fp = NULL; + +/* parse() + * + * parse a line read from the input filter rule file + */ +struct frentry *parse(line, linenum) +char *line; +int linenum; +{ + static fripf_t fip; + char *cps[31], **cpp, *endptr, *proto = NULL, *s; + struct protoent *p = NULL; + int i, cnt = 1, j; + u_int k; + + if (fp == NULL) { + fp = malloc(sizeof(*fp)); + if (fp == NULL) + return NULL; + } + + while (*line && ISSPACE(*line)) + line++; + if (!*line) + return NULL; + + bzero((char *)fp, sizeof(*fp)); + bzero((char *)&fip, sizeof(fip)); + fp->fr_v = use_inet6 ? 6 : 4; + fp->fr_ipf = &fip; + fp->fr_dsize = sizeof(fip); + fp->fr_ip.fi_v = fp->fr_v; + fp->fr_mip.fi_v = 0xf; + fp->fr_type = FR_T_NONE; + fp->fr_loglevel = 0xffff; + fp->fr_isc = (void *)-1; + fp->fr_tag = FR_NOTAG; + + /* + * break line up into max of 20 segments + */ + if (opts & OPT_DEBUG) + fprintf(stderr, "parse [%s]\n", line); + for (i = 0, *cps = strtok(line, " \b\t\r\n"); cps[i] && i < 30; cnt++) + cps[++i] = strtok(NULL, " \b\t\r\n"); + cps[i] = NULL; + + if (cnt < 3) { + fprintf(stderr, "%d: not enough segments in line\n", linenum); + return NULL; + } + + cpp = cps; + /* + * The presence of an '@' followed by a number gives the position in + * the current rule list to insert this one. + */ + if (**cpp == '@') + fp->fr_hits = (U_QUAD_T)atoi(*cpp++ + 1) + 1; + + /* + * Check the first keyword in the rule and any options that are + * expected to follow it. + */ + if (!strcasecmp("block", *cpp)) { + fp->fr_flags |= FR_BLOCK; + if (!strncasecmp(*(cpp+1), "return-icmp-as-dest", 19) && + (i = 19)) + fp->fr_flags |= FR_FAKEICMP; + else if (!strncasecmp(*(cpp+1), "return-icmp", 11) && (i = 11)) + fp->fr_flags |= FR_RETICMP; + if (fp->fr_flags & FR_RETICMP) { + cpp++; + if (strlen(*cpp) == i) { + if (*(cpp + 1) && **(cpp +1) == '(') { + cpp++; + i = 0; + } else + i = -1; + } + + /* + * The ICMP code is not required to follow in ()'s + */ + if ((i >= 0) && (*(*cpp + i) == '(')) { + i++; + j = icmpcode(*cpp + i); + if (j == -1) { + fprintf(stderr, + "%d: unrecognised icmp code %s\n", + linenum, *cpp + 20); + return NULL; + } + fp->fr_icode = j; + } + } else if (!strncasecmp(*(cpp+1), "return-rst", 10)) { + fp->fr_flags |= FR_RETRST; + cpp++; + } + } else if (!strcasecmp("count", *cpp)) { + fp->fr_flags |= FR_ACCOUNT; + } else if (!strcasecmp("pass", *cpp)) { + fp->fr_flags |= FR_PASS; + } else if (!strcasecmp("auth", *cpp)) { + fp->fr_flags |= FR_AUTH; + } else if (fp->fr_arg != 0) { + printf("skip %u", fp->fr_arg); + } else if (!strcasecmp("preauth", *cpp)) { + fp->fr_flags |= FR_PREAUTH; + } else if (!strcasecmp("nomatch", *cpp)) { + fp->fr_flags |= FR_NOMATCH; + } else if (!strcasecmp("skip", *cpp)) { + cpp++; + if (ratoui(*cpp, &k, 0, UINT_MAX)) + fp->fr_arg = k; + else { + fprintf(stderr, "%d: integer must follow skip\n", + linenum); + return NULL; + } + } else if (!strcasecmp("log", *cpp)) { + fp->fr_flags |= FR_LOG; + if (!strcasecmp(*(cpp+1), "body")) { + fp->fr_flags |= FR_LOGBODY; + cpp++; + } + if (!strcasecmp(*(cpp+1), "first")) { + fp->fr_flags |= FR_LOGFIRST; + cpp++; + } + if (*cpp && !strcasecmp(*(cpp+1), "or-block")) { + fp->fr_flags |= FR_LOGORBLOCK; + cpp++; + } + if (!strcasecmp(*(cpp+1), "level")) { + cpp++; + if (loglevel(cpp, &fp->fr_loglevel, linenum) == -1) + return NULL; + cpp++; + } + } else { + /* + * Doesn't start with one of the action words + */ + fprintf(stderr, "%d: unknown keyword (%s)\n", linenum, *cpp); + return NULL; + } + if (!*++cpp) { + fprintf(stderr, "%d: missing 'in'/'out' keyword\n", linenum); + return NULL; + } + + /* + * Get the direction for filtering. Impose restrictions on direction + * if blocking with returning ICMP or an RST has been requested. + */ + if (!strcasecmp("in", *cpp)) + fp->fr_flags |= FR_INQUE; + else if (!strcasecmp("out", *cpp)) { + fp->fr_flags |= FR_OUTQUE; + if (fp->fr_flags & FR_RETICMP) { + fprintf(stderr, + "%d: Can only use return-icmp with 'in'\n", + linenum); + return NULL; + } else if (fp->fr_flags & FR_RETRST) { + fprintf(stderr, + "%d: Can only use return-rst with 'in'\n", + linenum); + return NULL; + } + } + if (!*++cpp) { + fprintf(stderr, "%d: missing source specification\n", linenum); + return NULL; + } + + if (!strcasecmp("log", *cpp)) { + if (!*++cpp) { + fprintf(stderr, "%d: missing source specification\n", + linenum); + return NULL; + } + if (FR_ISPASS(fp->fr_flags)) + fp->fr_flags |= FR_LOGP; + else if (FR_ISBLOCK(fp->fr_flags)) + fp->fr_flags |= FR_LOGB; + if (*cpp && !strcasecmp(*cpp, "body")) { + fp->fr_flags |= FR_LOGBODY; + cpp++; + } + if (*cpp && !strcasecmp(*cpp, "first")) { + fp->fr_flags |= FR_LOGFIRST; + cpp++; + } + if (*cpp && !strcasecmp(*cpp, "or-block")) { + if (!FR_ISPASS(fp->fr_flags)) { + fprintf(stderr, + "%d: or-block must be used with pass\n", + linenum); + return NULL; + } + fp->fr_flags |= FR_LOGORBLOCK; + cpp++; + } + if (*cpp && !strcasecmp(*cpp, "level")) { + if (loglevel(cpp, &fp->fr_loglevel, linenum) == -1) + return NULL; + cpp++; + cpp++; + } + } + + if (*cpp && !strcasecmp("quick", *cpp)) { + if (fp->fr_arg != 0) { + fprintf(stderr, "%d: cannot use skip with quick\n", + linenum); + return NULL; + } + cpp++; + fp->fr_flags |= FR_QUICK; + } + + /* + * Parse rule options that are available if a rule is tied to an + * interface. + */ + *fp->fr_ifname = '\0'; + *fp->fr_oifname = '\0'; + if (*cpp && !strcasecmp(*cpp, "on")) { + if (!*++cpp) { + fprintf(stderr, "%d: interface name missing\n", + linenum); + return NULL; + } + (void)strncpy(fp->fr_ifname, *cpp, IFNAMSIZ-1); + fp->fr_ifname[IFNAMSIZ-1] = '\0'; + cpp++; + if (!*cpp) { + if ((fp->fr_flags & FR_RETMASK) == FR_RETRST) { + fprintf(stderr, + "%d: %s can only be used with TCP\n", + linenum, "return-rst"); + return NULL; + } + return fp; + } + + if (!strcasecmp(*cpp, "out-via")) { + if (fp->fr_flags & FR_OUTQUE) { + fprintf(stderr, + "out-via must be used with in\n"); + return NULL; + } + cpp++; + (void)strncpy(fp->fr_oifname, *cpp, IFNAMSIZ-1); + fp->fr_oifname[IFNAMSIZ-1] = '\0'; + cpp++; + } else if (!strcasecmp(*cpp, "in-via")) { + if (fp->fr_flags & FR_INQUE) { + fprintf(stderr, + "in-via must be used with out\n"); + return NULL; + } + cpp++; + (void)strncpy(fp->fr_oifname, *cpp, IFNAMSIZ-1); + fp->fr_oifname[IFNAMSIZ-1] = '\0'; + cpp++; + } + + if (!strcasecmp(*cpp, "dup-to") && *(cpp + 1)) { + cpp++; + if (to_interface(&fp->fr_dif, *cpp, linenum)) + return NULL; + cpp++; + } + if (*cpp && !strcasecmp(*cpp, "to") && *(cpp + 1)) { + cpp++; + if (to_interface(&fp->fr_tif, *cpp, linenum)) + return NULL; + cpp++; + } else if (*cpp && !strcasecmp(*cpp, "fastroute")) { + if (!(fp->fr_flags & FR_INQUE)) { + fprintf(stderr, + "can only use %s with 'in'\n", + "fastroute"); + return NULL; + } + fp->fr_flags |= FR_FASTROUTE; + cpp++; + } + + /* + * Set the "other" interface name. Lets you specify both + * inbound and outbound interfaces for state rules. Do not + * prevent both interfaces from being the same. + */ + strcpy(fp->fr_ifnames[3], "*"); + if ((*cpp != NULL) && (*(cpp + 1) != NULL) && + ((((fp->fr_flags & FR_INQUE) != 0) && + (strcasecmp(*cpp, "out-via") == 0)) || + (((fp->fr_flags & FR_OUTQUE) != 0) && + (strcasecmp(*cpp, "in-via") == 0)))) { + cpp++; + + s = strchr(*cpp, ','); + if (s != NULL) { + *s++ = '\0'; + (void)strncpy(fp->fr_ifnames[3], s, + IFNAMSIZ - 1); + fp->fr_ifnames[3][IFNAMSIZ - 1] = '\0'; + } + + (void)strncpy(fp->fr_ifnames[2], *cpp, IFNAMSIZ - 1); + fp->fr_ifnames[2][IFNAMSIZ - 1] = '\0'; + cpp++; + } else + strcpy(fp->fr_ifnames[2], "*"); + + } + + if (*cpp && !strcasecmp(*cpp, "tos")) { + if (!*++cpp) { + fprintf(stderr, "%d: tos missing value\n", linenum); + return NULL; + } + fp->fr_tos = strtol(*cpp, NULL, 0); + fp->fr_mip.fi_tos = 0xff; + cpp++; + } + + if (*cpp && !strcasecmp(*cpp, "ttl")) { + if (!*++cpp) { + fprintf(stderr, "%d: ttl missing hopcount value\n", + linenum); + return NULL; + } + if (ratoi(*cpp, &i, 0, 255)) + fp->fr_ttl = i; + else { + fprintf(stderr, "%d: invalid ttl (%s)\n", + linenum, *cpp); + return NULL; + } + fp->fr_mip.fi_ttl = 0xff; + cpp++; + } + + /* + * check for "proto <protoname>" only decode udp/tcp/icmp as protoname + */ + if (*cpp && !strcasecmp(*cpp, "proto")) { + if (!*++cpp) { + fprintf(stderr, "%d: protocol name missing\n", linenum); + return NULL; + } + fp->fr_type = FR_T_IPF; + proto = *cpp++; + if (!strcasecmp(proto, "tcp/udp")) { + fp->fr_flx |= FI_TCPUDP; + fp->fr_mflx |= FI_TCPUDP; + } else if (use_inet6 && !strcasecmp(proto, "icmp")) { + fprintf(stderr, +"%d: use proto ipv6-icmp with IPv6 (or use proto 1 if you really mean icmp)\n", + linenum); + return NULL; + } else { + fp->fr_proto = getproto(proto); + fp->fr_mip.fi_p = 0xff; + } + } + if ((fp->fr_proto != IPPROTO_TCP) && + ((fp->fr_flags & FR_RETMASK) == FR_RETRST)) { + fprintf(stderr, "%d: %s can only be used with TCP\n", + linenum, "return-rst"); + return NULL; + } + + /* + * get the from host and bit mask to use against packets + */ + + if (!*cpp) { + fprintf(stderr, "%d: missing source specification\n", linenum); + return NULL; + } + if (!strcasecmp(*cpp, "all")) { + cpp++; + if (!*cpp) { + if (fp->fr_type == FR_T_NONE) { + fp->fr_dsize = 0; + fp->fr_data = NULL; + } + return fp; + } + fp->fr_type = FR_T_IPF; +#ifdef IPFILTER_BPF + } else if (!strcmp(*cpp, "{")) { + struct bpf_program bpf; + struct pcap *p; + char **cp; + u_32_t l; + + if (fp->fr_type != FR_T_NONE) { + fprintf(stderr, + "%d: cannot mix BPF/ipf matching\n", linenum); + return NULL; + } + fp->fr_type = FR_T_BPFOPC; + cpp++; + if (!strncmp(*cpp, "0x", 2)) { + fp->fr_data = malloc(4); + for (cp = cpp, i = 0; *cp; cp++, i++) { + if (!strcmp(*cp, "}")) + break; + fp->fr_data = realloc(fp->fr_data, + (i + 1) * 4); + l = strtoul(*cp, NULL, 0); + ((u_32_t *)fp->fr_data)[i] = l; + } + if (!*cp) { + fprintf(stderr, "Missing closing '}'\n"); + return NULL; + } + fp->fr_dsize = i * sizeof(l); + bpf.bf_insns = fp->fr_data; + bpf.bf_len = fp->fr_dsize / sizeof(struct bpf_insn); + } else { + for (cp = cpp; *cp; cp++) { + if (!strcmp(*cp, "}")) + break; + (*cp)[-1] = ' '; + } + if (!*cp) { + fprintf(stderr, "Missing closing '}'\n"); + return NULL; + } + + bzero((char *)&bpf, sizeof(bpf)); + p = pcap_open_dead(DLT_RAW, 1); + if (!p) { + fprintf(stderr, "pcap_open_dead failed\n"); + return NULL; + } + + if (pcap_compile(p, &bpf, *cpp, 1, 0xffffffff)) { + pcap_perror(p, "ipf"); + pcap_close(p); + fprintf(stderr, "pcap parsing failed\n"); + return NULL; + } + pcap_close(p); + fp->fr_dsize = bpf.bf_len * sizeof(struct bpf_insn); + fp->fr_data = bpf.bf_insns; + if (!bpf_validate(fp->fr_data, bpf.bf_len)) { + fprintf(stderr, "BPF validation failed\n"); + return NULL; + } + if (opts & OPT_DEBUG) + bpf_dump(&bpf, 0); + } + cpp = cp; + (*cpp)++; +#endif + } else { + fp->fr_type = FR_T_IPF; + + if (strcasecmp(*cpp, "from")) { + fprintf(stderr, "%d: unexpected keyword (%s) - from\n", + linenum, *cpp); + return NULL; + } + if (!*++cpp) { + fprintf(stderr, "%d: missing host after from\n", + linenum); + return NULL; + } + if (**cpp == '!') { + fp->fr_flags |= FR_NOTSRCIP; + (*cpp)++; + } else if (!strcmp(*cpp, "!")) { + fp->fr_flags |= FR_NOTSRCIP; + cpp++; + } + + s = *cpp; + i = hostmask(&cpp, proto, fp->fr_ifname, (u_32_t *)&fp->fr_src, + (u_32_t *)&fp->fr_smsk, linenum); + if (i == -1) + return NULL; + if (*fp->fr_ifname && !strcasecmp(s, fp->fr_ifname)) + fp->fr_satype = FRI_DYNAMIC; + if (i == 1) { + if (fp->fr_v == 6) { + fprintf(stderr, + "can only use pools with ipv4\n"); + return NULL; + } + fp->fr_satype = FRI_LOOKUP; + } + + if (ports(&cpp, proto, &fp->fr_sport, &fp->fr_scmp, + &fp->fr_stop, linenum)) + return NULL; + + if (!*cpp) { + fprintf(stderr, "%d: missing to fields\n", linenum); + return NULL; + } + + /* + * do the same for the to field (destination host) + */ + if (strcasecmp(*cpp, "to")) { + fprintf(stderr, "%d: unexpected keyword (%s) - to\n", + linenum, *cpp); + return NULL; + } + if (!*++cpp) { + fprintf(stderr, "%d: missing host after to\n", linenum); + return NULL; + } + + if (**cpp == '!') { + fp->fr_flags |= FR_NOTDSTIP; + (*cpp)++; + } else if (!strcmp(*cpp, "!")) { + fp->fr_flags |= FR_NOTDSTIP; + cpp++; + } + + s = *cpp; + i = hostmask(&cpp, proto, fp->fr_ifname, (u_32_t *)&fp->fr_dst, + (u_32_t *)&fp->fr_dmsk, linenum); + if (i == -1) + return NULL; + if (*fp->fr_ifname && !strcasecmp(s, fp->fr_ifname)) + fp->fr_datype = FRI_DYNAMIC; + if (i == 1) { + if (fp->fr_v == 6) { + fprintf(stderr, + "can only use pools with ipv4\n"); + return NULL; + } + fp->fr_datype = FRI_LOOKUP; + } + + if (ports(&cpp, proto, &fp->fr_dport, &fp->fr_dcmp, + &fp->fr_dtop, linenum)) + return NULL; + } + + if (fp->fr_type == FR_T_IPF) { + /* + * check some sanity, make sure we don't have icmp checks + * with tcp or udp or visa versa. + */ + if (fp->fr_proto && (fp->fr_dcmp || fp->fr_scmp) && + fp->fr_proto != IPPROTO_TCP && + fp->fr_proto != IPPROTO_UDP) { + fprintf(stderr, + "%d: port operation on non tcp/udp\n",linenum); + return NULL; + } + if (fp->fr_icmp && fp->fr_proto != IPPROTO_ICMP) { + fprintf(stderr, + "%d: icmp comparisons on wrong protocol\n", + linenum); + return NULL; + } + + if (!*cpp) + return fp; + + if (*cpp && (fp->fr_type == FR_T_IPF) && + !strcasecmp(*cpp, "flags")) { + if (!*++cpp) { + fprintf(stderr, "%d: no flags present\n", + linenum); + return NULL; + } + fp->fr_tcpf = tcp_flags(*cpp, &fp->fr_tcpfm, linenum); + cpp++; + } + + /* + * extras... + */ + if ((fp->fr_v == 4) && *cpp && (!strcasecmp(*cpp, "with") || + !strcasecmp(*cpp, "and"))) + if (extras(&cpp, fp, linenum)) + return NULL; + + /* + * icmp types for use with the icmp protocol + */ + if (*cpp && !strcasecmp(*cpp, "icmp-type")) { + if (fp->fr_proto != IPPROTO_ICMP && + fp->fr_proto != IPPROTO_ICMPV6) { + fprintf(stderr, + "%d: icmp with wrong protocol (%d)\n", + linenum, fp->fr_proto); + return NULL; + } + if (addicmp(&cpp, fp, linenum)) + return NULL; + fp->fr_icmp = htons(fp->fr_icmp); + fp->fr_icmpm = htons(fp->fr_icmpm); + } + } + + /* + * Keep something... + */ + while (*cpp && !strcasecmp(*cpp, "keep")) + if (addkeep(&cpp, fp, linenum)) + return NULL; + + /* + * This is here to enforce the old interface binding behaviour. + * That is, "on X" is equivalent to "<dir> on X <!dir>-via -,X" + */ + if (fp->fr_flags & FR_KEEPSTATE) { + if (*fp->fr_ifnames[0] && !*fp->fr_ifnames[3]) { + bcopy(fp->fr_ifnames[0], fp->fr_ifnames[3], + sizeof(fp->fr_ifnames[3])); + strncpy(fp->fr_ifnames[2], "*", + sizeof(fp->fr_ifnames[3])); + } + } + + /* + * head of a new group ? + */ + if (*cpp && !strcasecmp(*cpp, "head")) { + if (fp->fr_arg != 0) { + fprintf(stderr, "%d: cannot use skip with head\n", + linenum); + return NULL; + } + if (!*++cpp) { + fprintf(stderr, "%d: head without group #\n", linenum); + return NULL; + } + if (strlen(*cpp) > FR_GROUPLEN) { + fprintf(stderr, "%d: head name too long #\n", linenum); + return NULL; + } + strncpy(fp->fr_grhead, *cpp, FR_GROUPLEN); + cpp++; + } + + /* + * reference to an already existing group ? + */ + if (*cpp && !strcasecmp(*cpp, "group")) { + if (!*++cpp) { + fprintf(stderr, "%d: group without group #\n", + linenum); + return NULL; + } + if (strlen(*cpp) > FR_GROUPLEN) { + fprintf(stderr, "%d: group name too long #\n", linenum); + return NULL; + } + strncpy(fp->fr_group, *cpp, FR_GROUPLEN); + cpp++; + } + + if (*cpp && !strcasecmp(*cpp, "tag")) { + if (!*++cpp) { + fprintf(stderr, "%d: tag id missing value\n", linenum); + return NULL; + } + fp->fr_tag = strtol(*cpp, NULL, 0); + cpp++; + } + + /* + * pps counter + */ + if (*cpp && !strcasecmp(*cpp, "pps")) { + if (!*++cpp) { + fprintf(stderr, "%d: pps without rate\n", linenum); + return NULL; + } + if (ratoui(*cpp, &k, 0, INT_MAX)) + fp->fr_pps = k; + else { + fprintf(stderr, "%d: invalid pps rate (%s)\n", + linenum, *cpp); + return NULL; + } + cpp++; + } + + /* + * leftovers...yuck + */ + if (*cpp && **cpp) { + fprintf(stderr, "%d: unknown words at end: [", linenum); + for (; *cpp; cpp++) + fprintf(stderr, "%s ", *cpp); + fprintf(stderr, "]\n"); + return NULL; + } + + /* + * lazy users... + */ + if (fp->fr_type == FR_T_IPF) { + if ((fp->fr_tcpf || fp->fr_tcpfm) && + (fp->fr_proto != IPPROTO_TCP)) { + fprintf(stderr, + "%d: TCP protocol not specified\n", linenum); + return NULL; + } + if (!(fp->fr_flx & FI_TCPUDP) && + (fp->fr_proto != IPPROTO_TCP) && + (fp->fr_proto != IPPROTO_UDP) && + (fp->fr_dcmp || fp->fr_scmp)) { + if (!fp->fr_proto) { + fp->fr_flx |= FI_TCPUDP; + fp->fr_mflx |= FI_TCPUDP; + } else { + fprintf(stderr, + "%d: port check for non-TCP/UDP\n", + linenum); + return NULL; + } + } + } + if (*fp->fr_oifname && strcmp(fp->fr_oifname, "*") && + !(fp->fr_flags & FR_KEEPSTATE)) { + fprintf(stderr, "%d: *-via <if> must be used %s\n", + linenum, "with keep-state"); + return NULL; + } + return fp; +} |