exporting RSA public keys in openssh known_hosts format.
[monkeysphere.git] / 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 int loglevel = 0;
16
17
18 void err(const char* fmt, ...) {
19   va_list ap;
20   va_start(ap, fmt);
21   vfprintf(stderr, fmt, ap);
22   va_end(ap);
23 }
24
25 void logfunc(int level, const char* string) {
26   fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
27 }
28
29 void init_keyid(gnutls_openpgp_keyid_t keyid) {
30   memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
31 }
32
33
34
35 void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
36 {
37   static const char hex[16] = "0123456789ABCDEF";
38   unsigned int kix = 0, outix = 0;
39   
40   while (kix < sizeof(gnutls_openpgp_keyid_t)) {
41     out[outix] = hex[(keyid[kix] >> 4) & 0x0f];
42     out[outix + 1] = hex[keyid[kix] & 0x0f];
43     kix++;
44     outix += 2;
45   }
46 }
47
48
49 int init_gnutls() {
50   const char* version = NULL;
51   const char* debug_string = NULL;
52   int ret;
53
54   if (ret = gnutls_global_init(), ret) {
55     err("Failed to do gnutls_global_init() (error: %d)\n", ret);
56     return 1;
57   }
58
59   version = gnutls_check_version(NULL);
60
61   if (version) 
62     err("gnutls version: %s\n", version);
63   else {
64     err("no version found!\n");
65     return 1;
66   }
67
68   if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) {
69     loglevel = atoi(debug_string);
70     gnutls_global_set_log_function(logfunc);
71     
72     gnutls_global_set_log_level(loglevel);
73     err("set log level to %d\n", loglevel);
74   }
75   return 0;
76 }
77
78 void init_datum(gnutls_datum_t* d) {
79   d->data = NULL;
80   d->size = 0;
81 }
82 void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) {
83   dest->data = gnutls_realloc(dest->data, src->size);
84   dest->size = src->size;
85   memcpy(dest->data, src->data, src->size);
86 }
87 int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) {
88   if (a->size > b->size) {
89     err("a is larger\n");
90     return 1;
91   }
92   if (a->size < b->size) {
93     err("b is larger\n");
94     return -1;
95   }
96   return memcmp(a->data, b->data, a->size);
97 }
98 void free_datum(gnutls_datum_t* d) {
99   gnutls_free(d->data);
100   d->data = NULL;
101   d->size = 0;
102 }
103
104 /* read the passed-in string, store in a single datum */
105 int set_datum_string(gnutls_datum_t* d, const char* s) {
106   unsigned int x = strlen(s)+1;
107   unsigned char* c = NULL;
108
109   c = gnutls_realloc(d->data, x);
110   if (NULL == c)
111     return -1;
112   d->data = c;
113   d->size = x;
114   memcpy(d->data, s, x);
115   return 0;
116 }
117
118 /* read the passed-in file descriptor until EOF, store in a single
119    datum */
120 int set_datum_fd(gnutls_datum_t* d, int fd) {
121   unsigned int bufsize = 1024;
122   unsigned int len = 0;
123
124   FILE* f = fdopen(fd, "r");
125   if (bufsize > d->size) {
126     bufsize = 1024;
127     d->data = gnutls_realloc(d->data, bufsize);
128     if (d->data == NULL) {
129       err("out of memory!\n");
130       return -1;
131     }
132     d->size = bufsize;
133   } else {
134     bufsize = d->size;
135   }
136   f = fdopen(fd, "r");
137   if (NULL == f) {
138     err("could not fdopen FD %d\n", fd);
139   }
140   clearerr(f);
141   while (!feof(f) && !ferror(f)) { 
142     if (len == bufsize) {
143       /* allocate more space by doubling: */
144       bufsize *= 2;
145       d->data = gnutls_realloc(d->data, bufsize);
146       if (d->data == NULL) {
147         err("out of memory!\n"); 
148         return -1;
149       };
150       d->size = bufsize;
151     }
152     len += fread(d->data + len, 1, bufsize - len, f);
153     /*     err("read %d bytes\n", len); */
154   }
155   if (ferror(f)) {
156     err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
157     return -1;
158   }
159     
160   /* touch up buffer size to match reality: */
161   d->data = gnutls_realloc(d->data, len);
162   d->size = len;
163   return 0;
164 }
165
166 /* read the file indicated (by name) in the fname parameter.  store
167    its entire contents in a single datum. */
168 int set_datum_file(gnutls_datum_t* d, const char* fname) {
169   struct stat sbuf;
170   unsigned char* c = NULL;
171   FILE* file = NULL;
172   size_t x = 0;
173
174   if (0 != stat(fname, &sbuf)) {
175     err("failed to stat '%s'\n", fname);
176     return -1;
177   }
178   
179   c = gnutls_realloc(d->data, sbuf.st_size);
180   if (NULL == c) {
181     err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
182     return -1;
183   }
184
185   d->data = c;
186   d->size = sbuf.st_size;
187   file = fopen(fname, "r");
188   if (NULL == file) {
189     err("failed to open '%s' for reading\n",  fname);
190     return -1;
191   }
192
193   x = fread(d->data, d->size, 1, file);
194   if (x != 1) {
195     err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
196     fclose(file);
197     return -1;
198   }
199   fclose(file);
200   return 0;
201 }
202
203 int write_datum_fd(int fd, const gnutls_datum_t* d) {
204   if (d->size != write(fd, d->data, d->size)) {
205     err("failed to write body of datum.\n");
206     return -1;
207   }
208   return 0;
209 }
210
211
212 int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
213   uint32_t len = htonl(d->size);
214   if (write(fd, &len, sizeof(len)) != sizeof(len)) {
215     err("failed to write size of datum.\n");
216     return -2;
217   }
218   return write_datum_fd(fd, d);
219 }
220
221 int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
222   unsigned int i;
223   int ret;
224
225   for (i = 0; i < num; i++)
226     if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
227       return ret;
228
229   return 0;
230 }
231
232
233 int datum_from_string(gnutls_datum_t* d, const char* str) {
234   d->size = strlen(str);
235   d->data = gnutls_realloc(d->data, d->size);
236   if (d->data == 0)
237     return ENOMEM;
238   memcpy(d->data, str, d->size);
239   return 0;
240 }
241
242
243 int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
244   int p[2];
245   int ret;
246
247   if (pid == NULL) {
248     err("bad pointer passed to create_writing_pipe()\n");
249     return -1;
250   }
251
252   if (ret = pipe(p), ret == -1) {
253     err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
254     return -1;
255   }
256
257   *pid = fork();
258   if (*pid == -1) {
259     err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
260     return -1;
261   }
262   if (*pid == 0) { /* this is the child */
263     close(p[1]); /* close unused write end */
264     
265     if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
266       err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
267       exit(1);
268     }
269     execv(path, argv);
270     err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
271     exit(1);
272   } else { /* this is the parent */
273     close(p[0]); /* close unused read end */
274     return p[1];
275   }
276 }
277
278 int validate_ssh_host_userid(const char* userid) {
279   char* oldlocale = setlocale(LC_ALL, "C");
280   
281   /* choke if userid does not match the expected format
282      ("ssh://fully.qualified.domain.name") */
283   if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
284     err("The user ID should start with ssh:// for a host key\n");
285     goto fail;
286   }
287   /* so that isalnum will work properly */
288   userid += strlen("ssh://");
289   while (0 != (*userid)) {
290     if (!isalnum(*userid)) {
291       err("label did not start with a letter or a digit! (%s)\n", userid);
292       goto fail;
293     }
294     userid++;
295     while (isalnum(*userid) || ('-' == (*userid)))
296       userid++;
297     if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
298                                                  check last char
299                                                  isalnum */
300       if (!isalnum(*(userid - 1))) {
301         err("label did not end with a letter or a digit!\n");
302         goto fail;
303       }
304       if ('.' == (*userid)) /* advance to the start of the next label */
305         userid++;
306     } else {
307       err("invalid character in domain name: %c\n", *userid);
308       goto fail;
309     }
310   }
311   /* ensure that the last character is valid: */
312   if (!isalnum(*(userid - 1))) {
313     err("hostname did not end with a letter or a digit!\n");
314     goto fail;
315   }
316   /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
317      make sure that we've got an OK string? */
318
319   return 0;
320
321  fail:
322   setlocale(LC_ALL, oldlocale);
323   return 1;
324 }