* configure.ac: New option --disable-finger.
authorWerner Koch <wk@gnupg.org>
Mon, 11 Oct 2004 08:44:35 +0000 (08:44 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 11 Oct 2004 08:44:35 +0000 (08:44 +0000)
* keyserver.c (keyserver_spawn): Print an empty string in log_info
if the host is not set (e.g. finger).

* gpgkeys_finger.c: New.

ChangeLog
configure.ac
g10/ChangeLog
g10/ccid-driver.c
g10/ccid-driver.h
g10/iso7816.h
g10/keyserver.c
keyserver/ChangeLog
keyserver/Makefile.am
keyserver/gpgkeys_finger [new file with mode: 0755]

index 42b961b..fc63de9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2004-10-11  Werner Koch  <wk@g10code.com>
+
+       * configure.ac: New option --disable-finger.
+
 2004-09-17  Werner Koch  <wk@g10code.com>
 
        * configure.ac: Don't check for usb_create_match or
index 7404ae3..12bc382 100644 (file)
@@ -303,6 +303,13 @@ if test "$use_exec" = yes ; then
         try_http=$enableval, try_http=yes)
     AC_MSG_RESULT($try_http)
 
+    AC_MSG_CHECKING([whether Finger key fetching support is requested])
+    AC_ARG_ENABLE(http,
+      AC_HELP_STRING([--disable-finger],
+                     [disable Finger key fetching interface]),
+        try_finger=$enableval, try_finger=yes)
+    AC_MSG_RESULT($try_finger)
+
     AC_MSG_CHECKING([whether email keyserver support is requested])
     AC_ARG_ENABLE(mailto,
     [  --disable-mailto        disable email keyserver interface],
@@ -492,6 +499,10 @@ if test x"$try_http" = xyes ; then
   AC_SUBST(GPGKEYS_HTTP,"gpgkeys_http$EXEEXT")
 fi
 
+if test x"$try_finger" = xyes ; then
+  AC_SUBST(GPGKEYS_FINGER,"gpgkeys_finger$EXEEXT")
+fi
+
 dnl Must check for network library requirements before doing link tests
 dnl for ldap, for example. If ldap libs are static (or dynamic and without
 dnl ELF runtime link paths), then link will fail and LDAP support won't
index 6af768e..b98a377 100644 (file)
@@ -1,3 +1,8 @@
+2004-10-11  Werner Koch  <wk@g10code.com>
+
+       * keyserver.c (keyserver_spawn): Print an empty string in log_info
+       if the host is not set (e.g. finger).
+
 2004-10-10  David Shaw  <dshaw@jabberwocky.com>
 
        * card-util.c, keyedit.c, openfile.c, pkclist.c, delkey.c,
        revoked/expired/expires string change of 2004-09-29 was too
        simple.  Use two styles for each tag.
 
+2004-10-06  Werner Koch  <wk@g10code.com>
+
+       * ccid-driver.c (ccid_open_reader): Store the vendor ID.
+       (ccid_transceive_secure): New.
+       (parse_ccid_descriptor): Workaround for an SCM reader problem.
+       (send_escape_cmd): New.
+
 2004-10-05  David Shaw  <dshaw@jabberwocky.com>
 
        * passphrase.c (agent_get_passphrase): Use keystrs for agent
index 77fea94..287a8d8 100644 (file)
 
 
 
-
-
 enum {
   RDR_to_PC_NotifySlotChange= 0x50,
   RDR_to_PC_HardwareError   = 0x51,
@@ -183,12 +181,21 @@ enum {
 };
 
 
+/* We need to know the vendor to do some hacks. */
+enum {
+  VENDOR_SCM = 0x04e6
+};
+
+
 /* Store information on the driver's state.  A pointer to such a
    structure is used as handle for most functions. */
 struct ccid_driver_s 
 {
   usb_dev_handle *idev;
   char *rid;
+  unsigned short id_vendor;
+  unsigned short id_product;
+  unsigned short bcd_device;
   int seqno;
   unsigned char t1_ns;
   unsigned char t1_nr;
@@ -251,6 +258,8 @@ parse_ccid_descriptor (ccid_driver_t handle,
   handle->max_ifsd = 32;
   handle->ifsd = 0;
   handle->has_pinpad = 0;
+  DEBUGOUT_3 ("idVendor: %04X  idProduct: %04X  bcdDevice: %04X\n",
+              handle->id_vendor, handle->id_product, handle->bcd_device);
   if (buflen < 54 || buf[0] < 54)
     {
       DEBUGOUT ("CCID device descriptor is too short\n");
@@ -417,8 +426,25 @@ parse_ccid_descriptor (ccid_driver_t handle,
                 "this is not available\n");
       return -1;
     }
-  else
-    return 0;
+
+
+  /* SCM drivers get stuck in their internal USB stack if they try to
+     send a frame of n*wMaxPacketSize back to us.  Given that
+     wMaxPacketSize is 64 for these readers we set the IFSD to a value
+     lower than that:
+        64 - 10 CCID header -  4 T1frame - 2 reserved = 48 */
+  if (handle->id_vendor == VENDOR_SCM
+      /* FIXME: check whether it is the same
+                firmware version for all drivers.  */
+      && handle->bcd_device < 0x0513
+      && handle->max_ifsd > 48)
+    {
+      DEBUGOUT ("enabling workaround for buggy SCM readers\n");
+      handle->max_ifsd = 48;
+    }
+
+
+  return 0;
 }
 
 
@@ -436,7 +462,7 @@ get_escaped_usb_string (usb_dev_handle *idev, int idx,
   if (!idx)
     return NULL;
 
-  /* Fixme: The next line for the current Valgrid without support
+  /* Fixme: The next line is for the current Valgrid without support
      for USB IOCTLs. */
   memset (buf, 0, sizeof buf);
 
@@ -825,6 +851,9 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
     }
   (*handle)->idev = idev;
   (*handle)->rid = rid;
+  (*handle)->id_vendor = dev->descriptor.idVendor;
+  (*handle)->id_product = dev->descriptor.idProduct;
+  (*handle)->bcd_device = dev->descriptor.bcdDevice;
 
   DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n",  readerno, rid );
 
@@ -1082,6 +1111,43 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
 }
 
 
+/* Note that this fucntion won't return the error codes NO_CARD or
+   CARD_INACTIVE */
+static int 
+send_escape_cmd (ccid_driver_t handle,
+                 const unsigned char *data, size_t datalen)
+{
+  int i, rc;
+  unsigned char msg[100];
+  size_t msglen;
+  unsigned char seqno;
+
+  if (datalen > sizeof msg - 10)
+    return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large.  */
+
+  msg[0] = PC_to_RDR_Escape;
+  msg[5] = 0; /* slot */
+  msg[6] = seqno = handle->seqno++;
+  msg[7] = 0; /* RFU */
+  msg[8] = 0; /* RFU */
+  msg[9] = 0; /* RFU */
+  memcpy (msg+10, data, datalen);
+  msglen = 10 + datalen;
+  set_msg_len (msg, datalen);
+
+  DEBUGOUT ("sending");
+  for (i=0; i < msglen; i++)
+    DEBUGOUT_CONT_1 (" %02X", msg[i]);
+  DEBUGOUT_LF ();
+  rc = bulk_out (handle, msg, msglen);
+  if (rc)
+    return rc;
+  rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape, seqno);
+
+  return rc;
+}
+
+
 /* experimental */
 int
 ccid_poll (ccid_driver_t handle)
@@ -1617,10 +1683,232 @@ ccid_transceive (ccid_driver_t handle,
 }
 
 
+/* Send the CCID Secure command to the reader.  APDU_BUF should contain the APDU   template.  PIN_MODE defines now the pin gets formatted:
+   
+     1 := The PIN is ASCII encoded and of variable length.  The
+          length of the PIN entered will be put into Lc by the reader.
+          The APDU should me made up of 4 bytes without Lc.
+
+   PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0
+   may be used t enable usbale defaults.  PIN_PADLEN should be 0
+   
+   When called with RESP and NRESP set to NULL, the function will
+   merely check whether the reader supports the secure command for the
+   given APDU and PIN_MODE. */
+int
+ccid_transceive_secure (ccid_driver_t handle,
+                        const unsigned char *apdu_buf, size_t apdu_buflen,
+                        int pin_mode, int pinlen_min, int pinlen_max,
+                        int pin_padlen, 
+                        unsigned char *resp, size_t maxresplen, size_t *nresp)
+{
+  int rc;
+  unsigned char send_buffer[10+259], recv_buffer[10+259];
+  unsigned char *msg, *tpdu, *p;
+  size_t msglen, tpdulen, n;
+  unsigned char seqno;
+  int i;
+  size_t dummy_nresp;
+  int testmode;
+
+  testmode = !resp && !nresp;
+
+  if (!nresp)
+    nresp = &dummy_nresp;
+  *nresp = 0;
+
+  if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
+    ;
+  else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
+    return CCID_DRIVER_ERR_NOT_SUPPORTED; /* Not yet by our code. */
+  else
+    return CCID_DRIVER_ERR_NOT_SUPPORTED;
+    
+  if (pin_mode != 1)
+    return CCID_DRIVER_ERR_NOT_SUPPORTED;
+
+  if (pin_padlen != 0)
+    return CCID_DRIVER_ERR_NOT_SUPPORTED;
+
+  if (!pinlen_min)
+    pinlen_min = 1;
+  if (!pinlen_max)
+    pinlen_max = 25;
+
+  /* Note that the 25 is the maximum value the SPR532 allows.  */
+  if (pinlen_min < 1 || pinlen_min > 25
+      || pinlen_max < 1 || pinlen_max > 25 
+      || pinlen_min > pinlen_max)
+    return CCID_DRIVER_ERR_INV_VALUE;
+
+  /* We have only tested this with an SCM reader so better don't risk
+     anything and do not allow the use with other readers. */
+  if (handle->id_vendor != VENDOR_SCM)
+    return CCID_DRIVER_ERR_NOT_SUPPORTED;
+
+  if (testmode)
+    return 0; /* Success */
+    
+  msg = send_buffer;
+  if (handle->id_vendor == VENDOR_SCM)
+    {
+      DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
+      rc = send_escape_cmd (handle, "\x80\x02\x00", 3);
+      if (rc)
+        return rc;
+    }
+
+  msg[0] = PC_to_RDR_Secure;
+  msg[5] = 0; /* slot */
+  msg[6] = seqno = handle->seqno++;
+  msg[7] = 4; /* bBWI */
+  msg[8] = 0; /* RFU */
+  msg[9] = 0; /* RFU */
+  msg[10] = 0; /* Perform PIN verification. */
+  msg[11] = 0; /* Timeout in seconds. */
+  msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
+  if (handle->id_vendor == VENDOR_SCM)
+    {
+      /* For the SPR532 the next 2 bytes need to be zero.  We do this
+         for all SCM product. Kudos to to Martin Paljak for this
+         hint.  */
+      msg[13] = msg[14] = 0;
+    }
+  else
+    {
+      msg[13] = 0x00; /* bmPINBlockString:
+                         0 bits of pin length to insert. 
+                         0 bytes of PIN block size.  */
+      msg[14] = 0x00; /* bmPINLengthFormat:
+                         Units are bytes, position is 0. */
+    }
+  msg[15] = pinlen_min;   /* wPINMaxExtraDigit-Minimum.  */
+  msg[16] = pinlen_max;   /* wPINMaxExtraDigit-Maximum.  */
+  msg[17] = 0x02; /* bEntryValidationCondition:
+                     Validation key pressed */
+  if (pinlen_min && pinlen_max && pinlen_min == pinlen_max)
+    msg[17] |= 0x01; /* Max size reached.  */
+  msg[18] = 0xff; /* bNumberMessage: Default. */
+  msg[19] = 0x04; /* wLangId-High. */
+  msg[20] = 0x09; /* wLangId-Low:  English FIXME: use the first entry. */
+  msg[21] = 0;    /* bMsgIndex. */
+  /* bTeoProlog follows: */
+  msg[22] = handle->nonnull_nad? ((1 << 4) | 0): 0;
+  msg[23] = ((handle->t1_ns & 1) << 6); /* I-block */
+  msg[24] = 4; /* apdulen.  */
+  /* APDU follows:  */
+  msg[25] = apdu_buf[0]; /* CLA */
+  msg[26] = apdu_buf[1]; /* INS */
+  msg[27] = apdu_buf[2]; /* P1 */
+  msg[28] = apdu_buf[3]; /* P2 */
+  msglen = 29;
+  set_msg_len (msg, msglen - 10);
+
+  DEBUGOUT ("sending");
+  for (i=0; i < msglen; i++)
+    DEBUGOUT_CONT_1 (" %02X", msg[i]);
+  DEBUGOUT_LF ();
+  
+  rc = bulk_out (handle, msg, msglen);
+  if (rc)
+    return rc;
+  
+  msg = recv_buffer;
+  rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
+                RDR_to_PC_DataBlock, seqno);
+  if (rc)
+    return rc;
+  
+  tpdu = msg + 10;
+  tpdulen = msglen - 10;
+  
+  if (tpdulen < 4) 
+    {
+      usb_clear_halt (handle->idev, 0x82);
+      return CCID_DRIVER_ERR_ABORTED; 
+    }
+#ifdef DEBUG_T1
+  fprintf (stderr, "T1: got %c-block seq=%d err=%d\n",
+           ((msg[11] & 0xc0) == 0x80)? 'R' :
+           (msg[11] & 0x80)? 'S' : 'I',
+           ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
+           ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0
+           );
+#endif
+
+  if (!(tpdu[1] & 0x80))
+    { /* This is an I-block. */
+      /* Last block sent was successful. */
+      handle->t1_ns ^= 1;
+
+      if (!!(tpdu[1] & 0x40) != handle->t1_nr)
+        { /* Reponse does not match our sequence number. */
+          DEBUGOUT ("I-block with wrong seqno received\n");
+          return CCID_DRIVER_ERR_CARD_IO_ERROR;
+        }
+
+      handle->t1_nr ^= 1;
+
+      p = tpdu + 3; /* Skip the prologue field. */
+      n = tpdulen - 3 - 1; /* Strip the epilogue field. */
+      /* fixme: verify the checksum. */
+      if (resp)
+        {
+          if (n > maxresplen)
+            {
+              DEBUGOUT_2 ("provided buffer too short for received data "
+                          "(%u/%u)\n",
+                          (unsigned int)n, (unsigned int)maxresplen);
+              return CCID_DRIVER_ERR_INV_VALUE;
+            }
+              
+          memcpy (resp, p, n); 
+          resp += n;
+          *nresp += n;
+          maxresplen -= n;
+        }
+          
+      if (!(tpdu[1] & 0x20))
+        return 0; /* No chaining requested - ready. */
+      
+      DEBUGOUT ("chaining requested but not supported for Secure operation\n");
+      return CCID_DRIVER_ERR_CARD_IO_ERROR;
+    }
+  else if ((tpdu[1] & 0xc0) == 0x80)
+    { /* This is a R-block. */
+      if ( (tpdu[1] & 0x0f)) 
+        { /* Error: repeat last block */
+          DEBUGOUT ("No retries supported for Secure operation\n");
+          return CCID_DRIVER_ERR_CARD_IO_ERROR;
+        }
+      else if (!!(tpdu[1] & 0x40) == handle->t1_ns)
+        { /* Reponse does not match our sequence number. */
+          DEBUGOUT ("R-block with wrong seqno received on more bit\n");
+          return CCID_DRIVER_ERR_CARD_IO_ERROR;
+        }
+      else
+        { /* Send next chunk. */
+          DEBUGOUT ("chaining not supported on Secure operation\n");
+          return CCID_DRIVER_ERR_CARD_IO_ERROR;
+        }
+    }
+  else 
+    { /* This is a S-block. */
+      DEBUGOUT_2 ("T1 S-block %s received cmd=%d for Secure operation\n",
+                  (tpdu[1] & 0x20)? "response": "request",
+                  (tpdu[1] & 0x1f));
+      return CCID_DRIVER_ERR_CARD_IO_ERROR;
+    } 
+
+  return 0;
+}
+
+
 
 
 #ifdef TEST
 
