root/sbin/ipf/ipmon/ipmon_y.y

/*
 * Copyright (C) 2012 by Darren Reed.
 *
 * See the IPFILTER.LICENCE file for details on licencing.
 */
%{
#include "ipf.h"
#include <syslog.h>
#undef  OPT_NAT
#undef  OPT_VERBOSE
#include "ipmon_l.h"
#include "ipmon.h"

#include <dlfcn.h>

#define YYDEBUG 1

extern  void    yyerror(char *);
extern  int     yyparse(void);
extern  int     yylex(void);
extern  int     yydebug;
extern  FILE    *yyin;
extern  int     yylineNum;
extern  int     ipmonopts;

typedef struct  opt_s   {
        struct  opt_s   *o_next;
        int             o_line;
        int             o_type;
        int             o_num;
        char            *o_str;
        struct in_addr  o_ip;
        int             o_logfac;
        int             o_logpri;
} opt_t;

static  void    build_action(opt_t *, ipmon_doing_t *);
static  opt_t   *new_opt(int);
static  void    free_action(ipmon_action_t *);
static  void    print_action(ipmon_action_t *);
static  int     find_doing(char *);
static  ipmon_doing_t *build_doing(char *, char *);
static  void    print_match(ipmon_action_t *);
static  int     install_saver(char *, char *);

static  ipmon_action_t  *alist = NULL;

ipmon_saver_int_t       *saverlist = NULL;
%}

%union  {
        char    *str;
        u_32_t  num;
        struct in_addr  addr;
        struct opt_s    *opt;
        union   i6addr  ip6;
        struct ipmon_doing_s    *ipmd;
}

%token  <num>   YY_NUMBER YY_HEX
%token  <str>   YY_STR
%token  <ip6>   YY_IPV6
%token  YY_COMMENT
%token  YY_CMP_EQ YY_CMP_NE YY_CMP_LE YY_CMP_GE YY_CMP_LT YY_CMP_GT
%token  YY_RANGE_OUT YY_RANGE_IN

%token  IPM_MATCH IPM_BODY IPM_COMMENT IPM_DIRECTION IPM_DSTIP IPM_DSTPORT
%token  IPM_EVERY IPM_GROUP IPM_INTERFACE IPM_IN IPM_NO IPM_OUT IPM_LOADACTION
%token  IPM_PACKET IPM_PACKETS IPM_POOL IPM_PROTOCOL IPM_RESULT IPM_RULE
%token  IPM_SECOND IPM_SECONDS IPM_SRCIP IPM_SRCPORT IPM_LOGTAG IPM_WITH
%token  IPM_DO IPM_DOING IPM_TYPE IPM_NAT
%token  IPM_STATE IPM_NATTAG IPM_IPF
%type   <addr> ipv4
%type   <opt> direction dstip dstport every group interface
%type   <opt> protocol result rule srcip srcport logtag matching
%type   <opt> matchopt nattag type
%type   <num> typeopt
%type   <ipmd> doopt doing

%%
file:   action
        | file action
        ;

action: line ';'
        | assign ';'
        | IPM_COMMENT
        | YY_COMMENT
        ;

line:   IPM_MATCH '{' matching ';' '}' IPM_DO '{' doing ';' '}'
                                                { build_action($3, $8);
                                                  resetlexer();
                                                }
        | IPM_LOADACTION YY_STR YY_STR  { if (install_saver($2, $3))
                                                yyerror("install saver");
                                        }
        ;

assign: YY_STR assigning YY_STR                 { set_variable($1, $3);
                                                  resetlexer();
                                                  free($1);
                                                  free($3);
                                                  yyvarnext = 0;
                                                }
        ;

assigning:
        '='                                     { yyvarnext = 1; }
        ;

matching:
        matchopt                                { $$ = $1; }
        | matchopt ',' matching                 { $1->o_next = $3; $$ = $1; }
        ;

