root/sbin/ipf/common/lexer.c

/*
 * Copyright (C) 2012 by Darren Reed.
 *
 * See the IPFILTER.LICENCE file for details on licencing.
 */
#include <ctype.h>
#include "ipf.h"
#ifdef  IPFILTER_SCAN
# include "netinet/ip_scan.h"
#endif
#include <sys/ioctl.h>
#include <sys/param.h>
#include <syslog.h>
#ifdef  TEST_LEXER
# define        NO_YACC
union   {
        int             num;
        char            *str;
        struct in_addr  ipa;
        i6addr_t        ip6;
} yylval;
#endif
#include "lexer.h"
#include "y.tab.h"

FILE *yyin;

#define ishex(c)        (ISDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || \
                         ((c) >= 'A' && (c) <= 'F'))
#define TOOLONG         -3

extern int      string_start;
extern int      string_end;
extern char     *string_val;
extern int      pos;
extern int      yydebug;

char            *yystr = NULL;
int             yytext[YYBUFSIZ+1];
char            yychars[YYBUFSIZ+1];
int             yylineNum = 1;
int             yypos = 0;
int             yylast = -1;
int             yydictfixed = 0;
int             yyexpectaddr = 0;
int             yybreakondot = 0;
int             yyvarnext = 0;
int             yytokentype = 0;
wordtab_t       *yywordtab = NULL;
int             yysavedepth = 0;
wordtab_t       *yysavewords[30];


static  wordtab_t       *yyfindkey(char *);
static  int             yygetc(int);
static  void            yyunputc(int);
static  int             yyswallow(int);
static  char            *yytexttostr(int, int);
static  void            yystrtotext(char *);
static  char            *yytexttochar(void);

static int
yygetc(int docont)
{
        int c;

        if (yypos < yylast) {
                c = yytext[yypos++];
                if (c == '\n')
                        yylineNum++;
                return (c);
        }

        if (yypos == YYBUFSIZ)
                return (TOOLONG);

        if (pos >= string_start && pos <= string_end) {
                c = string_val[pos - string_start];
                yypos++;
        } else {
                c = fgetc(yyin);
                if (docont && (c == '\\')) {
                        c = fgetc(yyin);
                        if (c == '\n') {
                                yylineNum++;
                                c = fgetc(yyin);
                        }
                }
        }
        if (c == '\n')
                yylineNum++;
        yytext[yypos++] = c;
        yylast = yypos;
        yytext[yypos] = '\0';

        return (c);
}


static void
yyunputc(int c)
{
        if (c == '\n')
                yylineNum--;
        yytext[--yypos] = c;
}


static int
yyswallow(int last)
{
        int c;

        while (((c = yygetc(0)) > '\0') && (c != last))
                ;

        if (c != EOF)
                yyunputc(c);
        if (c == last)
                return (0);
        return (-1);
}


static char *
yytexttochar(void)
{
        int i;

        for (i = 0; i < yypos; i++)
                yychars[i] = (char)(yytext[i] & 0xff);
        yychars[i] = '\0';
        return (yychars);
}


static void
yystrtotext(char *str)
{
        int len;
        char *s;

        len = strlen(str);
        if (len > YYBUFSIZ)
                len = YYBUFSIZ;

        for (s = str; *s != '\0' && len > 0; s++, len--)
                yytext[yylast++] = *s;
        yytext[yylast] = '\0';
}


static char *
yytexttostr(int offset, int max)
{
        char *str;
        int i;

        if ((yytext[offset] == '\'' || yytext[offset] == '"') &&
            (yytext[offset] == yytext[offset + max - 1])) {
                offset++;
                max--;
        }

        if (max > yylast)
                max = yylast;
        str = malloc(max + 1);
        if (str != NULL) {
                for (i = offset; i < max; i++)
                        str[i - offset] = (char)(yytext[i] & 0xff);
                str[i - offset] = '\0';
        }
        return (str);
}


