/*      $NetBSD: parser.c,v 1.13 2024/01/15 19:44:07 andvar Exp $       */
/*      $KAME: parser.c,v 1.16 2002/02/20 10:40:39 kjc Exp $    */
/*
* Copyright (C) 1999-2002
*      Sony Computer Science Laboratories, Inc.  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. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SONY CSL AND CONTRIBUTORS ``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 SONY CSL OR CONTRIBUTORS 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/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h>
#include <err.h>

#include <altq/altq.h>
#include <altq/altq_cdnr.h>
#include <altq/altq_red.h>
#include <altq/altq_rio.h>
#include "altq_qop.h"
#include "qop_cdnr.h"

static int is_qdisc_name(const char *);
static int qdisc_interface_parser(const char *, const char *, int, char **);
static int qdisc_class_parser(const char *, const char *, const char *,
                             const char *, int, char **);
static int next_word(char **, char *);

static int get_ifname(char **, char **);
static int get_addr(char **, struct in_addr *, struct in_addr *);
static int get_port(const char *, u_int16_t *);
static int get_proto(const char *, int *);
static int get_fltr_opts(char **, char *, size_t, int *);
static int interface_parser(char *);
static int class_parser(char *) ;
static int filter_parser(char *);
#ifdef INET6
static int filter6_parser(char *);
static int get_ip6addr(char **, struct in6_addr *, struct in6_addr *);
#endif
static int ctl_parser(char *);
static int delete_parser(char *);
static int red_parser(char *);
static int rio_parser(char *);
static int conditioner_parser(char *);
static int tc_action_parser(char *, char **, struct tc_action *);

#define MAX_LINE        1024
#define MAX_WORD        128
#define MAX_ARGS        64
#define MAX_ACTIONS     16

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#define EQUAL(s1, s2)   (strcmp((s1), (s2)) == 0)

int     line_no = 0;
int     filter_dontwarn;

static char     curifname[IFNAMSIZ];
static struct if_nameindex *if_namelist = NULL;

struct cmd_tab {
       const char      *cmd;
       int             (*parser)(char *);
       const char      *help;
} cmd_tab[] = {
       {"?",           NULL,   "?"},
       {"help",        NULL,   "help"},
       {"quit",        NULL,   "quit"},
       {"interface",   interface_parser,       "interface if_name [bandwidth bps] [cbq|hfsc]"},
       {"class",       class_parser,   "class discipline if_name class_name [parent]"},
       {"filter",      filter_parser,  "filter if_name class_name [name filt_name] dst [netmask #] dport src [netmask #] sport proto [tos # [tosmask #] [gpi #] [dontwarn]"},
       {"altq",        ctl_parser,     "altq if_name {enable|disable}"},
       {"delete",      delete_parser,  "delete if_name class_name [filter_name]"},
#ifdef INET6
       {"filter6",     filter6_parser, "filter6 if_name class_name [name filt_name] dst[/prefix] dport src[/prefix] sport proto [flowlabel #][tclass # [tclassmask #]][gpi #] [dontwarn]"},
#endif
       {"red",         red_parser,     "red th_min th_max inv_pmax"},
       {"rio",         rio_parser,     "rio low_th_min low_th_max low_inv_pmax med_th_min med_th_max med_inv_pmax high_th_min high_th_max high_inv_pmax"},
       {"conditioner", conditioner_parser,     "conditioner if_name cdnr_name <tc_action>"},
       {"debug",       NULL,           "debug"},
       {NULL,          NULL,           NULL}   /* termination */
};