matchopt:
        direction                               { $$ = $1; }
        | dstip                                 { $$ = $1; }
        | dstport                               { $$ = $1; }
        | every                                 { $$ = $1; }
        | group                                 { $$ = $1; }
        | interface                             { $$ = $1; }
        | protocol                              { $$ = $1; }
        | result                                { $$ = $1; }
        | rule                                  { $$ = $1; }
        | srcip                                 { $$ = $1; }
        | srcport                               { $$ = $1; }
        | logtag                                { $$ = $1; }
        | nattag                                { $$ = $1; }
        | type                                  { $$ = $1; }
        ;

doing:
        doopt                                   { $$ = $1; }
        | doopt ',' doing                       { $1->ipmd_next = $3; $$ = $1; }
        ;

doopt:
        YY_STR                          { if (find_doing($1) != IPM_DOING)
                                                yyerror("unknown action");
                                        }
        '(' YY_STR ')'                  { $$ = build_doing($1, $4);
                                          if ($$ == NULL)
                                                yyerror("action building");
                                        }
        | YY_STR                        { if (find_doing($1) == IPM_DOING)
                                                $$ = build_doing($1, NULL);
                                        }
        ;

direction:
        IPM_DIRECTION '=' IPM_IN                { $$ = new_opt(IPM_DIRECTION);
                                                  $$->o_num = IPM_IN; }
        | IPM_DIRECTION '=' IPM_OUT             { $$ = new_opt(IPM_DIRECTION);
                                                  $$->o_num = IPM_OUT; }
        ;

dstip:  IPM_DSTIP '=' ipv4 '/' YY_NUMBER        { $$ = new_opt(IPM_DSTIP);
                                                  $$->o_ip = $3;
                                                  $$->o_num = $5; }
        ;

dstport:
        IPM_DSTPORT '=' YY_NUMBER               { $$ = new_opt(IPM_DSTPORT);
                                                  $$->o_num = $3; }
        | IPM_DSTPORT '=' YY_STR                { $$ = new_opt(IPM_DSTPORT);
                                                  $$->o_str = $3; }
        ;

every:  IPM_EVERY IPM_SECOND                    { $$ = new_opt(IPM_SECOND);
                                                  $$->o_num = 1; }
        | IPM_EVERY YY_NUMBER IPM_SECONDS       { $$ = new_opt(IPM_SECOND);
                                                  $$->o_num = $2; }
        | IPM_EVERY IPM_PACKET                  { $$ = new_opt(IPM_PACKET);
                                                  $$->o_num = 1; }
        | IPM_EVERY YY_NUMBER IPM_PACKETS       { $$ = new_opt(IPM_PACKET);
                                                  $$->o_num = $2; }
        ;

group:  IPM_GROUP '=' YY_NUMBER                 { $$ = new_opt(IPM_GROUP);
                                                  $$->o_num = $3; }
        | IPM_GROUP '=' YY_STR                  { $$ = new_opt(IPM_GROUP);
                                                  $$->o_str = $3; }
        ;

interface:
        IPM_INTERFACE '=' YY_STR                { $$ = new_opt(IPM_INTERFACE);
                                                  $$->o_str = $3; }
        ;

logtag: IPM_LOGTAG '=' YY_NUMBER                { $$ = new_opt(IPM_LOGTAG);
                                                  $$->o_num = $3; }
        ;

nattag: IPM_NATTAG '=' YY_STR                   { $$ = new_opt(IPM_NATTAG);
                                                  $$->o_str = $3; }
        ;

protocol:
        IPM_PROTOCOL '=' YY_NUMBER              { $$ = new_opt(IPM_PROTOCOL);
                                                  $$->o_num = $3; }
        | IPM_PROTOCOL '=' YY_STR               { $$ = new_opt(IPM_PROTOCOL);
                                                  $$->o_num = getproto($3);
                                                  free($3);
                                                }
        ;

result: IPM_RESULT '=' YY_STR                   { $$ = new_opt(IPM_RESULT);
                                                  $$->o_str = $3; }
        ;

rule:   IPM_RULE '=' YY_NUMBER                  { $$ = new_opt(IPM_RULE);
                                                  $$->o_num = YY_NUMBER; }
        ;