int
yylex(void)
{
        static int prior = 0;
        static int priornum = 0;
        int c, n, isbuilding, rval, lnext, nokey = 0;
        char *name;
        int triedv6 = 0;

        isbuilding = 0;
        lnext = 0;
        rval = 0;

        if (yystr != NULL) {
                free(yystr);
                yystr = NULL;
        }

nextchar:
        c = yygetc(0);
        if (yydebug > 1)
                printf("yygetc = (%x) %c [%*.*s]\n",
                       c, c, yypos, yypos, yytexttochar());

        switch (c)
        {
        case '\n' :
                lnext = 0;
                nokey = 0;
        case '\t' :
        case '\r' :
        case ' ' :
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                if (yylast > yypos) {
                        bcopy(yytext + yypos, yytext,
                              sizeof(yytext[0]) * (yylast - yypos + 1));
                }
                yylast -= yypos;
                if (yyexpectaddr == 2)
                        yyexpectaddr = 0;
                yypos = 0;
                lnext = 0;
                nokey = 0;
                goto nextchar;

        case '\\' :
                if (lnext == 0) {
                        lnext = 1;
                        if (yylast == yypos) {
                                yylast--;
                                yypos--;
                        } else
                                yypos--;
                        if (yypos == 0)
                                nokey = 1;
                        goto nextchar;
                }
                break;
        }

        if (lnext == 1) {
                lnext = 0;
                if ((isbuilding == 0) && !ISALNUM(c)) {
                        prior = c;
                        return (c);
                }
                goto nextchar;
        }

        switch (c)
        {
        case '#' :
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                yyswallow('\n');
                rval = YY_COMMENT;
                goto done;

        case '$' :
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                n = yygetc(0);
                if (n == '{') {
                        if (yyswallow('}') == -1) {
                                rval = -2;
                                goto done;
                        }
                        (void) yygetc(0);
                } else {
                        if (!ISALPHA(n)) {
                                yyunputc(n);
                                break;
                        }
                        do {
                                n = yygetc(1);
                        } while (ISALPHA(n) || ISDIGIT(n) || n == '_');
                        yyunputc(n);
                }

                name = yytexttostr(1, yypos);           /* skip $ */

                if (name != NULL) {
                        string_val = get_variable(name, NULL, yylineNum);
                        free(name);
                        if (string_val != NULL) {
                                name = yytexttostr(yypos, yylast);
                                if (name != NULL) {
                                        yypos = 0;
                                        yylast = 0;
                                        yystrtotext(string_val);
                                        yystrtotext(name);
                                        free(string_val);
                                        free(name);
                                        goto nextchar;
                                }
                                free(string_val);
                        }
                }
                break;

        case '\'':
        case '"' :
                if (isbuilding == 1) {
                        goto done;
                }
                do {
                        n = yygetc(1);
                        if (n == EOF || n == TOOLONG) {
                                rval = -2;
                                goto done;
                        }
                        if (n == '\n') {
                                yyunputc(' ');
                                yypos++;
                        }
                } while (n != c);
                rval = YY_STR;
                goto done;
                /* NOTREACHED */

        case EOF :
                yylineNum = 1;
                yypos = 0;
                yylast = -1;
                yyexpectaddr = 0;
                yybreakondot = 0;
                yyvarnext = 0;
                yytokentype = 0;
                if (yydebug)
                        fprintf(stderr, "reset at EOF\n");
                prior = 0;
                return (0);
        }

        if (strchr("=,/;{}()@", c) != NULL) {
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                rval = c;
                goto done;
        } else if (c == '.') {
                if (isbuilding == 0) {
                        rval = c;
                        goto done;
                }
                if (yybreakondot != 0) {
                        yyunputc(c);
                        goto done;
                }
        }

        switch (c)
        {
        case '-' :
                n = yygetc(0);
                if (n == '>') {
                        isbuilding = 1;
                        goto done;
                }
                yyunputc(n);
                if (yyexpectaddr) {
                        if (isbuilding == 1)
                                yyunputc(c);
                        else
                                rval = '-';
                        goto done;
                }
                if (isbuilding == 1)
                        break;
                rval = '-';
                goto done;

        case '!' :
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                n = yygetc(0);
                if (n == '=') {
                        rval = YY_CMP_NE;
                        goto done;
                }
                yyunputc(n);
                rval = '!';
                goto done;

        case '<' :
                if (yyexpectaddr)
                        break;
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                n = yygetc(0);
                if (n == '=') {
                        rval = YY_CMP_LE;
                        goto done;
                }
                if (n == '>') {
                        rval = YY_RANGE_OUT;
                        goto done;
                }
                yyunputc(n);
                rval = YY_CMP_LT;
                goto done;

        case '>' :
                if (yyexpectaddr)
                        break;
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                n = yygetc(0);
                if (n == '=') {
                        rval = YY_CMP_GE;
                        goto done;
                }
                if (n == '<') {
                        rval = YY_RANGE_IN;
                        goto done;
                }
                yyunputc(n);
                rval = YY_CMP_GT;
                goto done;
        }

        /*
         * Now for the reason this is here...IPv6 address parsing.
         * The longest string we can expect is of this form:
         * 0000:0000:0000:0000:0000:0000:000.000.000.000
         * not:
         * 0000:0000:0000:0000:0000:0000:0000:0000
         */
#ifdef  USE_INET6
        if (yyexpectaddr != 0 && isbuilding == 0 &&
            (ishex(c) || isdigit(c) || c == ':')) {
                char ipv6buf[45 + 1], *s, oc;
                int start;

buildipv6:
                start = yypos;
                s = ipv6buf;
                oc = c;

                if (prior == YY_NUMBER && c == ':') {
                        snprintf(s, sizeof(ipv6buf), "%d", priornum);
                        s += strlen(s);
                }

                /*
                 * Perhaps we should implement stricter controls on what we
                 * swallow up here, but surely it would just be duplicating
                 * the code in inet_pton() anyway.
                 */
                do {
                        *s++ = c;
                        c = yygetc(1);
                } while ((ishex(c) || c == ':' || c == '.') &&
                         (s - ipv6buf < 46));
                yyunputc(c);
                *s = '\0';

                if (inet_pton(AF_INET6, ipv6buf, &yylval.ip6) == 1) {
                        rval = YY_IPV6;
                        yyexpectaddr = 0;
                        goto done;
                }
                yypos = start;
                c = oc;
        }
#endif

        if ((c == ':') && (rval != YY_IPV6) && (triedv6 == 0)) {
#ifdef  USE_INET6
                yystr = yytexttostr(0, yypos - 1);
                if (yystr != NULL) {
                        char *s;

                        for (s = yystr; *s && ishex(*s); s++)
                                ;
                        if (!*s && *yystr) {
                                isbuilding = 0;
                                c = *yystr;
                                free(yystr);
                                triedv6 = 1;
                                yypos = 1;
                                goto buildipv6;
                        }
                        free(yystr);
                }
#endif
                if (isbuilding == 1) {
                        yyunputc(c);
                        goto done;
                }
                rval = ':';
                goto done;
        }

        if (isbuilding == 0 && c == '0') {
                n = yygetc(0);
                if (n == 'x') {
                        do {
                                n = yygetc(1);
                        } while (ishex(n));
                        yyunputc(n);
                        rval = YY_HEX;
                        goto done;
                }
                yyunputc(n);
        }

        /*
         * No negative numbers with leading - sign..
         */
        if (isbuilding == 0 && ISDIGIT(c)) {
                do {
                        n = yygetc(1);
                } while (ISDIGIT(n));
                yyunputc(n);
                rval = YY_NUMBER;
                goto done;
        }

        isbuilding = 1;
        goto nextchar;

done:
        yystr = yytexttostr(0, yypos);

        if (yydebug)
                printf("isbuilding %d yyvarnext %d nokey %d fixed %d addr %d\n",
                       isbuilding, yyvarnext, nokey, yydictfixed, yyexpectaddr);
        if (isbuilding == 1) {
                wordtab_t *w;

                w = NULL;
                isbuilding = 0;

                if ((yyvarnext == 0) && (nokey == 0)) {
                        w = yyfindkey(yystr);
                        if (w == NULL && yywordtab != NULL && !yydictfixed) {
                                yyresetdict();
                                w = yyfindkey(yystr);
                        }
                } else
                        yyvarnext = 0;
                if (w != NULL)
                        rval = w->w_value;
                else
                        rval = YY_STR;
        }

        if (rval == YY_STR) {
                if (yysavedepth > 0 && !yydictfixed)
                        yyresetdict();
                if (yyexpectaddr != 0)
                        yyexpectaddr = 0;
        }

        yytokentype = rval;

        if (yydebug)
                printf("lexed(%s) %d,%d,%d [%d,%d,%d] => %d @%d\n",
                       yystr, isbuilding, yyexpectaddr, yysavedepth,
                       string_start, string_end, pos, rval, yysavedepth);

        switch (rval)
        {
        case YY_NUMBER :
                sscanf(yystr, "%u", &yylval.num);
                break;

        case YY_HEX :
                sscanf(yystr, "0x%x", (u_int *)&yylval.num);
                break;

        case YY_STR :
                yylval.str = strdup(yystr);
                break;

        default :
                break;
        }

        if (yylast > 0) {
                bcopy(yytext + yypos, yytext,
                      sizeof(yytext[0]) * (yylast - yypos + 1));
                yylast -= yypos;
                yypos = 0;
        }

        if (rval == YY_NUMBER)
                priornum = yylval.num;
        prior = rval;
        return (rval);
}


