708b4a88233dc180e88420de893f5672904c0f7a
[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 <assert.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include <time.h>
34
35 #include "hostinfod.h"
36
37 #define PROTOCOL_VERSION "1.0"
38 #define CRLF "\r\n"
39
40 apr_hash_t *commands = NULL;
41
42 static const char *string_of_arg_type (enum arg_type);
43 static void init_commands_hash (void);
44 static int not_printable (const char *str);
45
46 void
47 execute_command (const struct timespec *now,
48                  struct guest_description *hval, const char *command)
49 {
50   size_t len, i, j;
51   int neg;
52   char *cmd;
53   apr_array_header_t args;
54   struct arg arg;
55   command_fn fn;
56
57   debug ("%s: %s", hval->name, command);
58
59   /* Create a new pool for allocation during the lifetime of the
60    * request/response.  **NB** the hval->reply field is allocated
61    * from this pool, which is why it gets nulled below.
62    */
63   if (hval->rpool)
64     apr_pool_destroy (hval->rpool);
65   hval->rpool = NULL;
66   hval->reply = NULL;
67   hval->reply_size = hval->reply_posn = 0;
68   apr_pool_create (&hval->rpool, hval->pool);
69
70   /* Split up the command.  Commands have a very narrowly-defined
71    * format, and we reject any malformed commands with a 400 error.
72    *
73    * NB. A lot of the code below assumes 7 bit, printable ASCII,
74    * and this is only safe because of the call to 'not_printable'
75    * here.
76    */
77   len = strlen (command);
78   if (len < 1 || len > 4094 || not_printable (command)) {
79     warning ("%s: command too short, too long or contained unprintable chars",
80              hval->name);
81     send_error (hval, 400);
82     return;
83   }
84
85   /* Command is alphanumeric string, non-zero length. */
86   i = strspn (command,
87               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
88               "abcdefghijklmnopqrstuvwxyz"
89               "0123456789");
90   if (i < 1) {
91     warning ("%s: no command part in request", hval->name);
92     send_error (hval, 400);
93     return;
94   }
95
96   cmd = apr_pstrmemdup (hval->rpool, command, i);
97   for (j = 0; j < i; ++j)
98     cmd[j] = tolower (cmd[j]);
99
100   args = apr_array_make (hval->rpool, 0, sizeof (struct arg));
101
102   while (command[i]) {  /* command[i] is the space before the next arg */
103     if (command[i] != ' ') {
104       warning ("%s: there must be a single space between command and each argument",
105                hval->name);
106       send_error (hval, 400);
107       return;
108     }
109
110     neg = 0;
111
112     i++;
113     switch (command[i]) {
114     case '\0':
115       warning ("%s: trailing space after command", hval->name);
116       send_error (hval, 400);
117       return;
118
119     case '"':                   /* string literal */
120       arg.type = arg_type_string;
121       XXX;
122       break;
123
124     case '-':                   /* integer literal */
125       neg = 1;
126     case '0':
127     case '1':
128     case '2':
129     case '3':
130     case '4':
131     case '5':
132     case '6':
133     case '7':
134     case '8':
135     case '9':
136       arg.type = arg_type_int;
137       XXX ();
138       break;
139
140     case 't':                   /* boolean */
141     case 'f':
142     case 'T':
143     case 'F':
144       arg.type = arg_type_bool;
145       if (strncasecmp (&command[i], "true", 4) == 0) {
146         arg.u.i = 1;
147         i += 4;
148       } else if (strncasecmp (&command[i], "false", 5) == 0) {
149         arg.u.i = 0;
150         i += 4;
151       } else
152         goto unknown_arg;
153       break;
154
155     default:
156     unknown_arg:
157       warning ("%s: unknown or malformed argument starting at position %d ('%c')",
158                hval->name, i, command[i]);
159       send_error (hval, 400);
160       return;
161     } /* switch */
162
163     APR_ARRAY_PUSH (args, struct arg) = *arg;
164   }
165
166   if (verbose) {
167     /* Debug output what the guest sent / what we decoded. */
168     debug ("%s: command '%s' with %d args", hval->name, cmd, args->nelts);
169     for (i = 0; i < args->nelts; ++i) {
170       struct arg arg = APR_ARRAY_IDX (args, i, struct arg);
171
172       switch (arg.type) {
173       case arg_type_bool:
174       case arg_type_int:
175         debug ("%s: arg %d : %s = %ld",
176                hval->name, i, string_of_arg_type (arg.type), arg.u.i);
177         break;
178       case arg_type_string:
179         debug ("%s: arg %d : %s = %s",
180                hval->name, i, string_of_arg_type (arg.type), arg.u.str);
181         break;
182       }
183     }
184   }
185
186   /* Know about this command? */
187   fn = apr_hash_get (commands, cmd, APR_HASH_KEY_STRING);
188   if (!fn) {
189     send_error (hval, 404);
190     return;
191   }
192
193   /* Before dispatching the command, check the command is enabled
194    * and guest is not calling it too frequently.
195    */
196   error ("XXXXXXX enabled check not implemented XXXXXXX");
197   error ("XXXXXXX frequency check not implemented XXXXXXX");
198
199   /* Dispatch the command. */
200   fn (hval, cmd, args);
201 }
202
203 /* All commands must consist only of printable 7 bit ASCII.
204  * NB. Don't use isprint(3).
205  */
206 static int
207 not_printable (const char *str)
208 {
209   int c;
210
211   while ((c = *(unsigned char *)str)) {
212     if (c < 32 || c > 126)
213       return 0;
214     str++;
215   }
216   return 1;
217 }
218
219 /* For single line replies. */
220 void
221 send_reply (struct guest_description *hval, int code, const char *fs, ...)
222 {
223   const char *msg;
224
225   /* All success codes must be 2xx. */
226   assert (code >= 200 && code < 300);
227
228   XXX;
229 }
230
231 void
232 send_error (struct guest_description *hval, int code)
233 {
234   const char *msg;
235
236   /* All errors must be 4xx or 5xx. */
237   assert (code >= 400 && code < 600);
238
239   /* NB: If you add a code, update COMMON STATUS CODES section
240    * in hostinfo-protocol.pod.
241    */
242   switch (code) {
243   case 400: msg = "Bad request"; break;
244   case 401: msg = "Command disabled"; break;
245   case 404: msg = "Command not found"; break;
246   case 406: msg = "Too frequent"; break;
247   case 500: msg = "Internal server error"; break;
248   default:  msg = "Unknown error"; break;
249   }
250
251   /* Construct the reply. */
252   hval->reply =
253     apr_psprintf (hval->rpool, PROTOCOL_VERSION " %03d %s" CRLF,
254                   code, msg);
255   hval->reply_size = strlen (hval->reply);
256   hval->reply_posn = 0;
257   hval->state = guest_state_reply;
258
259   /* Penalty is always increased on errors, to ensure the guest
260    * cannot flood us with invalid commands.
261    */
262   hval->penalty++;
263 }