/*
* read one line from the specified stream. if it's a command,
* execute the command.
* returns 1 if OK, 0 if error or EOF.
*/
int
do_command(FILE *fp)
{
       char    cmd_line[MAX_LINE], cmd[MAX_WORD], *cp;
       struct cmd_tab *tp;
       int     len, rval;

       /*
        * read a line from the stream and make it a null-terminated string
        */
       cp = cmd_line;
read_line:
       if (fgets(cp, &cmd_line[MAX_LINE] - cp, fp) == NULL)
               /* EOF or error */
               return(0);
       line_no++;

       /* null-terminate the line */
       if ((len = strlen(cmd_line)) > 0) {
               cp = cmd_line + len - 1;
               if (*cp == '\n') {
                       /* if escaped newline, read next line */
                       if (len > 1 &&  *(cp - 1) == '\\')
                               goto read_line;
                       *cp = '\0';
               } else if (!feof(fp))
                       err(1, "LINE %d too long!", line_no);
       }
       /* trim comments */
       if ((cp = strchr(cmd_line, '#')) != NULL)
               *cp = '\0';

       cp = cmd_line;
       if ((len = next_word(&cp, cmd)) == 0)
               /* no command in this line */
               return (1);

       /* fnind the corresponding parser */
       rval = 0;
       for (tp = cmd_tab; tp->cmd != NULL; tp++)
               if (strncmp(cmd, tp->cmd, len) == 0)
                       break;

       if (tp->cmd == NULL) {
               if (fp == stdin) {
                       printf(" ?? %s\n", cmd);
                       rval = 1;
               } else
                       LOG(LOG_ERR, 0, "unknown command: %s", cmd);
               return (rval);
       }

       if (tp->parser != NULL)
               rval = (*tp->parser)(cp);
       else {
               /* handle other commands */
               if (strcmp(tp->cmd, "quit") == 0)
                       rval = 0;
               else if (strcmp(tp->cmd, "help") == 0 ||
                        strcmp(tp->cmd, "?") == 0) {
                       for (tp = cmd_tab; tp->cmd != NULL; tp++)
                               printf("%s\n", tp->help);
                       rval = 1;
               } else if (strcmp(tp->cmd, "debug") == 0) {
                       if (m_debug & DEBUG_ALTQ) {
                               /* turn off verbose */
                               l_debug = LOG_INFO;
                               m_debug &= ~DEBUG_ALTQ;
                       } else {
                               /* turn on verbose */
                               l_debug = LOG_DEBUG;
                               m_debug |= DEBUG_ALTQ;
                       }
                       rval = 1;
               }
       }
       return (rval);
}

static int
is_qdisc_name(const char *qname)
{
       struct qdisc_parser *qp;

       for (qp = qdisc_parser; qp->qname != NULL; qp++)
               if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0)
                       return (1);
       return (0);
}

static int
qdisc_interface_parser(const char * qname, const char *ifname,
                      int argc, char **argv)
{
       struct qdisc_parser *qp;

       for (qp = qdisc_parser; qp->qname != NULL; qp++)
               if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0)
                       return (*qp->interface_parser)(ifname, argc, argv);
       return (0);
}

static int
qdisc_class_parser(const char *qname, const char *ifname,
                  const char *class_name, const char *parent_name,
                  int argc, char **argv)
{
       struct qdisc_parser *qp;
       struct ifinfo   *ifinfo;

       for (qp = qdisc_parser; qp->qname != NULL; qp++)
               if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0) {
                       if (qp->class_parser == NULL) {
                               LOG(LOG_ERR, 0,
                                   "class can't be specified for %s", qp->qname);
                               return (0);
                       }
                       if ((ifinfo = ifname2ifinfo(ifname)) == NULL) {
                               LOG(LOG_ERR, 0, "no such interface");
                               return (0);
                       }
                       if (strncmp(ifinfo->qdisc->qname, qname,
                                   strlen(ifinfo->qdisc->qname)) != 0) {
                               LOG(LOG_ERR, 0,
                                   "qname doesn't match the interface");
                               return (0);
                       }
                       return (*qp->class_parser)(ifname, class_name,
                                                  parent_name, argc, argv);
               }
       return (0);
}

/*
* read the config file
*/
int
qcmd_config(void)
{
       FILE    *fp;
       int     rval;

       if (if_namelist != NULL)
               if_freenameindex(if_namelist);
       if_namelist = if_nameindex();
       curifname[0] = '\0';

       LOG(LOG_INFO, 0, "ALTQ config file is %s", altqconfigfile);

       fp = fopen(altqconfigfile, "r");
       if (fp == NULL) {
               LOG(LOG_ERR, errno, "can't open %s", altqconfigfile, 0);
               return (QOPERR_INVAL);
       }
       line_no = 0;
       rval = 1;
       while (rval)
               rval = do_command(fp);

       if (!feof(fp)) {
               LOG(LOG_ERR, 0, "Error in %s, line %d.  config failed.",
                   altqconfigfile, line_no);
               (void) qcmd_destroyall();
               rval = QOPERR_INVAL;
       } else
               rval = 0;

       (void)fclose(fp);
       line_no = 0;
       return (rval);
}

