spamdb: allow keys to be specified in list mode

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

spamdb: allow keys to be specified in list mode

Todd C. Miller
I often want to query a specific key instead of dumping out the
entire database.  This lets you do things like:

$ spamdb 180.124.41.143
TRAPPED|180.124.41.143|1508373987

or:

$ spamdb 74.125.198.26 180.124.41.143 107.161.26.205
WHITE|74.125.198.26|||1506223558|1506223558|1509333958|1|0
TRAPPED|180.124.41.143|1508373987

No error is displayed for missing addresses but the exit value will
be 1 instead of 0 in this case.

 - todd

Index: usr.sbin/spamdb/spamdb.8
===================================================================
RCS file: /cvs/src/usr.sbin/spamdb/spamdb.8,v
retrieving revision 1.19
diff -u -p -u -r1.19 spamdb.8
--- usr.sbin/spamdb/spamdb.8 11 Oct 2017 18:25:07 -0000 1.19
+++ usr.sbin/spamdb/spamdb.8 18 Oct 2017 16:17:41 -0000
@@ -22,10 +22,8 @@
 .Nd spamd database tool
 .Sh SYNOPSIS
 .Nm spamdb
-.Oo Oo Fl Tt Oc
-.Fl a Ar keys Oc
-.Oo Oo Fl GTt Oc
-.Fl d Ar keys Oc
+.Op Fl adGTt
+.Op Ar keys ...
 .Sh DESCRIPTION
 .Nm
 manipulates the spamd database in
@@ -35,7 +33,7 @@ used for
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
-.It Fl a Ar keys
+.It Fl a
 Add or update the entries for
 .Ar keys .
 This can be used to whitelist one or more IP addresses
@@ -46,7 +44,7 @@ If any
 specified match entries already in the spamd database,
 .Nm
 updates the entry's time last seen to now.
-.It Fl d Ar keys
+.It Fl d
 Delete entries for
 .Ar keys .
 .It Fl G
@@ -90,9 +88,12 @@ Otherwise
 .Ar keys
 must be numerical IP addresses.
 .Ss DATABASE OUTPUT FORMAT
-If invoked without any arguments,
+If invoked without any options,
 .Nm
 lists the contents of the database in a text format.
+If one or more
+.Ar keys
+are specified, only matching entries will be printed.
 .Pp
 For SPAMTRAP entries the format is:
 .Pp
Index: usr.sbin/spamdb/spamdb.c
===================================================================
RCS file: /cvs/src/usr.sbin/spamdb/spamdb.c,v
retrieving revision 1.33
diff -u -p -u -r1.33 spamdb.c
--- usr.sbin/spamdb/spamdb.c 12 Oct 2017 09:28:56 -0000 1.33
+++ usr.sbin/spamdb/spamdb.c 18 Oct 2017 16:21:21 -0000
@@ -186,10 +186,81 @@ dbupdate(DB *db, char *ip, int add, int
 }
 
 int
