Fix small bug in man page.
[monkeysphere.git] / src / keytrans / gnutls-helpers.c
1 /* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
2 /* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
3 /* License: GPL v3 or later */
4
5 #include "gnutls-helpers.h"
6 /* for htonl() */
7 #include <arpa/inet.h>
8
9 /* for setlocale() */
10 #include <locale.h>
11
12 /* for isalnum() */
13 #include <ctype.h>
14
15 /* for exit() */
16 #include <unistd.h>
17
18 /* higher levels allow more frivolous error messages through. 
19    this is set with the MONKEYSPHERE_DEBUG variable */
20 static int loglevel = 0;
21
22 void err(int level, const char* fmt, ...) {
23   va_list ap;
24   if (level > loglevel)
25     return;
26   va_start(ap, fmt);
27   vfprintf(stderr, fmt, ap);
28   va_end(ap);
29   fflush(stderr);
30 }
31
32 void logfunc(int level, const char* string) {
33   fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
34 }
35
36 void init_keyid(gnutls_openpgp_keyid_t keyid) {
37   memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
38 }
39
40
41
42 void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
43 {
44   static const char hex[16] = "0123456789ABCDEF";
45   unsigned int kix = 0, outix = 0;
46   
47   while (kix < sizeof(gnutls_openpgp_keyid_t)) {
48     out[outix] = hex[(keyid[kix] >> 4) & 0x0f];
49     out[outix + 1] = hex[keyid[kix] & 0x0f];
50     kix++;
51     outix += 2;
52   }
53 }
54
55 unsigned char hex2bin(unsigned char x) {
56   if ((x >= '0') && (x <= '9')) 
57     return x - '0';
58   if ((x >= 'A') && (x <= 'F')) 
59     return 10 + x - 'A';
60   if ((x >= 'a') && (x <= 'f')) 
61     return 10 + x - 'a';
62   return 0xff;
63 }
64
65 void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) {
66   unsigned int pkix = 0, outkix = 0;
67   
68   while (pkix < sizeof(printable_keyid)) {
69     unsigned hi = hex2bin(in[pkix]);
70     unsigned lo = hex2bin(in[pkix + 1]);
71     if (hi == 0xff) {
72       err(0, "character '%c' is not a hex char\n", in[pkix]);
73       exit(1);
74     }
75     if (lo == 0xff) {
76       err(0, "character '%c' is not a hex char\n", in[pkix + 1]);
77       exit(1);
78     }
79     out[outkix] = lo | (hi << 4);
80
81     pkix += 2;
82     outkix++;
83   }
84 }
85
86 int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) {
87   printable_keyid p;
88   int ret;
89
90   ret = convert_string_to_printable_keyid(p, str);
91   if (ret == 0) 
92     collapse_printable_keyid(out, p);
93   return ret;
94 }
95 int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) {
96   int arglen, x;
97   arglen = 0;
98   x = 0;
99   while ((arglen <= sizeof(printable_keyid)) &&
100          (str[x] != '\0')) {
101     if (isxdigit(str[x])) {
102       if (arglen == sizeof(printable_keyid)) {
103         err(0, "There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str);
104         return 1;
105       }
106       pkeyid[arglen] = str[x];
107       arglen++;
108     }
109     x++;
110   }
111   
112   if (arglen != sizeof(printable_keyid)) {
113     err(0, "Keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid));
114     return 1;
115   }
116   return 0;
117 }
118
119
120
121 int init_gnutls() {
122   const char* version = NULL;
123   const char* debug_string = NULL;
124   int ret;
125
126   if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) {
127     loglevel = atoi(debug_string);
128   }
129
130   if (ret = gnutls_global_init(), ret) {
131     err(0, "Failed to do gnutls_global_init() (error: %d)\n", ret);
132     return 1;
133   }
134
135   version = gnutls_check_version(NULL);
136
137   if (version) 
138     err(1, "gnutls version: %s\n", version);
139   else {
140     err(0, "no gnutls version found!\n");
141     return 1;
142   }
143
144   gnutls_global_set_log_function(logfunc);
145   
146   gnutls_global_set_log_level(loglevel);
147   err(1, "set log level to %d\n", loglevel);
148
149   return 0;
150 }
151
152 void init_datum(gnutls_datum_t* d) {
153   d->data = NULL;
154   d->size = 0;
155 }
156 void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) {
157   dest->data = gnutls_realloc(dest->data, src->size);
158   dest->size = src->size;
159   memcpy(dest->data, src->data, src->size);
160 }
161 int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) {
162   if (a->size > b->size) {
163     err(0,"a is larger\n");
164     return 1;
165   }
166   if (a->size < b->size) {
167     err(0,"b is larger\n");
168     return -1;
169   }
170   return memcmp(a->data, b->data, a->size);
171 }
172 void free_datum(gnutls_datum_t* d) {
173   gnutls_free(d->data);
174   d->data = NULL;
175   d->size = 0;
176 }
177
178 /* read the passed-in string, store in a single datum */
179 int set_datum_string(gnutls_datum_t* d, const char* s) {
180   unsigned int x = strlen(s)+1;
181   unsigned char* c = NULL;
182
183   c = gnutls_realloc(d->data, x);
184   if (NULL == c)
185     return -1;
186   d->data = c;
187   d->size = x;
188   memcpy(d->data, s, x);
189   return 0;
190 }
191
192 /* read the passed-in file descriptor until EOF, store in a single
193    datum */
194 int set_datum_fd(gnutls_datum_t* d, int fd) {
195   unsigned int bufsize = 1024;
196   unsigned int len = 0;
197
198   FILE* f = fdopen(fd, "r");
199   if (bufsize > d->size) {
200     bufsize = 1024;
201     d->data = gnutls_realloc(d->data, bufsize);
202     if (d->data == NULL) {
203       err(0,"out of memory!\n");
204       return -1;
205     }
206     d->size = bufsize;
207   } else {
208     bufsize = d->size;
209   }
210   f = fdopen(fd, "r");
211   if (NULL == f) {
212     err(0,"could not fdopen FD %d\n", fd);
213   }
214   clearerr(f);
215   while (!feof(f) && !ferror(f)) { 
216     if (len == bufsize) {
217       /* allocate more space by doubling: */
218       bufsize *= 2;
219       d->data = gnutls_realloc(d->data, bufsize);
220       if (d->data == NULL) {
221         err(0,"out of memory!\n"); 
222         return -1;
223       };
224       d->size = bufsize;
225     }
226     len += fread(d->data + len, 1, bufsize - len, f);
227     /*     err(0,"read %d bytes\n", len); */
228   }
229   if (ferror(f)) {
230     err(0,"Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
231     return -1;
232   }
233     
234   /* touch up buffer size to match reality: */
235   d->data = gnutls_realloc(d->data, len);
236   d->size = len;
237   return 0;
238 }
239
240 /* read the file indicated (by name) in the fname parameter.  store
241    its entire contents in a single datum. */
242 int set_datum_file(gnutls_datum_t* d, const char* fname) {
243   struct stat sbuf;
244   unsigned char* c = NULL;
245   FILE* file = NULL;
246   size_t x = 0;
247
248   if (0 != stat(fname, &sbuf)) {
249     err(0,"failed to stat '%s'\n", fname);
250     return -1;
251   }
252   
253   c = gnutls_realloc(d->data, sbuf.st_size);
254   if (NULL == c) {
255     err(0,"failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
256     return -1;
257   }
258
259   d->data = c;
260   d->size = sbuf.st_size;
261   file = fopen(fname, "r");
262   if (NULL == file) {
263     err(0,"failed to open '%s' for reading\n",  fname);
264     return -1;
265   }
266
267   x = fread(d->data, d->size, 1, file);
268   if (x != 1) {
269     err(0,"tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
270     fclose(file);
271     return -1;
272   }
273   fclose(file);
274   return 0;
275 }
276
277 int write_datum_fd(int fd, const gnutls_datum_t* d) {
278   if (d->size != write(fd, d->data, d->size)) {
279     err(0,"failed to write body of datum.\n");
280     return -1;
281   }
282   return 0;
283 }
284
285
286 int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
287   uint32_t len;
288   int looks_negative = (d->data[0] & 0x80);
289   unsigned char zero = 0;
290
291   /* if the first bit is 1, then the datum will appear negative in the
292      MPI encoding style used by OpenSSH.  In that case, we'll increase
293      the length by one, and dump out one more byte */
294
295   if (looks_negative) {
296     len = htonl(d->size + 1);
297   } else {
298     len = htonl(d->size);
299   }
300   if (write(fd, &len, sizeof(len)) != sizeof(len)) {
301     err(0,"failed to write size of datum.\n");
302     return -2;
303   }
304   if (looks_negative) {
305     if (write(fd, &zero, 1) != 1) {
306       err(0,"failed to write padding byte for MPI.\n");
307       return -2;
308     }
309   }
310   return write_datum_fd(fd, d);
311 }
312
313 int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
314   unsigned int i;
315   int ret;
316
317   for (i = 0; i < num; i++)
318     if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
319       return ret;
320
321   return 0;
322 }
323
324
325 int datum_from_string(gnutls_datum_t* d, const char* str) {
326   d->size = strlen(str);
327   d->data = gnutls_realloc(d->data, d->size);
328   if (d->data == 0)
329     return ENOMEM;
330   memcpy(d->data, str, d->size);
331   return 0;
332 }
333
334
335 int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
336   int p[2];
337   int ret;
338
339   if (pid == NULL) {
340     err(0,"bad pointer passed to create_writing_pipe()\n");
341     return -1;
342   }
343
344   if (ret = pipe(p), ret == -1) {
345     err(0,"failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
346     return -1;
347   }
348
349   *pid = fork();
350   if (*pid == -1) {
351     err(0,"Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
352     return -1;
353   }
354   if (*pid == 0) { /* this is the child */
355     close(p[1]); /* close unused write end */
356     
357     if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
358       err(0,"Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
359       exit(1);
360     }
361     execv(path, argv);
362     err(0,"exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
363     /* close the open file descriptors */
364     close(p[0]);
365     close(0);
366
367     exit(1);
368   } else { /* this is the parent */
369     close(p[0]); /* close unused read end */
370     return p[1];
371   }
372 }
373
374 int validate_ssh_host_userid(const char* userid) {
375   char* oldlocale = setlocale(LC_ALL, "C");
376   
377   /* choke if userid does not match the expected format
378      ("ssh://fully.qualified.domain.name") */
379   if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
380     err(0,"The user ID should start with ssh:// for a host key\n");
381     goto fail;
382   }
383   /* so that isalnum will work properly */
384   userid += strlen("ssh://");
385   while (0 != (*userid)) {
386     if (!isalnum(*userid)) {
387       err(0,"label did not start with a letter or a digit! (%s)\n", userid);
388       goto fail;
389     }
390     userid++;
391     while (isalnum(*userid) || ('-' == (*userid)))
392       userid++;
393     if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
394                                                  check last char
395                                                  isalnum */
396       if (!isalnum(*(userid - 1))) {
397         err(0,"label did not end with a letter or a digit!\n");
398         goto fail;
399       }
400       if ('.' == (*userid)) /* advance to the start of the next label */
401         userid++;
402     } else {
403       err(0,"invalid character in domain name: %c\n", *userid);
404       goto fail;
405     }
406   }
407   /* ensure that the last character is valid: */
408   if (!isalnum(*(userid - 1))) {
409     err(0,"hostname did not end with a letter or a digit!\n");
410     goto fail;
411   }
412   /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
413      make sure that we've got an OK string? */
414
415   return 0;
416
417  fail:
418   setlocale(LC_ALL, oldlocale);
419   return 1;
420 }
421
422 /* http://tools.ietf.org/html/rfc4880#section-5.5.2 */
423 size_t get_openpgp_mpi_size(gnutls_datum_t* d) {
424   return 2 + d->size;
425 }
426
427 int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) {
428   uint16_t x;
429
430   x = d->size * 8;
431   x = htons(x);
432   
433   write(fd, &x, sizeof(x));
434   write(fd, d->data, d->size);
435   
436   return 0;
437 }