4b7f13729900b6dd09f45aaf05d700600ede19f6
[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
12 #include "common.h"
13 #include "sensor.h"
14 #include "control.h"
15 #include "options.h"
16
17 /**The time period (in seconds) before the control key expires @ */
18 #define CONTROL_TIMEOUT 180
19
20 /**Contextual information related to FCGI requests*/
21 struct FCGIContext {
22         /**The time of last valid user access possessing the control key*/
23         time_t control_timestamp;
24         char control_key[41];
25         char control_ip[16];
26         /**The name of the current module**/
27         const char *current_module;
28         /**For debugging purposes?**/
29         int response_number;
30 };
31
32 /**
33  * Identifies current version info. Useful for testing that the API is running.
34  * TODO - Consider adding info about available sensors and actuators (eg capabilities)?
35  */ 
36 static void IdentifyHandler(FCGIContext *context, char *params) {
37         bool identSensors = false, identActuators = false;
38         const char *key, *value;
39         int i;
40
41         while ((params = FCGI_KeyPair(params, &key, &value))) {
42                 if (!strcmp(key, "sensors")) {
43                         identSensors = !identSensors;
44                 } else if (!strcmp(key, "actuators")) {
45                         identActuators = !identActuators;
46                 }
47         }
48
49         FCGI_BeginJSON(context, STATUS_OK);
50         FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
51         FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
52         if (identSensors) {
53                 FCGI_JSONKey("sensors");
54                 FCGI_JSONValue("{\n\t\t");
55                 for (i = 0; i < NUMSENSORS; i++) {
56                         if (i > 0) {
57                                 FCGI_JSONValue(",\n\t\t");
58                         }
59                         FCGI_JSONValue("\"%d\" : \"%s\"", i, g_sensor_names[i]); 
60                 }
61                 FCGI_JSONValue("\n\t}");
62         }
63         if (identActuators) {
64                 FCGI_JSONKey("actuators");
65                 FCGI_JSONValue("{\n\t\t");
66                 for (i = 0; i < NUMACTUATORS; i++) {
67                         if (i > 0) {
68                                 FCGI_JSONValue(",\n\t\t");
69                         }
70                         FCGI_JSONValue("\"%d\" : \"%s\"", i, g_actuator_names[i]); 
71                 }
72                 FCGI_JSONValue("\n\t}");
73         }
74         FCGI_EndJSON();
75 }
76
77 /**
78  * Gives the user a key that determines who has control over
79  * the system at any one time. The key can be forcibly generated, revoking
80  * any previous control keys. To be used in conjunction with HTTP 
81  * basic authentication.
82  * This function will generate a JSON response that indicates success/failure.
83  * @param context The context to work in
84  * @param force Whether to force key generation or not.
85  */ 
86 void FCGI_BeginControl(FCGIContext *context, bool force) {
87         time_t now = time(NULL);
88         bool expired = now - context->control_timestamp > CONTROL_TIMEOUT;
89         
90         if (force || !*(context->control_key) || expired) {
91                 SHA_CTX sha1ctx;
92                 unsigned char sha1[20];
93                 int i = rand();
94
95                 SHA1_Init(&sha1ctx);
96                 SHA1_Update(&sha1ctx, &now, sizeof(now));
97                 SHA1_Update(&sha1ctx, &i, sizeof(i));
98                 SHA1_Final(sha1, &sha1ctx);
99
100                 context->control_timestamp = now;
101                 for (i = 0; i < 20; i++)
102                         sprintf(context->control_key + i * 2, "%02x", sha1[i]);
103                 snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR"));
104                 FCGI_BeginJSON(context, STATUS_OK);
105                 FCGI_JSONPair("key", context->control_key);
106                 FCGI_EndJSON();         
107         } else {
108                 char buf[128];
109                 strftime(buf, 128, "%H:%M:%S %d-%m-%Y",
110                         localtime(&(context->control_timestamp))); 
111                 FCGI_BeginJSON(context, STATUS_UNAUTHORIZED);
112                 FCGI_JSONPair("description", "Another user already has control");
113                 FCGI_JSONPair("current_user", context->control_ip); 
114                 FCGI_JSONPair("when", buf);
115                 FCGI_EndJSON();
116         }
117 }
118
119 /**
120  * Given an FCGIContext, determines if the current user (as specified by
121  * the key) has control or not. If validated, the context control_timestamp is
122  * updated.
123  * @param context The context to work in
124  * @param key The control key to be validated.
125  * @return TRUE if authorized, FALSE if not.
126  */
127 bool FCGI_HasControl(FCGIContext *context, const char *key) {
128         time_t now = time(NULL);
129         int result = (now - context->control_timestamp) <= CONTROL_TIMEOUT &&
130                                  key != NULL && !strcmp(context->control_key, key);
131         if (result) {
132                 context->control_timestamp = now; //Update the control_timestamp
133         }
134         return result;
135 }
136
137
138 /**
139  * Revokes the current control key, if present.
140  * @param context The context to work in
141  */
142 void FCGI_EndControl(FCGIContext *context) {
143         *(context->control_key) = 0;
144         FCGI_BeginJSON(context, STATUS_OK);
145         FCGI_EndJSON();
146         return;
147 }
148
149 /**
150  * Extracts a key/value pair from a request string.
151  * Note that the input is modified by this function.
152  * @param in The string from which to extract the pair
153  * @param key A pointer to a variable to hold the key string
154  * @param value A pointer to a variable to hold the value string
155  * @return A pointer to the start of the next search location, or NULL if
156  *         the EOL is reached.
157  */
158 char *FCGI_KeyPair(char *in, const char **key, const char **value)
159 {
160         char *ptr;
161         if (!in || !*in) { //Invalid input or string is EOL
162                 return NULL;
163         }
164
165         *key = in;
166         //Find either = or &, whichever comes first
167         if ((ptr = strpbrk(in, "=&"))) {
168                 if (*ptr == '&') { //No value specified
169                         *value = ptr;
170                         *ptr++ = 0;
171                 } else {
172                         //Stopped at an '=' sign
173                         *ptr++ = 0;
174                         *value = ptr;
175                         if ((ptr = strchr(ptr,'&'))) {
176                                 *ptr++ = 0;
177                         } else {
178                                 ptr = "";
179                         }
180                 }
181         } else { //No value specified and no other pair
182                 ptr = "";
183                 *value = ptr;
184         }
185         return ptr;
186 }
187
188 /**
189  * Begins a response to the client in JSON format.
190  * @param context The context to work in.
191  * @param status_code The status code to be returned.
192  */
193 void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
194 {
195         printf("Content-type: application/json; charset=utf-8\r\n\r\n");
196         printf("{\r\n");
197         printf("\t\"module\" : \"%s\"", context->current_module);
198         FCGI_JSONLong("status", status_code);
199
200         // Jeremy: Should we include a timestamp in the JSON; something like this?
201         double start_time = g_options.start_time.tv_sec + 1e-6*(g_options.start_time.tv_usec);
202         struct timeval now;
203         gettimeofday(&now, NULL);
204         double current_time = now.tv_sec + 1e-6*(now.tv_usec);
205         FCGI_JSONDouble("start_time", start_time);
206         FCGI_JSONDouble("current_time", current_time);
207         FCGI_JSONDouble("running_time", current_time - start_time);
208         
209 }
210
211 /**
212  * Adds a key/value pair to a JSON response. The response must have already
213  * been initiated by FCGI_BeginJSON. Note that characters are not escaped.
214  * @param key The key of the JSON entry
215  * @param value The value associated with the key.
216  */
217 void FCGI_JSONPair(const char *key, const char *value)
218 {
219         printf(",\r\n\t\"%s\" : \"%s\"", key, value);
220 }
221
222 /**
223  * Similar to FCGI_JSONPair except for signed integer values.
224  * @param key The key of the JSON entry
225  * @param value The value associated with the key
226  */
227 void FCGI_JSONLong(const char *key, long value)
228 {
229         printf(",\r\n\t\"%s\" : %ld", key, value);
230 }
231
232 /**
233  * Similar to FCGI_JsonPair except for floating point values.
234  * @param key The key of the JSON entry
235  * @param value The value associated with the key
236  */
237 void FCGI_JSONDouble(const char *key, double value)
238 {
239         printf(",\r\n\t\"%s\" : %f", key, value);
240 }
241
242 /**
243  * Similar to FCGI_JsonPair except for boolean values.
244  * @param key The key of the JSON entry
245  * @param value The value associated with the key
246  */
247 void FCGI_JSONBool(const char *key, bool value)
248 {
249         printf(",\r\n\t\"%s\" : %s", key, value ? "true" : "false");
250 }
251
252 /**
253  * Begins a JSON entry by writing the key. To be used in conjunction
254  * with FCGI_JsonValue.
255  * @param key The key of the JSON entry
256  */
257 void FCGI_JSONKey(const char *key)
258 {
259         printf(",\r\n\t\"%s\" : ", key);
260 }
261
262 /**
263  * Ends a JSON response that was initiated by FCGI_BeginJSON.
264  */
265 void FCGI_EndJSON() 
266 {
267         printf("\r\n}\r\n");
268 }
269
270 /**
271  * To be used when the input parameters are invalid. The return data will
272  * have a status of STATUS_ERROR, along with other debugging information.
273  * @param context The context to work in
274  */
275 void FCGI_RejectJSON(FCGIContext *context)
276 {
277         FCGI_RejectJSONEx(context, STATUS_ERROR, "Invalid request");
278 }
279
280 /**
281  * To be used when the input parameters are rejected. The return data
282  * will also have debugging information provided.
283  * @param context The context to work in
284  * @param status The status the return data should have.
285  * @param description A short description of why the input was rejected.
286  */
287 void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description)
288 {
289         if (description == NULL)
290                 description = "Unknown";
291         
292         Log(LOGINFO, "%s: Rejected query with: %d: %s", context->current_module, status, description);
293         FCGI_BeginJSON(context, status);
294         FCGI_JSONPair("description", description);
295         FCGI_JSONLong("responsenumber", context->response_number);
296         FCGI_JSONPair("params", getenv("QUERY_STRING"));
297         FCGI_JSONPair("host", getenv("SERVER_HOSTNAME"));
298         FCGI_JSONPair("user", getenv("REMOTE_USER"));
299         FCGI_JSONPair("ip", getenv("REMOTE_ADDR"));
300         FCGI_EndJSON();
301 }
302
303 /**
304  * Generates a response to the client as described by the format parameter and
305  * extra arguments (exactly like printf). To be used when none of the other
306  * predefined functions will work exactly as needed. Extra care should be taken
307  * to ensure the correctness of the output.
308  * @param format The format string
309  * @param ... Any extra arguments as required by the format string.
310  */
311 void FCGI_PrintRaw(const char *format, ...)
312 {
313         va_list list;
314         va_start(list, format);
315         vprintf(format, list);
316         va_end(list);
317 }
318
319 /**
320  * Main FCGI request loop that receives/responds to client requests.
321  * @param data Reserved.
322  * @returns NULL (void* required for consistency with pthreads, although at the moment this runs in the main thread anyway)
323  * TODO: Get this to exit with the rest of the program!
324  */ 
325 void * FCGI_RequestLoop (void *data)
326 {
327         FCGIContext context = {0};
328         
329         Log(LOGDEBUG, "First request...");
330         //TODO: The FCGI_Accept here is blocking. 
331         //              That means that if another thread terminates the program, this thread
332         //               will not terminate until the next request is made.
333         while (FCGI_Accept() >= 0) {
334
335                 if (Thread_Runstate() != RUNNING)
336                 {
337                         //TODO: Yeah... deal with this better :P
338                         Log(LOGERR, "FIXME; FCGI gets request after other threads have finished.");
339                         printf("Content-type: text/plain\r\n\r\n+++OUT OF CHEESE ERROR+++\n");
340                         break;
341                 }
342                 
343                 Log(LOGDEBUG, "Got request #%d", context.response_number);
344                 ModuleHandler module_handler = NULL;
345                 char module[BUFSIZ], params[BUFSIZ];
346                 
347                 //strncpy doesn't zero-truncate properly
348                 snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
349                 snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
350                 
351                 //Remove trailing slashes (if present) from module query
352                 size_t lastchar = strlen(module) - 1;
353                 if (lastchar > 0 && module[lastchar] == '/')
354                         module[lastchar] = 0;
355                 
356                 if (!*module || !strcmp("identify", module)) {
357                         module_handler = IdentifyHandler;
358                 } else if (!strcmp("control", module)) {
359                         module_handler = Control_Handler;
360                 } else if (!strcmp("sensors", module)) {
361                         module_handler = Sensor_Handler;
362                 }
363
364                 context.current_module = module;
365                 if (module_handler) {
366                         module_handler(&context, params);
367                 } else {
368                         strncat(module, " (unhandled)", BUFSIZ);
369                         FCGI_RejectJSON(&context);
370                 }
371                 context.response_number++;
372
373                 Log(LOGDEBUG, "Waiting for request #%d", context.response_number);
374         }
375
376         Log(LOGDEBUG, "Thread exiting.");
377         Thread_QuitProgram(false);
378         // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return.
379         return NULL;
380 }

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