srcip:  IPM_SRCIP '=' ipv4 '/' YY_NUMBER        { $$ = new_opt(IPM_SRCIP);
                                                  $$->o_ip = $3;
                                                  $$->o_num = $5; }
        ;

srcport:
        IPM_SRCPORT '=' YY_NUMBER               { $$ = new_opt(IPM_SRCPORT);
                                                  $$->o_num = $3; }
        | IPM_SRCPORT '=' YY_STR                { $$ = new_opt(IPM_SRCPORT);
                                                  $$->o_str = $3; }
        ;

type:   IPM_TYPE '=' typeopt                    { $$ = new_opt(IPM_TYPE);
                                                  $$->o_num = $3; }
        ;

typeopt:
        IPM_IPF                                 { $$ = IPL_MAGIC; }
        | IPM_NAT                               { $$ = IPL_MAGIC_NAT; }
        | IPM_STATE                             { $$ = IPL_MAGIC_STATE; }
        ;



ipv4:   YY_NUMBER '.' YY_NUMBER '.' YY_NUMBER '.' YY_NUMBER
                { if ($1 > 255 || $3 > 255 || $5 > 255 || $7 > 255) {
                        yyerror("Invalid octet string for IP address");
                        return(0);
                  }
                  $$.s_addr = ($1 << 24) | ($3 << 16) | ($5 << 8) | $7;
                  $$.s_addr = htonl($$.s_addr);
                }
%%
static  struct  wordtab yywords[] = {
        { "body",       IPM_BODY },
        { "direction",  IPM_DIRECTION },
        { "do",         IPM_DO },
        { "dstip",      IPM_DSTIP },
        { "dstport",    IPM_DSTPORT },
        { "every",      IPM_EVERY },
        { "group",      IPM_GROUP },
        { "in",         IPM_IN },
        { "interface",  IPM_INTERFACE },
        { "ipf",        IPM_IPF },
        { "load_action",IPM_LOADACTION },
        { "logtag",     IPM_LOGTAG },
        { "match",      IPM_MATCH },
        { "nat",        IPM_NAT },
        { "nattag",     IPM_NATTAG },
        { "no",         IPM_NO },
        { "out",        IPM_OUT },
        { "packet",     IPM_PACKET },
        { "packets",    IPM_PACKETS },
        { "protocol",   IPM_PROTOCOL },
        { "result",     IPM_RESULT },
        { "rule",       IPM_RULE },
        { "second",     IPM_SECOND },
        { "seconds",    IPM_SECONDS },
        { "srcip",      IPM_SRCIP },
        { "srcport",    IPM_SRCPORT },
        { "state",      IPM_STATE },
        { "with",       IPM_WITH },
        { NULL,         0 }
};

static int macflags[17][2] = {
        { IPM_DIRECTION,        IPMAC_DIRECTION },
        { IPM_DSTIP,            IPMAC_DSTIP     },
        { IPM_DSTPORT,          IPMAC_DSTPORT   },
        { IPM_GROUP,            IPMAC_GROUP     },
        { IPM_INTERFACE,        IPMAC_INTERFACE },
        { IPM_LOGTAG,           IPMAC_LOGTAG    },
        { IPM_NATTAG,           IPMAC_NATTAG    },
        { IPM_PACKET,           IPMAC_EVERY     },
        { IPM_PROTOCOL,         IPMAC_PROTOCOL  },
        { IPM_RESULT,           IPMAC_RESULT    },
        { IPM_RULE,             IPMAC_RULE      },
        { IPM_SECOND,           IPMAC_EVERY     },
        { IPM_SRCIP,            IPMAC_SRCIP     },
        { IPM_SRCPORT,          IPMAC_SRCPORT   },
        { IPM_TYPE,             IPMAC_TYPE      },
        { IPM_WITH,             IPMAC_WITH      },
        { 0, 0 }
};