static int
next_word(char **cpp, char *b)
{
       char    *cp;
       int     i;

       cp = *cpp;
       while (*cp == ' ' || *cp == '\t')
               cp++;
       for (i = 0; i < MAX_WORD - 1; i++) {
               if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == '\0')
                       break;
               *b++ = *cp++;
       }
       *b = '\0';
       *cpp = cp;
       return (i);
}

char *
cur_ifname(void)
{
       return (curifname);
}

u_int
get_ifindex(const char *ifname)
{
       struct if_nameindex *ifnp;

       for (ifnp = if_namelist; ifnp->if_name != NULL; ifnp++)
               if (strcmp(ifname, ifnp->if_name) == 0)
                       return (ifnp->if_index);
       return (0);
}

static int
get_ifname(char **cpp, char **ifnamep)
{
       char w[MAX_WORD], *ocp;
       struct if_nameindex *ifnp;

       ocp = *cpp;
       if (next_word(&ocp, w) && if_namelist != NULL)
               for (ifnp = if_namelist; ifnp->if_name != NULL; ifnp++)
                       if (strcmp(w, ifnp->if_name) == 0) {
                               /* if_name found. advance the word pointer */
                               *cpp = ocp;
                               strlcpy(curifname, w, sizeof(curifname));
                               *ifnamep = curifname;
                               return (1);
                       }

       /* this is not interface name. use one in the context. */
       if (curifname[0] == '\0')
               return (0);
       *ifnamep = curifname;
       return (1);
}

/* set address and netmask in network byte order */
static int
get_addr(char **cpp, struct in_addr *addr, struct in_addr *mask)
{
       char w[MAX_WORD], *ocp;
       struct in_addr tmp;

       addr->s_addr = 0;
       mask->s_addr = 0xffffffff;

       if (!next_word(cpp, w))
               return (0);

       if (inet_aton((char *)w, &tmp) != 1) {
               /* try gethostbyname */
               struct hostent *h;

               if ((h = gethostbyname(w)) == NULL ||
                   h->h_addrtype != AF_INET || h->h_length != 4)
                       return (0);
               bcopy(h->h_addr, &tmp, (size_t)h->h_length);
       }
       addr->s_addr = tmp.s_addr;

       /* check if netmask option is present */
       ocp = *cpp;
       if (next_word(&ocp, w) && EQUAL(w, "netmask")) {
               if (!next_word(&ocp, w))
                       return (0);
               if (inet_aton((char *)w, (struct in_addr *)&tmp) != 1)
                       return (0);

               mask->s_addr = tmp.s_addr;
               *cpp = ocp;
               return (1);
       }
       /* no netmask option */
       return (1);
}

/* returns service number in network byte order */
static int
get_port(const char *name, u_int16_t *port_no)
{
       struct servent *s;
       u_int16_t num;

       if (isdigit((unsigned char)name[0])) {
               num = (u_int16_t)strtol(name, NULL, 0);
               *port_no = htons(num);
               return (1);
       }

       if ((s = getservbyname(name, 0)) == NULL)
               return (0);

       *port_no = (u_int16_t)s->s_port;
       return (1);
}

static int
get_proto(const char *name, int *proto_no)
{
       struct protoent *p;

       if (isdigit((unsigned char)name[0])) {
               *proto_no = (int)strtol(name, NULL, 0);
               return (1);
       }

       if ((p = getprotobyname(name)) == NULL)
               return (0);

       *proto_no = p->p_proto;
       return (1);
}

static int
get_fltr_opts(char **cpp, char *fltr_name, size_t len, int *ruleno)
{
       char w[MAX_WORD], *ocp;

       ocp = *cpp;
       while (next_word(&ocp, w)) {
               if (EQUAL(w, "name")) {
                       if (!next_word(&ocp, w))
                               return (0);
                       strlcpy(fltr_name, w, len);
                       *cpp = ocp;
               } else if (EQUAL(w, "ruleno")) {
                       if (!next_word(&ocp, w))
                               return (0);
                       *ruleno = (int)strtol(w, NULL, 0);
                       *cpp = ocp;
               } else
                       break;
       }
       return (1);
}


