Merge branch 'master' of https://github.com/szmoore/MCTX3420.git
[matches/MCTX3420.git] / server / fastcgi.c
1 /**
2  * @file fastcgi.c
3  * @brief Runs the FCGI request loop to handle web interface requests.
4  *
5  * fcgi_stdio.h must be included before all else so the stdio function
6  * redirection works ok.
7  */
8
9 #include <fcgi_stdio.h>
10 #include <openssl/sha.h>
11 #include <stdarg.h>
12
13 #include "common.h"
14 #include "sensor.h"
15 #include "actuator.h"
16 #include "control.h"
17 #include "options.h"
18 #include "image.h"
19 #include "pin_test.h"
20 #include "login.h"
21
22 /**The time period (in seconds) before the control key expires */
23 #define CONTROL_TIMEOUT 180
24
25
26
27 /**
28  * Identifies build information and the current API version to the user.
29  * Also useful for testing that the API is running and identifying the 
30  * sensors and actuators present.
31  * @param context The context to work in
32  * @param params User specified paramters: [actuators, sensors]
33  */ 
34 static void IdentifyHandler(FCGIContext *context, char *params) {
35         bool ident_sensors = false, ident_actuators = false;
36
37         int i;
38
39         FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
40                                          {"actuators", &ident_actuators, FCGI_BOOL_T}};
41         if (!FCGI_ParseRequest(context, params, values, 2))
42                 return;
43
44         FCGI_BeginJSON(context, STATUS_OK);
45         FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
46         FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
47         FCGI_JSONLong("api_version", API_VERSION);
48
49         //Sensor and actuator information
50         if (ident_sensors) {
51                 FCGI_JSONKey("sensors");
52                 FCGI_JSONValue("{\n\t\t");
53                 for (i = 0; i < NUMSENSORS; i++) {
54                         if (i > 0) {
55                                 FCGI_JSONValue(",\n\t\t");
56                         }
57                         FCGI_JSONValue("\"%d\" : \"%s\"", i, g_sensor_names[i]); 
58                 }
59                 FCGI_JSONValue("\n\t}");
60         }
61         if (ident_actuators) {
62                 FCGI_JSONKey("actuators");
63                 FCGI_JSONValue("{\n\t\t");
64                 for (i = 0; i < NUMACTUATORS; i++) {
65                         if (i > 0) {
66                                 FCGI_JSONValue(",\n\t\t");
67                         }
68                         FCGI_JSONValue("\"%d\" : \"%s\"", i, g_actuator_names[i]); 
69                 }
70                 FCGI_JSONValue("\n\t}");
71         }
72         FCGI_EndJSON();
73 }
74
75 /**
76  * Gives the user a key that determines who has control over
77  * the system at any one time. The key can be forcibly generated, revoking
78  * any previous control keys. To be used in conjunction with HTTP 
79  * basic authentication.
80  * This function will generate a JSON response that indicates success/failure.
81  * @param context The context to work in
82  * @param force Whether to force key generation or not.
83  */ 
84 void FCGI_LockControl(FCGIContext *context, bool force) {
85         time_t now = time(NULL);
86         bool expired = now - context->control_timestamp > CONTROL_TIMEOUT;
87         
88         if (force || !*(context->control_key) || expired) 
89         {
90                 SHA_CTX sha1ctx;
91                 unsigned char sha1[20];
92                 int i = rand();
93
94                 SHA1_Init(&sha1ctx);
95                 SHA1_Update(&sha1ctx, &now, sizeof(now));
96                 SHA1_Update(&sha1ctx, &i, sizeof(i));
97                 SHA1_Final(sha1, &sha1ctx);
98
99                 context->control_timestamp = now;
100                 for (i = 0; i < 20; i++)
101                         sprintf(context->control_key + i * 2, "%02x", sha1[i]);
102                 snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR"));
103         }
104 }
105
106 /**
107  * Given an FCGIContext, determines if the current user (as specified by
108  * the key) has control or not. If validated, the context control_timestamp is
109  * updated.
110  * @param context The context to work in
111  * @param key The control key to be validated.
112  * @return TRUE if authorized, FALSE if not.
113  */
114 bool FCGI_HasControl(FCGIContext *context, const char *key) {
115         time_t now = time(NULL);
116         int result = (now - context->control_timestamp) <= CONTROL_TIMEOUT &&
117                                  key != NULL && !strcmp(context->control_key, key);
118         if (result) {
119                 context->control_timestamp = now; //Update the control_timestamp
120         }
121         return result;
122 }
123
124
125 /**
126  * Revokes the current control key, if present.
127  * @param context The context to work in
128  */
129 void FCGI_ReleaseControl(FCGIContext *context) {
130         *(context->control_key) = 0;
131         FCGI_BeginJSON(context, STATUS_OK);
132         FCGI_EndJSON();
133         return;
134 }
135
136 /**
137  * Extracts a key/value pair from a request string.
138  * Note that the input is modified by this function.
139  * @param in The string from which to extract the pair
140  * @param key A pointer to a variable to hold the key string
141  * @param value A pointer to a variable to hold the value string
142  * @return A pointer to the start of the next search location, or NULL if
143  *         the EOL is reached.
144  */
145 char *FCGI_KeyPair(char *in, const char **key, const char **value)
146 {
147         char *ptr;
148         if (!in || !*in) { //Invalid input or string is EOL
149                 return NULL;
150         }
151
152         *key = in;
153         //Find either = or &, whichever comes first
154         if ((ptr = strpbrk(in, "=&"))) {
155                 if (*ptr == '&') { //No value specified
156                         *value = ptr;
157                         *ptr++ = 0;
158                 } else {
159                         //Stopped at an '=' sign
160                         *ptr++ = 0;
161                         *value = ptr;
162                         if ((ptr = strchr(ptr,'&'))) {
163                                 *ptr++ = 0;
164                         } else {
165                                 ptr = "";
166                         }
167                 }
168         } else { //No value specified and no other pair
169                 ptr = "";
170                 *value = ptr;
171         }
172         return ptr;
173 }
174
175 /**
176  * Aids in parsing request parameters. 
177  * Input: The expected keys along with their type and whether or not
178  * they're required.
179  * @param context The context to work in
180  * @param params The parameter string to be parsed
181  * @param values An array of FCGIValue's that specify expected keys
182  * @param count The number of elements in 'values'.
183  * @return true If the parameter string was parsed successfully, false otherwise.
184  *         Modes of failure include: Invalid a parsing error on the value,
185  *                                   an unknown key is specified,
186  *                                   a key/value pair is specified more than once, or
187  *                                   not all required keys were present.
188  *         If this function returns false, it is guaranteed that FCGI_RejectJSON
189  *         has already been called with the appropriate description message.
190  */
191 bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count)
192 {
193         const char *key, *value;
194         char buf[BUFSIZ], *ptr;
195         size_t i;
196         
197         while ((params = FCGI_KeyPair(params, &key, &value))) {
198                 for (i = 0; i < count; i++) {
199                         if (!strcmp(key, values[i].key)) {
200                                 FCGIValue *val = &values[i];
201
202                                 if (FCGI_RECEIVED(val->flags)) {
203                                         snprintf(buf, BUFSIZ, "Value already specified for '%s'.", key);
204                                         FCGI_RejectJSON(context, buf);
205                                         return false;
206                                 }
207                                 val->flags |= FCGI_PARAM_RECEIVED;
208
209                                 switch(FCGI_TYPE(val->flags)) {
210                                         case FCGI_BOOL_T:
211                                                 if (!*value) //No value: Default true
212                                                         *((bool*) val->value) = true;
213                                                 else {
214                                                         *((bool*) val->value) = !!(strtol(value, &ptr, 10));
215                                                         if (*ptr) {
216                                                                 snprintf(buf, BUFSIZ, "Expected bool for '%s' but got '%s'", key, value);
217                                                                 FCGI_RejectJSON(context, buf);
218                                                                 return false;
219                                                         }
220                                                 }
221                                                 break;
222                                         case FCGI_INT_T: case FCGI_LONG_T: {
223                                                 long parsed = strtol(value, &ptr, 10);
224                                                 if (!*value || *ptr) {
225                                                         snprintf(buf, BUFSIZ, "Expected int for '%s' but got '%s'", key, value);
226                                                         FCGI_RejectJSON(context, buf);
227                                                         return false;
228                                                 }
229
230                                                 if (FCGI_TYPE(val->flags) == FCGI_INT_T)
231                                                         *((int*) val->value) = (int) parsed;
232                                                 else
233                                                         *((long*) val->value) = parsed;
234                                         }       break;
235                                         case FCGI_DOUBLE_T:
236                                                 *((double*) val->value) = strtod(value, &ptr);
237                                                 if (!*value || *ptr) {
238                                                         snprintf(buf, BUFSIZ, "Expected float for '%s' but got '%s'", key, value);
239                                                         FCGI_RejectJSON(context, buf);
240                                                         return false;
241                                                 }
242                                                 break;
243                                         case FCGI_STRING_T:
244                                                 *((const char**) val->value) = value;
245                                                 break;
246                                         default:
247                                                 Fatal("Invalid type %d given", FCGI_TYPE(val->flags));
248                                 }
249                                 break; //No need to search any more
250                         }
251                 } //End for loop
252                 if (i == count) {
253                         snprintf(buf, BUFSIZ, "Unknown key '%s' specified", key);
254                         FCGI_RejectJSON(context, buf);
255                         return false;
256                 }
257         }
258
259         //Check that required parameters are received
260         for (i = 0; i < count; i++) {
261                 if (FCGI_IS_REQUIRED(values[i].flags) && !FCGI_RECEIVED(values[i].flags)) {
262                         snprintf(buf, BUFSIZ, "Key '%s' required, but was not given.", values[i].key);
263                         FCGI_RejectJSON(context, buf);
264                         return false;
265                 }
266         }
267         return true;
268 }
269
270 /**
271  * Begins a response to the client in JSON format.
272  * @param context The context to work in.
273  * @param status_code The status code to be returned.
274  */
275 void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
276 {
277         printf("Content-type: application/json; charset=utf-8\r\n\r\n");
278         printf("{\r\n");
279         printf("\t\"module\" : \"%s\"", context->current_module);
280         FCGI_JSONLong("status", status_code);
281         //Time and running statistics
282         struct timeval now;
283         gettimeofday(&now, NULL);
284         FCGI_JSONDouble("start_time", TIMEVAL_TO_DOUBLE(g_options.start_time));
285         FCGI_JSONDouble("current_time", TIMEVAL_TO_DOUBLE(now));
286         FCGI_JSONDouble("running_time", TIMEVAL_DIFF(now, g_options.start_time));
287         FCGI_JSONPair("control_state", Control_GetModeName());
288 }
289
290 /**
291  * Adds a key/value pair to a JSON response. The response must have already
292  * been initiated by FCGI_BeginJSON. Special characters are not escaped.
293  * @param key The key of the JSON entry
294  * @param value The value associated with the key.
295  */
296 void FCGI_JSONPair(const char *key, const char *value)
297 {
298         printf(",\r\n\t\"%s\" : \"%s\"", key, value);
299 }
300
301 /**
302  * Similar to FCGI_JSONPair except for signed integer values.
303  * @param key The key of the JSON entry
304  * @param value The value associated with the key
305  */
306 void FCGI_JSONLong(const char *key, long value)
307 {
308         printf(",\r\n\t\"%s\" : %ld", key, value);
309 }
310
311 /**
312  * Similar to FCGI_JsonPair except for floating point values.
313  * @param key The key of the JSON entry
314  * @param value The value associated with the key
315  */
316 void FCGI_JSONDouble(const char *key, double value)
317 {
318         printf(",\r\n\t\"%s\" : %f", key, value);
319 }
320
321 /**
322  * Similar to FCGI_JsonPair except for boolean values.
323  * @param key The key of the JSON entry
324  * @param value The value associated with the key
325  */
326 void FCGI_JSONBool(const char *key, bool value)
327 {
328         printf(",\r\n\t\"%s\" : %s", key, value ? "true" : "false");
329 }
330
331 /**
332  * Begins a JSON entry by writing the key. To be used in conjunction
333  * with FCGI_JsonValue.
334  * @param key The key of the JSON entry
335  */
336 void FCGI_JSONKey(const char *key)
337 {
338         printf(",\r\n\t\"%s\" : ", key);
339 }
340
341 /**
342  * Ends a JSON response that was initiated by FCGI_BeginJSON.
343  */
344 void FCGI_EndJSON() 
345 {
346         printf("\r\n}\r\n");
347 }
348
349 /**
350  * To be used when the input parameters are rejected. The return data
351  * will also have debugging information provided.
352  * @param context The context to work in
353  * @param status The status the return data should have.
354  * @param description A short description of why the input was rejected.
355  */
356 void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description)
357 {
358         if (description == NULL)
359                 description = "Unknown";
360         
361         Log(LOGINFO, "%s: Rejected query with: %d: %s", context->current_module, status, description);
362         FCGI_BeginJSON(context, status);
363         FCGI_JSONPair("description", description);
364         FCGI_JSONLong("responsenumber", context->response_number);
365         //FCGI_JSONPair("params", getenv("QUERY_STRING"));
366         FCGI_JSONPair("host", getenv("SERVER_HOSTNAME"));
367         FCGI_JSONPair("user", getenv("REMOTE_USER"));
368         FCGI_JSONPair("ip", getenv("REMOTE_ADDR"));
369         FCGI_EndJSON();
370 }
371
372 /**
373  * Generates a response to the client as described by the format parameter and
374  * extra arguments (exactly like printf). To be used when none of the other
375  * predefined functions will work exactly as needed. Extra care should be taken
376  * to ensure the correctness of the output.
377  * @param format The format string
378  * @param ... Any extra arguments as required by the format string.
379  */
380 void FCGI_PrintRaw(const char *format, ...)
381 {
382         va_list list;
383         va_start(list, format);
384         vprintf(format, list);
385         va_end(list);
386 }
387
388
389 /**
390  * Write binary data
391  * See fwrite
392  */
393 void FCGI_WriteBinary(void * data, size_t size, size_t num_elem)
394 {
395         Log(LOGDEBUG,"Writing!");
396         fwrite(data, size, num_elem, stdout);
397 }
398
399 /**
400  * Escapes a string so it can be used safely.
401  * Currently escapes to ensure the validity for use as a JSON string
402  * Does not support unicode specifiers in the form of \uXXXX.
403  * @param buf The string to be escaped
404  * @return The escaped string (return value == buf)
405  */
406 char *FCGI_EscapeText(char *buf)
407 {
408         int length, i;
409         length = strlen(buf);
410         
411         //Escape special characters. Must count down to escape properly
412         for (i = length - 1; i >= 0; i--) {
413                 if (buf[i] < 0x20) { //Control characters
414                         buf[i] = ' ';
415                 } else if (buf[i] == '"') {
416                         if (i-1 >= 0 && buf[i-1] == '\\') 
417                                 i--;
418                         else
419                                 buf[i] = '\'';
420                 } else if (buf[i] == '\\') {
421                         if (i-1 >= 0 && buf[i-1] == '\'')
422                                 i--;
423                         else
424                                 buf[i] = ' ';
425                 }
426         }
427         return buf;
428 }
429
430 /**
431  * Main FCGI request loop that receives/responds to client requests.
432  * @param data Reserved.
433  * @returns NULL (void* required for consistency with pthreads, although at the moment this runs in the main thread anyway)
434  * TODO: Get this to exit with the rest of the program!
435  */ 
436 void * FCGI_RequestLoop (void *data)
437 {
438         FCGIContext context = {0};
439         
440         Log(LOGDEBUG, "Start loop");
441         while (FCGI_Accept() >= 0) {
442                 
443                 ModuleHandler module_handler = NULL;
444                 char module[BUFSIZ], params[BUFSIZ], cookie[BUFSIZ];
445                 
446                 //strncpy doesn't zero-truncate properly
447                 snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
448                 snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
449                 snprintf(cookie, BUFSIZ, "%s", getenv("COOKIE_STRING"));
450
451                 Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params);
452                 Log(LOGDEBUG, "Cookie: %s", cookie);
453
454
455                 
456                 //Remove trailing slashes (if present) from module query
457                 size_t lastchar = strlen(module) - 1;
458                 if (lastchar > 0 && module[lastchar] == '/')
459                         module[lastchar] = 0;
460
461                 //Escape all special characters
462                 FCGI_EscapeText(params);
463
464                 //Default to the 'identify' module if none specified
465                 if (!*module) 
466                         strcpy(module, "identify");
467                 
468                 if (!strcmp("identify", module)) {
469                         module_handler = IdentifyHandler;
470                 } else if (!strcmp("control", module)) {
471                         module_handler = Control_Handler;
472                 } else if (!strcmp("sensors", module)) {
473                         module_handler = Sensor_Handler;
474                 } else if (!strcmp("actuators", module)) {
475                         module_handler = Actuator_Handler;
476                 } else if (!strcmp("image", module)) {
477                         module_handler = Image_Handler;
478                 } else if (!strcmp("pin", module)) { 
479                         module_handler = Pin_Handler; // *Debug only* pin test module
480                 } else if (!strcmp("bind", module)) {
481                         module_handler = Login_Handler;
482                 } else if (!strcmp("unbind", module)) {
483                         module_handler = Logout_Handler;
484                 }
485
486                 context.current_module = module;
487                 context.response_number++;
488                 
489
490
491                 if (module_handler) 
492                 {
493                         if (module_handler != Login_Handler)
494                         {
495                                 if (cookie[0] == '\0')
496                                 {
497                                         FCGI_RejectJSON(&context, "Please login.");
498                                         continue;
499                                 }
500                                 if (!FCGI_HasControl(&context, cookie))
501                                 {
502                                         FCGI_RejectJSON(&context, "Invalid control key.");
503                                         continue;       
504                                 }
505                         }
506
507                         module_handler(&context, params);
508                 } 
509                 else 
510                 {
511                         FCGI_RejectJSON(&context, "Unhandled module");
512                 }
513                 
514
515                 
516         }
517
518         Log(LOGDEBUG, "Thread exiting.");
519         // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return.
520         return NULL;
521 }

UCC git Repository :: git.ucc.asn.au