Fix SensorHandler while loop
[matches/MCTX3420.git] / server / fastcgi.c
1 /**
2  * @file fastcgi.c
3  * @purpose 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 "fastcgi.h"
12 #include "common.h"
13 #include "sensor.h"
14 #include "log.h"
15 #include <time.h>
16
17 static void LoginHandler(void *data, char *params) {
18         static char loginkey[41] = {0}, ip[256];
19         static time_t timestamp = 0;
20         const char *key, *value;
21         int force = 0, end = 0;
22
23         while ((params = FCGI_KeyPair(params, &key, &value))) {
24                 if (!strcmp(key, "force"))
25                         force = !force;
26                 else if (!strcmp(key, "end"))
27                         end = !end;
28         }
29
30         if (end) {
31                 *loginkey = 0;
32                 FCGI_BeginJSON(200, "login");
33                 FCGI_EndJSON();
34                 return;
35         }
36
37         time_t now = time(NULL);
38         if (force || !*loginkey || (now - timestamp > 180)) {
39                 SHA_CTX sha1ctx;
40                 unsigned char sha1[20];
41                 int i = rand();
42
43                 SHA1_Init(&sha1ctx);
44                 SHA1_Update(&sha1ctx, &now, sizeof(now));
45                 SHA1_Update(&sha1ctx, &i, sizeof(i));
46                 SHA1_Final(sha1, &sha1ctx);
47
48                 timestamp = now;
49                 for (i = 0; i < 20; i++)
50                         sprintf(loginkey+i*2, "%02x", sha1[i]);
51                 sprintf(ip, "%s", getenv("REMOTE_ADDR"));
52                 FCGI_BeginJSON(200, "login");
53                 FCGI_BuildJSON("key", loginkey);
54                 FCGI_EndJSON();
55         } else {
56                 char buf[128];
57                 strftime(buf, 128, "%H:%M:%S %d-%m-%Y",localtime(&timestamp)); 
58                 FCGI_BeginJSON(401, "login");
59                 FCGI_BuildJSON("description", "Already logged in");
60                 FCGI_BuildJSON("user", ip); 
61                 FCGI_BuildJSON("time", buf);
62                 FCGI_EndJSON();
63         }
64 }
65
66 /**
67  * Handle a request to the sensor module
68  * @param data - Data to pass to module (?)
69  * @param params - Parameters passed
70  */
71 static void SensorHandler(void * data, char * params)
72 {
73         static DataPoint buffer[SENSOR_QUERYBUFSIZ];
74         StatusCodes status = STATUS_OK;
75         const char * key; const char * value;
76
77         int sensor_id = SENSOR_NONE;
78
79         while ((params = FCGI_KeyPair(params, &key, &value)) != NULL)
80         {
81                 Log(LOGDEBUG, "Got key=%s and value=%s", key, value);
82                 if (strcmp(key, "id") == 0)
83                 {
84                         if (sensor_id != SENSOR_NONE)
85                         {
86                                 Log(LOGERR, "Only one sensor id should be specified");
87                                 status = STATUS_BADREQUEST;
88                                 break;
89                         }
90                         //TODO: Use human readable sensor identifier string for API?
91                         sensor_id = atoi(value);
92                         if (sensor_id == 0 && strcmp(value, "0") != 0)
93                         {
94                                 Log(LOGERR, "Sensor id not an integer; %s", value);
95                                 status = STATUS_BADREQUEST;
96                                 break;
97                         }
98                 }
99                 else
100                 {
101                         Log(LOGERR, "Unknown key \"%s\" (value = %s)", key, value);
102                         status = STATUS_BADREQUEST;
103                         break;
104                 }               
105         }
106
107         if (sensor_id == SENSOR_NONE)
108         {
109                 Log(LOGERR, "No sensor id specified");
110                 status = STATUS_BADREQUEST;
111         }
112         else if (sensor_id >= NUMSENSORS || sensor_id < 0)
113         {
114                 Log(LOGERR, "Invalid sensor id %d", sensor_id);
115                 status = STATUS_BADREQUEST;
116         }
117
118         FCGI_BeginJSON(status, "sensor");
119         
120         if (status != STATUS_BADREQUEST)
121         {
122                 FCGI_BuildJSON(key, value); // should spit back sensor ID
123                 //Log(LOGDEBUG, "Call Sensor_Query...");
124                 int amount_read = Sensor_Query(&(g_sensors[sensor_id]), buffer, SENSOR_QUERYBUFSIZ);
125                 //Log(LOGDEBUG, "Read %d DataPoints", amount_read);
126                 //Log(LOGDEBUG, "Produce JSON response");
127                 printf(",\r\n\t\"data\" : [");
128                 for (int i = 0; i < amount_read; ++i)
129                 {
130                         printf("[%f,%f]", buffer[i].time, buffer[i].value);
131                         if (i+1 < amount_read)
132                                 printf(",");
133                 }
134                 printf("]");
135                 //Log(LOGDEBUG, "Done producing JSON response");
136         }
137         FCGI_EndJSON();         
138         
139 }
140
141 /**
142  * Extracts a key/value pair from a request string.
143  * Note that the input is modified by this function.
144  * @param in The string from which to extract the pair
145  * @param key A pointer to a variable to hold the key string
146  * @param value A pointer to a variable to hold the value string
147  * @return A pointer to the start of the next search location, or NULL if
148  *         the EOL is reached.
149  */
150 char *FCGI_KeyPair(char *in, const char **key, const char **value)
151 {
152         char *ptr;
153         if (!in || !*in) { //Invalid input or string is EOL
154                 return NULL;
155         }
156
157         *key = in;
158         //Find either = or &, whichever comes first
159         if ((ptr = strpbrk(in, "=&"))) {
160                 if (*ptr == '&') { //No value specified
161                         *value = ptr;
162                         *ptr++ = 0;
163                 } else {
164                         //Stopped at an '=' sign
165                         *ptr++ = 0;
166                         *value = ptr;
167                         if ((ptr = strchr(ptr,'&'))) {
168                                 *ptr++ = 0;
169                         } else {
170                                 ptr = "";
171                         }
172                 }
173         } else { //No value specified and no other pair
174                 ptr = "";
175                 *value = ptr;
176         }
177         return ptr;
178 }
179
180 /**
181  * Begins a response to the client in JSON format.
182  * @param status_code The HTTP status code to be returned.
183  * @param module The name of the module that initiated the response.
184  */
185 void FCGI_BeginJSON(StatusCodes status_code, const char *module)
186 {
187         switch (status_code) {
188                 case STATUS_OK:
189                         break;
190                 case STATUS_UNAUTHORIZED:
191                         printf("Status: 401 Unauthorized\r\n");
192                         break;
193                 default:
194                         printf("Status: 400 Bad Request\r\n");
195         }
196         printf("Content-type: application/json; charset=utf-8\r\n\r\n");
197         printf("{\r\n");
198         printf("\t\"module\" : \"%s\"", module);
199 }
200
201 /**
202  * Adds a key/value pair to a JSON response. The response must have already
203  * been initiated by FCGI_BeginJSON. Note that characters are not escaped.
204  * @param key The key of the JSON entry
205  * &param value The value associated with the key.
206  */
207 void FCGI_BuildJSON(const char *key, const char *value)
208 {
209         printf(",\r\n\t\"%s\" : \"%s\"", key, value);
210 }
211
212 /**
213  * Ends a JSON response that was initiated by FCGI_BeginJSON.
214  */
215 void FCGI_EndJSON() 
216 {
217         printf("\r\n}\r\n");
218 }
219
220 /**
221  * Main FCGI request loop that receives/responds to client requests.
222  * @param data A data field to be passed to the selected module handler.
223  */ 
224 void FCGI_RequestLoop (void * data)
225 {
226         int count = 0;
227         while (FCGI_Accept() >= 0)   {
228                 ModuleHandler module_handler = NULL;
229                 char module[BUFSIZ], params[BUFSIZ];
230
231                 //strncpy doesn't zero-truncate properly
232                 snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
233                 snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
234                 
235                 //Remove trailing slashes (if present) from module query
236                 size_t lastchar = strlen(module) - 1;
237                 if (lastchar > 0 && module[lastchar] == '/')
238                         module[lastchar] = 0;
239                 
240
241                 if (!strcmp("sensors", module)) {
242                         module_handler = SensorHandler;
243                 } else if (!strcmp("login", module)) {
244                         module_handler = LoginHandler;
245                 } else if (!strcmp("actuators", module)) {
246                         
247                 }
248
249                 if (module_handler) {
250                         module_handler(data, params);
251                 } else {
252                         char buf[BUFSIZ];
253                         
254                         FCGI_BeginJSON(400, module);
255                         FCGI_BuildJSON("description", "400 Invalid response");
256                         snprintf(buf, BUFSIZ, "%d", count);
257                         FCGI_BuildJSON("request-number", buf);
258                         FCGI_BuildJSON("params", params);
259                         FCGI_BuildJSON("host", getenv("SERVER_HOSTNAME"));
260                         FCGI_BuildJSON("user", getenv("REMOTE_USER"));
261                         FCGI_BuildJSON("userip", getenv("REMOTE_ADDR"));
262                         FCGI_EndJSON();
263                 }
264
265                 count++;
266         }
267 }

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