2006-05-23 18:19:43 +02:00
|
|
|
/* pka.c - DNS Public Key Association RR access
|
2009-12-07 18:12:28 +01:00
|
|
|
* Copyright (C) 2005, 2009 Free Software Foundation, Inc.
|
2006-05-23 18:19:43 +02:00
|
|
|
*
|
|
|
|
* 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
|
2007-07-04 21:49:40 +02:00
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
2006-05-23 18:19:43 +02:00
|
|
|
* (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
|
2007-07-04 21:49:40 +02:00
|
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
2006-05-23 18:19:43 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#ifdef USE_DNS_PKA
|
|
|
|
#include <sys/types.h>
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/nameser.h>
|
|
|
|
#include <resolv.h>
|
|
|
|
#endif
|
|
|
|
#endif /* USE_DNS_PKA */
|
2009-12-07 16:52:27 +01:00
|
|
|
#ifdef USE_ADNS
|
|
|
|
# include <adns.h>
|
|
|
|
# ifndef HAVE_ADNS_FREE
|
|
|
|
# define adns_free free
|
|
|
|
# endif
|
|
|
|
#endif
|
2006-05-23 18:19:43 +02:00
|
|
|
|
|
|
|
#include "util.h"
|
|
|
|
#include "pka.h"
|
|
|
|
|
|
|
|
#ifdef USE_DNS_PKA
|
|
|
|
/* Parse the TXT resource record. Format is:
|
|
|
|
|
|
|
|
v=pka1;fpr=a4d94e92b0986ab5ee9dcd755de249965b0358a2;uri=string
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2006-05-23 18:19:43 +02:00
|
|
|
For simplicity white spaces are not allowed. Because we expect to
|
|
|
|
use a new RRTYPE for this in the future we define the TXT really
|
|
|
|
strict for simplicity: No white spaces, case sensitivity of the
|
|
|
|
names, order must be as given above. Only URI is optional.
|
|
|
|
|
|
|
|
This function modifies BUFFER. On success 0 is returned, the 20
|
|
|
|
byte fingerprint stored at FPR and BUFFER contains the URI or an
|
|
|
|
empty string.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
parse_txt_record (char *buffer, unsigned char *fpr)
|
|
|
|
{
|
|
|
|
char *p, *pend;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
p = buffer;
|
|
|
|
pend = strchr (p, ';');
|
|
|
|
if (!pend)
|
|
|
|
return -1;
|
|
|
|
*pend++ = 0;
|
|
|
|
if (strcmp (p, "v=pka1"))
|
|
|
|
return -1; /* Wrong or missing version. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2006-05-23 18:19:43 +02:00
|
|
|
p = pend;
|
|
|
|
pend = strchr (p, ';');
|
|
|
|
if (pend)
|
|
|
|
*pend++ = 0;
|
|
|
|
if (strncmp (p, "fpr=", 4))
|
|
|
|
return -1; /* Missing fingerprint part. */
|
|
|
|
p += 4;
|
|
|
|
for (i=0; i < 20 && hexdigitp (p) && hexdigitp (p+1); i++, p += 2)
|
|
|
|
fpr[i] = xtoi_2 (p);
|
|
|
|
if (i != 20)
|
|
|
|
return -1; /* Fingerprint consists not of exactly 40 hexbytes. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2006-05-23 18:19:43 +02:00
|
|
|
p = pend;
|
|
|
|
if (!p || !*p)
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
*buffer = 0;
|
2006-05-23 18:19:43 +02:00
|
|
|
return 0; /* Success (no URI given). */
|
|
|
|
}
|
|
|
|
if (strncmp (p, "uri=", 4))
|
|
|
|
return -1; /* Unknown part. */
|
|
|
|
p += 4;
|
|
|
|
/* There is an URI, copy it to the start of the buffer. */
|
|
|
|
while (*p)
|
|
|
|
*buffer++ = *p++;
|
|
|
|
*buffer = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* For the given email ADDRESS lookup the PKA information in the DNS.
|
|
|
|
|
|
|
|
On success the 20 byte SHA-1 fingerprint is stored at FPR and the
|
|
|
|
URI will be returned in an allocated buffer. Note that the URI
|
2008-03-06 19:28:47 +01:00
|
|
|
might be an zero length string as this information is optional.
|
2006-05-23 18:19:43 +02:00
|
|
|
Caller must xfree the returned string.
|
|
|
|
|
|
|
|
On error NULL is returned and the 20 bytes at FPR are not
|
|
|
|
defined. */
|
|
|
|
char *
|
|
|
|
get_pka_info (const char *address, unsigned char *fpr)
|
|
|
|
{
|
2009-12-07 16:52:27 +01:00
|
|
|
#ifdef USE_ADNS
|
|
|
|
int rc;
|
|
|
|
adns_state state;
|
|
|
|
const char *domain;
|
|
|
|
char *name;
|
|
|
|
adns_answer *answer = NULL;
|
|
|
|
char *buffer = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-12-07 16:52:27 +01:00
|
|
|
domain = strrchr (address, '@');
|
|
|
|
if (!domain || domain == address || !domain[1])
|
|
|
|
return NULL; /* Invalid mail address given. */
|
|
|
|
name = xtrymalloc (strlen (address) + 5 + 1);
|
|
|
|
if (!name)
|
|
|
|
return NULL;
|
|
|
|
memcpy (name, address, domain - address);
|
|
|
|
strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
|
|
|
|
|
|
|
|
rc = adns_init (&state, adns_if_noerrprint, NULL);
|
|
|
|
if (rc)
|
|
|
|
{
|
|
|
|
log_error ("error initializing adns: %s\n", strerror (errno));
|
|
|
|
xfree (name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = adns_synchronous (state, name, adns_r_txt, adns_qf_quoteok_query,
|
|
|
|
&answer);
|
|
|
|
xfree (name);
|
|
|
|
if (rc)
|
|
|
|
{
|
|
|
|
log_error ("DNS query failed: %s\n", strerror (errno));
|
|
|
|
adns_finish (state);
|
|
|
|
return NULL;
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
if (answer->status != adns_s_ok
|
2009-12-07 16:52:27 +01:00
|
|
|
|| answer->type != adns_r_txt || !answer->nrrs)
|
|
|
|
{
|
|
|
|
log_error ("DNS query returned an error: %s (%s)\n",
|
|
|
|
adns_strerror (answer->status),
|
|
|
|
adns_errabbrev (answer->status));
|
|
|
|
adns_free (answer);
|
|
|
|
adns_finish (state);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We use a PKA records iff there is exactly one record. */
|
|
|
|
if (answer->nrrs == 1 && answer->rrs.manyistr[0]->i != -1)
|
|
|
|
{
|
|
|
|
buffer = xtrystrdup (answer->rrs.manyistr[0]->str);
|
|
|
|
if (parse_txt_record (buffer, fpr))
|
|
|
|
{
|
|
|
|
xfree (buffer);
|
|
|
|
buffer = NULL; /* Not a valid gpg trustdns RR. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
adns_free (answer);
|
|
|
|
adns_finish (state);
|
|
|
|
return buffer;
|
|
|
|
|
|
|
|
#else /*!USE_ADNS*/
|
2006-05-23 18:19:43 +02:00
|
|
|
unsigned char answer[PACKETSZ];
|
|
|
|
int anslen;
|
|
|
|
int qdcount, ancount, nscount, arcount;
|
|
|
|
int rc;
|
|
|
|
unsigned char *p, *pend;
|
|
|
|
const char *domain;
|
|
|
|
char *name;
|
2010-04-20 03:11:35 +02:00
|
|
|
HEADER header;
|
2006-05-23 18:19:43 +02:00
|
|
|
|
|
|
|
domain = strrchr (address, '@');
|
|
|
|
if (!domain || domain == address || !domain[1])
|
|
|
|
return NULL; /* invalid mail address given. */
|
|
|
|
|
2008-04-07 21:31:12 +02:00
|
|
|
name = xtrymalloc (strlen (address) + 5 + 1);
|
|
|
|
if (!name)
|
|
|
|
return NULL;
|
2006-05-23 18:19:43 +02:00
|
|
|
memcpy (name, address, domain - address);
|
|
|
|
strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
|
|
|
|
|
|
|
|
anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
|
|
|
|
xfree (name);
|
|
|
|
if (anslen < sizeof(HEADER))
|
|
|
|
return NULL; /* DNS resolver returned a too short answer. */
|
2010-04-20 03:11:35 +02:00
|
|
|
|
|
|
|
/* Don't despair: A good compiler should optimize this away, as
|
|
|
|
header is just 32 byte and constant at compile time. It's
|
|
|
|
one way to comply with strict aliasing rules. */
|
|
|
|
memcpy (&header, answer, sizeof (header));
|
|
|
|
|
|
|
|
if ( (rc=header.rcode) != NOERROR )
|
2006-05-23 18:19:43 +02:00
|
|
|
return NULL; /* DNS resolver returned an error. */
|
|
|
|
|
|
|
|
/* We assume that PACKETSZ is large enough and don't do dynmically
|
|
|
|
expansion of the buffer. */
|
|
|
|
if (anslen > PACKETSZ)
|
|
|
|
return NULL; /* DNS resolver returned a too long answer */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2010-04-20 03:11:35 +02:00
|
|
|
qdcount = ntohs (header.qdcount);
|
|
|
|
ancount = ntohs (header.ancount);
|
|
|
|
nscount = ntohs (header.nscount);
|
|
|
|
arcount = ntohs (header.arcount);
|
2006-05-23 18:19:43 +02:00
|
|
|
|
|
|
|
if (!ancount)
|
|
|
|
return NULL; /* Got no answer. */
|
|
|
|
|
|
|
|
p = answer + sizeof (HEADER);
|
|
|
|
pend = answer + anslen; /* Actually points directly behind the buffer. */
|
|
|
|
|
|
|
|
while (qdcount-- && p < pend)
|
|
|
|
{
|
|
|
|
rc = dn_skipname (p, pend);
|
|
|
|
if (rc == -1)
|
|
|
|
return NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
p += rc + QFIXEDSZ;
|
2006-05-23 18:19:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ancount > 1)
|
|
|
|
return NULL; /* more than one possible gpg trustdns record - none used. */
|
|
|
|
|
|
|
|
while (ancount-- && p <= pend)
|
|
|
|
{
|
|
|
|
unsigned int type, class, txtlen, n;
|
|
|
|
char *buffer, *bufp;
|
|
|
|
|
|
|
|
rc = dn_skipname (p, pend);
|
|
|
|
if (rc == -1)
|
|
|
|
return NULL;
|
|
|
|
p += rc;
|
|
|
|
if (p >= pend - 10)
|
|
|
|
return NULL; /* RR too short. */
|
|
|
|
|
|
|
|
type = *p++ << 8;
|
|
|
|
type |= *p++;
|
|
|
|
class = *p++ << 8;
|
|
|
|
class |= *p++;
|
|
|
|
p += 4;
|
|
|
|
txtlen = *p++ << 8;
|
|
|
|
txtlen |= *p++;
|
|
|
|
if (type != T_TXT || class != C_IN)
|
|
|
|
return NULL; /* Answer does not match the query. */
|
|
|
|
|
|
|
|
buffer = bufp = xmalloc (txtlen + 1);
|
|
|
|
while (txtlen && p < pend)
|
|
|
|
{
|
|
|
|
for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
|
|
|
|
*bufp++ = *p++;
|
|
|
|
}
|
|
|
|
*bufp = 0;
|
|
|
|
if (parse_txt_record (buffer, fpr))
|
|
|
|
{
|
|
|
|
xfree (buffer);
|
|
|
|
return NULL; /* Not a valid gpg trustdns RR. */
|
|
|
|
}
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2009-12-07 16:52:27 +01:00
|
|
|
#endif /*!USE_ADNS*/
|
2006-05-23 18:19:43 +02:00
|
|
|
}
|
2009-12-07 16:52:27 +01:00
|
|
|
|
2006-05-23 18:19:43 +02:00
|
|
|
#else /* !USE_DNS_PKA */
|
|
|
|
|
|
|
|
/* Dummy version of the function if we can't use the resolver
|
|
|
|
functions. */
|
|
|
|
char *
|
|
|
|
get_pka_info (const char *address, unsigned char *fpr)
|
|
|
|
{
|
2010-03-22 13:46:05 +01:00
|
|
|
(void)address;
|
|
|
|
(void)fpr;
|
2006-05-23 18:19:43 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif /* !USE_DNS_PKA */
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef TEST
|
|
|
|
int
|
|
|
|
main(int argc,char *argv[])
|
|
|
|
{
|
|
|
|
unsigned char fpr[20];
|
|
|
|
char *uri;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (argc < 2)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "usage: pka mail-addresses\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
argc--;
|
|
|
|
argv++;
|
|
|
|
|
|
|
|
for (; argc; argc--, argv++)
|
|
|
|
{
|
|
|
|
uri = get_pka_info ( *argv, fpr );
|
|
|
|
printf ("%s", *argv);
|
|
|
|
if (uri)
|
|
|
|
{
|
|
|
|
putchar (' ');
|
|
|
|
for (i=0; i < 20; i++)
|
|
|
|
printf ("%02X", fpr[i]);
|
|
|
|
if (*uri)
|
|
|
|
printf (" %s", uri);
|
|
|
|
xfree (uri);
|
|
|
|
}
|
|
|
|
putchar ('\n');
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* TEST */
|
|
|
|
|
|
|
|
/*
|
|
|
|
Local Variables:
|
2009-12-07 16:52:27 +01:00
|
|
|
compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv ../tools/no-libgcrypt.o ../jnlib/libjnlib.a"
|
2006-05-23 18:19:43 +02:00
|
|
|
End:
|
|
|
|
*/
|