#define DISCIPLINE_NONE         0

static int
interface_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *ap, *cp = cmdbuf;
       char    *ifname, *argv[MAX_ARGS], qdisc_name[MAX_WORD];
       int     argc;

       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name");
               return (0);
       }

       /* create argument list & look for scheduling discipline options. */
       snprintf(qdisc_name, sizeof qdisc_name, "null");
       argc = 0;
       ap = w;
       while (next_word(&cp, ap)) {
               if (is_qdisc_name(ap))
                       strlcpy(qdisc_name, ap, sizeof qdisc_name);

               argv[argc] = ap;
               ap += strlen(ap) + 1;
               argc++;
               if (argc >= MAX_ARGS) {
                       LOG(LOG_ERR, 0, "too many args");
                       return (0);
               }
       }

       return qdisc_interface_parser(qdisc_name, ifname, argc, argv);
}


static int
class_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       char    *ifname, qdisc_name[MAX_WORD];
       char    class_name[MAX_WORD], parent_name[MAX_WORD];
       char    *clname = class_name;
       char    *parent = NULL;
       char    *argv[MAX_ARGS], *ap;
       int     argc;

       /* get scheduling class */
       if (!next_word(&cp, qdisc_name)) {
               LOG(LOG_ERR, 0, "missing discipline");
               return (0);
       }
       if (!is_qdisc_name(qdisc_name)) {
               LOG(LOG_ERR, 0, "unknown discipline '%s'", qdisc_name);
               return (0);
       }

       /* get interface name */
       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name");
               return (0);
       }

       /* get class name */
       if (!next_word(&cp, class_name)) {
               LOG(LOG_ERR, 0, "missing class name");
               return (0);
       }

       /* get parent name */
       if (!next_word(&cp, parent_name)) {
               LOG(LOG_ERR, 0, "missing parent class");
               return (0);
       }
       if (!EQUAL(parent_name, "null") && !EQUAL(parent_name, "NULL"))
               parent = parent_name;
       else
               parent = NULL;

       ap = w;
       argc = 0;
       while (next_word(&cp, ap)) {
               argv[argc] = ap;
               ap += strlen(ap) + 1;
               argc++;
               if (argc >= MAX_ARGS) {
                       LOG(LOG_ERR, 0, "too many args");
                       return (0);
               }
       }

       return qdisc_class_parser(qdisc_name, ifname, clname, parent,
                                 argc, argv);
}

static int
filter_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       char    *ifname, class_name[MAX_WORD], fltr_name[MAX_WORD];
       char    *flname = NULL;
       struct flow_filter      sfilt;
       int     protocol;
       u_char  tos, tosmask;
       int     ruleno;
       int     dontwarn = 0;
       int     error;

       memset(&sfilt, 0, sizeof(sfilt));
       sfilt.ff_flow.fi_family = AF_INET;

       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name in filter command");
               return (0);
       }

       if (!next_word(&cp, class_name)) {
               LOG(LOG_ERR, 0, "missing class name in filter command");
               return (0);
       }

       fltr_name[0] = '\0';
       ruleno = 0;
       if (!get_fltr_opts(&cp, &fltr_name[0], sizeof(fltr_name), &ruleno)) {
               LOG(LOG_ERR, 0, "bad filter option");
               return (0);
       }
       if (fltr_name[0] != '\0')
               flname = fltr_name;
       sfilt.ff_ruleno = ruleno;

       /* get filter destination Address */
       if (!get_addr(&cp, &sfilt.ff_flow.fi_dst, &sfilt.ff_mask.mask_dst)) {
               LOG(LOG_ERR, 0, "bad filter destination address");
               return (0);
       }

       /* get filter destination port */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter destination port");
               return (0);
       }
       if (!get_port(w, &sfilt.ff_flow.fi_dport)) {
               LOG(LOG_ERR, 0, "bad filter destination port");
               return (0);
       }

       /* get filter source address */
       if (!get_addr(&cp, &sfilt.ff_flow.fi_src, &sfilt.ff_mask.mask_src)) {
               LOG(LOG_ERR, 0, "bad filter source address");
               return (0);
       }

       /* get filter source port */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter source port");
               return (0);
       }
       if (!get_port(w, &sfilt.ff_flow.fi_sport)) {
               LOG(LOG_ERR, 0, "bad filter source port");
               return (0);
       }

       /* get filter protocol id */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter protocol");
               return (0);
       }
       if (!get_proto(w, &protocol)) {
               LOG(LOG_ERR, 0, "bad protocol");
               return (0);
       }
       sfilt.ff_flow.fi_proto = protocol;

       while (next_word(&cp, w)) {
               if (EQUAL(w, "tos")) {
                       tos = 0;
                       tosmask = 0xff;

                       if (next_word(&cp, w)) {
                               tos = (u_char)strtol(w, NULL, 0);
                               if (next_word(&cp, w)) {
                                       if (EQUAL(w, "tosmask")) {
                                               next_word(&cp, w);
                                               tosmask = (u_char)strtol(w, NULL, 0);
                                       }
                               }
                       }
                       sfilt.ff_flow.fi_tos = tos;
                       sfilt.ff_mask.mask_tos = tosmask;
               } else if (EQUAL(w, "gpi")) {
                       if (next_word(&cp, w)) {
                               sfilt.ff_flow.fi_gpi =
                                       (u_int32_t)strtoul(w, NULL, 0);
                               sfilt.ff_flow.fi_gpi =
                                       htonl(sfilt.ff_flow.fi_gpi);
                       }
               } else if (EQUAL(w, "dontwarn"))
                       dontwarn = 1;
       }

       /*
        * Add the filter.
        */
       filter_dontwarn = dontwarn;     /* XXX */
       error = qcmd_add_filter(ifname, class_name, flname, &sfilt);
       filter_dontwarn = 0;            /* XXX */
       if (error) {
               LOG(LOG_ERR, 0,
                   "can't add filter to class '%s' on interface '%s'",
                   class_name, ifname);
               return (0);
       }
       return (1);
}

