* options.h, g10.c (main): Add keyserver-option honor-keyserver-url.
[gnupg.git] / keyserver / gpgkeys_ldap.c
index 10ace54..42003b4 100644 (file)
@@ -1,5 +1,5 @@
 /* gpgkeys_ldap.c - talk to a LDAP keyserver
- * Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2002, 2004 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
 #include <stdlib.h>
 #include <errno.h>
-#ifdef NEED_LBER_H
-#include <lber.h>
-#endif
 #include <ldap.h>
+#include "util.h"
 #include "keyserver.h"
 
 #ifdef __riscos__
 #include "util.h"
 #endif
 
+extern char *optarg;
+extern int optind;
+
 #define GET    0
 #define SEND   1
 #define SEARCH 2
-#define MAX_LINE 80
+#define MAX_LINE 256
+
+static int verbose=0,include_disabled=0,include_revoked=0,include_subkeys=0;
+static int real_ldap=0;
+static char *basekeyspacedn=NULL;
+static char host[80]={'\0'};
+static char portstr[10]={'\0'};
+static char *pgpkeystr="pgpKey";
+static FILE *input=NULL,*output=NULL,*console=NULL;
+static LDAP *ldap=NULL;
+
+#if !HAVE_SETENV
+int setenv(const char *name, const char *value, int overwrite);
+#endif
 
-int verbose=0,include_disabled=0,include_revoked=0,include_subkeys=0;
-char *basekeyspacedn=NULL;
-char host[80]={'\0'};
-char portstr[10]={'\0'};
-char *pgpkeystr="pgpKey";
-FILE *input=NULL,*output=NULL,*console=NULL;
-LDAP *ldap=NULL;
+#if !HAVE_UNSETENV
+int unsetenv(const char *name);
+#endif
 
 struct keylist
 {
@@ -54,11 +67,7 @@ struct keylist
   struct keylist *next;
 };
 
-#ifdef __riscos__
-RISCOS_GLOBAL_STATICS("LDAP Keyfetcher Heap")
-#endif /* __riscos__ */
-
-int
+static int
 ldap_err_to_gpg_err(int err)
 {
   int ret;
@@ -81,10 +90,10 @@ ldap_err_to_gpg_err(int err)
   return ret;
 }
 
-int
+static int
 ldap_to_gpg_err(LDAP *ld)
 {
-#if defined(HAVE_LDAP_GET_OPTION)
+#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
 
   int err;
 
@@ -106,22 +115,612 @@ ldap_to_gpg_err(LDAP *ld)
 #endif
 }
 
