* gpgkeys_ldap.c (epoch2ldaptime): New. Converse of ldap2epochtime.
authorDavid Shaw <dshaw@jabberwocky.com>
Sun, 22 Feb 2004 00:08:53 +0000 (00:08 +0000)
committerDavid Shaw <dshaw@jabberwocky.com>
Sun, 22 Feb 2004 00:08:53 +0000 (00:08 +0000)
(make_one_attr): New. Build a modification list in memory to send to the
LDAP server. (build_attrs): New. Parse INFO lines sent over by gpg.
(free_mod_values): New.  Unwinds a modification list.
(send_key_keyserver): Renamed from old send_key(). (send_key): New
function to send a key to a LDAP server. (main): Use send_key() for real
LDAP servers, send_key_keyserver() otherwise.

keyserver/ChangeLog
keyserver/gpgkeys_ldap.c

index 2724b7b..0b52911 100644 (file)
@@ -1,3 +1,16 @@
+2004-02-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (epoch2ldaptime): New.  Converse of
+       ldap2epochtime.
+       (make_one_attr): New. Build a modification list in memory to send
+       to the LDAP server.
+       (build_attrs): New. Parse INFO lines sent over by gpg.
+       (free_mod_values): New.  Unwinds a modification list.
+       (send_key_keyserver): Renamed from old send_key().
+       (send_key): New function to send a key to a LDAP server.
+       (main): Use send_key() for real LDAP servers, send_key_keyserver()
+       otherwise.
+
 2004-02-20  David Shaw  <dshaw@jabberwocky.com>
 
        * gpgkeys_ldap.c: Replacement prototypes for setenv and unsetenv.
index afc241f..1dcf8c6 100644 (file)
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <ldap.h>
+#include "util.h"
 #include "keyserver.h"
 
 #ifdef __riscos__
@@ -41,7 +42,7 @@ extern int optind;
 #define GET    0
 #define SEND   1
 #define SEARCH 2
-#define MAX_LINE 80
+#define MAX_LINE 256
 
 static int verbose=0,include_disabled=0,include_revoked=0,include_subkeys=0;
 static int real_ldap=0;
@@ -66,7 +67,7 @@ struct keylist
   struct keylist *next;
 };
 
-int
+static int
 ldap_err_to_gpg_err(int err)
 {
   int ret;
@@ -89,7 +90,7 @@ ldap_err_to_gpg_err(int err)
   return ret;
 }
 
-int
+static int
 ldap_to_gpg_err(LDAP *ld)
 {
 #if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
@@ -114,7 +115,7 @@ ldap_to_gpg_err(LDAP *ld)
 #endif
 }
 
-int
+static int
 key_in_keylist(const char *key,struct keylist *list)
 {
   struct keylist *keyptr=list;
@@ -130,7 +131,7 @@ key_in_keylist(const char *key,struct keylist *list)
   return 0;
 }
 
-int
+static int
 add_key_to_keylist(const char *key,struct keylist **list)
 {
   struct keylist *keyptr=malloc(sizeof(struct keylist));
@@ -150,7 +151,7 @@ add_key_to_keylist(const char *key,struct keylist **list)
   return 0;
 }
 
-void
+static void
 free_keylist(struct keylist *list)
 {
   while(list!=NULL)
@@ -162,10 +163,509 @@ free_keylist(struct keylist *list)
     }
 }
 
