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