* Makefile.am: Add automake conditionals to symlink gpgkeys_ldaps to
[gnupg.git] / keyserver / gpgkeys_ldap.c
index 8dcff83..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;
 
@@ -272,7 +272,10 @@ get_key(char *getkey)
   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 +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",
@@ -382,7 +385,7 @@ get_key(char *getkey)
                {
                  /* it's not a duplicate, so add it */
 
-                 int rc=add_key_to_keylist(vals[0],&dupelist);
+                 int rc=add_key_to_keylist(certid[0],&dupelist);
                  if(rc)
                    {
                      ret=rc;
@@ -543,7 +546,7 @@ search_key(char *searchkey)
   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",
@@ -789,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)
@@ -816,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':
@@ -854,6 +973,7 @@ main(int argc,char *argv[])
     {
       char commandstr[7];
       char optionstr[30];
+      char schemestr[80];
       char hash;
 
       if(line[0]=='\n')
@@ -889,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)
@@ -941,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;
        }
@@ -961,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));
@@ -1022,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:
@@ -1159,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++;