-int
+static time_t
+ldap2epochtime(const char *timestr)
+{
+  struct tm pgptime;
+  time_t answer;
+
+  memset(&pgptime,0,sizeof(pgptime));
+
+  /* YYYYMMDDHHmmssZ */
+
+  sscanf(timestr,"%4d%2d%2d%2d%2d%2d",
+        &pgptime.tm_year,
+        &pgptime.tm_mon,
+        &pgptime.tm_mday,
+        &pgptime.tm_hour,
+        &pgptime.tm_min,
+        &pgptime.tm_sec);
+
+  pgptime.tm_year-=1900;
+  pgptime.tm_isdst=-1;
+  pgptime.tm_mon--;
+
+  /* mktime takes the timezone into account, and we can't have that.
+     I'd use timegm, but it's not portable. */
+
+#ifdef HAVE_TIMEGM
+  answer=timegm(&pgptime);
+#else
+  {
+    char *zone=getenv("TZ");
+    setenv("TZ","UTC",1);
+    tzset();
+    answer=mktime(&pgptime);
+    if(zone)
+      setenv("TZ",zone,1);
+    else
+      unsetenv("TZ");
+    tzset();
+  }
+#endif
+
+  return answer;
+}
+
+/* Caller must free */
+static char *
+epoch2ldaptime(time_t stamp)
+{
+  struct tm *ldaptime;
+  char buf[16];
+
+  ldaptime=gmtime(&stamp);
+
+  ldaptime->tm_year+=1900;
+  ldaptime->tm_mon++;
+
+  /* YYYYMMDDHHmmssZ */
+
+  sprintf(buf,"%04d%02d%02d%02d%02d%02dZ",
+         ldaptime->tm_year,
+         ldaptime->tm_mon,
+         ldaptime->tm_mday,
+         ldaptime->tm_hour,
+         ldaptime->tm_min,
+         ldaptime->tm_sec);
+
+  return strdup(buf);
+}
+
+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(strcmp((*m)->mod_type,attr)==0)
+       {
+         char **ptr=(*m)->mod_values;
+         int numvalues=0;
+
+         if(ptr)
+           while(*ptr++)
+             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;
+      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;
+      grow[nummods+1]=NULL;
+    }
+
+  return 1;
+}
+
+static void
+build_attrs(LDAPMod ***modlist,char *line)
+{
+  char *record;
+  int i;
+
+  /* Remove trailing whitespace */
+  for(i=strlen(line);i>0;i--)
+    if(ascii_isspace(line[i-1]))
+      line[i-1]='\0';
+    else
+      break;
+
+  if((record=strsep(&line,":"))==NULL)
+    return;
+
+  if(ascii_strcasecmp("pub",record)==0)
+    {
+      char *tok;
+      int disabled=0,revoked=0;
+
+      /* The long keyid */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       {
+         make_one_attr(modlist,"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",tok);
+             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",tok);
+             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(ascii_strcasecmp("uid",record)==0)
+    {
+      char *userid,*tok;
+
+      /* The user ID string */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==0)
+       return;
+
+      userid=tok;
+
+      /* By definition, de-%-encoding is always smaller than the
+         original string so we can decode in place. */
+
+      i=0;
+
+      while(*tok)
+       if(tok[0]=='%' && tok[1] && tok[2])
+         {
+           if((userid[i]=hextobyte(&tok[1]))==-1)
+             userid[i]='?';
+
+           i++;
+           tok+=3;
+         }
+       else
+         userid[i++]=*tok++;
+
+      /* We don't care about the other info provided in the uid: line
+        since the LDAP schema doesn't need it. */
+
+      make_one_attr(modlist,"pgpUserID",userid);
+    }
+}
+
+static void
+free_mod_values(LDAPMod *mod)
+{
+  char **ptr;
+
+  if(!mod->mod_values)
+    return;
+
+  for(ptr=mod->mod_values;*ptr;ptr++)
+    {
+      //      printf("freeing %s with %s as item\n",mod->mod_type,*ptr);
+      free(*ptr);
+    }
+
+  free(mod->mod_values);
+}
+
+static int
 send_key(int *eof)
 {
   int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+  char *dn=NULL,line[MAX_LINE],*key=NULL;
+  char keyid[17];
+  LDAPMod **modlist,**ml;
+
+  modlist=malloc(sizeof(LDAPMod *));
+  if(!modlist)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  *modlist=NULL;
+
+  /* Assemble the INFO stuff into LDAP attributes */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the INFO BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  if(strlen(keyid)!=16)
+    {
+      printf("bad\n");
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  dn=malloc(strlen("pgpCertID=")+16+1+strlen(basekeyspacedn)+1);
+  if(dn==NULL)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  sprintf(dn,"pgpCertID=%s,%s",keyid,basekeyspacedn);
+
+  key=malloc(1);
+  if(!key)
+    {
+      fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  key[0]='\0';
+
+  /* Now parse each line until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       build_attrs(&modlist,line);
+       //      printf("line %s\n",line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no INFO %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  begin=end=0;
+
+  /* Read and throw away stdin until we see the BEGIN */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  /* Now slurp up everything until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       char *tempkey;
+       keysize+=strlen(line);
+       tempkey=realloc(key,keysize);
+       if(tempkey==NULL)
+         {
+           fprintf(console,"gpgkeys: unable to reallocate for key\n");
+           ret=KEYSERVER_NO_MEMORY;
+           goto fail;
+         }
+       else
+         key=tempkey;
+
+       strcat(key,line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  make_one_attr(&modlist,"objectClass","pgpKeyInfo");
+  make_one_attr(&modlist,"pgpKey",key);
+
+  err=ldap_add_s(ldap,dn,modlist);
+
+  /* If it's there already, we just turn around and send a modify
+     command for the same key to bring it 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. */
+
+  if(err==LDAP_ALREADY_EXISTS)
+    err=ldap_modify_s(ldap,dn,modlist);
+
+  if(err!=LDAP_SUCCESS)
+    {
+      printf("err %d\n",err);
+      fprintf(console,"gpgkeys: error adding key %s to keyserver: %s\n",
+             keyid,ldap_err2string(err));
+      ret=ldap_err_to_gpg_err(err);
+      goto fail;
+    }
+
+  ret=KEYSERVER_OK;
+
+ fail:
+  /* Unwind and free the whole modlist structure */
+  for(ml=modlist;*ml;ml++)
+    {
+      free_mod_values(*ml);
+      free(*ml);
+    }
+
+  free(modlist);
+  free(dn);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+  return ret;
+}
+
+static int
+send_key_keyserver(int *eof)
+{
+  int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
   char *dn=NULL,line[MAX_LINE],*key[2]={NULL,NULL};
   char keyid[17];
   LDAPMod mod, *attrs[2];
@@ -273,7 +773,7 @@ send_key(int *eof)
 }
 
 /* Note that key-not-found is not a fatal error */
-int
+static int
 get_key(char *getkey)
 {
   LDAPMessage *res,*each;
@@ -507,51 +1007,7 @@ get_key(char *getkey)
   return ret;
 }
 
-time_t
-ldap2epochtime(const char *timestr)
-{
-  struct tm pgptime;
-  time_t answer;
-
-  memset(&pgptime,0,sizeof(pgptime));
-
-  /* YYYYMMDDHHmmssZ */
-
-  sscanf(timestr,"%4d%2d%2d%2d%2d%2d",
-        &pgptime.tm_year,
-        &pgptime.tm_mon,
-        &pgptime.tm_mday,
-        &pgptime.tm_hour,
-        &pgptime.tm_min,
-        &pgptime.tm_sec);
-
-  pgptime.tm_year-=1900;
-  pgptime.tm_isdst=-1;
-  pgptime.tm_mon--;
-
-  /* mktime takes the timezone into account, and we can't have that.
-     I'd use timegm, but it's not portable. */
-
-#ifdef HAVE_TIMEGM
-  answer=timegm(&pgptime);
-#else
-  {
-    char *zone=getenv("TZ");
-    setenv("TZ","UTC",1);
-    tzset();
-    answer=mktime(&pgptime);
-    if(zone)
-      setenv("TZ",zone,1);
-    else
-      unsetenv("TZ");
-    tzset();
-  }
-#endif
-
-  return answer;
-}
-
-void
+static void
 printquoted(FILE *stream,char *string,char delim)
 {
   while(*string)
@@ -567,7 +1023,7 @@ printquoted(FILE *stream,char *string,char delim)
 
 /* Returns 0 on success and -1 on error.  Note that key-not-found is
    not an error! */
-int
+static int
 search_key(char *searchkey)
 {
   char **vals;
@@ -799,7 +1255,7 @@ search_key(char *searchkey)
   return KEYSERVER_OK;
 }
 
-void
+static void
 fail_all(struct keylist *keylist,int action,int err)
 {
   if(!keylist)
@@ -1331,8 +1787,16 @@ main(int argc,char *argv[])
 
        do
          {
-           if(send_key(&eof)!=KEYSERVER_OK)
-             failed++;
+           if(real_ldap)
+             {
+               if(send_key(&eof)!=KEYSERVER_OK)
+                 failed++;
+             }
+           else
+             {
+               if(send_key_keyserver(&eof)!=KEYSERVER_OK)
+                 failed++;
+             }
          }
        while(!eof);
       }