64 bit fixes.
[virt-hostinfo.git] / hostinfod / commands.c
1 /* virt-hostinfo
2  * Copyright (C) 2009 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /* This code parses and executes the commands sent by guests.  It
20  * is therefore particularly security sensitive.  The protocol is
21  * documented in hostinfo-protocol(5).
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include <assert.h>
32 #include <ctype.h>
33 #include <unistd.h>
34 #include <time.h>
35 #include <math.h>
36
37 #include <apr_general.h>
38 #include <apr_pools.h>
39 #include <apr_hash.h>
40 #include <apr_strings.h>
41
42 #include "hostinfod.h"
43
44 #define PROTOCOL_VERSION "1.0"
45 #define CRLF "\r\n"
46
47 apr_hash_t *commands = NULL;
48
49 static const char *string_of_arg_type (enum arg_type);
50 static int not_printable (const char *str);
51 static int contains_crlf (const char *str);
52 static char *parse_c_string (struct guest_description *hval, const char *str, size_t *posn);
53 static int parse_long (struct guest_description *hval, const char *str, size_t *posn, long *ret);
54
55 void
56 execute_command (const struct timespec *now,
57                  struct guest_description *hval, const char *command)
58 {
59   size_t len, i, j;
60   int neg;
61   char *cmd;
62   apr_array_header_t *args;
63   struct arg arg;
64   command_fn fn;
65   int enabled;
66   double interval;
67   struct timespec *last;
68
69   debug ("%s: %s", hval->name, command);
70
71   /* Create a new pool for allocation during the lifetime of the
72    * request/response.  **NB** the hval->reply field is allocated
73    * from this pool, which is why it gets nulled below.
74    */
75   if (hval->rpool)
76     apr_pool_destroy (hval->rpool);
77   hval->rpool = NULL;
78   hval->reply = NULL;
79   hval->reply_size = hval->reply_posn = 0;
80   apr_pool_create (&hval->rpool, hval->pool);
81
82   /* Split up the command.  Commands have a very narrowly-defined
83    * format, and we reject any malformed commands with a 400 error.
84    *
85    * NB. A lot of the code below assumes 7 bit, printable ASCII,
86    * and this is only safe because of the call to 'not_printable'
87    * here.
88    */
89   len = strlen (command);
90   if (len < 1 || len > 4094 || not_printable (command)) {
91     warning ("%s: command too short, too long or contained unprintable chars (len = %zu)",
92              hval->name, len);
93     send_error (hval, 400);
94     return;
95   }
96
97   /* Command is alphanumeric string, non-zero length. */
98   i = strspn (command,
99               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
100               "abcdefghijklmnopqrstuvwxyz"
101               "0123456789");
102   if (i < 1) {
103     warning ("%s: no command part in request", hval->name);
104     send_error (hval, 400);
105     return;
106   }
107
108   cmd = apr_pstrmemdup (hval->rpool, command, i);
109   for (j = 0; j < i; ++j)
110     cmd[j] = tolower (cmd[j]);
111
112   args = apr_array_make (hval->rpool, 0, sizeof (struct arg));
113
114   while (command[i]) {  /* command[i] is the space before the next arg */
115     if (command[i] != ' ') {
116       warning ("%s: there must be a single space between command and each argument",
117                hval->name);
118       send_error (hval, 400);
119       return;
120     }
121
122     neg = 0;
123
124     i++;
125     switch (command[i]) {
126     case '\0':
127       warning ("%s: trailing space after command", hval->name);
128       send_error (hval, 400);
129       return;
130
131     case '"':                   /* string literal */
132       arg.type = arg_type_string;
133       arg.u.str = parse_c_string (hval, command, &i);
134       if (!arg.u.str) {
135         send_error (hval, 400);
136         return;
137       }
138       break;
139
140     case '-':                   /* integer literal */
141       neg = 1;
142     case '0':
143     case '1':
144     case '2':
145     case '3':
146     case '4':
147     case '5':
148     case '6':
149     case '7':
150     case '8':
151     case '9':
152       arg.type = arg_type_int;
153       if (parse_long (hval, command, &i, &arg.u.i) == -1) {
154         send_error (hval, 400);
155         return;
156       }
157       break;
158
159     case 't':                   /* boolean */
160     case 'f':
161     case 'T':
162     case 'F':
163       arg.type = arg_type_bool;
164       if (strncasecmp (&command[i], "true", 4) == 0) {
165         arg.u.i = 1;
166         i += 4;
167       } else if (strncasecmp (&command[i], "false", 5) == 0) {
168         arg.u.i = 0;
169         i += 5;
170       } else
171         goto unknown_arg;
172       break;
173
174     default:
175     unknown_arg:
176       warning ("%s: unknown or malformed argument starting at position %zu ('%c')",
177                hval->name, i, command[i]);
178       send_error (hval, 400);
179       return;
180     } /* switch */
181
182     APR_ARRAY_PUSH (args, struct arg) = arg;
183   }
184
185   if (verbose) {
186     /* Debug output what the guest sent / what we decoded. */
187     debug ("%s: command '%s' with %d args", hval->name, cmd, args->nelts);
188     for (i = 0; i < args->nelts; ++i) {
189       struct arg arg = APR_ARRAY_IDX (args, i, struct arg);
190
191       switch (arg.type) {
192       case arg_type_bool:
193       case arg_type_int:
194         debug ("%s: arg %zu : %s = %ld",
195                hval->name, i, string_of_arg_type (arg.type), arg.u.i);
196         break;
197       case arg_type_string:
198         debug ("%s: arg %zu : %s = %s",
199                hval->name, i, string_of_arg_type (arg.type), arg.u.str);
200         break;
201       }
202     }
203   }
204
205   /* Know about this command? */
206   fn = apr_hash_get (commands, cmd, APR_HASH_KEY_STRING);
207   if (!fn) {
208     send_error (hval, 404);
209     return;
210   }
211
212   /* Before dispatching the command, check the command is enabled
213    * and guest is not calling it too frequently.
214    */
215   check_guests_file (hval, cmd, &interval, &enabled);
216
217   if (!enabled) {
218     warning ("%s: guest tried disabled command '%s'", hval->name, cmd);
219     send_error (hval, 401);
220     return;
221   }
222
223   last = apr_hash_get (hval->lasttime, cmd, APR_HASH_KEY_STRING);
224   if (last) {
225     struct timespec timediff;
226     double interval_int, interval_frac;
227     struct timespec interval_ts;
228
229     diff_timespec (&timediff, now, last);
230
231     interval_frac = modf (interval, &interval_int);
232     interval_ts.tv_sec = interval_int;
233     interval_ts.tv_nsec = interval_frac * 1000000000;
234
235     debug ("%s: %s: interval %ds %ldns, time since last %ds %ldns",
236            hval->name, cmd,
237            (int) interval_ts.tv_sec, interval_ts.tv_nsec,
238            (int) timediff.tv_sec, timediff.tv_nsec);
239
240     if (interval_ts.tv_sec > timediff.tv_sec ||
241         (interval_ts.tv_sec == timediff.tv_sec &&
242          interval_ts.tv_nsec > timediff.tv_nsec)) {
243       warning ("%s: command '%s' exceeded interval allowed", hval->name, cmd);
244       send_error (hval, 406);
245       return;
246     }
247   }
248
249   last = apr_pmemdup (hval->pool, now, sizeof *now);
250   apr_hash_set (hval->lasttime, cmd, APR_HASH_KEY_STRING, last);
251
252   /* Dispatch the command. */
253   fn (hval, cmd, args);
254 }
255
256 /* All commands must consist only of printable 7 bit ASCII.
257  * NB. Don't use isprint(3).
258  */
259 static int
260 not_printable (const char *str)
261 {
262   int c;
263
264   while ((c = *(unsigned char *)str)) {
265     if (c < 32 || c > 126)
266       return 1;
267     str++;
268   }
269   return 0;
270 }
271
272 static int
273 contains_crlf (const char *str)
274 {
275   int c;
276
277   while ((c = *(unsigned char *)str)) {
278     if (c == '\r' || c == '\n')
279       return 1;
280     str++;
281   }
282   return 0;
283 }
284
285 static int
286 hexval (int c)
287 {
288   switch (c) {
289   case '0': case '1': case '2': case '3': case '4':
290   case '5': case '6': case '7': case '8': case '9':
291     return c - '0';
292   case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
293     return c - 'a' + 10;
294   case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
295     return c - 'A' + 10;
296   default:
297     abort ();
298   }
299 }
300
301 static char *
302 parse_c_string (struct guest_description *hval, const char *str, size_t *posn)
303 {
304   apr_array_header_t *r;
305   int c;
306
307   r = apr_array_make (hval->rpool, 0, 1);
308 #define APPEND(c) APR_ARRAY_PUSH (r,char) = (c);
309
310   assert (str[*posn] == '"');
311   (*posn)++;
312
313   while (str[*posn] != '"') {
314     if (str[*posn] == '\\') {   /* Start of \escape sequence. */
315       switch (str[*posn+1]) {
316       case '0': case '1': case '2': case '3':
317         if ((str[*posn+2] >= '0' && str[*posn+2] <= '7') &&
318             (str[*posn+3] >= '0' && str[*posn+3] <= '7')) {
319           c = (str[*posn+1] - '0') * 0100;
320           c += (str[*posn+2] - '0') * 010;
321           c += str[*posn+3] - '0';
322           if (c != 0) {
323             APPEND (c);
324             *posn += 4;
325           } else {
326             warning ("%s: \\0 cannot appear in string literal", hval->name);
327             return NULL;
328           }
329           break;
330         } else {
331           warning ("%s: invalid octal sequence in string literal", hval->name);
332           return NULL;
333         }
334       case 'x':
335         if (isxdigit (str[*posn+2]) && isxdigit (str[*posn+3])) {
336           c = hexval (str[*posn+2]) * 0x10;
337           c += hexval (str[*posn+3]);
338           if (c != 0) {
339             APPEND (c);
340             *posn += 4;
341           } else {
342             warning ("%s: \\0 cannot appear in string literal", hval->name);
343             return NULL;
344           }
345           break;
346         } else {
347           warning ("%s: invalid hex sequence in string literal", hval->name);
348           return NULL;
349         }
350       case 'a':
351         APPEND ('\a');
352         *posn += 2;
353         break;
354       case 'b':
355         APPEND ('\b');
356         *posn += 2;
357         break;
358       case 'f':
359         APPEND ('\f');
360         *posn += 2;
361         break;
362       case 'n':
363         APPEND ('\n');
364         *posn += 2;
365         break;
366       case 'r':
367         APPEND ('\r');
368         *posn += 2;
369         break;
370       case 't':
371         APPEND ('\t');
372         *posn += 2;
373         break;
374       case 'v':
375         APPEND ('\v');
376         *posn += 2;
377         break;
378       case '"':
379         APPEND ('"');
380         *posn += 2;
381         break;
382       case '\\':
383         APPEND ('\\');
384         *posn += 2;
385         break;
386
387       default:
388         warning ("%s: unterminated escape sequence in string literal",
389                  hval->name);
390         return NULL;
391       }
392     } else if (str[*posn] == '\0') { /* Unterminated string literal. */
393       warning ("%s: unterminated string literal in request", hval->name);
394       return NULL;
395     } else {                    /* Ordinary character. */
396       APPEND (str[*posn]);
397       (*posn)++;
398     }
399   }
400
401   /* Finish off the string and return it. */
402   APPEND ('\0');
403   (*posn)++;                    /* Skips over the final quote. */
404
405   return r->elts;
406 }
407
408 static int
409 parse_long (struct guest_description *hval,
410             const char *str, size_t *posn, long *ret)
411 {
412   error ("XXXXXXX parse_long not implemented XXXXXXX");
413   return -1;
414 }
415
416 /* For single line replies. */
417 void
418 send_reply (struct guest_description *hval, int code, const char *fs, ...)
419 {
420   va_list args;
421   char *msg;
422
423   /* All success codes must be 2xx. */
424   assert (code >= 200 && code < 300);
425
426   va_start (args, fs);
427   msg = apr_pvsprintf (hval->rpool, fs, args);
428   va_end (args);
429
430   /* The result string must not contain any CR or LF characters.  If
431    * not it's an internal error in the command, or else the caller has
432    * (somehow) managed to pass a bad string through.
433    */
434   if (contains_crlf (msg)) {
435     error ("%s: send_reply: refusing the send a reply message containing CR/LF characters.  This is a serious internal error in the current command.",
436            hval->name);
437     send_error (hval, 500);
438     return;
439   }
440
441   /* Send reply. */
442   hval->reply =
443     apr_psprintf (hval->rpool, PROTOCOL_VERSION " %03d %s" CRLF,
444                   code, msg);
445   hval->reply_size = strlen (hval->reply);
446   hval->reply_posn = 0;
447   hval->state = guest_state_reply;
448 }
449
450 void
451 send_error (struct guest_description *hval, int code)
452 {
453   const char *msg;
454
455   /* All errors must be 4xx or 5xx. */
456   assert (code >= 400 && code < 600);
457
458   /* NB: If you add a code, update COMMON STATUS CODES section
459    * in hostinfo-protocol.pod.
460    */
461   switch (code) {
462   case 400: msg = "Bad request"; break;
463   case 401: msg = "Command disabled"; break;
464   case 404: msg = "Command not found"; break;
465   case 406: msg = "Too frequent"; break;
466   case 500: msg = "Internal server error"; break;
467   default:  msg = "Unknown error"; break;
468   }
469
470   /* Construct the reply. */
471   hval->reply =
472     apr_psprintf (hval->rpool, PROTOCOL_VERSION " %03d %s" CRLF,
473                   code, msg);
474   hval->reply_size = strlen (hval->reply);
475   hval->reply_posn = 0;
476   hval->state = guest_state_reply;
477
478   /* Penalty is always increased on errors, to ensure the guest
479    * cannot flood us with invalid commands.
480    */
481   hval->penalty++;
482 }
483
484 static const char *
485 string_of_arg_type (enum arg_type t)
486 {
487   switch (t) {
488   case arg_type_string: return "string";
489   case arg_type_int:    return "int";
490   case arg_type_bool:   return "bool";
491   }
492   abort ();
493 }
494
495 int
496 get_args (apr_array_header_t *args, const char *argfs, ...)
497 {
498   va_list vargs;
499   int i = 0, ret = 0;
500
501   va_start (vargs, argfs);
502
503   while (*argfs) {
504     struct arg arg;
505
506     if (i >= args->nelts) {
507       ret = -1;
508       goto end;
509     }
510
511     arg = APR_ARRAY_IDX (args, i, struct arg);
512
513     switch (*argfs) {
514     case 's':
515       if (arg.type == arg_type_string)
516         * va_arg (vargs, const char **) = arg.u.str;
517       else {
518         ret = -1;
519         goto end;
520       }
521       break;
522
523     case 'i':
524       if (arg.type == arg_type_int)
525         * va_arg (vargs, int *) = arg.u.i;
526       else {
527         ret = -1;
528         goto end;
529       }
530       break;
531
532     case 'b':
533       if (arg.type == arg_type_bool)
534         * va_arg (vargs, int *) = arg.u.i;
535       else {
536         ret = -1;
537         goto end;
538       }
539       break;
540
541     default:
542       error ("get_args: invalid character '%c'", *argfs);
543       ret = -1;
544       goto end;
545     }
546
547     argfs++;
548     i++;
549   }
550
551   if (i < args->nelts)
552     ret = -1;
553
554  end:
555   va_end (vargs);
556
557   return ret;
558 }