-int
+static int
+key_in_keylist(const char *key,struct keylist *list)
+{
+  struct keylist *keyptr=list;
+
+  while(keyptr!=NULL)
+    {
+      if(strcasecmp(key,keyptr->str)==0)
+       return 1;
+
+      keyptr=keyptr->next;
+    }
+
+  return 0;
+}
+
+static int
+add_key_to_keylist(const char *key,struct keylist **list)
+{
+  struct keylist *keyptr=malloc(sizeof(struct keylist));
+
+  if(keyptr==NULL)
+    {
+      fprintf(console,"gpgkeys: out of memory when deduping "
+             "key list\n");
+      return KEYSERVER_NO_MEMORY;
+    }
+
+  strncpy(keyptr->str,key,MAX_LINE);
+  keyptr->str[MAX_LINE-1]='\0';
+  keyptr->next=*list;
+  *list=keyptr;
+
+  return 0;
+}
+
+static void
+free_keylist(struct keylist *list)
+{
+  while(list!=NULL)
+    {
+      struct keylist *keyptr=list;
+
+      list=keyptr->next;
+      free(keyptr);
+    }
+}
+
+static time_t
+ldap2epochtime(const char *timestr)
+{
+  struct tm pgptime;
+  time_t answer;
+
+  memset(&pgptime,0,sizeof(pgptime));
+
+  /* YYYYMMDDHHmmssZ */
+
+  sscanf(timestr,"%4d%2d%2d%2d%2d%2d",
+        &pgptime.tm_year,
+        &pgptime.tm_mon,
+        &pgptime.tm_mday,
+        &pgptime.tm_hour,
+        &pgptime.tm_min,
+        &pgptime.tm_sec);
+
+  pgptime.tm_year-=1900;
+  pgptime.tm_isdst=-1;
+  pgptime.tm_mon--;
+
+  /* mktime takes the timezone into account, and we can't have that.
+     I'd use timegm, but it's not portable. */
+
+#ifdef HAVE_TIMEGM
+  answer=timegm(&pgptime);
+#else
+  {
+    char *zone=getenv("TZ");
+    setenv("TZ","UTC",1);
+    tzset();
+    answer=mktime(&pgptime);
+    if(zone)
+      setenv("TZ",zone,1);
+    else
+      unsetenv("TZ");
+    tzset();
+  }
+#endif
+
+  return answer;
+}
+
+/* Caller must free */
+static char *
+epoch2ldaptime(time_t stamp)
+{
+  struct tm *ldaptime;
+  char buf[16];
+
+  ldaptime=gmtime(&stamp);
+
+  ldaptime->tm_year+=1900;
+  ldaptime->tm_mon++;
+
+  /* YYYYMMDDHHmmssZ */
+
+  sprintf(buf,"%04d%02d%02d%02d%02d%02dZ",
+         ldaptime->tm_year,
+         ldaptime->tm_mon,
+         ldaptime->tm_mday,
+         ldaptime->tm_hour,
+         ldaptime->tm_min,
+         ldaptime->tm_sec);
+
+  return strdup(buf);
+}
+
+/* Passing a NULL for value effectively deletes that attribute.  This
+   doesn't mean "delete" in the sense of removing something from the
+   modlist, but "delete" in the LDAP sense of adding a modlist item
+   that specifies LDAP_MOD_REPLACE and a null attribute for the given
+   attribute.  LDAP_MOD_DELETE doesn't work here as we don't know if
+   the attribute in question exists or not. */
+
+static int
+make_one_attr(LDAPMod ***modlist,int unique,char *attr,const char *value)
+{
+  LDAPMod **m;
+  int nummods=0;
+
+  /* Search modlist for the attribute we're playing with. */
+  for(m=*modlist;*m;m++)
+    {
+      if(strcasecmp((*m)->mod_type,attr)==0)
+       {
+         char **ptr=(*m)->mod_values;
+         int numvalues=0;
+
+         /* We have this attribute already, so when the REPLACE
+            happens, the server attributes will be replaced
+            anyway. */
+         if(!value)
+           return 1;
+
+         if(ptr)
+           for(ptr=(*m)->mod_values;*ptr;ptr++)
+             {
+               if(unique && strcmp(*ptr,value)==0)
+                 return 1;
+               numvalues++;
+             }
+
+         ptr=realloc((*m)->mod_values,sizeof(char *)*(numvalues+2));
+         if(!ptr)
+           return 0;
+
+         (*m)->mod_values=ptr;
+         ptr[numvalues]=strdup(value);
+         if(!ptr[numvalues])
+           return 0;
+
+         ptr[numvalues+1]=NULL;
+         break;
+       }
+
+      nummods++;
+    }
+
+  /* We didn't find the attr, so make one and add it to the end */
+  if(!*m)
+    {
+      LDAPMod **grow;
+
+      grow=realloc(*modlist,sizeof(LDAPMod *)*(nummods+2));
+      if(!grow)
+       return 0;
+
+      *modlist=grow;
+      grow[nummods]=malloc(sizeof(LDAPMod));
+      if(!grow[nummods])
+       return 0;
+      grow[nummods]->mod_op=LDAP_MOD_REPLACE;
+      grow[nummods]->mod_type=attr;
+      if(value)
+       {
+         grow[nummods]->mod_values=malloc(sizeof(char *)*2);
+         if(!grow[nummods]->mod_values)
+           {
+             grow[nummods]=NULL;
+             return 0;
+           }
+
+         /* Is this the right thing?  Can a UTF8-encoded user ID have
+            embedded nulls? */
+         grow[nummods]->mod_values[0]=strdup(value);
+         if(!grow[nummods]->mod_values[0])
+           {
+             free(grow[nummods]->mod_values);
+             grow[nummods]=NULL;
+             return 0;
+           }
+
+         grow[nummods]->mod_values[1]=NULL;
+       }
+      else
+       grow[nummods]->mod_values=NULL;
+
+      grow[nummods+1]=NULL;
+    }
+
+  return 1;
+}
+
+static void
+build_attrs(LDAPMod ***modlist,char *line)
+{
+  char *record;
+  int i;
+
+  /* Remove trailing whitespace */
+  for(i=strlen(line);i>0;i--)
+    if(ascii_isspace(line[i-1]))
+      line[i-1]='\0';
+    else
+      break;
+
+  if((record=strsep(&line,":"))==NULL)
+    return;
+
+  if(ascii_strcasecmp("pub",record)==0)
+    {
+      char *tok;
+      int disabled=0,revoked=0;
+
+      /* The long keyid */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       {
+         make_one_attr(modlist,0,"pgpCertID",tok);
+         make_one_attr(modlist,0,"pgpKeyID",&tok[8]);
+       }
+      else
+       return;
+
+      /* The primary pubkey algo */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      switch(atoi(tok))
+       {
+       case 1:
+         make_one_attr(modlist,0,"pgpKeyType","RSA");
+         break;
+
+       case 17:
+         make_one_attr(modlist,0,"pgpKeyType","DSS/DH");
+         break;
+       }
+
+      /* Size of primary key */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char padded[6];
+         int val=atoi(tok);
+
+         /* We zero pad this on the left to make PGP happy. */
+
+         if(val<99999 && val>0)
+           {
+             sprintf(padded,"%05u",atoi(tok));
+             make_one_attr(modlist,0,"pgpKeySize",padded);
+           }
+       }
+
+      /* pk timestamp */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char *stamp=epoch2ldaptime(atoi(tok));
+         if(stamp)
+           {
+             make_one_attr(modlist,0,"pgpKeyCreateTime",stamp);
+             free(stamp);
+           }
+       }
+
+      /* pk expire */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char *stamp=epoch2ldaptime(atoi(tok));
+         if(stamp)
+           {
+             make_one_attr(modlist,0,"pgpKeyExpireTime",stamp);
+             free(stamp);
+           }
+       }
+
+      /* flags */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      while(*tok)
+       switch(*tok++)
+         {
+         case 'r':
+         case 'R':
+           revoked=1;
+           break;
+           
+         case 'd':
+         case 'D':
+           disabled=1;
+           break;
+         }
+
+      /*
+       Note that we always create the pgpDisabled and pgpRevoked
+       attributes, regardless of whether the key is disabled/revoked
+       or not.  This is because a very common search is like
+       "(&(pgpUserID=*isabella*)(pgpDisabled=0))"
+      */
+
+      make_one_attr(modlist,0,"pgpDisabled",disabled?"1":"0");
+      make_one_attr(modlist,0,"pgpRevoked",revoked?"1":"0");
+    }
+  else if(ascii_strcasecmp("uid",record)==0)
+    {
+      char *userid,*tok;
+
+      /* The user ID string */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==0)
+       return;
+
+      userid=tok;
+
+      /* By definition, de-%-encoding is always smaller than the
+         original string so we can decode in place. */
+
+      i=0;
+
+      while(*tok)
+       if(tok[0]=='%' && tok[1] && tok[2])
+         {
+           if((userid[i]=hextobyte(&tok[1]))==-1)
+             userid[i]='?';
+
+           i++;
+           tok+=3;
+         }
+       else
+         userid[i++]=*tok++;
+
+      /* We don't care about the other info provided in the uid: line
+        since the LDAP schema doesn't need it. */
+
+      make_one_attr(modlist,0,"pgpUserID",userid);
+    }
+  else if(ascii_strcasecmp("sig",record)==0)
+    {
+      char *tok;
+
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       make_one_attr(modlist,1,"pgpSignerID",tok);
+    }
+}
+
+static void
+free_mod_values(LDAPMod *mod)
+{
+  char **ptr;
+
+  if(!mod->mod_values)
+    return;
+
+  for(ptr=mod->mod_values;*ptr;ptr++)
+    free(*ptr);
+
+  free(mod->mod_values);
+}
+
+static int
 send_key(int *eof)
 {
-  int err,gotit=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
-  char *dn=NULL;
-  char line[MAX_LINE];
-  char *key[2]={NULL,NULL};
+  int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+  char *dn=NULL,line[MAX_LINE],*key=NULL;
+  char keyid[17];
+  LDAPMod **modlist,**ml;
+
+  modlist=malloc(sizeof(LDAPMod *));
+  if(!modlist)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  *modlist=NULL;
+
+  /* Going on the assumption that modify operations are more frequent
+     than adds, I'm setting up the modify operations here first. */
+  make_one_attr(&modlist,0,"pgpDisabled",NULL);
+  make_one_attr(&modlist,0,"pgpKeyID",NULL);
+  make_one_attr(&modlist,0,"pgpKeyType",NULL);
+  make_one_attr(&modlist,0,"pgpUserID",NULL);
+  make_one_attr(&modlist,0,"pgpKeyCreateTime",NULL);
+  make_one_attr(&modlist,0,"pgpSignerID",NULL);
+  make_one_attr(&modlist,0,"pgpRevoked",NULL);
+  make_one_attr(&modlist,0,"pgpSubKeyID",NULL);
+  make_one_attr(&modlist,0,"pgpKeySize",NULL);
+  make_one_attr(&modlist,0,"pgpKeyExpireTime",NULL);
+  make_one_attr(&modlist,0,"pgpCertID",NULL);
+  /* Note the count of these deleted attributes.  They're to be used
+     later. */
+
+  /* Assemble the INFO stuff into LDAP attributes */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the INFO BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  if(strlen(keyid)!=16)
+    {
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  dn=malloc(strlen("pgpCertID=")+16+1+strlen(basekeyspacedn)+1);
+  if(dn==NULL)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  sprintf(dn,"pgpCertID=%s,%s",keyid,basekeyspacedn);
+
+  key=malloc(1);
+  if(!key)
+    {
+      fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  key[0]='\0';
+
+  /* Now parse each line until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      build_attrs(&modlist,line);
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no INFO %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  begin=end=0;
+
+  /* Read and throw away stdin until we see the BEGIN */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  /* Now slurp up everything until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       char *tempkey;
+       keysize+=strlen(line);
+       tempkey=realloc(key,keysize);
+       if(tempkey==NULL)
+         {
+           fprintf(console,"gpgkeys: unable to reallocate for key\n");
+           ret=KEYSERVER_NO_MEMORY;
+           goto fail;
+         }
+       else
+         key=tempkey;
+
+       strcat(key,line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  make_one_attr(&modlist,0,"objectClass","pgpKeyInfo");
+  make_one_attr(&modlist,0,"pgpKey",key);
+
+  /* If it's not there, we just turn around and send an add command
+     for the same key.  Otherwise, the modify brings the server copy
+     into compliance with our copy.  Note that unlike the LDAP
+     keyserver (and really, any other keyserver) this does NOT merge
+     signatures, but replaces the whole key.  This should make some
+     people very happy. */
+
+  err=ldap_modify_s(ldap,dn,modlist);
+  if(err==LDAP_NO_SUCH_OBJECT)
+    {
+      /* This [11] is the deleted count from earlier */
+      LDAPMod **addlist=&modlist[11];
+      err=ldap_add_s(ldap,dn,addlist);
+    }
+
+  if(err!=LDAP_SUCCESS)
+    {
+      fprintf(console,"gpgkeys: error adding key %s to keyserver: %s\n",
+             keyid,ldap_err2string(err));
+      ret=ldap_err_to_gpg_err(err);
+      goto fail;
+    }
+
+  ret=KEYSERVER_OK;
+
+ fail:
+  /* Unwind and free the whole modlist structure */
+  for(ml=modlist;*ml;ml++)
+    {
+      free_mod_values(*ml);
+      free(*ml);
+    }
+
+  free(modlist);
+  free(dn);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+  return ret;
+}
+
+static int
+send_key_keyserver(int *eof)
+{
+  int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+  char *dn=NULL,line[MAX_LINE],*key[2]={NULL,NULL};
   char keyid[17];
   LDAPMod mod, *attrs[2];
 
-  memset (&mod, 0, sizeof mod);
-  mod.mod_op      = LDAP_MOD_ADD;
-  mod.mod_type    = pgpkeystr;
-  mod.mod_values  = key;
-  attrs[0]    = &mod;
-  attrs[1]    = NULL;
+  memset(&mod,0,sizeof(mod));
+  mod.mod_op=LDAP_MOD_ADD;
+  mod.mod_type=pgpkeystr;
+  mod.mod_values=key;
+  attrs[0]=&mod;
+  attrs[1]=NULL;
 
   dn=malloc(strlen("pgpCertid=virtual,")+strlen(basekeyspacedn)+1);
   if(dn==NULL)
@@ -149,11 +748,11 @@ send_key(int *eof)
   while(fgets(line,MAX_LINE,input)!=NULL)
     if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
       {
-       gotit=1;
+       begin=1;
        break;
       }
 
-  if(!gotit)
+  if(!begin)
     {
       /* i.e. eof before the KEY BEGIN was found.  This isn't an
         error. */
@@ -162,14 +761,12 @@ send_key(int *eof)
       goto fail;
     }
 
-  gotit=0;
-
   /* Now slurp up everything until we see the END */
 
   while(fgets(line,MAX_LINE,input)!=NULL)
     if(sscanf(line,"KEY %16s END\n",keyid)==1)
       {
-       gotit=1;
+       end=1;
        break;
       }
     else
@@ -186,7 +783,7 @@ send_key(int *eof)
        strcat(key[0],line);
       }
 
-  if(!gotit)
+  if(!end)
     {
       fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
       *eof=1;
@@ -210,7 +807,7 @@ send_key(int *eof)
   free(key[0]);
   free(dn);
 
-  if(ret!=0)
+  if(ret!=0 && begin)
     fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
 
   /* Not a fatal error */
@@ -221,15 +818,17 @@ send_key(int *eof)
 }
 
 /* Note that key-not-found is not a fatal error */
-int
+static int
 get_key(char *getkey)
 {
-  char **vals;
   LDAPMessage *res,*each;
   int ret=KEYSERVER_INTERNAL_ERROR,err,count;
   struct keylist *dupelist=NULL;
   char search[62];
-  char *attrs[]={"replaceme","pgpuserid","pgpkeyid","pgpcertid","pgprevoked",
+  /* This ordering is significant - specifically, "pgpcertid" needs to
+     be the second item in the list, since everything after it may be
+     discarded if the user isn't in verbose mode. */
+  char *attrs[]={"replaceme","pgpcertid","pgpuserid","pgpkeyid","pgprevoked",
                 "pgpdisabled","pgpkeycreatetime","modifytimestamp",
                 "pgpkeysize","pgpkeytype",NULL};
   attrs[0]=pgpkeystr; /* Some compilers don't like using variables as
@@ -288,7 +887,7 @@ get_key(char *getkey)
     fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search);
 
   if(!verbose)
-    attrs[1]=NULL;
+    attrs[2]=NULL; /* keep only pgpkey(v2) and pgpcertid */
 
   if(verbose)
     fprintf(console,"gpgkeys: requesting key 0x%s from ldap://%s%s%s\n",
@@ -320,148 +919,124 @@ get_key(char *getkey)
       each=ldap_first_entry(ldap,res);
       while(each!=NULL)
        {
-         struct keylist *keyptr=dupelist;
+         char **vals,**certid;
 
          /* Use the long keyid to remove duplicates.  The LDAP server
             returns the same keyid more than once if there are
             multiple user IDs on the key.  Note that this does NOT
             mean that a keyid that exists multiple times on the
             keyserver will not be fetched.  It means that each KEY,
-            no matter how many user IDs share it's keyid, will be
+            no matter how many user IDs share its keyid, will be
             fetched only once.  If a keyid that belongs to more than
             one key is fetched, the server quite properly responds
             with all matching keys. -ds */
 
-         vals=ldap_get_values(ldap,each,"pgpcertid");
-         if(vals!=NULL)
+         certid=ldap_get_values(ldap,each,"pgpcertid");
+         if(certid!=NULL)
            {
-             while(keyptr!=NULL)
-               {
-                 if(strcasecmp(keyptr->str,vals[0])==0)
-                   break;
-
-                 keyptr=keyptr->next;
-               }
-
-             if(!keyptr)
+             if(!key_in_keylist(certid[0],dupelist))
                {
                  /* it's not a duplicate, so add it */
 
-                 keyptr=malloc(sizeof(struct keylist));
-                 if(keyptr==NULL)
+                 int rc=add_key_to_keylist(certid[0],&dupelist);
+                 if(rc)
                    {
-                     fprintf(console,"gpgkeys: out of memory when deduping "
-                             "key list\n");
-                     ret=KEYSERVER_NO_MEMORY;
+                     ret=rc;
                      goto fail;
                    }
 
-                 strncpy(keyptr->str,vals[0],MAX_LINE);
-                 keyptr->str[MAX_LINE-1]='\0';
-
-                 keyptr->next=dupelist;
-                 dupelist=keyptr;
-                 keyptr=NULL;
-               }
-
-             ldap_value_free(vals);
-           }
-
-         if(!keyptr) /* it's not a duplicate */
-           {
-             if(verbose)
-               {
-                 vals=ldap_get_values(ldap,each,"pgpuserid");
-                 if(vals!=NULL)
+                 if(verbose)
                    {
-                     /* This is wrong, as the user ID is UTF8.  A
-                        better way to handle this would be to send it
-                        over to gpg and display it on that side of
-                        the pipe. */
-                     fprintf(console,"\nUser ID:\t%s\n",vals[0]);
-                     ldap_value_free(vals);
+                     vals=ldap_get_values(ldap,each,"pgpuserid");
+                     if(vals!=NULL)
+                       {
+                         /* This is wrong, as the user ID is UTF8.  A
+                            better way to handle this would be to send it
+                            over to gpg and display it on that side of
+                            the pipe. */
+                         fprintf(console,"\nUser ID:\t%s\n",vals[0]);
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"pgprevoked");
+                     if(vals!=NULL)
+                       {
+                         if(atoi(vals[0])==1)
+                           fprintf(console,"\t\t** KEY REVOKED **\n");
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"pgpdisabled");
+                     if(vals!=NULL)
+                       {
+                         if(atoi(vals[0])==1)
+                           fprintf(console,"\t\t** KEY DISABLED **\n");
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"pgpkeyid");
+                     if(vals!=NULL)
+                       {
+                         fprintf(console,"Short key ID:\t%s\n",vals[0]);
+                         ldap_value_free(vals);
+                       }
+
+                     fprintf(console,"Long key ID:\t%s\n",certid[0]);
+
+                     /* YYYYMMDDHHmmssZ */
+
+                     vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
+                     if(vals!=NULL)
+                       {
+                         if(strlen(vals[0])==15)
+                           fprintf(console,"Key created:\t%.2s/%.2s/%.4s\n",
+                                   &vals[0][4],&vals[0][6],vals[0]);
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"modifytimestamp");
+                     if(vals!=NULL)
+                       {
+                         if(strlen(vals[0])==15)
+                           fprintf(console,"Key modified:\t%.2s/%.2s/%.4s\n",
+                                   &vals[0][4],&vals[0][6],vals[0]);
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"pgpkeysize");
+                     if(vals!=NULL)
+                       {
+                         if(atoi(vals[0])>0)
+                           fprintf(console,"Key size:\t%d\n",atoi(vals[0]));
+                         ldap_value_free(vals);
+                       }
+
+                     vals=ldap_get_values(ldap,each,"pgpkeytype");
+                     if(vals!=NULL)
+                       {
+                         fprintf(console,"Key type:\t%s\n",vals[0]);
+                         ldap_value_free(vals);
+                       }
                    }
 
-                 vals=ldap_get_values(ldap,each,"pgprevoked");
-                 if(vals!=NULL)
-                   {
-                     if(atoi(vals[0])==1)
-                       fprintf(console,"\t\t** KEY REVOKED **\n");
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"pgpdisabled");
-                 if(vals!=NULL)
-                   {
-                     if(atoi(vals[0])==1)
-                       fprintf(console,"\t\t** KEY DISABLED **\n");
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"pgpkeyid");
-                 if(vals!=NULL)
+                 vals=ldap_get_values(ldap,each,pgpkeystr);
+                 if(vals==NULL)
                    {
-                     fprintf(console,"Short key ID:\t%s\n",vals[0]);
-                     ldap_value_free(vals);
-                   }
+                     int errtag=ldap_to_gpg_err(ldap);
 
-                 vals=ldap_get_values(ldap,each,"pgpcertid");
-                 if(vals!=NULL)
-                   {
-                     fprintf(console,"Long key ID:\t%s\n",vals[0]);
-                     ldap_value_free(vals);
+                     fprintf(console,"gpgkeys: unable to retrieve key %s "
+                             "from keyserver\n",getkey);
+                     fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
                    }
-
-                 /* YYYYMMDDHHmmssZ */
-
-                 vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
-                 if(vals!=NULL)
-                   {
-                     if(strlen(vals[0])==15)
-                       fprintf(console,"Key created:\t%.2s/%.2s/%.4s\n",
-                               &vals[0][4],&vals[0][6],vals[0]);
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"modifytimestamp");
-                 if(vals!=NULL)
-                   {
-                     if(strlen(vals[0])==15)
-                       fprintf(console,"Key modified:\t%.2s/%.2s/%.4s\n",
-                               &vals[0][4],&vals[0][6],vals[0]);
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"pgpkeysize");
-                 if(vals!=NULL)
-                   {
-                     fprintf(console,"Key size:\t%d\n",atoi(vals[0]));
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"pgpkeytype");
-                 if(vals!=NULL)
+                 else
                    {
-                     fprintf(console,"Key type:\t%s\n",vals[0]);
+                     fprintf(output,"%sKEY 0x%s END\n",vals[0],getkey);
+
                      ldap_value_free(vals);
                    }
                }
 
-             vals=ldap_get_values(ldap,each,pgpkeystr);
-             if(vals==NULL)
-               {
-                 int errtag=ldap_to_gpg_err(ldap);
-
-                 fprintf(console,"gpgkeys: unable to retrieve key %s "
-                         "from keyserver\n",getkey);
-                 fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
-               }
-             else
-               {
-                 fprintf(output,"%sKEY 0x%s END\n",vals[0],getkey);
-
-                 ldap_value_free(vals);
-               }
+             ldap_value_free(certid);
            }
 
          each=ldap_next_entry(ldap,each);
@@ -472,48 +1047,18 @@ get_key(char *getkey)
 
  fail:
   ldap_msgfree(res);
-
-  /* free up the dupe checker */
-  while(dupelist!=NULL)
-    {
-      struct keylist *keyptr=dupelist;
-
-      dupelist=keyptr->next;
-      free(keyptr);
-    }
+  free_keylist(dupelist);
 
   return ret;
 }
 
-time_t ldap2epochtime(const char *timestr)
-{
-  struct tm pgptime;
-
-  memset(&pgptime,0,sizeof(pgptime));
-
-  /* YYYYMMDDHHmmssZ */
-
-  sscanf(timestr,"%4d%2d%2d%2d%2d%2d",
-        &pgptime.tm_year,
-        &pgptime.tm_mon,
-        &pgptime.tm_mday,
-        &pgptime.tm_hour,
-        &pgptime.tm_min,
-        &pgptime.tm_sec);
-
-  pgptime.tm_year-=1900;
-  pgptime.tm_isdst=-1;
-  pgptime.tm_mon--;
-
-  return mktime(&pgptime);
-}
-
-void printquoted(FILE *stream,char *string,char delim)
+static void
+printquoted(FILE *stream,char *string,char delim)
 {
   while(*string)
     {
-      if(*string==delim || *string=='\\')
-       fprintf(stream,"\\x%02x",*string);
+      if(*string==delim || *string=='%')
+       fprintf(stream,"%%%02x",*string);
       else
        fputc(*string,stream);
 
@@ -523,13 +1068,14 @@ void printquoted(FILE *stream,char *string,char delim)
 
 /* Returns 0 on success and -1 on error.  Note that key-not-found is
    not an error! */
-int
+static int
 search_key(char *searchkey)
 {
   char **vals;
   LDAPMessage *res,*each;
-  int err,count;
-  /* The maxium size of the search, including the optional stuff and
+  int err,count=0;
+  struct keylist *dupelist=NULL;
+  /* The maximum size of the search, including the optional stuff and
      the trailing \0 */
   char search[2+12+MAX_LINE+2+15+14+1+1];
   char *attrs[]={"pgpcertid","pgpuserid","pgprevoked","pgpdisabled",
@@ -555,7 +1101,7 @@ search_key(char *searchkey)
 
   err=ldap_search_s(ldap,basekeyspacedn,
                    LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
-  if(err!=0)
+  if(err!=LDAP_SUCCESS && err!=LDAP_SIZELIMIT_EXCEEDED)
     {
       int errtag=ldap_err_to_gpg_err(err);
 
@@ -564,119 +1110,197 @@ search_key(char *searchkey)
       return errtag;
     }
 
-  count=ldap_count_entries(ldap,res);
+  /* The LDAP server doesn't return a real count of unique keys, so we
+     can't use ldap_count_entries here. */
+  each=ldap_first_entry(ldap,res);
+  while(each!=NULL)
+    {
+      char **certid=ldap_get_values(ldap,each,"pgpcertid");
+
+      if(certid!=NULL)
+       {
+         if(!key_in_keylist(certid[0],dupelist))
+           {
+             int rc=add_key_to_keylist(certid[0],&dupelist);
+             if(rc!=0)
+               {
+                 fprintf(output,"SEARCH %s FAILED %d\n",searchkey,rc);
+                 free_keylist(dupelist);
+                 return rc;
+               }
+
+             count++;
+           }
+       }
+
+      each=ldap_next_entry(ldap,each);
+    }
+
+  if(err==LDAP_SIZELIMIT_EXCEEDED)
+    fprintf(console,"gpgkeys: search results exceeded server limit.  First %d results shown.\n",count);
+
+  free_keylist(dupelist);
+  dupelist=NULL;
 
   if(count<1)
-    fprintf(output,"COUNT 0\n");
+    fprintf(output,"info:1:0\n");
   else
     {
-      fprintf(output,"COUNT %d\n",count);
+      fprintf(output,"info:1:%d\n",count);
 
       each=ldap_first_entry(ldap,res);
       while(each!=NULL)
        {
-         int flags=0;
+         char **certid;
 
-         vals=ldap_get_values(ldap,each,"pgpcertid");
-         if(vals!=NULL)
+         certid=ldap_get_values(ldap,each,"pgpcertid");
+         if(certid!=NULL)
            {
-             fprintf(output,"%s:",vals[0]);
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+             LDAPMessage *uids;
 
-         vals=ldap_get_values(ldap,each,"pgpuserid");
-         if(vals!=NULL)
-           {
-             /* Need to escape any colons */
-             printquoted(output,vals[0],':');
-             fputc(':',output);
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+             /* Have we seen this certid before? */
+             if(!key_in_keylist(certid[0],dupelist))
+               {
+                 int rc=add_key_to_keylist(certid[0],&dupelist);
+                 if(rc)
+                   {
+                     fprintf(output,"SEARCH %s FAILED %d\n",searchkey,rc);
+                     free_keylist(dupelist);
+                     ldap_value_free(certid);
+                     ldap_msgfree(res);
+                     return rc;
+                   }
 
-         vals=ldap_get_values(ldap,each,"pgprevoked");
-         if(vals!=NULL)
-           {
-             if(atoi(vals[0])==1)
-               flags|=1;
-             ldap_value_free(vals);
-           }
+                 fprintf(output,"pub:%s:",certid[0]);
 
-         vals=ldap_get_values(ldap,each,"pgpdisabled");
-         if(vals!=NULL)
-           {
-             if(atoi(vals[0])==1)
-               flags|=2;
-             ldap_value_free(vals);
-           }
+                 vals=ldap_get_values(ldap,each,"pgpkeytype");
+                 if(vals!=NULL)
+                   {
+                     /* The LDAP server doesn't exactly handle this
+                        well. */
+                     if(strcasecmp(vals[0],"RSA")==0)
+                       fprintf(output,"1");
+                     else if(strcasecmp(vals[0],"DSS/DH")==0)
+                       fprintf(output,"17");
+                     ldap_value_free(vals);
+                   }
 
-         fprintf(output,"%d:",flags);
+                 fputc(':',output);
 
-         /* YYYYMMDDHHmmssZ */
+                 vals=ldap_get_values(ldap,each,"pgpkeysize");
+                 if(vals!=NULL)
+                   {
+                     /* Not sure why, but some keys are listed with a
+                        key size of 0.  Treat that like an
+                        unknown. */
+                     if(atoi(vals[0])>0)
+                       fprintf(output,"%d",atoi(vals[0]));
+                     ldap_value_free(vals);
+                   }
 
-         vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
-         if(vals!=NULL && strlen(vals[0])==15)
-           {
-             fprintf(output,"%u:",(unsigned int)ldap2epochtime(vals[0]));
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+                 fputc(':',output);
 
-         vals=ldap_get_values(ldap,each,"pgpkeyexpiretime");
-         if(vals!=NULL && strlen(vals[0])==15)
-           {
-             fprintf(output,"%u:",(unsigned int)ldap2epochtime(vals[0]));
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+                 /* YYYYMMDDHHmmssZ */
 
-         vals=ldap_get_values(ldap,each,"modifytimestamp");
-         if(vals!=NULL && strlen(vals[0])==15)
-           {
-             fprintf(output,"%u:",(unsigned int)ldap2epochtime(vals[0]));
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+                 vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
 
-         vals=ldap_get_values(ldap,each,"pgpkeytype");
-         if(vals!=NULL)
-           {
-             fprintf(output,"%s:",vals[0]);
-             ldap_value_free(vals);
-           }
-         else
-           fputc(':',output);
+                 fputc(':',output);
 
-         vals=ldap_get_values(ldap,each,"pgpkeysize");
-         if(vals!=NULL)
-           {
-             /* Not sure why, but some keys are listed with a key size of
-                0.  Treat that like an unknown. */
-             if(atoi(vals[0])>0)
-               fprintf(output,"%d",atoi(vals[0]));
-             ldap_value_free(vals);
-           }
+                 vals=ldap_get_values(ldap,each,"pgpkeyexpiretime");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
+
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"pgprevoked");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"r");
+                     ldap_value_free(vals);
+                   }
+
+                 vals=ldap_get_values(ldap,each,"pgpdisabled");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"d");
+                     ldap_value_free(vals);
+                   }
+
+#if 0
+                 /* This is not yet specified in the keyserver
+                    protocol, but may be someday. */
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"modifytimestamp");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
+#endif
+
+                 fprintf(output,"\n");
 
-         fputc('\n',output);
+                 /* Now print all the uids that have this certid */
+                 uids=ldap_first_entry(ldap,res);
+                 while(uids!=NULL)
+                   {
+                     vals=ldap_get_values(ldap,uids,"pgpcertid");
+                     if(vals!=NULL)
+                       {
+                         if(strcasecmp(certid[0],vals[0])==0)
+                           {
+                             char **uidvals;
+
+                             fprintf(output,"uid:");
+
+                             uidvals=ldap_get_values(ldap,uids,"pgpuserid");
+                             if(uidvals!=NULL)
+                               {
+                                 /* Need to escape any colons */
+                                 printquoted(output,uidvals[0],':');
+                                 ldap_value_free(uidvals);
+                               }
+
+                             fprintf(output,"\n");
+                           }
+
+                         ldap_value_free(vals);
+                       }
+
+                     uids=ldap_next_entry(ldap,uids);
+                   }
+               }
+
+             ldap_value_free(certid);
+           }
 
          each=ldap_next_entry(ldap,each);
        }
     }
 
   ldap_msgfree(res);
+  free_keylist(dupelist);
 
   fprintf(output,"SEARCH %s END\n",searchkey);
 
   return KEYSERVER_OK;
 }
 
-void
+static void
 fail_all(struct keylist *keylist,int action,int err)
 {
   if(!keylist)
@@ -700,30 +1324,156 @@ fail_all(struct keylist *keylist,int action,int err)
       }
 }
 
-int main(int argc,char *argv[])
+static int
+find_basekeyspacedn(void)
 {
-  int port=0,arg,err,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
-  char line[MAX_LINE],**vals;
-  int version,failed=0;
-  char *attrs[]={"basekeyspacedn","version","software",NULL};
+  int err,i;
+  char *attr[]={"namingContexts",NULL,NULL,NULL};
   LDAPMessage *res;
-  struct keylist *keylist=NULL,*keyptr=NULL;
+  char **context;
 
-#ifdef __riscos__
-  riscos_global_defaults();
-#endif
+  /* Look for namingContexts */
+  err=ldap_search_s(ldap,"",LDAP_SCOPE_BASE,"(objectClass=*)",attr,0,&res);
+  if(err==LDAP_SUCCESS)
+    {
+      context=ldap_get_values(ldap,res,"namingContexts");
+      if(context)
+       {
+         attr[0]="pgpBaseKeySpaceDN";
+         attr[1]="pgpVersion";
+         attr[2]="pgpSoftware";
+
+         real_ldap=1;
+
+         /* We found some, so try each namingContext as the search base
+            and look for pgpBaseKeySpaceDN.  Because we found this, we
+            know we're talking to a regular-ish LDAP server and not a
+            LDAP keyserver. */
+
+         for(i=0;context[i] && !basekeyspacedn;i++)
+           {
+             char **vals;
+             LDAPMessage *si_res;
+             err=ldap_search_s(ldap,context[i],LDAP_SCOPE_ONELEVEL,
+                               "(cn=pgpServerInfo)",attr,0,&si_res);
+             if(err!=LDAP_SUCCESS)
+               return err;
+
+             vals=ldap_get_values(ldap,si_res,"pgpBaseKeySpaceDN");
+             if(vals)
+               {
+                 /* This is always "OU=ACTIVE,O=PGP KEYSPACE,C=US", but
+                    it might not be in the future. */
+
+                 basekeyspacedn=strdup(vals[0]);
+                 ldap_value_free(vals);
+               }
+
+             if(verbose>1)
+               {
+                 vals=ldap_get_values(ldap,si_res,"pgpSoftware");
+                 if(vals)
+                   {
+                     fprintf(console,"Server: \t%s\n",vals[0]);
+                     ldap_value_free(vals);
+                   }
+
+                 vals=ldap_get_values(ldap,si_res,"pgpVersion");
+                 if(vals)
+                   {
+                     fprintf(console,"Version:\t%s\n",vals[0]);
+                     ldap_value_free(vals);
+                   }
+               }
+
+             ldap_msgfree(si_res);
+           }
+
+         ldap_value_free(context);
+       }
+
+      ldap_msgfree(res);
+    }
+  else
+    {
+      /* We don't have an answer yet, which means the server might be
+        a LDAP keyserver. */
+      char **vals;
+      LDAPMessage *si_res;
+
+      attr[0]="pgpBaseKeySpaceDN";
+      attr[1]="version";
+      attr[2]="software";
+
+      err=ldap_search_s(ldap,"cn=pgpServerInfo",LDAP_SCOPE_BASE,
+                       "(objectClass=*)",attr,0,&si_res);
+      if(err!=LDAP_SUCCESS)
+       return err;
+
+      vals=ldap_get_values(ldap,si_res,"baseKeySpaceDN");
+      if(vals)
+       {
+         basekeyspacedn=strdup(vals[0]);
+         ldap_value_free(vals);
+       }
+
+      if(verbose>1)
+       {
+         vals=ldap_get_values(ldap,si_res,"software");
+         if(vals)
+           {
+             fprintf(console,"Server: \t%s\n",vals[0]);
+             ldap_value_free(vals);
+           }
+       }
+
+      vals=ldap_get_values(ldap,si_res,"version");
+      if(vals)
+       {
+         if(verbose>1)
+           fprintf(console,"Version:\t%s\n",vals[0]);
+
+         /* If the version is high enough, use the new pgpKeyV2
+            attribute.  This design if iffy at best, but it matches how
+            PGP does it.  I figure the NAI folks assumed that there would
+            never be a LDAP keyserver vendor with a different numbering
+            scheme. */
+         if(atoi(vals[0])>1)
+           pgpkeystr="pgpKeyV2";
+
+         ldap_value_free(vals);
+       }
+
+      ldap_msgfree(si_res);
+    }   
+
+  return LDAP_SUCCESS;
+}
+
+int
+main(int argc,char *argv[])
+{
+  int port=0,arg,err,action=-1,ret=KEYSERVER_INTERNAL_ERROR;
+  char line[MAX_LINE];
+  int version,failed=0,use_ssl=0,use_tls=0;
+  struct keylist *keylist=NULL,*keyptr=NULL;
 
   console=stderr;
 
-  while((arg=getopt(argc,argv,"ho:"))!=-1)
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
     switch(arg)
       {
       default:
       case 'h':
        fprintf(console,"-h\thelp\n");
+       fprintf(console,"-V\tversion\n");
        fprintf(console,"-o\toutput to this file\n");
        return KEYSERVER_OK;
 
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
+       return KEYSERVER_OK;
+
       case 'o':
        output=fopen(optarg,"w");
        if(output==NULL)
@@ -759,6 +1509,7 @@ int main(int argc,char *argv[])
     {
       char commandstr[7];
       char optionstr[30];
+      char schemestr[80];
       char hash;
 
       if(line[0]=='\n')
@@ -794,9 +1545,20 @@ int main(int argc,char *argv[])
          continue;
        }
 
+      if(sscanf(line,"SCHEME %79s\n",schemestr)==1)
+       {
+         schemestr[79]='\0';
+         if(strcasecmp(schemestr,"ldaps")==0)
+           {
+             port=636;
+             use_ssl=1;
+           }
+         continue;
+       }
+
       if(sscanf(line,"VERSION %d\n",&version)==1)
        {
-         if(version!=0)
+         if(version!=KEYSERVER_PROTO_VERSION)
            {
              ret=KEYSERVER_VERSION_ERROR;
              goto fail;
@@ -846,6 +1608,26 @@ int main(int argc,char *argv[])
              else
                include_subkeys=1;
            }
+         else if(strncasecmp(start,"tls",3)==0)
+           {
+             if(no)
+               use_tls=0;
+             else if(start[3]=='=')
+               {
+                 if(strcasecmp(&start[4],"no")==0)
+                   use_tls=0;
+                 else if(strcasecmp(&start[4],"try")==0)
+                   use_tls=1;
+                 else if(strcasecmp(&start[4],"warn")==0)
+                   use_tls=2;
+                 else if(strcasecmp(&start[4],"require")==0)
+                   use_tls=3;
+                 else
+                   use_tls=1;
+               }
+             else if(start[3]=='\0')
+               use_tls=1;
+           }
 
          continue;
        }
@@ -866,7 +1648,7 @@ int main(int argc,char *argv[])
            break;
          else
            {
-             if(line[0]=='\n')
+             if(line[0]=='\n' || line[0]=='\0')
                break;
 
              work=malloc(sizeof(struct keylist));
@@ -904,7 +1686,7 @@ int main(int argc,char *argv[])
 
   /* Send the response */
 
-  fprintf(output,"VERSION 0\n");
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
   fprintf(output,"PROGRAM %s\n\n",VERSION);
 
   if(verbose>1)
@@ -916,6 +1698,8 @@ int main(int argc,char *argv[])
              action==SEND?"SEND":"SEARCH");
     }
 
+  /* Note that this tries all A records on a given host (or at least,
+     OpenLDAP does). */
   ldap=ldap_init(host,port);
   if(ldap==NULL)
     {
@@ -925,80 +1709,109 @@ int main(int argc,char *argv[])
       goto fail;
     }
 
-  err=ldap_simple_bind_s(ldap,NULL,NULL);
-  if(err!=0)
+  if(use_ssl)
     {
-      fprintf(console,"gpgkeys: internal LDAP bind error: %s\n",
-             ldap_err2string(err));
-      fail_all(keylist,action,ldap_err_to_gpg_err(err));
-      goto fail;
+      if(!real_ldap)
+       {
+         fprintf(console,"gpgkeys: unable to make SSL connection: %s\n",
+                 "not supported by the NAI LDAP keyserver");
+         fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
+         goto fail;
+       }
+      else
+       {
+#if defined(LDAP_OPT_X_TLS_HARD) && defined(HAVE_LDAP_SET_OPTION)
+         int ssl=LDAP_OPT_X_TLS_HARD;
+         err=ldap_set_option(ldap,LDAP_OPT_X_TLS,&ssl);
+         if(err!=LDAP_SUCCESS)
+           {
+             fprintf(console,"gpgkeys: unable to make SSL connection: %s\n",
+                     ldap_err2string(err));
+             fail_all(keylist,action,ldap_err_to_gpg_err(err));
+             goto fail;
+           }
+#else
+         fprintf(console,"gpgkeys: unable to make SSL connection: %s\n",
+                 "not built with LDAPS support");
+         fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
+         goto fail;
+#endif
+       }
     }
 
-  /* Get the magic info record */
-
-  err=ldap_search_s(ldap,"cn=PGPServerInfo",LDAP_SCOPE_BASE,
-                   "(objectclass=*)",attrs,0,&res);
-  if(err!=0)
+  if((err=find_basekeyspacedn()))
     {
-      fprintf(console,"gpgkeys: error retrieving LDAP server info: %s\n",
+      fprintf(console,"gpgkeys: unable to retrieve LDAP base: %s\n",
              ldap_err2string(err));
       fail_all(keylist,action,ldap_err_to_gpg_err(err));
       goto fail;
     }
 
-  if(ldap_count_entries(ldap,res)!=1)
-    {
-      fprintf(console,"gpgkeys: more than one serverinfo record\n");
-      fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
-      goto fail;
-    }
-
-  if(verbose>1)
+  /* use_tls: 0=don't use, 1=try silently to use, 2=try loudly to use,
+     3=force use. */
+  if(use_tls)
     {
-      vals=ldap_get_values(ldap,res,"software");
-      if(vals!=NULL)
+      if(!real_ldap)
+       {
+         if(use_tls>=2)
+           fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                   "not supported by the NAI LDAP keyserver");
+         if(use_tls==3)
+           {
+             fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
+             goto fail;
+           }
+       }
+      else
        {
-         fprintf(console,"Server: \t%s\n",vals[0]);
-         ldap_value_free(vals);
-       }
-    }
+#if defined(HAVE_LDAP_START_TLS_S) && defined(HAVE_LDAP_SET_OPTION)
+         int ver=LDAP_VERSION3;
 
-  vals=ldap_get_values(ldap,res,"version");
-  if(vals!=NULL)
-    {
-      if(verbose>1)
-       fprintf(console,"Version:\t%s\n",vals[0]);
+         err=LDAP_SUCCESS;
 
-      /* If the version is high enough, use the new pgpKeyV2
-        attribute.  This design if iffy at best, but it matches how
-        PGP does it.  I figure the NAI folks assumed that there would
-        never be a LDAP keyserver vendor with a different numbering
-        scheme. */
-      if(atoi(vals[0])>1)
-       pgpkeystr="pgpKeyV2";
+         err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+         if(err==LDAP_SUCCESS)
+           err=ldap_start_tls_s(ldap,NULL,NULL);
 
-      ldap_value_free(vals);
+         if(err!=LDAP_SUCCESS && use_tls>=2)
+           {
+             fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                     ldap_err2string(err));
+             /* Are we forcing it? */
+             if(use_tls==3)
+               {
+                 fail_all(keylist,action,ldap_err_to_gpg_err(err));
+                 goto fail;
+               }
+           }
+         else if(verbose>1)
+           fprintf(console,"gpgkeys: TLS started successfully.\n");
+#else
+         if(use_tls>=2)
+           fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                   "not built with TLS support");
+         if(use_tls==3)
+           {
+             fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
+             goto fail;
+           }
+#endif
+       }
     }
 
-  /* This is always "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not
-     be in the future. */
+  /* The LDAP keyserver doesn't require this, but it might be useful
+     if someone stores keys on a V2 LDAP server somewhere.  (V3
+     doesn't require a bind). */
 
-  vals=ldap_get_values(ldap,res,"basekeyspacedn");
-  if(vals!=NULL)
+  err=ldap_simple_bind_s(ldap,NULL,NULL);
+  if(err!=0)
     {
-      basekeyspacedn=strdup(vals[0]);
-      ldap_value_free(vals);
-      if(basekeyspacedn==NULL)
-       {
-         fprintf(console,"gpgkeys: can't allocate string space "
-                 "for LDAP base\n");
-         fail_all(keylist,action,KEYSERVER_NO_MEMORY);
-         goto fail;
-       }
+      fprintf(console,"gpgkeys: internal LDAP bind error: %s\n",
+             ldap_err2string(err));
+      fail_all(keylist,action,ldap_err_to_gpg_err(err));
+      goto fail;
     }
 
-  ldap_msgfree(res);
-
   switch(action)
     {
     case GET:
@@ -1019,8 +1832,16 @@ int main(int argc,char *argv[])
 
        do
          {
-           if(send_key(&eof)!=KEYSERVER_OK)
-             failed++;
+           if(real_ldap)
+             {
+               if(send_key(&eof)!=KEYSERVER_OK)
+                 failed++;
+             }
+           else
+             {
+               if(send_key_keyserver(&eof)!=KEYSERVER_OK)
+                 failed++;
+             }
          }
        while(!eof);
       }
@@ -1062,7 +1883,8 @@ int main(int argc,char *argv[])
          }
 
        /* Nail that last "*" */
-       searchkey[strlen(searchkey)-1]='\0';
+       if(*searchkey)
+         searchkey[strlen(searchkey)-1]='\0';
 
        if(search_key(searchkey)!=KEYSERVER_OK)
          failed++;