+
 static void
 print_error (int err)
 {
@@ -1682,6 +1970,9 @@ main (int argc, char **argv)
   unsigned int slotstat;
   unsigned char result[512];
   size_t resultlen;
+  int no_pinpad = 0;
+  int verify_123456 = 0;
+  int did_verify = 0;
 
   if (argc)
     {
@@ -1706,6 +1997,16 @@ main (int argc, char **argv)
           ccid_set_debug_level (1);
           argc--; argv++;
         }
+      else if ( !strcmp (*argv, "--no-pinpad"))
+        {
+          no_pinpad = 1;
+          argc--; argv++;
+        }
+      else if ( !strcmp (*argv, "--verify-123456"))
+        {
+          verify_123456 = 1;
+          argc--; argv++;
+        }
       else
         break;
     }
@@ -1755,28 +2056,55 @@ main (int argc, char **argv)
     print_result (rc, result, resultlen);
   }
 
-  ccid_poll (ccid);
+  if (!no_pinpad)
+    {
+    }
+
+  if (!no_pinpad)
+    {
+      static unsigned char apdu[] = { 0, 0x20, 0, 0x81 };
+
+      
+      if (ccid_transceive_secure (ccid,
+                                  apdu, sizeof apdu,
+                                  1, 0, 0, 0,
+                                  NULL, 0, NULL))
+        fputs ("can't verify using a PIN-Pad reader\n", stderr);
+      else
+        {
+          fputs ("verifying CHV1 using the PINPad ....\n", stderr);
+          
+          rc = ccid_transceive_secure (ccid,
+                                       apdu, sizeof apdu,
+                                       1, 0, 0, 0,
+                                       result, sizeof result, &resultlen);
+          print_result (rc, result, resultlen);
+          did_verify = 1;
+        }
+    }
+  
+  if (verify_123456 && !did_verify)
+    {
+      fputs ("verifying that CHV1 is 123456....\n", stderr);
+      {
+        static unsigned char apdu[] = {0, 0x20, 0, 0x81,
+                                       6, '1','2','3','4','5','6'};
+        rc = ccid_transceive (ccid, apdu, sizeof apdu,
+                              result, sizeof result, &resultlen);
+        print_result (rc, result, resultlen);
+      }
+    }
 