+print_entry(DBT *dbk, DBT *dbd)
+{
+ struct gdata gd;
+ char *a, *cp;
+
+ if ((dbk->size < 1) || gdcopyin(dbd, &gd) == -1) {
+ warnx("bogus size db entry - bad db file?");
+ return (1);
+ }
+ a = malloc(dbk->size + 1);
+ if (a == NULL)
+ err(1, "malloc");
+ memcpy(a, dbk->data, dbk->size);
+ a[dbk->size]='\0';
+ cp = strchr(a, '\n');
+ if (cp == NULL) {
+ /* this is a non-greylist entry */
+ switch (gd.pcount) {
+ case -1: /* spamtrap hit, with expiry time */
+ printf("TRAPPED|%s|%lld\n", a,
+    (long long)gd.expire);
+ break;
+ case -2: /* spamtrap address */
+ printf("SPAMTRAP|%s\n", a);
+ break;
+ default: /* whitelist */
+ printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
+    (long long)gd.first, (long long)gd.pass,
+    (long long)gd.expire, gd.bcount,
+    gd.pcount);
+ break;
+ }
+ } else {
+ char *helo, *from, *to;
+
+ /* greylist entry */
+ *cp = '\0';
+ helo = cp + 1;
+ from = strchr(helo, '\n');
+ if (from == NULL) {
+ warnx("No from part in grey key %s", a);
+ free(a);
+ return (1);
+ }
+ *from = '\0';
+ from++;
+ to = strchr(from, '\n');
+ if (to == NULL) {
+ /* probably old format - print it the
+ * with an empty HELO field instead
+ * of erroring out.
+ */  
+ printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
+    a, "", helo, from, (long long)gd.first,
+    (long long)gd.pass, (long long)gd.expire,
+    gd.bcount, gd.pcount);
+
+ } else {
+ *to = '\0';
+ to++;
+ printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
+    a, helo, from, to, (long long)gd.first,
+    (long long)gd.pass, (long long)gd.expire,
+    gd.bcount, gd.pcount);
+ }
+ }
+ free(a);
+
+ return (0);
+}
+
+int
 dblist(DB *db)
 {
  DBT dbk, dbd;
- struct gdata gd;
  int r;
 
  /* walk db, list in text format */
@@ -197,80 +268,52 @@ dblist(DB *db)
  memset(&dbd, 0, sizeof(dbd));
  for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
     r = db->seq(db, &dbk, &dbd, R_NEXT)) {
- char *a, *cp;
-
- if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
- db->close(db);
- errx(1, "bogus size db entry - bad db file?");
- }
- a = malloc(dbk.size + 1);
- if (a == NULL)
- err(1, "malloc");
- memcpy(a, dbk.data, dbk.size);
- a[dbk.size]='\0';
- cp = strchr(a, '\n');
- if (cp == NULL) {
- /* this is a non-greylist entry */
- switch (gd.pcount) {
- case -1: /* spamtrap hit, with expiry time */
- printf("TRAPPED|%s|%lld\n", a,
-    (long long)gd.expire);
- break;
- case -2: /* spamtrap address */
- printf("SPAMTRAP|%s\n", a);
- break;
- default: /* whitelist */
- printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
-    (long long)gd.first, (long long)gd.pass,
-    (long long)gd.expire, gd.bcount,
-    gd.pcount);
- break;
- }
- } else {
- char *helo, *from, *to;
-
- /* greylist entry */
- *cp = '\0';
- helo = cp + 1;
- from = strchr(helo, '\n');
- if (from == NULL) {
- warnx("No from part in grey key %s", a);
- free(a);
- goto bad;
- }
- *from = '\0';
- from++;
- to = strchr(from, '\n');
- if (to == NULL) {
- /* probably old format - print it the
- * with an empty HELO field instead
- * of erroring out.
- */  
- printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
-    a, "", helo, from, (long long)gd.first,
-    (long long)gd.pass, (long long)gd.expire,
-    gd.bcount, gd.pcount);
-
- } else {
- *to = '\0';
- to++;
- printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
-    a, helo, from, to, (long long)gd.first,
-    (long long)gd.pass, (long long)gd.expire,
-    gd.bcount, gd.pcount);
- }
+ if (print_entry(&dbk, &dbd) != 0) {
+ r = -1;
+ break;
  }
- free(a);
  }
  db->close(db);
  db = NULL;
- return (0);
- bad:
+ return (r == -1);
+}
+
+int
+dbshow(DB *db, char **addrs)
+{
+ DBT dbk, dbd;
+ int errors = 0;
+ char *a;
+
+ /* look up each addr */
+ while ((a = *addrs) != NULL) {
+ memset(&dbk, 0, sizeof(dbk));
+ dbk.size = strlen(a);
+ dbk.data = a;
+ memset(&dbd, 0, sizeof(dbd));
+ switch (db->get(db, &dbk, &dbd, 0)) {
+ case -1:
+ warn("db->get failed");
+ errors++;
+ goto done;
+ case 0:
+ if (print_entry(&dbk, &dbd) != 0) {
+ errors++;
+ goto done;
+ }
+ break;
+ case 1:
+ default:
+ /* not found */
+ errors++;
+ break;
+ }
+ addrs++;
+ }
+ done:
  db->close(db);
  db = NULL;
- errx(1, "incorrect db format entry");
- /* NOTREACHED */
- return (1);
+ return (errors);
 }
 
 extern char *__progname;