static opt_t *
new_opt(int type)
{
        opt_t *o;

        o = (opt_t *)calloc(1, sizeof(*o));
        o->o_type = type;
        o->o_line = yylineNum;
        o->o_logfac = -1;
        o->o_logpri = -1;
        return(o);
}

static void
build_action(opt_t *olist, ipmon_doing_t *todo)
{
        ipmon_action_t *a;
        opt_t *o;
        int i;

        a = (ipmon_action_t *)calloc(1, sizeof(*a));
        if (a == NULL)
                return;

        while ((o = olist) != NULL) {
                /*
                 * Check to see if the same comparator is being used more than
                 * once per matching statement.
                 */
                for (i = 0; macflags[i][0]; i++)
                        if (macflags[i][0] == o->o_type)
                                break;
                if (macflags[i][1] & a->ac_mflag) {
                        fprintf(stderr, "%s redfined on line %d\n",
                                yykeytostr(o->o_type), yylineNum);
                        if (o->o_str != NULL)
                                free(o->o_str);
                        olist = o->o_next;
                        free(o);
                        continue;
                }

                a->ac_mflag |= macflags[i][1];

                switch (o->o_type)
                {
                case IPM_DIRECTION :
                        a->ac_direction = o->o_num;
                        break;
                case IPM_DSTIP :
                        a->ac_dip = o->o_ip.s_addr;
                        a->ac_dmsk = htonl(0xffffffff << (32 - o->o_num));
                        break;
                case IPM_DSTPORT :
                        a->ac_dport = htons(o->o_num);
                        break;
                case IPM_INTERFACE :
                        a->ac_iface = o->o_str;
                        o->o_str = NULL;
                        break;
                case IPM_GROUP :
                        if (o->o_str != NULL)
                                strncpy(a->ac_group, o->o_str, FR_GROUPLEN);
                        else
                                sprintf(a->ac_group, "%d", o->o_num);
                        break;
                case IPM_LOGTAG :
                        a->ac_logtag = o->o_num;
                        break;
                case IPM_NATTAG :
                        strncpy(a->ac_nattag, o->o_str, sizeof(a->ac_nattag));
                        break;
                case IPM_PACKET :
                        a->ac_packet = o->o_num;
                        break;
                case IPM_PROTOCOL :
                        a->ac_proto = o->o_num;
                        break;
                case IPM_RULE :
                        a->ac_rule = o->o_num;
                        break;
                case IPM_RESULT :
                        if (!strcasecmp(o->o_str, "pass"))
                                a->ac_result = IPMR_PASS;
                        else if (!strcasecmp(o->o_str, "block"))
                                a->ac_result = IPMR_BLOCK;
                        else if (!strcasecmp(o->o_str, "nomatch"))
                                a->ac_result = IPMR_NOMATCH;
                        else if (!strcasecmp(o->o_str, "log"))
                                a->ac_result = IPMR_LOG;
                        break;
                case IPM_SECOND :
                        a->ac_second = o->o_num;
                        break;
                case IPM_SRCIP :
                        a->ac_sip = o->o_ip.s_addr;
                        a->ac_smsk = htonl(0xffffffff << (32 - o->o_num));
                        break;
                case IPM_SRCPORT :
                        a->ac_sport = htons(o->o_num);
                        break;
                case IPM_TYPE :
                        a->ac_type = o->o_num;
                        break;
                case IPM_WITH :
                        break;
                default :
                        break;
                }

                olist = o->o_next;
                if (o->o_str != NULL)
                        free(o->o_str);
                free(o);
        }

        a->ac_doing = todo;
        a->ac_next = alist;
        alist = a;

        if (ipmonopts & IPMON_VERBOSE)
                print_action(a);
}