#ifdef INET6
static int
filter6_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       char    *ifname, class_name[MAX_WORD], fltr_name[MAX_WORD];
       char    *flname = NULL;
       struct flow_filter6     sfilt;
       int     protocol;
       u_char  tclass, tclassmask;
       int     ruleno;
       int     dontwarn = 0;
       int     ret;

       memset(&sfilt, 0, sizeof(sfilt));
       sfilt.ff_flow6.fi6_family = AF_INET6;

       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name");
               return (0);
       }

       if (!next_word(&cp, class_name)) {
               LOG(LOG_ERR, 0, "missing class name");
               return (0);
       }

       fltr_name[0] = '\0';
       ruleno = 0;
       if (!get_fltr_opts(&cp, &fltr_name[0], sizeof(fltr_name), &ruleno)) {
               LOG(LOG_ERR, 0, "bad filter option");
               return (0);
       }
       if (fltr_name[0] != '\0')
               flname = fltr_name;
       sfilt.ff_ruleno = ruleno;

       /* get filter destination address */
       if (!get_ip6addr(&cp, &sfilt.ff_flow6.fi6_dst,
                        &sfilt.ff_mask6.mask6_dst)) {
               LOG(LOG_ERR, 0, "bad destination address");
               return (0);
       }

       /* get filter destination port */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter destination port");
               return (0);
       }
       if (!get_port(w, &sfilt.ff_flow6.fi6_dport)) {
               LOG(LOG_ERR, 0, "bad filter destination port");
               return (0);
       }

       /* get filter source address */
       if (!get_ip6addr(&cp, &sfilt.ff_flow6.fi6_src,
                        &sfilt.ff_mask6.mask6_src)) {
               LOG(LOG_ERR, 0, "bad source address");
               return (0);
       }

       /* get filter source port */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter source port");
               return (0);
       }
       if (!get_port(w, &sfilt.ff_flow6.fi6_sport)) {
               LOG(LOG_ERR, 0, "bad filter source port");
               return (0);
       }

       /* get filter protocol id */
       if (!next_word(&cp, w)) {
               LOG(LOG_ERR, 0, "missing filter protocol");
               return (0);
       }
       if (!get_proto(w, &protocol)) {
               LOG(LOG_ERR, 0, "bad protocol");
               return (0);
       }
       sfilt.ff_flow6.fi6_proto = protocol;

       while (next_word(&cp, w)) {
               if (EQUAL(w, "tclass")) {
                       tclass = 0;
                       tclassmask = 0xff;

                       if (next_word(&cp, w)) {
                               tclass = (u_char)strtol(w, NULL, 0);
                               if (next_word(&cp, w)) {
                                       if (EQUAL(w, "tclassmask")) {
                                               next_word(&cp, w);
                                               tclassmask =
                                                   (u_char)strtol(w, NULL, 0);
                                       }
                               }
                       }
                       sfilt.ff_flow6.fi6_tclass = tclass;
                       sfilt.ff_mask6.mask6_tclass = tclassmask;
               } else if (EQUAL(w, "gpi")) {
                       if (next_word(&cp, w)) {
                               sfilt.ff_flow6.fi6_gpi =
                                       (u_int32_t)strtoul(w, NULL, 0);
                               sfilt.ff_flow6.fi6_gpi =
                                       htonl(sfilt.ff_flow6.fi6_gpi);
                       }
               } else if (EQUAL(w, "flowlabel")) {
                       if (next_word(&cp, w)) {
                               sfilt.ff_flow6.fi6_flowlabel =
                                  (u_int32_t)strtoul(w, NULL, 0) & 0x000fffff;
                               sfilt.ff_flow6.fi6_flowlabel =
                                       htonl(sfilt.ff_flow6.fi6_flowlabel);
                       }
               } else if (EQUAL(w, "dontwarn"))
                       dontwarn = 1;
       }

       /*
        * Add the filter.
        */
       filter_dontwarn = dontwarn;     /* XXX */
       ret = qcmd_add_filter(ifname, class_name, flname,
                             (struct flow_filter *)&sfilt);
       filter_dontwarn = 0;            /* XXX */
       if (ret) {
               LOG(LOG_ERR, 0,
                   "can't add filter to class '%s' on interface '%s'",
                   class_name, ifname);
               return (0);
       }

       return (1);
}

