summaryrefslogtreecommitdiff
path: root/keyserver/gpgkeys_ldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'keyserver/gpgkeys_ldap.c')
-rw-r--r--keyserver/gpgkeys_ldap.c2379
1 files changed, 2379 insertions, 0 deletions
diff --git a/keyserver/gpgkeys_ldap.c b/keyserver/gpgkeys_ldap.c
new file mode 100644
index 0000000..bd85234
--- /dev/null
+++ b/keyserver/gpgkeys_ldap.c
@@ -0,0 +1,2379 @@
+/* gpgkeys_ldap.c - talk to a LDAP keyserver
+ * Copyright (C) 2001, 2002, 2004, 2005, 2006
+ * 2007 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the Free Software Foundation
+ * gives permission to link the code of the keyserver helper tools:
+ * gpgkeys_ldap, gpgkeys_curl and gpgkeys_hkp with the OpenSSL
+ * project's "OpenSSL" library (or with modified versions of it that
+ * use the same license as the "OpenSSL" library), and distribute the
+ * linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If
+ * you modify this file, you may extend this exception to your version
+ * of the file, but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <winldap.h>
+#else
+#ifdef NEED_LBER_H
+#include <lber.h>
+#endif
+/* For OpenLDAP, to enable the API that we're using. */
+#define LDAP_DEPRECATED 1
+#include <ldap.h>
+#endif
+
+#include "util.h"
+#include "keyserver.h"
+#include "ksutil.h"
+
+#ifdef __riscos__
+#include "util.h"
+#endif
+
+extern char *optarg;
+extern int optind;
+
+static int real_ldap=0;
+static char *basekeyspacedn=NULL;
+static char *pgpkeystr="pgpKey";
+static FILE *input=NULL,*output=NULL,*console=NULL;
+static LDAP *ldap=NULL;
+static struct ks_options *opt;
+
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm);
+#endif
+
+static int
+ldap_err_to_gpg_err(int err)
+{
+ int ret;
+
+ switch(err)
+ {
+ case LDAP_ALREADY_EXISTS:
+ ret=KEYSERVER_KEY_EXISTS;
+ break;
+
+ case LDAP_SERVER_DOWN:
+ ret=KEYSERVER_UNREACHABLE;
+ break;
+
+ default:
+ ret=KEYSERVER_GENERAL_ERROR;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+ldap_to_gpg_err(LDAP *ld)
+{
+#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
+
+ int err;
+
+ if(ldap_get_option(ld,LDAP_OPT_ERROR_NUMBER,&err)==0)
+ return ldap_err_to_gpg_err(err);
+ else
+ return KEYSERVER_GENERAL_ERROR;
+
+#elif defined(HAVE_LDAP_LD_ERRNO)
+
+ return ldap_err_to_gpg_err(ld->ld_errno);
+
+#else
+
+ /* We should never get here since the LDAP library should always
+ have either ldap_get_option or ld_errno, but just in case... */
+ return KEYSERVER_GENERAL_ERROR;
+
+#endif
+}
+
+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, so we use timegm() */
+
+ answer=timegm(&pgptime);
+
+ 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);
+}
+
+/* Append two onto the end of one. Two is not freed, but its pointers
+ are now part of one. Make sure you don't free them both! */
+static int
+join_two_modlists(LDAPMod ***one,LDAPMod **two)
+{
+ int i,one_count=0,two_count=0;
+ LDAPMod **grow;
+
+ for(grow=*one;*grow;grow++)
+ one_count++;
+
+ for(grow=two;*grow;grow++)
+ two_count++;
+
+ grow=realloc(*one,sizeof(LDAPMod *)*(one_count+two_count+1));
+ if(!grow)
+ return 0;
+
+ for(i=0;i<two_count;i++)
+ grow[one_count+i]=two[i];
+
+ grow[one_count+i]=NULL;
+
+ *one=grow;
+
+ return 1;
+}
+
+/* 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,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++)
+ {
+ /* Duplicate value */
+ if(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(ks_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,"pgpCertID",tok);
+ make_one_attr(modlist,"pgpKeyID",&tok[8]);
+ }
+ else
+ return;
+
+ /* The primary pubkey algo */
+ if((tok=strsep(&line,":"))==NULL)
+ return;
+
+ switch(atoi(tok))
+ {
+ case 1:
+ make_one_attr(modlist,"pgpKeyType","RSA");
+ break;
+
+ case 17:
+ make_one_attr(modlist,"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,"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,"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,"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,"pgpDisabled",disabled?"1":"0");
+ make_one_attr(modlist,"pgpRevoked",revoked?"1":"0");
+ }
+ else if(ks_strcasecmp("sub",record)==0)
+ {
+ char *tok;
+
+ /* The long keyid */
+ if((tok=strsep(&line,":"))==NULL)
+ return;
+
+ if(strlen(tok)==16)
+ make_one_attr(modlist,"pgpSubKeyID",tok);
+ else
+ return;
+
+ /* The subkey algo */
+ if((tok=strsep(&line,":"))==NULL)
+ return;
+
+ /* Size of subkey */
+ 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,"pgpKeySize",padded);
+ }
+ }
+
+ /* Ignore the rest of the items for subkeys since the LDAP
+ schema doesn't store them. */
+ }
+ else if(ks_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])
+ {
+ int c;
+
+ userid[i] = (c=hextobyte(&tok[1])) == -1 ? '?' : c;
+ i++;
+ tok+=3;
+ }
+ else
+ userid[i++]=*tok++;
+
+ userid[i]='\0';
+
+ /* 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,"pgpUserID",userid);
+ }
+ else if(ks_strcasecmp("sig",record)==0)
+ {
+ char *tok;
+
+ if((tok=strsep(&line,":"))==NULL)
+ return;
+
+ if(strlen(tok)==16)
+ make_one_attr(modlist,"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 *r_eof)
+{
+ int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+ char *dn=NULL,line[MAX_LINE],*key=NULL;
+ char keyid[17],state[6];
+ LDAPMod **modlist,**addlist,**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;
+
+ addlist=malloc(sizeof(LDAPMod *));
+ if(!addlist)
+ {
+ fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ *addlist=NULL;
+
+ /* Start by nulling out all attributes. We try and do a modify
+ operation first, so this ensures that we don't leave old
+ attributes lying around. */
+ make_one_attr(&modlist,"pgpDisabled",NULL);
+ make_one_attr(&modlist,"pgpKeyID",NULL);
+ make_one_attr(&modlist,"pgpKeyType",NULL);
+ make_one_attr(&modlist,"pgpUserID",NULL);
+ make_one_attr(&modlist,"pgpKeyCreateTime",NULL);
+ make_one_attr(&modlist,"pgpSignerID",NULL);
+ make_one_attr(&modlist,"pgpRevoked",NULL);
+ make_one_attr(&modlist,"pgpSubKeyID",NULL);
+ make_one_attr(&modlist,"pgpKeySize",NULL);
+ make_one_attr(&modlist,"pgpKeyExpireTime",NULL);
+ make_one_attr(&modlist,"pgpCertID",NULL);
+
+ /* Assemble the INFO stuff into LDAP attributes */
+
+ while(fgets(line,MAX_LINE,input)!=NULL)
+ if(sscanf(line,"INFO%*[ ]%16s%*[ ]%5s\n",keyid,state)==2
+ && strcmp(state,"BEGIN")==0)
+ {
+ begin=1;
+ break;
+ }
+
+ if(!begin)
+ {
+ /* i.e. eof before the INFO BEGIN was found. This isn't an
+ error. */
+ *r_eof=1;
+ ret=KEYSERVER_OK;
+ goto fail;
+ }
+
+ if(strlen(keyid)!=16)
+ {
+ *r_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%*[ ]%3s\n",keyid,state)==2
+ && strcmp(state,"END")==0)
+ {
+ end=1;
+ break;
+ }
+ else
+ build_attrs(&addlist,line);
+
+ if(!end)
+ {
+ fprintf(console,"gpgkeys: no INFO %s END found\n",keyid);
+ *r_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%*[ ]%5s\n",keyid,state)==2
+ && strcmp(state,"BEGIN")==0)
+ {
+ begin=1;
+ break;
+ }
+
+ if(!begin)
+ {
+ /* i.e. eof before the KEY BEGIN was found. This isn't an
+ error. */
+ *r_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%*[ ]%3s\n",keyid,state)==2
+ && strcmp(state,"END")==0)
+ {
+ 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);
+ *r_eof=1;
+ ret=KEYSERVER_KEY_INCOMPLETE;
+ goto fail;
+ }
+
+ make_one_attr(&addlist,"objectClass","pgpKeyInfo");
+ make_one_attr(&addlist,"pgpKey",key);
+
+ /* Now append addlist onto modlist */
+ if(!join_two_modlists(&modlist,addlist))
+ {
+ fprintf(console,"gpgkeys: unable to merge LDAP modification lists\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ /* Going on the assumption that modify operations are more frequent
+ than adds, we try a modify first. 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)
+ 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:
+ if (modlist)
+ {
+ /* Unwind and free the whole modlist structure */
+ for(ml=modlist;*ml;ml++)
+ {
+ free_mod_values(*ml);
+ free(*ml);
+ }
+ free(modlist);
+ }
+ free(addlist);
+ free(dn);
+ free(key);
+
+ if(ret!=0 && begin)
+ fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+ return ret;
+}
+
+static int
+send_key_keyserver(int *r_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],state[6];
+ 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;
+
+ dn=malloc(strlen("pgpCertid=virtual,")+strlen(basekeyspacedn)+1);
+ if(dn==NULL)
+ {
+ fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ strcpy(dn,"pgpCertid=virtual,");
+ strcat(dn,basekeyspacedn);
+
+ key[0]=malloc(1);
+ if(key[0]==NULL)
+ {
+ fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ key[0][0]='\0';
+
+ /* Read and throw away stdin until we see the BEGIN */
+
+ while(fgets(line,MAX_LINE,input)!=NULL)
+ if(sscanf(line,"KEY%*[ ]%16s%*[ ]%5s\n",keyid,state)==2
+ && strcmp(state,"BEGIN")==0)
+ {
+ begin=1;
+ break;
+ }
+
+ if(!begin)
+ {
+ /* i.e. eof before the KEY BEGIN was found. This isn't an
+ error. */
+ *r_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%*[ ]%3s\n",keyid,state)==2
+ && strcmp(state,"END")==0)
+ {
+ end=1;
+ break;
+ }
+ else
+ {
+ keysize+=strlen(line);
+ key[0]=realloc(key[0],keysize);
+ if(key[0]==NULL)
+ {
+ fprintf(console,"gpgkeys: unable to reallocate for key\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ strcat(key[0],line);
+ }
+
+ if(!end)
+ {
+ fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+ *r_eof=1;
+ ret=KEYSERVER_KEY_INCOMPLETE;
+ goto fail;
+ }
+
+ err=ldap_add_s(ldap,dn,attrs);
+ 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:
+
+ free(key[0]);
+ free(dn);
+
+ if(ret!=0 && begin)
+ fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+ /* Not a fatal error */
+ if(ret==KEYSERVER_KEY_EXISTS)
+ ret=KEYSERVER_OK;
+
+ return ret;
+}
+
+static void
+build_info(const char *certid,LDAPMessage *each)
+{
+ char **vals;
+
+ fprintf(output,"INFO %s BEGIN\n",certid);
+
+ fprintf(output,"pub:%s:",certid);
+
+ vals=ldap_get_values(ldap,each,"pgpkeytype");
+ if(vals!=NULL)
+ {
+ if(strcmp(vals[0],"RSA")==0)
+ fprintf(output,"1");
+ else if(strcmp(vals[0],"DSS/DH")==0)
+ fprintf(output,"17");
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,":");
+
+ vals=ldap_get_values(ldap,each,"pgpkeysize");
+ if(vals!=NULL)
+ {
+ if(atoi(vals[0])>0)
+ fprintf(output,"%d",atoi(vals[0]));
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,":");
+
+ vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
+ if(vals!=NULL)
+ {
+ if(strlen(vals[0])==15)
+ fprintf(output,"%u",(unsigned int)ldap2epochtime(vals[0]));
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,":");
+
+ vals=ldap_get_values(ldap,each,"pgpkeyexpiretime");
+ if(vals!=NULL)
+ {
+ if(strlen(vals[0])==15)
+ fprintf(output,"%u",(unsigned int)ldap2epochtime(vals[0]));
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,":");
+
+ vals=ldap_get_values(ldap,each,"pgprevoked");
+ if(vals!=NULL)
+ {
+ if(atoi(vals[0])==1)
+ fprintf(output,"r");
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,"\n");
+
+ vals=ldap_get_values(ldap,each,"pgpuserid");
+ if(vals!=NULL)
+ {
+ int i;
+
+ for(i=0;vals[i];i++)
+ fprintf(output,"uid:%s\n",vals[i]);
+ ldap_value_free(vals);
+ }
+
+ fprintf(output,"INFO %s END\n",certid);
+}
+
+/* Note that key-not-found is not a fatal error */
+static int
+get_key(char *getkey)
+{
+ LDAPMessage *res,*each;
+ int ret=KEYSERVER_INTERNAL_ERROR,err,count;
+ struct keylist *dupelist=NULL;
+ char search[62];
+ /* 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
+ array initializers. */
+
+ /* Build the search string */
+
+ /* GPG can send us a v4 fingerprint, a v3 or v4 long key id, or a v3
+ or v4 short key id */
+
+ if(strncmp(getkey,"0x",2)==0)
+ getkey+=2;
+
+ if(strlen(getkey)==32)
+ {
+ fprintf(console,
+ "gpgkeys: LDAP keyservers do not support v3 fingerprints\n");
+ fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+ fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_NOT_SUPPORTED);
+ return KEYSERVER_NOT_SUPPORTED;
+ }
+
+ if(strlen(getkey)>16)
+ {
+ char *offset=&getkey[strlen(getkey)-16];
+
+ /* fingerprint. Take the last 16 characters and treat it like a
+ long key id */
+
+ if(opt->flags.include_subkeys)
+ sprintf(search,"(|(pgpcertid=%.16s)(pgpsubkeyid=%.16s))",
+ offset,offset);
+ else
+ sprintf(search,"(pgpcertid=%.16s)",offset);
+ }
+ else if(strlen(getkey)>8)
+ {
+ /* long key id */
+
+ if(opt->flags.include_subkeys)
+ sprintf(search,"(|(pgpcertid=%.16s)(pgpsubkeyid=%.16s))",
+ getkey,getkey);
+ else
+ sprintf(search,"(pgpcertid=%.16s)",getkey);
+ }
+ else
+ {
+ /* short key id */
+
+ sprintf(search,"(pgpkeyid=%.8s)",getkey);
+ }
+
+ if(opt->verbose>2)
+ fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search);
+
+ if(!opt->verbose)
+ attrs[2]=NULL; /* keep only pgpkey(v2) and pgpcertid */
+
+ err=ldap_search_s(ldap,basekeyspacedn,
+ LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+ if(err!=0)
+ {
+ int errtag=ldap_err_to_gpg_err(err);
+
+ fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+ fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+ fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
+ return errtag;
+ }
+
+ count=ldap_count_entries(ldap,res);
+ if(count<1)
+ {
+ fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+ fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+ fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_KEY_NOT_FOUND);
+ }
+ else
+ {
+ /* There may be more than one unique result for a given keyID,
+ so we should fetch them all (test this by fetching short key
+ id 0xDEADBEEF). */
+
+ each=ldap_first_entry(ldap,res);
+ while(each!=NULL)
+ {
+ 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 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 */
+
+ certid=ldap_get_values(ldap,each,"pgpcertid");
+ if(certid!=NULL)
+ {
+ if(!key_in_keylist(certid[0],dupelist))
+ {
+ /* it's not a duplicate, so add it */
+
+ int rc=add_key_to_keylist(certid[0],&dupelist);
+ if(rc)
+ {
+ ret=rc;
+ goto fail;
+ }
+
+ build_info(certid[0],each);
+
+ fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
+ 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
+ {
+ print_nocr(output,vals[0]);
+ fprintf(output,"\nKEY 0x%s END\n",getkey);
+
+ ldap_value_free(vals);
+ }
+ }
+
+ ldap_value_free(certid);
+ }
+
+ each=ldap_next_entry(ldap,each);
+ }
+ }
+
+ ret=KEYSERVER_OK;
+
+ fail:
+ ldap_msgfree(res);
+ free_keylist(dupelist);
+
+ return ret;
+}
+
+#define LDAP_ESCAPE_CHARS "*()\\"
+
+/* Append string to buffer in a LDAP-quoted way */
+static void
+ldap_quote(char *buffer,const char *string)
+{
+ /* Find the end of buffer */
+ buffer+=strlen(buffer);
+
+ for(;*string;string++)
+ {
+ if(strchr(LDAP_ESCAPE_CHARS,*string))
+ {
+ sprintf(buffer,"\\%02X",*string);
+ buffer+=3;
+ }
+ else
+ *buffer++=*string;
+ }
+
+ *buffer='\0';
+}
+
+/* Note that key-not-found is not a fatal error */
+static int
+get_name(char *getkey)
+{
+ LDAPMessage *res,*each;
+ int ret=KEYSERVER_INTERNAL_ERROR,err,count;
+ /* The maximum size of the search, including the optional stuff and
+ the trailing \0 */
+ char search[2+12+(MAX_LINE*3)+2+15+14+1+1+20];
+ /* 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
+ array initializers. */
+
+ /* Build the search string */
+
+ search[0]='\0';
+
+ if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+ strcat(search,"(&");
+
+ strcat(search,"(pgpUserID=*");
+ ldap_quote(search,getkey);
+ strcat(search,"*)");
+
+ if(!opt->flags.include_disabled)
+ strcat(search,"(pgpDisabled=0)");
+
+ if(!opt->flags.include_revoked)
+ strcat(search,"(pgpRevoked=0)");
+
+ if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+ strcat(search,")");
+
+ if(opt->verbose>2)
+ fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search);
+
+ if(!opt->verbose)
+ attrs[2]=NULL; /* keep only pgpkey(v2) and pgpcertid */
+
+ err=ldap_search_s(ldap,basekeyspacedn,
+ LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+ if(err!=0)
+ {
+ int errtag=ldap_err_to_gpg_err(err);
+
+ fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+ fprintf(output,"NAME %s BEGIN\n",getkey);
+ fprintf(output,"NAME %s FAILED %d\n",getkey,errtag);
+ return errtag;
+ }
+
+ count=ldap_count_entries(ldap,res);
+ if(count<1)
+ {
+ fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+ fprintf(output,"NAME %s BEGIN\n",getkey);
+ fprintf(output,"NAME %s FAILED %d\n",getkey,KEYSERVER_KEY_NOT_FOUND);
+ }
+ else
+ {
+ /* There may be more than one result, but we return them all. */
+
+ each=ldap_first_entry(ldap,res);
+ while(each!=NULL)
+ {
+ char **vals,**certid;
+
+ certid=ldap_get_values(ldap,each,"pgpcertid");
+ if(certid!=NULL)
+ {
+ build_info(certid[0],each);
+
+ fprintf(output,"NAME %s BEGIN\n",getkey);
+
+ 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,"NAME %s FAILED %d\n",getkey,errtag);
+ }
+ else
+ {
+ print_nocr(output,vals[0]);
+ fprintf(output,"\nNAME %s END\n",getkey);
+
+ ldap_value_free(vals);
+ }
+
+ ldap_value_free(certid);
+ }
+
+ each=ldap_next_entry(ldap,each);
+ }
+ }
+
+ ret=KEYSERVER_OK;
+
+ ldap_msgfree(res);
+
+ return ret;
+}
+
+static void
+printquoted(FILE *stream,char *string,char delim)
+{
+ while(*string)
+ {
+ if(*string==delim || *string=='%')
+ fprintf(stream,"%%%02x",*string);
+ else
+ fputc(*string,stream);
+
+ string++;
+ }
+}
+
+/* Returns 0 on success and -1 on error. Note that key-not-found is
+ not an error! */
+static int
+search_key(const char *searchkey)
+{
+ char **vals,*search;
+ LDAPMessage *res,*each;
+ int err,count=0;
+ struct keylist *dupelist=NULL;
+ /* The maximum size of the search, including the optional stuff and
+ the trailing \0 */
+ char *attrs[]={"pgpcertid","pgpuserid","pgprevoked","pgpdisabled",
+ "pgpkeycreatetime","pgpkeyexpiretime","modifytimestamp",
+ "pgpkeysize","pgpkeytype",NULL};
+ enum ks_search_type search_type;
+
+ search=malloc(2+1+9+1+3+strlen(searchkey)+3+1+15+14+1+1+20);
+ if(!search)
+ {
+ fprintf(console,"gpgkeys: out of memory when building search list\n");
+ fprintf(output,"SEARCH %s FAILED %d\n",searchkey,KEYSERVER_NO_MEMORY);
+ return KEYSERVER_NO_MEMORY;
+ }
+
+ fprintf(output,"SEARCH %s BEGIN\n",searchkey);
+
+ search_type=classify_ks_search(&searchkey);
+
+ if(opt->debug)
+ fprintf(console,"search type is %d, and key is \"%s\"\n",
+ search_type,searchkey);
+
+ /* Build the search string */
+
+ search[0]='\0';
+
+ if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+ strcat(search,"(&");
+
+ strcat(search,"(");
+
+ switch(search_type)
+ {
+ case KS_SEARCH_KEYID_SHORT:
+ strcat(search,"pgpKeyID");
+ break;
+
+ case KS_SEARCH_KEYID_LONG:
+ strcat(search,"pgpCertID");
+ break;
+
+ default:
+ strcat(search,"pgpUserID");
+ break;
+ }
+
+ strcat(search,"=");
+
+ switch(search_type)
+ {
+ case KS_SEARCH_SUBSTR:
+ strcat(search,"*");
+ break;
+
+ case KS_SEARCH_MAIL:
+ strcat(search,"*<");
+ break;
+
+ case KS_SEARCH_MAILSUB:
+ strcat(search,"*<*");
+ break;
+
+ case KS_SEARCH_EXACT:
+ case KS_SEARCH_KEYID_LONG:
+ case KS_SEARCH_KEYID_SHORT:
+ break;
+ }
+
+ strcat(search,searchkey);
+
+ switch(search_type)
+ {
+ case KS_SEARCH_SUBSTR:
+ strcat(search,"*");
+ break;
+
+ case KS_SEARCH_MAIL:
+ strcat(search,">*");
+ break;
+
+ case KS_SEARCH_MAILSUB:
+ strcat(search,"*>*");
+ break;
+
+ case KS_SEARCH_EXACT:
+ case KS_SEARCH_KEYID_LONG:
+ case KS_SEARCH_KEYID_SHORT:
+ break;
+ }
+
+ strcat(search,")");
+
+ if(!opt->flags.include_disabled)
+ strcat(search,"(pgpDisabled=0)");
+
+ if(!opt->flags.include_revoked)
+ strcat(search,"(pgpRevoked=0)");
+
+ if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+ strcat(search,")");
+
+ if(opt->verbose>2)
+ fprintf(console,"gpgkeys: LDAP search for: %s\n",search);
+
+ err=ldap_search_s(ldap,basekeyspacedn,
+ LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+ free(search);
+ if(err!=LDAP_SUCCESS && err!=LDAP_SIZELIMIT_EXCEEDED)
+ {
+ int errtag=ldap_err_to_gpg_err(err);
+
+ fprintf(output,"SEARCH %s FAILED %d\n",searchkey,errtag);
+ fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+ return errtag;
+ }
+
+ /* 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)
+ {
+ if(count==1)
+ fprintf(console,"gpgkeys: search results exceeded server limit."
+ " First %d result shown.\n",count);
+ else
+ fprintf(console,"gpgkeys: search results exceeded server limit."
+ " First %d results shown.\n",count);
+ }
+
+ free_keylist(dupelist);
+ dupelist=NULL;
+
+ if(count<1)
+ fprintf(output,"info:1:0\n");
+ else
+ {
+ fprintf(output,"info:1:%d\n",count);
+
+ each=ldap_first_entry(ldap,res);
+ while(each!=NULL)
+ {
+ char **certid;
+
+ certid=ldap_get_values(ldap,each,"pgpcertid");
+ if(certid!=NULL)
+ {
+ LDAPMessage *uids;
+
+ /* 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;
+ }
+
+ fprintf(output,"pub:%s:",certid[0]);
+
+ 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);
+ }
+
+ 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);
+ }
+
+ fputc(':',output);
+
+ /* YYYYMMDDHHmmssZ */
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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");
+
+ /* 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;
+}
+
+static void
+fail_all(struct keylist *keylist,int err)
+{
+ if(!keylist)
+ return;
+
+ if(opt->action==KS_SEARCH)
+ {
+ fprintf(output,"SEARCH ");
+ while(keylist)
+ {
+ fprintf(output,"%s ",keylist->str);
+ keylist=keylist->next;
+ }
+ fprintf(output,"FAILED %d\n",err);
+ }
+ else
+ while(keylist)
+ {
+ fprintf(output,"KEY %s FAILED %d\n",keylist->str,err);
+ keylist=keylist->next;
+ }
+}
+
+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;
+ char *object;
+
+ object=malloc(17+strlen(context[i])+1);
+ if(!object)
+ return -1;
+
+ strcpy(object,"cn=pgpServerInfo,");
+ strcat(object,context[i]);
+
+ err=ldap_search_s(ldap,object,LDAP_SCOPE_BASE,
+ "(objectClass=*)",attr,0,&si_res);
+ free(object);
+
+ if(err==LDAP_NO_SUCH_OBJECT)
+ continue;
+ else if(err!=LDAP_SUCCESS)
+ return err;
+
+ vals=ldap_get_values(ldap,si_res,"pgpBaseKeySpaceDN");
+ if(vals)
+ {
+ basekeyspacedn=strdup(vals[0]);
+ ldap_value_free(vals);
+ }
+
+ if(opt->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;
+
+ /* For the LDAP keyserver, this is always "OU=ACTIVE,O=PGP
+ KEYSPACE,C=US", but it might not be in the future. */
+
+ vals=ldap_get_values(ldap,si_res,"baseKeySpaceDN");
+ if(vals)
+ {
+ basekeyspacedn=strdup(vals[0]);
+ ldap_value_free(vals);
+ }
+
+ if(opt->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(opt->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;
+}
+
+static void
+show_help (FILE *fp)
+{
+ fprintf (fp,"-h, --help\thelp\n");
+ fprintf (fp,"-V\t\tmachine readable version\n");
+ fprintf (fp,"--version\thuman readable version\n");
+ fprintf (fp,"-o\t\toutput to this file\n");
+}
+
+int
+main(int argc,char *argv[])
+{
+ int port=0,arg,err,ret=KEYSERVER_INTERNAL_ERROR;
+ char line[MAX_LINE],*binddn=NULL,*bindpw=NULL;
+ int failed=0,use_ssl=0,use_tls=0,bound=0;
+ struct keylist *keylist=NULL,*keyptr=NULL;
+
+ console=stderr;
+
+ /* Kludge to implement standard GNU options. */
+ if (argc > 1 && !strcmp (argv[1], "--version"))
+ {
+ fputs ("gpgkeys_ldap (GnuPG) " VERSION"\n", stdout);
+ return 0;
+ }
+ else if (argc > 1 && !strcmp (argv[1], "--help"))
+ {
+ show_help (stdout);
+ return 0;
+ }
+
+ while((arg=getopt(argc,argv,"hVo:"))!=-1)
+ switch(arg)
+ {
+ default:
+ case 'h':
+ show_help (console);
+ 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)
+ {
+ fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
+ optarg,strerror(errno));
+ return KEYSERVER_INTERNAL_ERROR;
+ }
+
+ break;
+ }
+
+ if(argc>optind)
+ {
+ input=fopen(argv[optind],"r");
+ if(input==NULL)
+ {
+ fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
+ argv[optind],strerror(errno));
+ return KEYSERVER_INTERNAL_ERROR;
+ }
+ }
+
+ if(input==NULL)
+ input=stdin;
+
+ if(output==NULL)
+ output=stdout;
+
+ opt=init_ks_options();
+ if(!opt)
+ return KEYSERVER_NO_MEMORY;
+
+ /* Get the command and info block */
+
+ while(fgets(line,MAX_LINE,input)!=NULL)
+ {
+ char optionstr[MAX_OPTION+1];
+
+ if(line[0]=='\n')
+ break;
+
+ err=parse_ks_options(line,opt);
+ if(err>0)
+ {
+ ret=err;
+ goto fail;
+ }
+ else if(err==0)
+ continue;
+
+ if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "[^\n]\n",optionstr)==1)
+ {
+ int no=0;
+ char *start=&optionstr[0];
+
+ optionstr[MAX_OPTION]='\0';
+
+ if(strncasecmp(optionstr,"no-",3)==0)
+ {
+ no=1;
+ start=&optionstr[3];
+ }
+
+ 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;
+ }
+ else if(strncasecmp(start,"basedn",6)==0)
+ {
+ if(no)
+ {
+ free(basekeyspacedn);
+ basekeyspacedn=NULL;
+ }
+ else if(start[6]=='=')
+ {
+ free(basekeyspacedn);
+ basekeyspacedn=strdup(&start[7]);
+ if(!basekeyspacedn)
+ {
+ fprintf(console,"gpgkeys: out of memory while creating "
+ "base DN\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ real_ldap=1;
+ }
+ }
+ else if(strncasecmp(start,"binddn",6)==0)
+ {
+ if(no)
+ {
+ free(binddn);
+ binddn=NULL;
+ }
+ else if(start[6]=='=')
+ {
+ free(binddn);
+ binddn=strdup(&start[7]);
+ if(!binddn)
+ {
+ fprintf(console,"gpgkeys: out of memory while creating "
+ "bind DN\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ real_ldap=1;
+ }
+ }
+ else if(strncasecmp(start,"bindpw",6)==0)
+ {
+ if(no)
+ {
+ free(bindpw);
+ bindpw=NULL;
+ }
+ else if(start[6]=='=')
+ {
+ free(bindpw);
+ bindpw=strdup(&start[7]);
+ if(!bindpw)
+ {
+ fprintf(console,"gpgkeys: out of memory while creating "
+ "bind password\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ real_ldap=1;
+ }
+ }
+
+ continue;
+ }
+ }
+
+ if(!opt->scheme)
+ {
+ fprintf(console,"gpgkeys: no scheme supplied!\n");
+ ret=KEYSERVER_SCHEME_NOT_FOUND;
+ goto fail;
+ }
+
+ if(strcasecmp(opt->scheme,"ldaps")==0)
+ {
+ port=636;
+ use_ssl=1;
+ }
+
+ if(opt->port)
+ port=atoi(opt->port);
+
+ if(!opt->host)
+ {
+ fprintf(console,"gpgkeys: no keyserver host provided\n");
+ goto fail;
+ }
+
+ if(opt->timeout && register_timeout()==-1)
+ {
+ fprintf(console,"gpgkeys: unable to register timeout handler\n");
+ return KEYSERVER_INTERNAL_ERROR;
+ }
+
+#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION)
+
+ if(opt->ca_cert_file)
+ {
+ err=ldap_set_option(NULL,LDAP_OPT_X_TLS_CACERTFILE,opt->ca_cert_file);
+ if(err!=LDAP_SUCCESS)
+ {
+ fprintf(console,"gpgkeys: unable to set ca-cert-file: %s\n",
+ ldap_err2string(err));
+ ret=KEYSERVER_INTERNAL_ERROR;
+ goto fail;
+ }
+ }
+#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */
+
+ /* SSL trumps TLS */
+ if(use_ssl)
+ use_tls=0;
+
+ /* If it's a GET or a SEARCH, the next thing to come in is the
+ keyids. If it's a SEND, then there are no keyids. */
+
+ if(opt->action==KS_SEND)
+ while(fgets(line,MAX_LINE,input)!=NULL && line[0]!='\n');
+ else if(opt->action==KS_GET
+ || opt->action==KS_GETNAME || opt->action==KS_SEARCH)
+ {
+ for(;;)
+ {
+ struct keylist *work;
+
+ if(fgets(line,MAX_LINE,input)==NULL)
+ break;
+ else
+ {
+ if(line[0]=='\n' || line[0]=='\0')
+ break;
+
+ work=malloc(sizeof(struct keylist));
+ if(work==NULL)
+ {
+ fprintf(console,"gpgkeys: out of memory while "
+ "building key list\n");
+ ret=KEYSERVER_NO_MEMORY;
+ goto fail;
+ }
+
+ strcpy(work->str,line);
+
+ /* Trim the trailing \n */
+ work->str[strlen(line)-1]='\0';
+
+ work->next=NULL;
+
+ /* Always attach at the end to keep the list in proper
+ order for searching */
+ if(keylist==NULL)
+ keylist=work;
+ else
+ keyptr->next=work;
+
+ keyptr=work;
+ }
+ }
+ }
+ else
+ {
+ fprintf(console,"gpgkeys: no keyserver command specified\n");
+ goto fail;
+ }
+
+ /* Send the response */
+
+ fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+ fprintf(output,"PROGRAM %s\n\n",VERSION);
+
+ if(opt->verbose>1)
+ {
+ fprintf(console,"Host:\t\t%s\n",opt->host);
+ if(port)
+ fprintf(console,"Port:\t\t%d\n",port);
+ fprintf(console,"Command:\t%s\n",ks_action_to_string(opt->action));
+ }
+
+ if(opt->debug)
+ {
+#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(HAVE_LDAP_SET_OPTION)
+ err=ldap_set_option(NULL,LDAP_OPT_DEBUG_LEVEL,&opt->debug);
+ if(err!=LDAP_SUCCESS)
+ fprintf(console,"gpgkeys: unable to set debug mode: %s\n",
+ ldap_err2string(err));
+ else
+ fprintf(console,"gpgkeys: debug level %d\n",opt->debug);
+#else
+ fprintf(console,"gpgkeys: not built with debugging support\n");
+#endif
+ }
+
+ /* We have a timeout set for the setup stuff since it could time out
+ as well. */
+ set_timeout(opt->timeout);
+
+ /* Note that this tries all A records on a given host (or at least,
+ OpenLDAP does). */
+ ldap=ldap_init(opt->host,port);
+ if(ldap==NULL)
+ {
+ fprintf(console,"gpgkeys: internal LDAP init error: %s\n",
+ strerror(errno));
+ fail_all(keylist,KEYSERVER_INTERNAL_ERROR);
+ goto fail;
+ }
+
+ if(use_ssl)
+ {
+#if defined(LDAP_OPT_X_TLS) && 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,ldap_err_to_gpg_err(err));
+ goto fail;
+ }
+
+ if(!opt->flags.check_cert)
+ ssl=LDAP_OPT_X_TLS_NEVER;
+
+ err=ldap_set_option(NULL,LDAP_OPT_X_TLS_REQUIRE_CERT,&ssl);
+ if(err!=LDAP_SUCCESS)
+ {
+ fprintf(console,
+ "gpgkeys: unable to set certificate validation: %s\n",
+ ldap_err2string(err));
+ fail_all(keylist,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,KEYSERVER_INTERNAL_ERROR);
+ goto fail;
+#endif
+ }
+
+ if(!basekeyspacedn)
+ if((err=find_basekeyspacedn()) || !basekeyspacedn)
+ {
+ fprintf(console,"gpgkeys: unable to retrieve LDAP base: %s\n",
+ err?ldap_err2string(err):"not found");
+ fail_all(keylist,ldap_err_to_gpg_err(err));
+ goto fail;
+ }
+
+ /* use_tls: 0=don't use, 1=try silently to use, 2=try loudly to use,
+ 3=force use. */
+ if(use_tls)
+ {
+ 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,KEYSERVER_INTERNAL_ERROR);
+ goto fail;
+ }
+ }
+ else
+ {
+#if defined(HAVE_LDAP_START_TLS_S) && defined(HAVE_LDAP_SET_OPTION)
+ int ver=LDAP_VERSION3;
+
+ err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+
+#ifdef LDAP_OPT_X_TLS
+ if(err==LDAP_SUCCESS)
+ {
+ if(opt->flags.check_cert)
+ ver=LDAP_OPT_X_TLS_HARD;
+ else
+ ver=LDAP_OPT_X_TLS_NEVER;
+
+ err=ldap_set_option(NULL,LDAP_OPT_X_TLS_REQUIRE_CERT,&ver);
+ }
+#endif
+
+ if(err==LDAP_SUCCESS)
+ err=ldap_start_tls_s(ldap,NULL,NULL);
+
+ if(err!=LDAP_SUCCESS)
+ {
+ if(use_tls>=2 || opt->verbose>2)
+ fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+ ldap_err2string(err));
+ /* Are we forcing it? */
+ if(use_tls==3)
+ {
+ fail_all(keylist,ldap_err_to_gpg_err(err));
+ goto fail;
+ }
+ }
+ else if(opt->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,KEYSERVER_INTERNAL_ERROR);
+ goto fail;
+ }
+#endif
+ }
+ }
+
+ /* By default we don't bind as there is usually no need to. For
+ cases where the server needs some authentication, the user can
+ use binddn and bindpw for auth. */
+
+ if(binddn)
+ {
+#ifdef HAVE_LDAP_SET_OPTION
+ int ver=LDAP_VERSION3;
+
+ err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+ if(err!=LDAP_SUCCESS)
+ {
+ fprintf(console,"gpgkeys: unable to go to LDAP 3: %s\n",
+ ldap_err2string(err));
+ fail_all(keylist,ldap_err_to_gpg_err(err));
+ goto fail;
+ }
+#endif
+
+ if(opt->verbose>2)
+ fprintf(console,"gpgkeys: LDAP bind to %s, pw %s\n",binddn,
+ bindpw?">not shown<":">none<");
+ err=ldap_simple_bind_s(ldap,binddn,bindpw);
+ if(err!=LDAP_SUCCESS)
+ {
+ fprintf(console,"gpgkeys: internal LDAP bind error: %s\n",
+ ldap_err2string(err));
+ fail_all(keylist,ldap_err_to_gpg_err(err));
+ goto fail;
+ }
+ else
+ bound=1;
+ }
+
+ if(opt->action==KS_GET)
+ {
+ keyptr=keylist;
+
+ while(keyptr!=NULL)
+ {
+ set_timeout(opt->timeout);
+
+ if(get_key(keyptr->str)!=KEYSERVER_OK)
+ failed++;
+
+ keyptr=keyptr->next;
+ }
+ }
+ else if(opt->action==KS_GETNAME)
+ {
+ keyptr=keylist;
+
+ while(keyptr!=NULL)
+ {
+ set_timeout(opt->timeout);
+
+ if(get_name(keyptr->str)!=KEYSERVER_OK)
+ failed++;
+
+ keyptr=keyptr->next;
+ }
+ }
+ else if(opt->action==KS_SEND)
+ {
+ int eof_seen = 0;
+
+ do
+ {
+ set_timeout(opt->timeout);
+
+ if(real_ldap)
+ {
+ if (send_key(&eof_seen) != KEYSERVER_OK)
+ failed++;
+ }
+ else
+ {
+ if (send_key_keyserver(&eof_seen) != KEYSERVER_OK)
+ failed++;
+ }
+ }
+ while (!eof_seen);
+ }
+ else if(opt->action==KS_SEARCH)
+ {
+ char *searchkey=NULL;
+ int len=0;
+
+ set_timeout(opt->timeout);
+
+ /* To search, we stick a * in between each key to search for.
+ This means that if the user enters words, they'll get
+ "enters*words". If the user "enters words", they'll get
+ "enters words" */
+
+ keyptr=keylist;
+ while(keyptr!=NULL)
+ {
+ len+=strlen(keyptr->str)+1;
+ keyptr=keyptr->next;
+ }
+
+ searchkey=malloc((len*3)+1);
+ if(searchkey==NULL)
+ {
+ ret=KEYSERVER_NO_MEMORY;
+ fail_all(keylist,KEYSERVER_NO_MEMORY);
+ goto fail;
+ }
+
+ searchkey[0]='\0';
+
+ keyptr=keylist;
+ while(keyptr!=NULL)
+ {
+ ldap_quote(searchkey,keyptr->str);
+ strcat(searchkey,"*");
+ keyptr=keyptr->next;
+ }
+
+ /* Nail that last "*" */
+ if(*searchkey)
+ searchkey[strlen(searchkey)-1]='\0';
+
+ if(search_key(searchkey)!=KEYSERVER_OK)
+ failed++;
+
+ free(searchkey);
+ }
+ else
+ assert (!"bad action");
+
+ if(!failed)
+ ret=KEYSERVER_OK;
+
+ fail:
+
+ while(keylist!=NULL)
+ {
+ struct keylist *current=keylist;
+ keylist=keylist->next;
+ free(current);
+ }
+
+ if(input!=stdin)
+ fclose(input);
+
+ if(output!=stdout)
+ fclose(output);
+
+ free_ks_options(opt);
+
+ if(ldap!=NULL && bound)
+ ldap_unbind_s(ldap);
+
+ free(basekeyspacedn);
+
+ return ret;
+}