@@ -278,7 +321,7 @@ extern char *__progname;
 static int
 usage(void)
 {
- fprintf(stderr, "usage: %s [[-Tt] -a keys] [[-GTt] -d keys]\n", __progname);
+ fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
  exit(1);
  /* NOTREACHED */
 }
@@ -335,7 +378,10 @@ main(int argc, char **argv)
 
  switch (action) {
  case 0:
- return dblist(db);
+ if (argc)
+ return dbshow(db, argv);
+ else
+ return dblist(db);
  case 1:
  if (type == GREY)
  errx(2, "cannot add GREY entries");

Reply | Threaded
Open this post in threaded view
|

Re: spamdb: allow keys to be specified in list mode

Jason McIntyre-2
On Wed, Oct 18, 2017 at 10:22:00AM -0600, Todd C. Miller wrote:

> I often want to query a specific key instead of dumping out the
> entire database.  This lets you do things like:
>
> $ spamdb 180.124.41.143
> TRAPPED|180.124.41.143|1508373987
>
> or:
>
> $ spamdb 74.125.198.26 180.124.41.143 107.161.26.205
> WHITE|74.125.198.26|||1506223558|1506223558|1509333958|1|0
> TRAPPED|180.124.41.143|1508373987
>
> No error is displayed for missing addresses but the exit value will
> be 1 instead of 0 in this case.
>
>  - todd
>

the man page bits are ok me.

personally i like your diff, since it makes spamdb usage appear simpler,
and saves having to run grep on something that is fairly common.

jmc

> Index: usr.sbin/spamdb/spamdb.8
> ===================================================================
> RCS file: /cvs/src/usr.sbin/spamdb/spamdb.8,v
> retrieving revision 1.19
> diff -u -p -u -r1.19 spamdb.8
> --- usr.sbin/spamdb/spamdb.8 11 Oct 2017 18:25:07 -0000 1.19
> +++ usr.sbin/spamdb/spamdb.8 18 Oct 2017 16:17:41 -0000
> @@ -22,10 +22,8 @@
>  .Nd spamd database tool
>  .Sh SYNOPSIS
>  .Nm spamdb
> -.Oo Oo Fl Tt Oc
> -.Fl a Ar keys Oc
> -.Oo Oo Fl GTt Oc
> -.Fl d Ar keys Oc
> +.Op Fl adGTt
> +.Op Ar keys ...
>  .Sh DESCRIPTION
>  .Nm
>  manipulates the spamd database in
> @@ -35,7 +33,7 @@ used for
>  .Pp
>  The options are as follows:
>  .Bl -tag -width Ds
> -.It Fl a Ar keys
> +.It Fl a
>  Add or update the entries for
>  .Ar keys .
>  This can be used to whitelist one or more IP addresses
> @@ -46,7 +44,7 @@ If any
>  specified match entries already in the spamd database,
>  .Nm
>  updates the entry's time last seen to now.
> -.It Fl d Ar keys
> +.It Fl d
>  Delete entries for
>  .Ar keys .
>  .It Fl G
> @@ -90,9 +88,12 @@ Otherwise
>  .Ar keys
>  must be numerical IP addresses.
>  .Ss DATABASE OUTPUT FORMAT
> -If invoked without any arguments,
> +If invoked without any options,
>  .Nm
>  lists the contents of the database in a text format.
> +If one or more
> +.Ar keys
> +are specified, only matching entries will be printed.
>  .Pp
>  For SPAMTRAP entries the format is:
>  .Pp
> Index: usr.sbin/spamdb/spamdb.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/spamdb/spamdb.c,v
> retrieving revision 1.33
> diff -u -p -u -r1.33 spamdb.c
> --- usr.sbin/spamdb/spamdb.c 12 Oct 2017 09:28:56 -0000 1.33
> +++ usr.sbin/spamdb/spamdb.c 18 Oct 2017 16:21:21 -0000
> @@ -186,10 +186,81 @@ dbupdate(DB *db, char *ip, int add, int
>  }
>  
>  int
> +print_entry(DBT *dbk, DBT *dbd)
> +{
> + struct gdata gd;
> + char *a, *cp;
> +
> + if ((dbk->size < 1) || gdcopyin(dbd, &gd) == -1) {
> + warnx("bogus size db entry - bad db file?");
> + return (1);
> + }
> + a = malloc(dbk->size + 1);
> + if (a == NULL)
> + err(1, "malloc");
> + memcpy(a, dbk->data, dbk->size);
> + a[dbk->size]='\0';
> + cp = strchr(a, '\n');
> + if (cp == NULL) {
> + /* this is a non-greylist entry */
> + switch (gd.pcount) {
> + case -1: /* spamtrap hit, with expiry time */
> + printf("TRAPPED|%s|%lld\n", a,
> +    (long long)gd.expire);
> + break;
> + case -2: /* spamtrap address */
> + printf("SPAMTRAP|%s\n", a);
> + break;
> + default: /* whitelist */
> + printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
> +    (long long)gd.first, (long long)gd.pass,
> +    (long long)gd.expire, gd.bcount,
> +    gd.pcount);
> + break;
> + }
> + } else {
> + char *helo, *from, *to;
> +
> + /* greylist entry */
> + *cp = '\0';
> + helo = cp + 1;
> + from = strchr(helo, '\n');
> + if (from == NULL) {
> + warnx("No from part in grey key %s", a);
> + free(a);
> + return (1);
> + }
> + *from = '\0';
> + from++;
> + to = strchr(from, '\n');
> + if (to == NULL) {
> + /* probably old format - print it the
> + * with an empty HELO field instead
> + * of erroring out.
> + */  
> + printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> +    a, "", helo, from, (long long)gd.first,
> +    (long long)gd.pass, (long long)gd.expire,
> +    gd.bcount, gd.pcount);
> +
> + } else {
> + *to = '\0';
> + to++;
> + printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> +    a, helo, from, to, (long long)gd.first,
> +    (long long)gd.pass, (long long)gd.expire,
> +    gd.bcount, gd.pcount);
> + }
> + }
> + free(a);
> +
> + return (0);
> +}
> +
> +int
>  dblist(DB *db)
>  {
>   DBT dbk, dbd;
> - struct gdata gd;
>   int r;
>  
>   /* walk db, list in text format */
> @@ -197,80 +268,52 @@ dblist(DB *db)
>   memset(&dbd, 0, sizeof(dbd));
>   for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
>      r = db->seq(db, &dbk, &dbd, R_NEXT)) {
> - char *a, *cp;
> -
> - if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
> - db->close(db);
> - errx(1, "bogus size db entry - bad db file?");
> - }
> - a = malloc(dbk.size + 1);
> - if (a == NULL)
> - err(1, "malloc");
> - memcpy(a, dbk.data, dbk.size);
> - a[dbk.size]='\0';
> - cp = strchr(a, '\n');
> - if (cp == NULL) {
> - /* this is a non-greylist entry */
> - switch (gd.pcount) {
> - case -1: /* spamtrap hit, with expiry time */
> - printf("TRAPPED|%s|%lld\n", a,
> -    (long long)gd.expire);
> - break;
> - case -2: /* spamtrap address */
> - printf("SPAMTRAP|%s\n", a);
> - break;
> - default: /* whitelist */
> - printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
> -    (long long)gd.first, (long long)gd.pass,
> -    (long long)gd.expire, gd.bcount,
> -    gd.pcount);
> - break;
> - }
> - } else {
> - char *helo, *from, *to;
> -
> - /* greylist entry */
> - *cp = '\0';
> - helo = cp + 1;
> - from = strchr(helo, '\n');
> - if (from == NULL) {
> - warnx("No from part in grey key %s", a);
> - free(a);
> - goto bad;
> - }
> - *from = '\0';
> - from++;
> - to = strchr(from, '\n');
> - if (to == NULL) {
> - /* probably old format - print it the
> - * with an empty HELO field instead
> - * of erroring out.
> - */  
> - printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> -    a, "", helo, from, (long long)gd.first,
> -    (long long)gd.pass, (long long)gd.expire,
> -    gd.bcount, gd.pcount);
> -
> - } else {
> - *to = '\0';
> - to++;
> - printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> -    a, helo, from, to, (long long)gd.first,
> -    (long long)gd.pass, (long long)gd.expire,
> -    gd.bcount, gd.pcount);
> - }
> + if (print_entry(&dbk, &dbd) != 0) {
> + r = -1;
> + break;
>   }
> - free(a);
>   }
>   db->close(db);
>   db = NULL;
> - return (0);
> - bad:
> + return (r == -1);
> +}
> +
> +int
> +dbshow(DB *db, char **addrs)
> +{
> + DBT dbk, dbd;
> + int errors = 0;
> + char *a;
> +
> + /* look up each addr */
> + while ((a = *addrs) != NULL) {
> + memset(&dbk, 0, sizeof(dbk));
> + dbk.size = strlen(a);
> + dbk.data = a;
> + memset(&dbd, 0, sizeof(dbd));
> + switch (db->get(db, &dbk, &dbd, 0)) {
> + case -1:
> + warn("db->get failed");
> + errors++;
> + goto done;
> + case 0:
> + if (print_entry(&dbk, &dbd) != 0) {
> + errors++;
> + goto done;
> + }
> + break;
> + case 1:
> + default:
> + /* not found */
> + errors++;
> + break;
> + }
> + addrs++;
> + }
> + done:
>   db->close(db);
>   db = NULL;
> - errx(1, "incorrect db format entry");
> - /* NOTREACHED */
> - return (1);
> + return (errors);
>  }
>  
>  extern char *__progname;
> @@ -278,7 +321,7 @@ extern char *__progname;
>  static int
>  usage(void)
>  {
> - fprintf(stderr, "usage: %s [[-Tt] -a keys] [[-GTt] -d keys]\n", __progname);
> + fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
>   exit(1);
>   /* NOTREACHED */
>  }
> @@ -335,7 +378,10 @@ main(int argc, char **argv)
>  
>   switch (action) {
>   case 0:
> - return dblist(db);
> + if (argc)
> + return dbshow(db, argv);
> + else
> + return dblist(db);
>   case 1:
>   if (type == GREY)
>   errx(2, "cannot add GREY entries");
>

Reply | Threaded
Open this post in threaded view
|

Re: spamdb: allow keys to be specified in list mode

Todd C. Miller
In reply to this post by Todd C. Miller
I have an OK on the man page bits.  Anyone else want to chime in?

 - todd

On Wed, 18 Oct 2017 10:22:00 -0600, "Todd C. Miller" wrote:

> I often want to query a specific key instead of dumping out the
> entire database.  This lets you do things like:
>
> $ spamdb 180.124.41.143
> TRAPPED|180.124.41.143|1508373987
>
> or:
>
> $ spamdb 74.125.198.26 180.124.41.143 107.161.26.205
> WHITE|74.125.198.26|||1506223558|1506223558|1509333958|1|0
> TRAPPED|180.124.41.143|1508373987
>
> No error is displayed for missing addresses but the exit value will
> be 1 instead of 0 in this case.
>
>  - todd
>
> Index: usr.sbin/spamdb/spamdb.8
> ===================================================================
> RCS file: /cvs/src/usr.sbin/spamdb/spamdb.8,v
> retrieving revision 1.19
> diff -u -p -u -r1.19 spamdb.8
> --- usr.sbin/spamdb/spamdb.8 11 Oct 2017 18:25:07 -0000 1.19
> +++ usr.sbin/spamdb/spamdb.8 18 Oct 2017 16:17:41 -0000
> @@ -22,10 +22,8 @@
>  .Nd spamd database tool
>  .Sh SYNOPSIS
>  .Nm spamdb
> -.Oo Oo Fl Tt Oc
> -.Fl a Ar keys Oc
> -.Oo Oo Fl GTt Oc
> -.Fl d Ar keys Oc
> +.Op Fl adGTt
> +.Op Ar keys ...
>  .Sh DESCRIPTION
>  .Nm
>  manipulates the spamd database in
> @@ -35,7 +33,7 @@ used for
>  .Pp
>  The options are as follows:
>  .Bl -tag -width Ds
> -.It Fl a Ar keys
> +.It Fl a
>  Add or update the entries for
>  .Ar keys .
>  This can be used to whitelist one or more IP addresses
> @@ -46,7 +44,7 @@ If any
>  specified match entries already in the spamd database,
>  .Nm
>  updates the entry's time last seen to now.
> -.It Fl d Ar keys
> +.It Fl d
>  Delete entries for
>  .Ar keys .
>  .It Fl G
> @@ -90,9 +88,12 @@ Otherwise
>  .Ar keys
>  must be numerical IP addresses.
>  .Ss DATABASE OUTPUT FORMAT
> -If invoked without any arguments,
> +If invoked without any options,
>  .Nm
>  lists the contents of the database in a text format.
> +If one or more
> +.Ar keys
> +are specified, only matching entries will be printed.
>  .Pp
>  For SPAMTRAP entries the format is:
>  .Pp
> Index: usr.sbin/spamdb/spamdb.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/spamdb/spamdb.c,v
> retrieving revision 1.33
> diff -u -p -u -r1.33 spamdb.c
> --- usr.sbin/spamdb/spamdb.c 12 Oct 2017 09:28:56 -0000 1.33
> +++ usr.sbin/spamdb/spamdb.c 18 Oct 2017 16:21:21 -0000
> @@ -186,10 +186,81 @@ dbupdate(DB *db, char *ip, int add, int
>  }
>  
>  int
> +print_entry(DBT *dbk, DBT *dbd)
> +{
> + struct gdata gd;
> + char *a, *cp;
> +
> + if ((dbk->size < 1) || gdcopyin(dbd, &gd) == -1) {
> + warnx("bogus size db entry - bad db file?");
> + return (1);
> + }
> + a = malloc(dbk->size + 1);
> + if (a == NULL)
> + err(1, "malloc");
> + memcpy(a, dbk->data, dbk->size);
> + a[dbk->size]='\0';
> + cp = strchr(a, '\n');
> + if (cp == NULL) {
> + /* this is a non-greylist entry */
> + switch (gd.pcount) {
> + case -1: /* spamtrap hit, with expiry time */
> + printf("TRAPPED|%s|%lld\n", a,
> +    (long long)gd.expire);
> + break;
> + case -2: /* spamtrap address */
> + printf("SPAMTRAP|%s\n", a);
> + break;
> + default: /* whitelist */
> + printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
> +    (long long)gd.first, (long long)gd.pass,
> +    (long long)gd.expire, gd.bcount,
> +    gd.pcount);
> + break;
> + }
> + } else {
> + char *helo, *from, *to;
> +
> + /* greylist entry */
> + *cp = '\0';
> + helo = cp + 1;
> + from = strchr(helo, '\n');
> + if (from == NULL) {
> + warnx("No from part in grey key %s", a);
> + free(a);
> + return (1);
> + }
> + *from = '\0';
> + from++;
> + to = strchr(from, '\n');
> + if (to == NULL) {
> + /* probably old format - print it the
> + * with an empty HELO field instead
> + * of erroring out.
> + */  
> + printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> +    a, "", helo, from, (long long)gd.first,
> +    (long long)gd.pass, (long long)gd.expire,
> +    gd.bcount, gd.pcount);
> +
> + } else {
> + *to = '\0';
> + to++;
> + printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
> +    a, helo, from, to, (long long)gd.first,
> +    (long long)gd.pass, (long long)gd.expire,
> +    gd.bcount, gd.pcount);
> + }
> + }
> + free(a);
> +
> + return (0);
> +}
> +
> +int
>  dblist(DB *db)
>  {
>   DBT dbk, dbd;
> - struct gdata gd;
>   int r;
>  
>   /* walk db, list in text format */
> @@ -197,80 +268,52 @@ dblist(DB *db)
>   memset(&dbd, 0, sizeof(dbd));
>   for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
>      r = db->seq(db, &dbk, &dbd, R_NEXT)) {
> - char *a, *cp;
> -
> - if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
> - db->close(db);
> - errx(1, "bogus size db entry - bad db file?");
> - }
> - a = malloc(dbk.size + 1);
> - if (a == NULL)
> - err(1, "malloc");
> - memcpy(a, dbk.data, dbk.size);
> - a[dbk.size]='\0';
> - cp = strchr(a, '\n');
> - if (cp == NULL) {
> - /* this is a non-greylist entry */
> - switch (gd.pcount) {
> - case -1: /* spamtrap hit, with expiry time */
> - printf("TRAPPED|%s|%lld\n", a,
> -    (long long)gd.expire);
> - break;
> - case -2: /* spamtrap address */
> - printf("SPAMTRAP|%s\n", a);
> - break;
> - default: /* whitelist */
> - printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
> -    (long long)gd.first, (long long)gd.pass,
> -    (long long)gd.expire, gd.bcount,
> -    gd.pcount);
> - break;
> - }
> - } else {
> - char *helo, *from, *to;
> -
> - /* greylist entry */
> - *cp = '\0';
> - helo = cp + 1;
> - from = strchr(helo, '\n');
> - if (from == NULL) {
> - warnx("No from part in grey key %s", a);
> - free(a);
> - goto bad;
> - }
> - *from = '\0';
> - from++;
> - to = strchr(from, '\n');
> - if (to == NULL) {
> - /* probably old format - print it the
> - * with an empty HELO field instead
> - * of erroring out.
> - */  
> - printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n
> ",
> -    a, "", helo, from, (long long)gd.first,
> -    (long long)gd.pass, (long long)gd.expire,
> -    gd.bcount, gd.pcount);
> -
> - } else {
> - *to = '\0';
> - to++;
> - printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n
> ",
> -    a, helo, from, to, (long long)gd.first,
> -    (long long)gd.pass, (long long)gd.expire,
> -    gd.bcount, gd.pcount);
> - }
> + if (print_entry(&dbk, &dbd) != 0) {
> + r = -1;
> + break;
>   }
> - free(a);
>   }
>   db->close(db);
>   db = NULL;
> - return (0);
> - bad:
> + return (r == -1);
> +}
> +
> +int
> +dbshow(DB *db, char **addrs)
> +{
> + DBT dbk, dbd;
> + int errors = 0;
> + char *a;
> +
> + /* look up each addr */
> + while ((a = *addrs) != NULL) {
> + memset(&dbk, 0, sizeof(dbk));
> + dbk.size = strlen(a);
> + dbk.data = a;
> + memset(&dbd, 0, sizeof(dbd));
> + switch (db->get(db, &dbk, &dbd, 0)) {
> + case -1:
> + warn("db->get failed");
> + errors++;
> + goto done;
> + case 0:
> + if (print_entry(&dbk, &dbd) != 0) {
> + errors++;
> + goto done;
> + }
> + break;
> + case 1:
> + default:
> + /* not found */
> + errors++;
> + break;
> + }
> + addrs++;
> + }
> + done:
>   db->close(db);
>   db = NULL;
> - errx(1, "incorrect db format entry");
> - /* NOTREACHED */
> - return (1);
> + return (errors);
>  }
>  
>  extern char *__progname;
> @@ -278,7 +321,7 @@ extern char *__progname;
>  static int
>  usage(void)
>  {
> - fprintf(stderr, "usage: %s [[-Tt] -a keys] [[-GTt] -d keys]\n", __progn
> ame);
> + fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
>   exit(1);
>   /* NOTREACHED */
>  }
> @@ -335,7 +378,10 @@ main(int argc, char **argv)
>  
>   switch (action) {
>   case 0:
> - return dblist(db);
> + if (argc)
> + return dbshow(db, argv);
> + else
> + return dblist(db);
>   case 1:
>   if (type == GREY)
>   errx(2, "cannot add GREY entries");
>