-/*   if (!ccid->has_pinpad) */
-/*     { */
-/*       fputs ("verifying that CHV1 is 123456....\n", stderr); */
-/*       { */
-/*         static unsigned char apdu[] = {0, 0x20, 0, 0x81, */
-/*                                        6, '1','2','3','4','5','6'}; */
-/*         rc = ccid_transceive (ccid, apdu, sizeof apdu, */
-/*                               result, sizeof result, &resultlen); */
-/*         print_result (rc, result, resultlen); */
-/*       } */
-/*     } */
-/*   else */
-/*     { */
-/*       fputs ("verifying CHV1 using the PINPad ....\n", stderr); */
-/*       { */
-/*         rc = ccid_secure_transceive (ccid, */
-/*                                      result, sizeof result, &resultlen); */
-/*         print_result (rc, result, resultlen); */
-/*       } */
-/*     } */
+  if (!rc)
+    {
+      fputs ("getting OpenPGP DO 0x5E ....\n", stderr);
+      {
+        static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 };
+        rc = ccid_transceive (ccid, apdu, sizeof apdu,
+                              result, sizeof result, &resultlen);
+        print_result (rc, result, resultlen);
+      }
+    }
 
   ccid_close_reader (ccid);
 
index cbadb40..9cb2325 100644 (file)
@@ -86,6 +86,11 @@ int ccid_slot_status (ccid_driver_t handle, int *statusbits);
 int ccid_transceive (ccid_driver_t handle,
                      const unsigned char *apdu, size_t apdulen,
                      unsigned char *resp, size_t maxresplen, size_t *nresp);
