* options.h, g10.c (main): Add keyserver-option honor-keyserver-url.
[gnupg.git] / keyserver / gpgkeys_ldap.c
index eb1ec0d..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,7 +115,7 @@ ldap_to_gpg_err(LDAP *ld)
 #endif
 }
 
-int
+static int
 key_in_keylist(const char *key,struct keylist *list)
 {
   struct keylist *keyptr=list;
@@ -122,7 +131,7 @@ key_in_keylist(const char *key,struct keylist *list)
   return 0;
 }
 
-int
+static int
 add_key_to_keylist(const char *key,struct keylist **list)
 {
   struct keylist *keyptr=malloc(sizeof(struct keylist));
@@ -142,7 +151,7 @@ add_key_to_keylist(const char *key,struct keylist **list)
   return 0;
 }
 
-void
+static void
 free_keylist(struct keylist *list)
 {
   while(list!=NULL)
@@ -154,20 +163,564 @@ free_keylist(struct keylist *list)
     }
 }
 
-int
+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,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)
@@ -265,14 +818,17 @@ send_key(int *eof)
 }
 
 /* Note that key-not-found is not a fatal error */
-int
+static int
 get_key(char *getkey)
 {
   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
@@ -331,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",
@@ -496,31 +1052,7 @@ get_key(char *getkey)
   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
+static void
 printquoted(FILE *stream,char *string,char delim)
 {
   while(*string)
@@ -536,14 +1068,14 @@ 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=0;
   struct keylist *dupelist=NULL;
-  /* The maxium size of the search, including the optional stuff and
+  /* 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",
@@ -569,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);
 
@@ -604,6 +1136,9 @@ search_key(char *searchkey)
       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;
 
@@ -765,7 +1300,7 @@ search_key(char *searchkey)
   return KEYSERVER_OK;
 }
 
-void
+static void
 fail_all(struct keylist *keylist,int action,int err)
 {
   if(!keylist)
@@ -789,20 +1324,140 @@ fail_all(struct keylist *keylist,int action,int err)
       }
 }
 
+static int
+find_basekeyspacedn(void)
+{
+  int err,i;
+  char *attr[]={"namingContexts",NULL,NULL,NULL};
+  LDAPMessage *res;
+  char **context;
+
+  /* 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],**vals;
-  int version,failed=0;
-  char *attrs[]={"basekeyspacedn","version","software",NULL};
-  LDAPMessage *res;
+  char line[MAX_LINE];
+  int version,failed=0,use_ssl=0,use_tls=0;
   struct keylist *keylist=NULL,*keyptr=NULL;
 
-#ifdef __riscos__
-  riscos_global_defaults();
-#endif
-
   console=stderr;
 
   while((arg=getopt(argc,argv,"hVo:"))!=-1)
@@ -816,7 +1471,7 @@ main(int argc,char *argv[])
        return KEYSERVER_OK;
 
       case 'V':
-       fprintf(stdout,"0\n%s\n",VERSION);
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
        return KEYSERVER_OK;
 
       case 'o':
@@ -854,6 +1509,7 @@ main(int argc,char *argv[])
     {
       char commandstr[7];
       char optionstr[30];
+      char schemestr[80];
       char hash;
 
       if(line[0]=='\n')
@@ -889,6 +1545,17 @@ 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!=KEYSERVER_PROTO_VERSION)
@@ -941,6 +1608,26 @@ 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;
        }
@@ -961,7 +1648,7 @@ main(int argc,char *argv[])
            break;
          else
            {
-             if(line[0]=='\n')
+             if(line[0]=='\n' || line[0]=='\0')
                break;
 
              work=malloc(sizeof(struct keylist));
@@ -1022,80 +1709,109 @@ 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)
+  /* use_tls: 0=don't use, 1=try silently to use, 2=try loudly to use,
+     3=force use. */
+  if(use_tls)
     {
-      fprintf(console,"gpgkeys: more than one serverinfo record\n");
-      fail_all(keylist,action,KEYSERVER_INTERNAL_ERROR);
-      goto fail;
-    }
-
-  if(verbose>1)
-    {
-      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:
@@ -1116,8 +1832,16 @@ 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);
       }
@@ -1159,7 +1883,8 @@ 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++;