/* gpgkeys_ldap.c - talk to a LDAP keyserver * Copyright (C) 2001, 2002 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include #include #include "keyserver.h" #ifdef __riscos__ #include #endif #define GET 0 #define SEND 1 #define SEARCH 2 #define MAX_LINE 80 int verbose=0,include_disabled=0,include_revoked=0,include_subkeys=1; char *basekeyspacedn=NULL; char host[80]; FILE *input=NULL,*output=NULL,*console=NULL; LDAP *ldap=NULL; struct keylist { char str[MAX_LINE]; struct keylist *next; }; /* Returns 0 on success, -1 on failure, and 1 on eof */ int send_key(void) { int err,gotit=0,keysize=1,ret=-1; char *dn=NULL; char line[MAX_LINE]; char *key[2]={0,0}; char keyid[17]; #ifndef __riscos__ LDAPMod mod={LDAP_MOD_ADD,"pgpKeyV2",{key}},*attrs[2]={&mod,NULL}; #else LDAPMod mod, *attrs[2]; mod.mod_op = LDAP_MOD_ADD; mod.mod_type = "pgpKeyV2"; mod.mod_values = 0; mod.mod_bvalues = 0; attrs[0] = &mod; attrs[1] = NULL; #endif dn=malloc(strlen("pgpCertid=virtual,")+strlen(basekeyspacedn)+1); if(dn==NULL) { fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n"); 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"); 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 BEGIN\n",keyid)==1) { gotit=1; break; } if(!gotit) { /* i.e. eof before the KEY BEGIN was found */ ret=1; goto fail; } gotit=0; /* 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) { gotit=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"); goto fail; } strcat(key[0],line); } if(!gotit) { fprintf(console,"gpgkeys: no KEY %s END found\n",keyid); 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)); goto fail; } ret=0; fail: free(key[0]); free(dn); if(ret!=0) fprintf(output,"KEY %s FAILED\n",keyid); return ret; } /* Returns 0 on success and -1 on failure. Note that key-not-found is not an error! */ int get_key(char *getkey) { char **vals; LDAPMessage *res,*each; int ret=-1,err,count; struct keylist *dupelist=NULL; char search[62]; char *attrs[]={"pgpKeyV2","pgpuserid","pgpkeyid","pgpcertid","pgprevoked", "pgpdisabled","pgpkeycreatetime","modifytimestamp", "pgpkeysize","pgpkeytype",NULL}; /* 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\n",getkey); return -1; } 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(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(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); } fprintf(output,"KEY 0x%s BEGIN\n",getkey); if(verbose>2) fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search); if(!verbose) attrs[1]=NULL; fprintf(console,"gpgkeys: requesting key 0x%s from LDAP keyserver %s\n", getkey,host); err=ldap_search_s(ldap,basekeyspacedn, LDAP_SCOPE_SUBTREE,search,attrs,0,&res); if(err!=0) { fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err)); fprintf(output,"KEY 0x%s FAILED\n",getkey); return -1; } 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 FAILED\n",getkey); } 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) { struct keylist *keyptr=dupelist; /* 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. */ vals=ldap_get_values(ldap,each,"pgpcertid"); if(vals!=NULL) { while(keyptr!=NULL) { if(strcasecmp(keyptr->str,vals[0])==0) break; keyptr=keyptr->next; } if(!keyptr) { /* it's not a duplicate, so add it */ keyptr=malloc(sizeof(struct keylist)); if(keyptr==NULL) { fprintf(console,"gpgkeys: out of memory when deduping " "key list\n"); 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(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); } 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 && 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 && 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) { 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,"pgpKeyV2"); if(vals==NULL) { fprintf(console,"gpgkeys: unable to retrieve key %s " "from keyserver\n",getkey); fprintf(output,"KEY 0x%s FAILED\n",getkey); } else { fprintf(output,"%sKEY 0x%s END\n",vals[0],getkey); ldap_value_free(vals); } } each=ldap_next_entry(ldap,each); } } ret=0; fail: ldap_msgfree(res); /* free up the dupe checker */ while(dupelist!=NULL) { struct keylist *keyptr=dupelist; dupelist=keyptr->next; free(keyptr); } return ret; } time_t ldap2epochtime(const char *timestr) { struct tm pgptime; memset(&pgptime,0,sizeof(pgptime)); /* YYYYMMDDHHmmssZ */ sscanf(timestr,"%4d%2d%2d%2d%2d%2d", &pgptime.tm_year, &pgptime.tm_mon, &pgptime.tm_mday, &pgptime.tm_hour, &pgptime.tm_min, &pgptime.tm_sec); pgptime.tm_year-=1900; pgptime.tm_isdst=-1; pgptime.tm_mon--; return mktime(&pgptime); } void printquoted(FILE *stream,char *string,char delim) { while(*string) { if(*string==delim) fprintf(stream,"\\x%02X",*string); else fputc(*string,stream); string++; } } /* Returns 0 on success and -1 on error. Note that key-not-found is not an error! */ int search_key(char *searchkey) { char **vals; LDAPMessage *res,*each; int err,count; /* The maxium 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", "pgpkeycreatetime","pgpkeyexpiretime","modifytimestamp", "pgpkeysize","pgpkeytype",NULL}; fprintf(output,"SEARCH %s BEGIN\n",searchkey); /* Build the search string */ sprintf(search,"%s(pgpuserid=*%s*)%s%s%s", (!(include_disabled&&include_revoked))?"(&":"", searchkey, include_disabled?"":"(pgpdisabled=0)", include_revoked?"":"(pgprevoked=0)", !(include_disabled&&include_revoked)?")":""); if(verbose>2) fprintf(console,"gpgkeys: LDAP search for: %s\n",search); fprintf(console,("gpgkeys: searching for \"%s\" from LDAP server %s\n"), searchkey,host); err=ldap_search_s(ldap,basekeyspacedn, LDAP_SCOPE_SUBTREE,search,attrs,0,&res); if(err!=0) { fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err)); return -1; } count=ldap_count_entries(ldap,res); if(count<1) fprintf(output,"COUNT 0\n"); else { fprintf(output,"COUNT %d\n",count); each=ldap_first_entry(ldap,res); while(each!=NULL) { int flags=0; vals=ldap_get_values(ldap,each,"pgpcertid"); if(vals!=NULL) { fprintf(output,"%s:",vals[0]); ldap_value_free(vals); } else fputc(':',output); vals=ldap_get_values(ldap,each,"pgpuserid"); if(vals!=NULL) { /* Need to escape any colons */ printquoted(output,vals[0],':'); fputc(':',output); ldap_value_free(vals); } else fputc(':',output); vals=ldap_get_values(ldap,each,"pgprevoked"); if(vals!=NULL) { if(atoi(vals[0])==1) flags|=1; ldap_value_free(vals); } vals=ldap_get_values(ldap,each,"pgpdisabled"); if(vals!=NULL) { if(atoi(vals[0])==1) flags|=2; ldap_value_free(vals); } fprintf(output,"%d:",flags); /* 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); } else 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); } else 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); } else fputc(':',output); vals=ldap_get_values(ldap,each,"pgpkeytype"); if(vals!=NULL) { fprintf(output,"%s:",vals[0]); ldap_value_free(vals); } else 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('\n',output); each=ldap_next_entry(ldap,each); } } ldap_msgfree(res); fprintf(output,"SEARCH %s END\n",searchkey); return 0; } 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; struct keylist *keylist=NULL,*keyptr=NULL; #ifdef __riscos__ __riscosify_control = __RISCOSIFY_NO_PROCESS; #endif console=stderr; while((arg=getopt(argc,argv,"ho:"))!=-1) switch(arg) { default: case 'h': fprintf(console,"-h\thelp\n"); fprintf(console,"-o\toutput to this file\n"); 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; /* Get the command and info block */ while(fgets(line,MAX_LINE,input)!=NULL) { char commandstr[7]; char portstr[10]; char optionstr[30]; char hash; if(line[0]=='\n') break; if(sscanf(line,"%c",&hash)==1 && hash=='#') continue; if(sscanf(line,"COMMAND %6s\n",commandstr)==1) { commandstr[6]='\0'; if(strcasecmp(commandstr,"get")==0) action=GET; else if(strcasecmp(commandstr,"send")==0) action=SEND; else if(strcasecmp(commandstr,"search")==0) action=SEARCH; continue; } if(sscanf(line,"HOST %79s\n",host)==1) { host[79]='\0'; continue; } if(sscanf(line,"PORT %9s\n",portstr)==1) { portstr[9]='\0'; port=atoi(portstr); continue; } if(sscanf(line,"VERSION %d\n",&version)==1) { if(version!=0) { ret=KEYSERVER_VERSION_ERROR; goto fail; } continue; } if(sscanf(line,"OPTION %29s\n",optionstr)==1) { int no=0; char *start=&optionstr[0]; optionstr[29]='\0'; if(strncasecmp(optionstr,"no-",3)==0) { no=1; start=&optionstr[3]; } if(strcasecmp(start,"verbose")==0) { if(no) verbose--; else verbose++; } else if(strcasecmp(start,"include-disabled")==0) { if(no) include_disabled=0; else include_disabled=1; } else if(strcasecmp(start,"include-revoked")==0) { if(no) include_revoked=0; else include_revoked=1; } else if(strcasecmp(start,"include-subkeys")==0) { if(no) include_subkeys=0; else include_subkeys=1; } continue; } } /* 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(action==SEND) while(fgets(line,MAX_LINE,input)!=NULL && line[0]!='\n'); else if(action==GET || action==SEARCH) { for(;;) { struct keylist *work; if(fgets(line,MAX_LINE,input)==NULL) break; else { if(line[0]=='\n') break; work=malloc(sizeof(struct keylist)); if(work==NULL) { fprintf(console,"gpgkeys: out of memory while " "building key list\n"); 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 0\n"); fprintf(output,"PROGRAM %s\n\n",VERSION); if(verbose>1) { fprintf(console,"Host:\t\t%s\n",host); if(port) fprintf(console,"Port:\t\t%d\n",port); fprintf(console,"Command:\t%s\n",action==GET?"GET": action==SEND?"SEND":"SEARCH"); } ldap=ldap_init(host,port); if(ldap==NULL) { fprintf(console,"gpgkeys: internal LDAP init error: %s\n",strerror(errno)); goto fail; } err=ldap_simple_bind_s(ldap,NULL,NULL); if(err!=0) { fprintf(console,"gpgkeys: internal LDAP bind error: %s\n", ldap_err2string(err)); goto fail; } /* Get the magic info record */ err=ldap_search_s(ldap,"cn=PGPServerInfo",LDAP_SCOPE_BASE, "(objectclass=*)",attrs,0,&res); if(err==-1) { fprintf(console,"gpgkeys: error retrieving LDAP server info: %s\n", ldap_err2string(err)); goto fail; } if(ldap_count_entries(ldap,res)!=1) { fprintf(console,"gpgkeys: more than one serverinfo record\n"); goto fail; } 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); } vals=ldap_get_values(ldap,res,"version"); if(vals!=NULL) { fprintf(console,"Version:\t%s\n",vals[0]); ldap_value_free(vals); } } /* 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) { basekeyspacedn=strdup(vals[0]); if(basekeyspacedn==NULL) { fprintf(console,"gpgkeys: can't allocate string space " "for LDAP base\n"); goto fail; } ldap_value_free(vals); } ldap_msgfree(res); switch(action) { case GET: keyptr=keylist; while(keyptr!=NULL) { if(get_key(keyptr->str)==-1) failed++; keyptr=keyptr->next; } break; case SEND: { int ret; do { ret=send_key(); if(ret==-1) failed++; } while(ret!=1); } break; case SEARCH: { char *searchkey=NULL; int len=0; /* 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+1); if(searchkey==NULL) goto fail; searchkey[0]='\0'; keyptr=keylist; while(keyptr!=NULL) { strcat(searchkey,keyptr->str); strcat(searchkey,"*"); keyptr=keyptr->next; } /* Nail that last "*" */ searchkey[strlen(searchkey)-1]='\0'; if(search_key(searchkey)==-1) { fprintf(output,"SEARCH %s FAILED\n",searchkey); failed++; } free(searchkey); } break; } 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); if(ldap!=NULL) ldap_unbind_s(ldap); free(basekeyspacedn); return ret; }