static int
get_ip6addr(char **cpp, struct in6_addr *addr, struct in6_addr *mask)
{
       char w[MAX_WORD], *prefix;
       u_char *cp;
       int len;

       *addr = in6addr_any;  /* set all 0 */
       *mask = in6addr_any;  /* set all 0 */

       if (!next_word(cpp, w))
               return (0);

       if (EQUAL(w, "0"))
               /* abbreviation of a wildcard (::0) */
               return (1);

       if ((prefix = strchr(w, '/')) != NULL) {
               /* address has prefix length */
               *prefix++ = '\0';
       }

       if (inet_pton(AF_INET6, w, addr) != 1)
               return (0);

       if (IN6_IS_ADDR_UNSPECIFIED(addr) && prefix == NULL)
               /* wildcard */
               return (1);

       /* convert address prefix length to address mask */
       if (prefix != NULL) {
               len = (int)strtol(prefix, NULL, 0);
               if ((len < 0) || (len > 128))
                       return (0);
               for (cp = (u_char *)mask; len > 7; len -= 8)
                       *cp++ = 0xff;
               if (len > 0)
                       *cp = (0xff << (8 - len)) & 0xff;

               IN6ADDR32_SET(addr, 0, IN6ADDR32_GET(mask, 0) &
                   IN6ADDR32_GET(addr, 0));
               IN6ADDR32_SET(addr, 1, IN6ADDR32_GET(mask, 1) &
                   IN6ADDR32_GET(addr, 1));
               IN6ADDR32_SET(addr, 2, IN6ADDR32_GET(mask, 2) &
                   IN6ADDR32_GET(addr, 2));
               IN6ADDR32_SET(addr, 3, IN6ADDR32_GET(mask, 3) &
                   IN6ADDR32_GET(addr, 3));
       } else
               /* full mask */
               memset(mask, 0xff, sizeof(struct in6_addr));

       return (1);
}

#endif /* INET6 */

static int
ctl_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       char    *ifname;
       int     state;
       int     rval;

       if (!get_ifname(&cp, &ifname)) {
               printf("missing interface name in %s, line %d",
                      altqconfigfile, line_no);
               return (0);
       }

       if (!next_word(&cp, w)) {
               state = is_q_enabled(ifname);
               printf("altq %s on %s\n",
                      state ? "enabled" : "disabled", ifname);
               return (1);
       }

       if (EQUAL(w, "enable")) {
               rval = qcmd_enable(ifname);
               printf("altq %s on %s\n",
                      (rval == 0) ? "enabled" : "enable failed!", ifname);
       } else if (EQUAL(w, "disable")) {
               rval = qcmd_disable(ifname);
               printf("altq %s on %s\n",
                      (rval == 0) ? "disabled" : "disable failed!", ifname);
       } else if (EQUAL(w, "reload")) {
               printf("reinitializing altq...\n");
               qcmd_destroyall();
               qcmd_init();
       } else
               return (0);
       return (1);
}