int
check_action(char *buf, char *log, int opts, int lvl)
{
        ipmon_action_t *a;
        struct timeval tv;
        ipmon_doing_t *d;
        ipmon_msg_t msg;
        ipflog_t *ipf;
        tcphdr_t *tcp;
        iplog_t *ipl;
        int matched;
        u_long t1;
        ip_t *ip;

        matched = 0;
        ipl = (iplog_t *)buf;
        ipf = (ipflog_t *)(ipl +1);
        ip = (ip_t *)(ipf + 1);
        tcp = (tcphdr_t *)((char *)ip + (IP_HL(ip) << 2));

        msg.imm_data = ipl;
        msg.imm_dsize = ipl->ipl_dsize;
        msg.imm_when = ipl->ipl_time.tv_sec;
        msg.imm_msg = log;
        msg.imm_msglen = strlen(log);
        msg.imm_loglevel = lvl;

        for (a = alist; a != NULL; a = a->ac_next) {
                verbose(0, "== checking config rule\n");
                if ((a->ac_mflag & IPMAC_DIRECTION) != 0) {
                        if (a->ac_direction == IPM_IN) {
                                if ((ipf->fl_flags & FR_INQUE) == 0) {
                                        verbose(8, "-- direction not in\n");
                                        continue;
                                }
                        } else if (a->ac_direction == IPM_OUT) {
                                if ((ipf->fl_flags & FR_OUTQUE) == 0) {
                                        verbose(8, "-- direction not out\n");
                                        continue;
                                }
                        }
                }

                if ((a->ac_type != 0) && (a->ac_type != ipl->ipl_magic)) {
                        verbose(8, "-- type mismatch\n");
                        continue;
                }

                if ((a->ac_mflag & IPMAC_EVERY) != 0) {
                        gettimeofday(&tv, NULL);
                        t1 = tv.tv_sec - a->ac_lastsec;
                        if (tv.tv_usec <= a->ac_lastusec)
                                t1--;
                        if (a->ac_second != 0) {
                                if (t1 < a->ac_second) {
                                        verbose(8, "-- too soon\n");
                                        continue;
                                }
                                a->ac_lastsec = tv.tv_sec;
                                a->ac_lastusec = tv.tv_usec;
                        }

                        if (a->ac_packet != 0) {
                                if (a->ac_pktcnt == 0)
                                        a->ac_pktcnt++;
                                else if (a->ac_pktcnt == a->ac_packet) {
                                        a->ac_pktcnt = 0;
                                        verbose(8, "-- packet count\n");
                                        continue;
                                } else {
                                        a->ac_pktcnt++;
                                        verbose(8, "-- packet count\n");
                                        continue;
                                }
                        }
                }

                if ((a->ac_mflag & IPMAC_DSTIP) != 0) {
                        if ((ip->ip_dst.s_addr & a->ac_dmsk) != a->ac_dip) {
                                verbose(8, "-- dstip wrong\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_DSTPORT) != 0) {
                        if (ip->ip_p != IPPROTO_UDP &&
                            ip->ip_p != IPPROTO_TCP) {
                                verbose(8, "-- not port protocol\n");
                                continue;
                        }
                        if (tcp->th_dport != a->ac_dport) {
                                verbose(8, "-- dport mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_GROUP) != 0) {
                        if (strncmp(a->ac_group, ipf->fl_group,
                                    FR_GROUPLEN) != 0) {
                                verbose(8, "-- group mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_INTERFACE) != 0) {
                        if (strcmp(a->ac_iface, ipf->fl_ifname)) {
                                verbose(8, "-- ifname mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_PROTOCOL) != 0) {
                        if (a->ac_proto != ip->ip_p) {
                                verbose(8, "-- protocol mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_RESULT) != 0) {
                        if ((ipf->fl_flags & FF_LOGNOMATCH) != 0) {
                                if (a->ac_result != IPMR_NOMATCH) {
                                        verbose(8, "-- ff-flags mismatch\n");
                                        continue;
                                }
                        } else if (FR_ISPASS(ipf->fl_flags)) {
                                if (a->ac_result != IPMR_PASS) {
                                        verbose(8, "-- pass mismatch\n");
                                        continue;
                                }
                        } else if (FR_ISBLOCK(ipf->fl_flags)) {
                                if (a->ac_result != IPMR_BLOCK) {
                                        verbose(8, "-- block mismatch\n");
                                        continue;
                                }
                        } else {        /* Log only */
                                if (a->ac_result != IPMR_LOG) {
                                        verbose(8, "-- log mismatch\n");
                                        continue;
                                }
                        }
                }

                if ((a->ac_mflag & IPMAC_RULE) != 0) {
                        if (a->ac_rule != ipf->fl_rule) {
                                verbose(8, "-- rule mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_SRCIP) != 0) {
                        if ((ip->ip_src.s_addr & a->ac_smsk) != a->ac_sip) {
                                verbose(8, "-- srcip mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_SRCPORT) != 0) {
                        if (ip->ip_p != IPPROTO_UDP &&
                            ip->ip_p != IPPROTO_TCP) {
                                verbose(8, "-- port protocol mismatch\n");
                                continue;
                        }
                        if (tcp->th_sport != a->ac_sport) {
                                verbose(8, "-- sport mismatch\n");
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_LOGTAG) != 0) {
                        if (a->ac_logtag != ipf->fl_logtag) {
                                verbose(8, "-- logtag %d != %d\n",
                                        a->ac_logtag, ipf->fl_logtag);
                                continue;
                        }
                }

                if ((a->ac_mflag & IPMAC_NATTAG) != 0) {
                        if (strncmp(a->ac_nattag, ipf->fl_nattag.ipt_tag,
                                    IPFTAG_LEN) != 0) {
                                verbose(8, "-- nattag mismatch\n");
                                continue;
                        }
                }

                matched = 1;
                verbose(8, "++ matched\n");

                /*
                 * It matched so now perform the saves
                 */
                for (d = a->ac_doing; d != NULL; d = d->ipmd_next)
                        (*d->ipmd_store)(d->ipmd_token, &msg);
        }

        return(matched);
}


static void
free_action(ipmon_action_t *a)
{
        ipmon_doing_t *d;

        while ((d = a->ac_doing) != NULL) {
                a->ac_doing = d->ipmd_next;
                (*d->ipmd_saver->ims_destroy)(d->ipmd_token);
                free(d);
        }

        if (a->ac_iface != NULL) {
                free(a->ac_iface);
                a->ac_iface = NULL;
        }
        a->ac_next = NULL;
        free(a);
}


int
load_config(char *file)
{
        FILE *fp;
        char *s;

        unload_config();

        s = getenv("YYDEBUG");
        if (s != NULL)
                yydebug = atoi(s);
        else
                yydebug = 0;

        yylineNum = 1;

        (void) yysettab(yywords);

        fp = fopen(file, "r");
        if (!fp) {
                perror("load_config:fopen:");
                return(-1);
        }
        yyin = fp;
        while (!feof(fp))
                yyparse();
        fclose(fp);
        return(0);
}


void
unload_config(void)
{
        ipmon_saver_int_t *sav, **imsip;
        ipmon_saver_t *is;
        ipmon_action_t *a;

        while ((a = alist) != NULL) {
                alist = a->ac_next;
                free_action(a);
        }

        /*
         * Look for savers that have been added in dynamically from the
         * configuration file.
         */
        for (imsip = &saverlist; (sav = *imsip) != NULL; ) {
                if (sav->imsi_handle == NULL)
                        imsip = &sav->imsi_next;
                else {
                        dlclose(sav->imsi_handle);

                        *imsip = sav->imsi_next;
                        is = sav->imsi_stor;
                        free(sav);

                        free(is->ims_name);
                        free(is);
                }
        }
}


void
dump_config(void)
{
        ipmon_action_t *a;

        for (a = alist; a != NULL; a = a->ac_next) {
                print_action(a);

                printf("#\n");
        }
}


static void
print_action(ipmon_action_t *a)
{
        ipmon_doing_t *d;

        printf("match { ");
        print_match(a);
        printf("; }\n");
        printf("do {");
        for (d = a->ac_doing; d != NULL; d = d->ipmd_next) {
                printf("%s", d->ipmd_saver->ims_name);
                if (d->ipmd_saver->ims_print != NULL) {
                        printf("(\"");
                        (*d->ipmd_saver->ims_print)(d->ipmd_token);
                        printf("\")");
                }
                printf(";");
        }
        printf("};\n");
}


void *
add_doing(ipmon_saver_t *saver)
{
        ipmon_saver_int_t *it;

        if (find_doing(saver->ims_name) == IPM_DOING)
                return(NULL);

        it = calloc(1, sizeof(*it));
        if (it == NULL)
                return(NULL);
        it->imsi_stor = saver;
        it->imsi_next = saverlist;
        saverlist = it;
        return(it);
}


static int
find_doing(char *string)
{
        ipmon_saver_int_t *it;

        for (it = saverlist; it != NULL; it = it->imsi_next) {
                if (!strcmp(it->imsi_stor->ims_name, string))
                        return(IPM_DOING);
        }
        return(0);
}


static ipmon_doing_t *
build_doing(char *target, char *options)
{
        ipmon_saver_int_t *it;
        char *strarray[2];
        ipmon_doing_t *d, *d1;
        ipmon_action_t *a;
        ipmon_saver_t *save;

        d = calloc(1, sizeof(*d));
        if (d == NULL)
                return(NULL);

        for (it = saverlist; it != NULL; it = it->imsi_next) {
                if (!strcmp(it->imsi_stor->ims_name, target))
                        break;
        }
        if (it == NULL) {
                free(d);
                return(NULL);
        }

        strarray[0] = options;
        strarray[1] = NULL;

        d->ipmd_token = (*it->imsi_stor->ims_parse)(strarray);
        if (d->ipmd_token == NULL) {
                free(d);
                return(NULL);
        }

        save = it->imsi_stor;
        d->ipmd_saver = save;
        d->ipmd_store = it->imsi_stor->ims_store;

        /*
         * Look for duplicate do-things that need to be dup'd
         */
        for (a = alist; a != NULL; a = a->ac_next) {
                for (d1 = a->ac_doing; d1 != NULL; d1 = d1->ipmd_next) {
                        if (save != d1->ipmd_saver)
                                continue;
                        if (save->ims_match == NULL || save->ims_dup == NULL)
                                continue;
                        if ((*save->ims_match)(d->ipmd_token, d1->ipmd_token))
                                continue;

                        (*d->ipmd_saver->ims_destroy)(d->ipmd_token);
                        d->ipmd_token = (*save->ims_dup)(d1->ipmd_token);
                        break;
                }
        }

        return(d);
}


static void
print_match(ipmon_action_t *a)
{
        char *coma = "";

        if ((a->ac_mflag & IPMAC_DIRECTION) != 0) {
                printf("direction = ");
                if (a->ac_direction == IPM_IN)
                        printf("in");
                else if (a->ac_direction == IPM_OUT)
                        printf("out");
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_DSTIP) != 0) {
                printf("%sdstip = ", coma);
                printhostmask(AF_INET, &a->ac_dip, &a->ac_dmsk);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_DSTPORT) != 0) {
                printf("%sdstport = %hu", coma, ntohs(a->ac_dport));
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_GROUP) != 0) {
                char group[FR_GROUPLEN+1];

                strncpy(group, a->ac_group, FR_GROUPLEN);
                group[FR_GROUPLEN] = '\0';
                printf("%sgroup = %s", coma, group);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_INTERFACE) != 0) {
                printf("%siface = %s", coma, a->ac_iface);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_LOGTAG) != 0) {
                printf("%slogtag = %u", coma, a->ac_logtag);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_NATTAG) != 0) {
                char tag[17];

                strncpy(tag, a->ac_nattag, 16);
                tag[16] = '\0';
                printf("%snattag = %s", coma, tag);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_PROTOCOL) != 0) {
                printf("%sprotocol = %u", coma, a->ac_proto);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_RESULT) != 0) {
                printf("%sresult = ", coma);
                switch (a->ac_result)
                {
                case IPMR_LOG :
                        printf("log");
                        break;
                case IPMR_PASS :
                        printf("pass");
                        break;
                case IPMR_BLOCK :
                        printf("block");
                        break;
                case IPMR_NOMATCH :
                        printf("nomatch");
                        break;
                }
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_RULE) != 0) {
                printf("%srule = %u", coma, a->ac_rule);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_EVERY) != 0) {
                if (a->ac_packet > 1) {
                        printf("%severy %d packets", coma, a->ac_packet);
                        coma = ", ";
                } else if (a->ac_packet == 1) {
                        printf("%severy packet", coma);
                        coma = ", ";
                }
                if (a->ac_second > 1) {
                        printf("%severy %d seconds", coma, a->ac_second);
                        coma = ", ";
                } else if (a->ac_second == 1) {
                        printf("%severy second", coma);
                        coma = ", ";
                }
        }

        if ((a->ac_mflag & IPMAC_SRCIP) != 0) {
                printf("%ssrcip = ", coma);
                printhostmask(AF_INET, &a->ac_sip, &a->ac_smsk);
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_SRCPORT) != 0) {
                printf("%ssrcport = %hu", coma, ntohs(a->ac_sport));
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_TYPE) != 0) {
                printf("%stype = ", coma);
                switch (a->ac_type)
                {
                case IPL_LOGIPF :
                        printf("ipf");
                        break;
                case IPL_LOGSTATE :
                        printf("state");
                        break;
                case IPL_LOGNAT :
                        printf("nat");
                        break;
                }
                coma = ", ";
        }

        if ((a->ac_mflag & IPMAC_WITH) != 0) {
                printf("%swith ", coma);
                coma = ", ";
        }
}


static int
install_saver(char *name, char *path)
{
        ipmon_saver_int_t *isi;
        ipmon_saver_t *is;
        char nbuf[80];

        if (find_doing(name) == IPM_DOING)
                return(-1);

        isi = calloc(1, sizeof(*isi));
        if (isi == NULL)
                return(-1);

        is = calloc(1, sizeof(*is));
        if (is == NULL)
                goto loaderror;

        is->ims_name = name;

#ifdef RTLD_LAZY
        isi->imsi_handle = dlopen(path, RTLD_LAZY);
#endif
#ifdef DL_LAZY
        isi->imsi_handle = dlopen(path, DL_LAZY);
#endif

        if (isi->imsi_handle == NULL)
                goto loaderror;

        snprintf(nbuf, sizeof(nbuf), "%sdup", name);
        is->ims_dup = (ims_dup_func_t)dlsym(isi->imsi_handle, nbuf);

        snprintf(nbuf, sizeof(nbuf), "%sdestroy", name);
        is->ims_destroy = (ims_destroy_func_t)dlsym(isi->imsi_handle, nbuf);
        if (is->ims_destroy == NULL)
                goto loaderror;

        snprintf(nbuf, sizeof(nbuf), "%smatch", name);
        is->ims_match = (ims_match_func_t)dlsym(isi->imsi_handle, nbuf);

        snprintf(nbuf, sizeof(nbuf), "%sparse", name);
        is->ims_parse = (ims_parse_func_t)dlsym(isi->imsi_handle, nbuf);
        if (is->ims_parse == NULL)
                goto loaderror;

        snprintf(nbuf, sizeof(nbuf), "%sprint", name);
        is->ims_print = (ims_print_func_t)dlsym(isi->imsi_handle, nbuf);
        if (is->ims_print == NULL)
                goto loaderror;

        snprintf(nbuf, sizeof(nbuf), "%sstore", name);
        is->ims_store = (ims_store_func_t)dlsym(isi->imsi_handle, nbuf);
        if (is->ims_store == NULL)
                goto loaderror;

        isi->imsi_stor = is;
        isi->imsi_next = saverlist;
        saverlist = isi;

        return(0);

loaderror:
        if (isi->imsi_handle != NULL)
                dlclose(isi->imsi_handle);
        free(isi);
        if (is != NULL)
                free(is);
        return(-1);
}