static wordtab_t *yyfindkey(char *key)
{
        wordtab_t *w;

        if (yywordtab == NULL)
                return (NULL);

        for (w = yywordtab; w->w_word != 0; w++)
                if (strcasecmp(key, w->w_word) == 0)
                        return (w);
        return (NULL);
}


char *
yykeytostr(int num)
{
        wordtab_t *w;

        if (yywordtab == NULL)
                return ("<unknown>");

        for (w = yywordtab; w->w_word; w++)
                if (w->w_value == num)
                        return (w->w_word);
        return ("<unknown>");
}


wordtab_t *
yysettab(wordtab_t *words)
{
        wordtab_t *save;

        save = yywordtab;
        yywordtab = words;
        return (save);
}


void
yyerror(char *msg)
{
        char *txt, letter[2];
        int freetxt = 0;

        if (yytokentype < 256) {
                letter[0] = yytokentype;
                letter[1] = '\0';
                txt =  letter;
        } else if (yytokentype == YY_STR || yytokentype == YY_HEX ||
                   yytokentype == YY_NUMBER) {
                if (yystr == NULL) {
                        txt = yytexttostr(yypos, YYBUFSIZ);
                        freetxt = 1;
                } else
                        txt = yystr;
        } else {
                txt = yykeytostr(yytokentype);
        }
        fprintf(stderr, "%s error at \"%s\", line %d\n", msg, txt, yylineNum);
        if (freetxt == 1)
                free(txt);
        exit(1);
}