+int ccid_transceive_secure (ccid_driver_t handle,
+                     const unsigned char *apdu, size_t apdulen,
+                     int pin_mode, 
+                     int pinlen_min, int pinlen_max, int pin_padlen, 
+                     unsigned char *resp, size_t maxresplen, size_t *nresp);
 
 
 
index 8f2b150..97de9ab 100644 (file)
@@ -25,6 +25,8 @@
 #include "cardglue.h"
 #endif
 
+gpg_error_t iso7816_map_sw (int sw);
+
 gpg_error_t iso7816_select_application (int slot,
                                         const char *aid, size_t aidlen);
 gpg_error_t iso7816_select_file (int slot, int tag, int is_dir,
index cfdfe56..5e29efd 100644 (file)
@@ -894,7 +894,7 @@ keyserver_spawn(int action,STRLIST list,KEYDB_SEARCH_DESC *desc,
 
            log_info(_("requesting key %s from %s server %s\n"),
                     keystr_from_desc(&desc[i]),
-                    keyserver->scheme,keyserver->host);
+                    keyserver->scheme,keyserver->host?keyserver->host:"");
          }
 
        fprintf(spawn->tochild,"\n");
@@ -1039,7 +1039,7 @@ keyserver_spawn(int action,STRLIST list,KEYDB_SEARCH_DESC *desc,
 
                log_info(_("sending key %s to %s server %s\n"),
                         keystr(block->pkt->pkt.public_key->keyid),
-                        keyserver->scheme,keyserver->host);
+                        keyserver->scheme,keyserver->host?keyserver->host:"");
 
                release_kbnode(block);
              }
