When outputting MPIs for OpenSSH format, ensure that the leading bit of the first...
[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;
214   int looks_negative = (d->data[0] & 0x80);
215   unsigned char zero = 0;
216
217   /* if the first bit is 1, then the datum will appear negative in the
218      MPI encoding style used by OpenSSH.  In that case, we'll increase
219      the length by one, and dump out one more byte */
220
221   if (looks_negative) {
222     len = htonl(d->size + 1);
223   } else {
224     len = htonl(d->size);
225   }
226   if (write(fd, &len, sizeof(len)) != sizeof(len)) {
227     err("failed to write size of datum.\n");
228     return -2;
229   }
230   if (looks_negative) {
231     if (write(fd, &zero, 1) != 1) {
232       err("failed to write padding byte for MPI.\n");
233       return -2;
234     }
235   }
236   return write_datum_fd(fd, d);
237 }
238
239 int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
240   unsigned int i;
241   int ret;
242
243   for (i = 0; i < num; i++)
244     if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
245       return ret;
246
247   return 0;
248 }
249
250
251 int datum_from_string(gnutls_datum_t* d, const char* str) {
252   d->size = strlen(str);
253   d->data = gnutls_realloc(d->data, d->size);
254   if (d->data == 0)
255     return ENOMEM;
256   memcpy(d->data, str, d->size);
257   return 0;
258 }
259
260
261 int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
262   int p[2];
263   int ret;
264
265   if (pid == NULL) {
266     err("bad pointer passed to create_writing_pipe()\n");
267     return -1;
268   }
269
270   if (ret = pipe(p), ret == -1) {
271     err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
272     return -1;
273   }
274
275   *pid = fork();
276   if (*pid == -1) {
277     err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
278     return -1;
279   }
280   if (*pid == 0) { /* this is the child */
281     close(p[1]); /* close unused write end */
282     
283     if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
284       err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
285       exit(1);
286     }
287     execv(path, argv);
288     err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
289     exit(1);
290   } else { /* this is the parent */
291     close(p[0]); /* close unused read end */
292     return p[1];
293   }
294 }
295
296 int validate_ssh_host_userid(const char* userid) {
297   char* oldlocale = setlocale(LC_ALL, "C");
298   
299   /* choke if userid does not match the expected format
300      ("ssh://fully.qualified.domain.name") */
301   if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
302     err("The user ID should start with ssh:// for a host key\n");
303     goto fail;
304   }
305   /* so that isalnum will work properly */
306   userid += strlen("ssh://");
307   while (0 != (*userid)) {
308     if (!isalnum(*userid)) {
309       err("label did not start with a letter or a digit! (%s)\n", userid);
310       goto fail;
311     }
312     userid++;
313     while (isalnum(*userid) || ('-' == (*userid)))
314       userid++;
315     if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
316                                                  check last char
317                                                  isalnum */
318       if (!isalnum(*(userid - 1))) {
319         err("label did not end with a letter or a digit!\n");
320         goto fail;
321       }
322       if ('.' == (*userid)) /* advance to the start of the next label */
323         userid++;
324     } else {
325       err("invalid character in domain name: %c\n", *userid);
326       goto fail;
327     }
328   }
329   /* ensure that the last character is valid: */
330   if (!isalnum(*(userid - 1))) {
331     err("hostname did not end with a letter or a digit!\n");
332     goto fail;
333   }
334   /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
335      make sure that we've got an OK string? */
336
337   return 0;
338
339  fail:
340   setlocale(LC_ALL, oldlocale);
341   return 1;
342 }