static int
delete_parser(char *cmdbuf)
{
       char    *cp = cmdbuf;
       char    *ifname, class_name[MAX_WORD], filter_name[MAX_WORD];
       int     ret;

       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name");
               return (0);
       }

       if (!next_word(&cp, class_name)) {
               LOG(LOG_ERR, 0, "missing class name");
               return (0);
       }

       /* check if filter is specified */
       if (next_word(&cp, filter_name)) {
               ret = qcmd_delete_filter(ifname, class_name, filter_name);
               if (ret) {
                       LOG(LOG_ERR, 0,
                           "can't delete filter '%s' on interface '%s'",
                           filter_name, ifname);
                       return (0);
               }
               return (1);
       }

       ret = qcmd_delete_class(ifname, class_name);
       if (ret) {
               LOG(LOG_ERR, 0,
                   "can't delete class '%s' on interface '%s'",
                   class_name, ifname);
               return (0);
       }

       return (1);
}

static int
red_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       int th_min, th_max, inv_pmax;

       if (!next_word(&cp, w))
               goto bad;
       th_min = (int)strtol(w, NULL, 0);

       if (!next_word(&cp, w))
               goto bad;
       th_max = (int)strtol(w, NULL, 0);

       if (!next_word(&cp, w))
               goto bad;
       inv_pmax = (int)strtol(w, NULL, 0);

       if (qop_red_set_defaults(th_min, th_max, inv_pmax) != 0) {
               LOG(LOG_ERR, 0, "can't set red default parameters");
               return (0);
       }

       return (1);

bad:
       LOG(LOG_ERR, 0, "bad red parameter");
       return (0);
}

static int
rio_parser(char *cmdbuf)
{
       char    w[MAX_WORD], *cp = cmdbuf;
       int     i;
       struct redparams params[RIO_NDROPPREC];

       for (i = 0; i < RIO_NDROPPREC; i++) {
               if (!next_word(&cp, w))
                       goto bad;
               params[i].th_min = (int)strtol(w, NULL, 0);

               if (!next_word(&cp, w))
                       goto bad;
               params[i].th_max = (int)strtol(w, NULL, 0);

               if (!next_word(&cp, w))
                       goto bad;
               params[i].inv_pmax = (int)strtol(w, NULL, 0);
       }

       if (qop_rio_set_defaults(&params[0]) != 0) {
               LOG(LOG_ERR, 0, "can't set rio default parameters");
               return (0);
       }

       return (1);

bad:
       LOG(LOG_ERR, 0, "bad rio parameter");
       return (0);
}

static int
conditioner_parser(char *cmdbuf)
{
       char    cdnr_name[MAX_WORD], *cp = cmdbuf;
       char    *ifname;
       struct tc_action action[MAX_ACTIONS];

       if (!get_ifname(&cp, &ifname)) {
               LOG(LOG_ERR, 0, "missing interface name");
               return (0);
       }

       /* get conditioner name */
       if (!next_word(&cp, cdnr_name)) {
               LOG(LOG_ERR, 0, "missing cdnr name");
               return (0);
       }

       if (tc_action_parser(ifname, &cp, &action[0]) == 0)
               return (0);

       if (qcmd_cdnr_add_element(NULL, ifname, cdnr_name, &action[0]) != 0)
               return (0);
       return (1);
}