@@ -1080,7 +1080,7 @@ keyserver_spawn(int action,STRLIST list,KEYDB_SEARCH_DESC *desc,
        fprintf(spawn->tochild,"\n");
 
        log_info(_("searching for \"%s\" from %s server %s\n"),
-                searchstr,keyserver->scheme,keyserver->host);
+               searchstr,keyserver->scheme,keyserver->host?keyserver->host:"");
 
        break;
       }
index 37dfa91..4b3855a 100644 (file)
@@ -1,3 +1,7 @@
+2004-10-11  Werner Koch  <wk@g10code.com>
+
+       * gpgkeys_finger.c: New.
+
 2004-08-27  Stefan Bellon  <sbellon@sbellon.de>
 
        * gpgkeys_hkp.c (search_key): Fix the prior faulty fix by
index ddf2a4c..c9f5825 100644 (file)
 ## Process this file with automake to produce Makefile.in
 
 INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/intl
-EXTRA_PROGRAMS = gpgkeys_ldap gpgkeys_hkp gpgkeys_http
+EXTRA_PROGRAMS = gpgkeys_ldap gpgkeys_hkp gpgkeys_http gpgkeys_finger
 EXTRA_SCRIPTS = gpgkeys_mailto
 libexecdir = @libexecdir@/@PACKAGE@
 
