* Makefile.am: Add automake conditionals to symlink gpgkeys_ldaps to
[gnupg.git] / keyserver / gpgkeys_ldap.c
index d215dfe..37c0ffa 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 "keyserver.h"
 
 #include "util.h"
 #endif
 
+extern char *optarg;
+extern int optind;
+
 #define GET    0
 #define SEND   1
 #define SEARCH 2
 #define MAX_LINE 80
 
-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;
+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;
 
 struct keylist
 {
@@ -54,10 +58,6 @@ struct keylist
   struct keylist *next;
 };
 
-#ifdef __riscos__
-RISCOS_GLOBAL_STATICS("LDAP Keyfetcher Heap")
-#endif /* __riscos__ */
-
 int
 ldap_err_to_gpg_err(int err)
 {
@@ -84,7 +84,7 @@ ldap_err_to_gpg_err(int err)
 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;
 
@@ -107,6 +107,54 @@ ldap_to_gpg_err(LDAP *ld)
 }
 
 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;
+}
+
+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;
+}
+
+void
+free_keylist(struct keylist *list)
+{
+  while(list!=NULL)
+    {
+      struct keylist *keyptr=list;
+
+      list=keyptr->next;
+      free(keyptr);
+    }
+}
+
+int
 send_key(int *eof)
 {
   int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
@@ -220,12 +268,14 @@ send_key(int *eof)
 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
@@ -284,7 +334,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",
@@ -316,149 +366,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)
-                   {
-                     /* 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(verbose)
                    {
-                     if(atoi(vals[0])==1)
-                       fprintf(console,"\t\t** KEY REVOKED **\n");
-                     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,"pgpdisabled");
-                 if(vals!=NULL)
+                 vals=ldap_get_values(ldap,each,pgpkeystr);
+                 if(vals==NULL)
                    {
-                     if(atoi(vals[0])==1)
-                       fprintf(console,"\t\t** KEY DISABLED **\n");
-                     ldap_value_free(vals);
-                   }
+                     int errtag=ldap_to_gpg_err(ldap);
 
-                 vals=ldap_get_values(ldap,each,"pgpkeyid");
-                 if(vals!=NULL)
-                   {
-                     fprintf(console,"Short key ID:\t%s\n",vals[0]);
-                     ldap_value_free(vals);
-                   }
-
-                 vals=ldap_get_values(ldap,each,"pgpcertid");
-                 if(vals!=NULL)
-                   {
-                     fprintf(console,"Long key ID:\t%s\n",vals[0]);
-                     ldap_value_free(vals);
-                   }
-
-                 /* 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);
+                     fprintf(console,"gpgkeys: unable to retrieve key %s "
+                             "from keyserver\n",getkey);
+                     fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
                    }
-
-                 vals=ldap_get_values(ldap,each,"pgpkeysize");
-                 if(vals!=NULL)
+                 else
                    {
-                     if(atoi(vals[0])>0)
-                       fprintf(console,"Key size:\t%d\n",atoi(vals[0]));
-                     ldap_value_free(vals);
-                   }
+                     fprintf(output,"%sKEY 0x%s END\n",vals[0],getkey);
 
-                 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,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);
@@ -469,15 +494,7 @@ 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;
 }
@@ -527,8 +544,9 @@ 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",
@@ -563,7 +581,34 @@ 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);
+    }
+
+  free_keylist(dupelist);
+  dupelist=NULL;
 
   if(count<1)
     fprintf(output,"info:1:0\n");
@@ -574,104 +619,149 @@ search_key(char *searchkey)
       each=ldap_first_entry(ldap,res);
       while(each!=NULL)
        {
-         fprintf(output,"pub:");
+         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);
-           }
+             LDAPMessage *uids;
 
-         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,"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,"pub:%s:",certid[0]);
 
-         fputc(':',output);
+                 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);
+                   }
 
-         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);
-           }
+                 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);
+                   }
 
-         /* YYYYMMDDHHmmssZ */
+                 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);
-           }
+                 /* YYYYMMDDHHmmssZ */
 