/*
* recursively parse '<'tc_action'>'
* note that array "action" grows during recursive parse.
*/
static int
tc_action_parser(char *ifname, char **cpp, struct tc_action *action)
{
       char    *cp, *start, *end;
       char    type[MAX_WORD], w[MAX_WORD];
       int     depth, i;
       struct tb_profile profile[2];

       /*
        * find a possibly nested pair of '<' and '>',
        * make them pointed by 'start' and 'end'.
        */
       start = strchr(*cpp, '<');
       if (start == NULL) {
               LOG(LOG_ERR, 0, "conditioner action missing");
               return (0);
       }
       depth = 1;
       cp = start + 1;
       do {
               end = strpbrk(cp, "<>");
               if (end == NULL) {
                       LOG(LOG_ERR, 0,
                           "conditioner action delimiter mismatch");
                       return (0);
               }
               if (*end == '<')
                       depth++;
               else if (*end == '>')
                       depth--;
               cp = end + 1;
       } while (depth > 0);
       *end = '\0';
       *cpp = end + 1;
       cp = start + 1;

       if (IsDebug(DEBUG_ALTQ)) {
               printf("tc_action_parser: [%s]\n", cp);
       }

       if (!next_word(&cp, type)) {
               LOG(LOG_ERR, 0, "missing conditioner action type");
               return (0);
       }

       /*
        * action type specific process
        */
       if (EQUAL(type, "conditioner")) {
               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0,
                           "missing conditioner name");
                       return (0);
               }
               action->tca_code = TCACODE_HANDLE;
               action->tca_handle = cdnr_name2handle(ifname, w);
               if (action->tca_handle == CDNR_NULL_HANDLE) {
                       LOG(LOG_ERR, 0,
                           "wrong conditioner name %s", w);
                       return (0);
               }
       } else if (EQUAL(type, "pass")) {
               action->tca_code = TCACODE_PASS;
       } else if (EQUAL(type, "drop")) {
               action->tca_code = TCACODE_DROP;
       } else if (EQUAL(type, "mark")) {
               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing dscp");
                       return (0);
               }
               action->tca_code = TCACODE_MARK;
               action->tca_dscp = (u_int8_t)strtol(w, NULL, 0);
       } else if (EQUAL(type, "tbmeter")) {
               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing tb profile");
                       return (0);
               }
               profile[0].rate = atobps(w);
               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing tb profile");
                       return (0);
               }
               profile[0].depth = atobytes(w);
               if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                       return (0);
               if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                       return (0);

               if (qcmd_cdnr_add_tbmeter(action, ifname, NULL, &profile[0],
                                         &action[1], &action[2]) != 0)
                       return (0);
       } else if (EQUAL(type, "trtcm")) {
               int coloraware = 0;     /* default is color-blind */

               for (i=0; i<2; i++) {
                       if (!next_word(&cp, w)) {
                               LOG(LOG_ERR, 0, "missing tb profile");
                               return (0);
                       }
                       profile[i].rate = atobps(w);
                       if (!next_word(&cp, w)) {
                               LOG(LOG_ERR, 0, "missing tb profile");
                               return (0);
                       }
                       profile[i].depth = atobytes(w);
               }
               if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                       return (0);
               if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                       return (0);
               if (tc_action_parser(ifname, &cp, &action[3]) == 0)
                       return (0);
               if (next_word(&cp, w)) {
                       if (EQUAL(w, "coloraware"))
                               coloraware = 1;
                       else if (EQUAL(w, "colorblind"))
                               coloraware = 0;
               }

               if (qcmd_cdnr_add_trtcm(action, ifname, NULL,
                                       &profile[0], &profile[1],
                                       &action[1], &action[2], &action[3],
                                       coloraware) != 0)
                       return (0);
       } else if (EQUAL(type, "tswtcm")) {
               u_int32_t cmtd_rate, peak_rate, avg_interval;

               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing cmtd rate");
                       return (0);
               }
               cmtd_rate = atobps(w);

               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing peak rate");
                       return (0);
               }
               peak_rate = atobps(w);

               if (!next_word(&cp, w)) {
                       LOG(LOG_ERR, 0, "missing avg interval");
                       return (0);
               }
               avg_interval = (u_int32_t)strtoul(w, NULL, 0);

               if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                       return (0);
               if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                       return (0);
               if (tc_action_parser(ifname, &cp, &action[3]) == 0)
                       return (0);

               if (qcmd_cdnr_add_tswtcm(action, ifname, NULL,
                                        cmtd_rate, peak_rate, avg_interval,
                                        &action[1], &action[2], &action[3])
                   != 0)
                       return (0);
       } else {
               LOG(LOG_ERR, 0, "unknown action type %s");
               return (0);
       }

       *end = '>';     /* restore the end delimiter */

       return (1);
}