-libexec_PROGRAMS = @GPGKEYS_LDAP@ @GPGKEYS_HKP@ @GPGKEYS_HTTP@
+libexec_PROGRAMS = @GPGKEYS_LDAP@ @GPGKEYS_HKP@ @GPGKEYS_HTTP@ @GPGKEYS_FINGER@
 libexec_SCRIPTS = @GPGKEYS_MAILTO@
 noinst_SCRIPTS = gpgkeys_test
 
 gpgkeys_ldap_LDADD = ../util/libutil.a @LDAPLIBS@ @NETLIBS@ @LIBINTL@ @CAPLIBS@ @GETOPT@ @W32LIBS@
 gpgkeys_hkp_LDADD = ../util/libutil.a @NETLIBS@ @SRVLIBS@ @LIBINTL@ @CAPLIBS@ @GETOPT@ @W32LIBS@
 gpgkeys_http_LDADD = ../util/libutil.a @NETLIBS@ @SRVLIBS@ @LIBINTL@ @CAPLIBS@ @GETOPT@ @W32LIBS@
+gpgkeys_finger_LDADD = ../util/libutil.a @NETLIBS@ @LIBINTL@ @CAPLIBS@ @GETOPT@ @W32LIBS@
 
 install-exec-hook:
 if GPGKEYS_LDAP
diff --git a/keyserver/gpgkeys_finger b/keyserver/gpgkeys_finger
new file mode 100755 (executable)
index 0000000..2e46a1e
Binary files /dev/null and b/keyserver/gpgkeys_finger differ