-         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,"pgpkeyexpiretime");
-         if(vals!=NULL && strlen(vals[0])==15)
-           {
-             fprintf(output,"%u",(unsigned int)ldap2epochtime(vals[0]));
-             ldap_value_free(vals);
-           }
+                 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);
+                   }
 
-         vals=ldap_get_values(ldap,each,"pgprevoked");
-         if(vals!=NULL)
-           {
-             if(atoi(vals[0])==1)
-               fprintf(output,"r");
-             ldap_value_free(vals);
-           }
+                 fputc(':',output);
 
-         vals=ldap_get_values(ldap,each,"pgpdisabled");
-         if(vals!=NULL)
-           {
-             if(atoi(vals[0])==1)
-               fprintf(output,"d");
-             ldap_value_free(vals);
-           }
+                 vals=ldap_get_values(ldap,each,"pgprevoked");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"r");
+                     ldap_value_free(vals);
+                   }
 
-         fputc(':',output);
+                 vals=ldap_get_values(ldap,each,"pgpdisabled");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"d");
+                     ldap_value_free(vals);
+                   }
 
-         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);
-           }
+#if 0
+                 /* This is not yet specified in the keyserver
+                    protocol, but may be someday. */
+                 fputc(':',output);
 
-         fprintf(output,"\nuid:");
+                 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
 
-         vals=ldap_get_values(ldap,each,"pgpuserid");
-         if(vals!=NULL)
-           {
-             /* Need to escape any colons */
-             printquoted(output,vals[0],':');
-             ldap_value_free(vals);
-           }
+                 fprintf(output,"\n");
 
-         fprintf(output,"\n");
+                 /* 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);
 
@@ -702,20 +792,136 @@ 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");
+      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)
@@ -729,7 +935,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':
@@ -767,6 +973,7 @@ main(int argc,char *argv[])
     {
       char commandstr[7];
       char optionstr[30];
+      char schemestr[80];
       char hash;
 
       if(line[0]=='\n')
@@ -802,6 +1009,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)
@@ -854,6 +1072,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;
        }
@@ -874,7 +1112,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));
@@ -935,80 +1173,105 @@ 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)
+  /* 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: error retrieving LDAP server info: %s\n",
-             ldap_err2string(err));
-      fail_all(keylist,action,ldap_err_to_gpg_err(err));
-      goto fail;
-    }
+      if(!real_ldap && use_tls)
+       {
+         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
+       {
+#if defined(HAVE_LDAP_START_TLS_S) && defined(HAVE_LDAP_SET_OPTION)
+         int ver=LDAP_VERSION3;
 
-  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;
-    }
+         err=LDAP_SUCCESS;
 
-  if(verbose>1)
-    {
-      vals=ldap_get_values(ldap,res,"software");
-      if(vals!=NULL)
-       {
-         fprintf(console,"Server: \t%s\n",vals[0]);
-         ldap_value_free(vals);
+         err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+         if(err==LDAP_SUCCESS)
+           err=ldap_start_tls_s(ldap,NULL,NULL);
+
+         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
        }
     }
 
-  vals=ldap_get_values(ldap,res,"version");
-  if(vals!=NULL)
+  err=ldap_simple_bind_s(ldap,NULL,NULL);
+  if(err!=0)
     {
-      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);
+      fprintf(console,"gpgkeys: internal LDAP bind error: %s\n",
+             ldap_err2string(err));
+      fail_all(keylist,action,ldap_err_to_gpg_err(err));
+      goto fail;
     }
 
-  /* This is always "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not
-     be in the future. */
-
-  vals=ldap_get_values(ldap,res,"basekeyspacedn");
-  if(vals!=NULL)
+  if((err=find_basekeyspacedn()))
     {
-      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: unable to retrieve LDAP base: %s\n",
+             ldap_err2string(err));
+      fail_all(keylist,action,ldap_err_to_gpg_err(err));
+      goto fail;
     }
 
-  ldap_msgfree(res);
-
   switch(action)
     {
     case GET:
@@ -1072,7 +1335,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++;