void
yysetfixeddict(wordtab_t *newdict)
{
        if (yydebug)
                printf("yysetfixeddict(%lx)\n", (u_long)newdict);

        if (yysavedepth == nitems(yysavewords)) {
                fprintf(stderr, "%d: at maximum dictionary depth\n",
                        yylineNum);
                return;
        }

        yysavewords[yysavedepth++] = yysettab(newdict);
        if (yydebug)
                printf("yysavedepth++ => %d\n", yysavedepth);
        yydictfixed = 1;
}


void
yysetdict(wordtab_t *newdict)
{
        if (yydebug)
                printf("yysetdict(%lx)\n", (u_long)newdict);

        if (yysavedepth == nitems(yysavewords)) {
                fprintf(stderr, "%d: at maximum dictionary depth\n",
                        yylineNum);
                return;
        }

        yysavewords[yysavedepth++] = yysettab(newdict);
        if (yydebug)
                printf("yysavedepth++ => %d\n", yysavedepth);
}

void
yyresetdict(void)
{
        if (yydebug)
                printf("yyresetdict(%d)\n", yysavedepth);
        if (yysavedepth > 0) {
                yysettab(yysavewords[--yysavedepth]);
                if (yydebug)
                        printf("yysavedepth-- => %d\n", yysavedepth);
        }
        yydictfixed = 0;
}



#ifdef  TEST_LEXER
int
main(int argc, char *argv[])
{
        int n;

        yyin = stdin;

        while ((n = yylex()) != 0)
                printf("%d.n = %d [%s] %d %d\n",
                        yylineNum, n, yystr, yypos, yylast);
}
#endif