Line data Source code
1 : /*!
2 : * \file
3 : * \author Christopher Stender
4 : * \date 2018-02-15
5 : * \version 0.1
6 : *
7 : * \brief Universal JSON API library.
8 : *
9 : * \details
10 : * libjapi is a universal JSON API library.
11 : *
12 : * \copyright
13 : * Copyright (c) 2023 Fraunhofer IIS
14 : *
15 : * Permission is hereby granted, free of charge, to any person obtaining a copy
16 : * of this software and associated documentation files (the “Software”), to deal
17 : * in the Software without restriction, including without limitation the rights
18 : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 : * copies of the Software, and to permit persons to whom the Software is
20 : * furnished to do so, subject to the following conditions:
21 : *
22 : * The above copyright notice and this permission notice shall be included in
23 : * all copies or substantial portions of the Software.
24 : *
25 : * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 : * THE SOFTWARE.
32 : */
33 :
34 : #include <assert.h>
35 : #include <signal.h>
36 : #include <stdio.h>
37 : #include <string.h> /* strcmp */
38 : #include <strings.h> /* strcasecmp */
39 : #include <sys/select.h>
40 : #include <sys/socket.h>
41 : #include <sys/time.h>
42 : #include <unistd.h>
43 :
44 : #include "japi.h"
45 :
46 : #include "creadline.h"
47 : #include "japi_intern.h"
48 : #include "japi_pushsrv.h"
49 : #include "japi_pushsrv_intern.h"
50 : #include "japi_utils.h"
51 : #include "networking.h"
52 : #include "prntdbg.h"
53 : #include "rw_n.h"
54 :
55 : /* Look for a request handler matching the name 'name'.
56 : *
57 : * NULL is returned if no suitable handler was found.
58 : */
59 75 : static japi_req_handler japi_get_request_handler(japi_context *ctx, const char *name)
60 : {
61 : japi_request *req;
62 :
63 75 : req = ctx->requests;
64 330 : while (req != NULL) {
65 :
66 185 : if (strcasecmp(name, req->name) == 0) {
67 5 : return req->func;
68 : }
69 180 : req = req->next;
70 : }
71 :
72 70 : return NULL;
73 : }
74 :
75 : /* Steps performed while processing a JSON request:
76 : * - Convert the received message into a JSON object
77 : * - Extract the request name
78 : * - Search a suitable request handler
79 : * - Call the request handler
80 : * - Prepare the JSON response
81 : * - Free memory
82 : */
83 4 : int japi_process_message(japi_context *ctx, const char *request, char **response,
84 : int socket)
85 : {
86 : const char *req_name;
87 : json_object *jreq;
88 : json_object *jreq_no;
89 : json_object *jresp;
90 : json_object *jresp_data;
91 : json_object *jargs;
92 : japi_req_handler req_handler;
93 : int ret;
94 : bool args;
95 :
96 4 : assert(ctx != NULL);
97 4 : assert(response != NULL);
98 4 : assert(socket >= 0);
99 :
100 4 : ret = -1;
101 4 : *response = NULL;
102 :
103 : /* Create JSON object from received message */
104 4 : jreq = json_tokener_parse(request);
105 4 : if (jreq == NULL) {
106 0 : fprintf(stderr, "ERROR: json_tokener_parse() failed. Received message: %s\n",
107 : request);
108 0 : return -1;
109 : }
110 :
111 : /* Only create new JSON objects after a valid JSON request was parsed. */
112 4 : jresp = json_object_new_object(); /* Response object */
113 4 : jresp_data = json_object_new_object();
114 :
115 4 : if ((japi_get_value_as_str(jreq, "japi_request", &req_name)) == 0) {
116 :
117 : /* Prepare response */
118 4 : json_object_object_add(jresp, "japi_response",
119 : json_object_new_string(req_name));
120 :
121 : /* Include japi_request_no in response, if included with request */
122 4 : if (json_object_object_get_ex(jreq, "japi_request_no", &jreq_no)) {
123 0 : json_object_get(jreq_no);
124 0 : json_object_object_add(jresp, "japi_request_no", jreq_no);
125 : }
126 :
127 : /* Get arguments as an JSON object */
128 4 : args = json_object_object_get_ex(jreq, "args", &jargs);
129 :
130 : /* Add an empty args JSON object if no args were given
131 : Otherwise, include args with response, if configured. */
132 4 : if (!args) {
133 2 : json_object_object_add(jreq, "args", NULL);
134 2 : json_object_object_get_ex(jreq, "args", &jargs);
135 : } else {
136 2 : if (ctx->include_args_in_response) {
137 2 : json_object_get(jargs);
138 2 : json_object_object_add(jresp, "args", jargs);
139 : }
140 : }
141 :
142 : /* Subscribe/unsubscribe service needs client socket */
143 8 : if (strcasecmp(req_name, "japi_pushsrv_subscribe") == 0 ||
144 4 : strcasecmp(req_name, "japi_pushsrv_unsubscribe") == 0) {
145 0 : json_object_object_add(jargs, "socket", json_object_new_int(socket));
146 : }
147 :
148 : /* Try to find a suitable handler for the given request */
149 4 : req_handler = japi_get_request_handler(ctx, req_name);
150 4 : if (req_handler == NULL) {
151 :
152 : /* No request handler found? Check if a fallback handler was registered. */
153 1 : req_handler = japi_get_request_handler(ctx, "request_not_found_handler");
154 :
155 1 : if (req_handler == NULL) {
156 1 : fprintf(stderr,
157 : "ERROR: No suitable request handler found. Falling back to "
158 : "default fallback handler. Request was: %s\n",
159 : req_name);
160 1 : req_handler =
161 : japi_get_request_handler(ctx, "japi_request_not_found_handler");
162 : } else {
163 0 : fprintf(stderr,
164 : "WARNING: No suitable request handler found. Falling back to "
165 : "user registered fallback handler. Request was: %s\n",
166 : req_name);
167 : }
168 : }
169 :
170 : /* Call request handler */
171 4 : req_handler(ctx, jargs, jresp_data);
172 :
173 : } else {
174 : /* Get request name */
175 0 : if (req_name == NULL) {
176 0 : fprintf(stderr, "ERROR: No keyword found!\n");
177 0 : goto out_free;
178 : }
179 : }
180 :
181 : /* Add response arguments */
182 4 : json_object_object_add(jresp, "data", jresp_data);
183 :
184 : /* Stringify response */
185 4 : *response = japi_get_jobj_as_ndstr(jresp);
186 4 : json_object_put(jresp);
187 :
188 4 : ret = 0;
189 :
190 : out_free:
191 : /* Free JSON request object */
192 4 : json_object_put(jreq);
193 :
194 4 : return ret;
195 : }
196 :
197 0 : int japi_shutdown(japi_context *ctx)
198 : {
199 0 : if (ctx == NULL) {
200 0 : fprintf(stderr, "ERROR: JAPI context is NULL.\n");
201 0 : return -1;
202 : }
203 :
204 0 : ctx->shutdown = true;
205 :
206 0 : return 0;
207 : }
208 :
209 5 : int japi_destroy(japi_context *ctx)
210 : {
211 : japi_request *req, *req_next;
212 : japi_pushsrv_context *psc, *psc_next;
213 :
214 5 : if (ctx == NULL) {
215 0 : fprintf(stderr, "ERROR: JAPI context is NULL.\n");
216 0 : return -1;
217 : }
218 :
219 5 : req = ctx->requests;
220 39 : while (req != NULL) {
221 29 : req_next = req->next;
222 29 : free(req);
223 29 : req = req_next;
224 : }
225 :
226 5 : psc = ctx->push_services;
227 14 : while (psc != NULL) {
228 4 : psc_next = psc->next;
229 4 : japi_pushsrv_destroy(ctx, psc);
230 4 : psc = psc_next;
231 : }
232 :
233 5 : pthread_mutex_destroy(&(ctx->lock));
234 5 : free(ctx);
235 :
236 5 : return 0;
237 : }
238 :
239 73 : int japi_register_request(japi_context *ctx, const char *req_name,
240 : japi_req_handler req_handler)
241 : {
242 : japi_request *req;
243 73 : char *bad_req_name = "japi_";
244 :
245 : /* Error handling */
246 73 : if (ctx == NULL) {
247 1 : fprintf(stderr, "ERROR: JAPI context is NULL.\n");
248 1 : return -1;
249 : }
250 :
251 72 : if ((req_name == NULL) || (strcmp(req_name, "") == 0)) {
252 2 : fprintf(stderr, "ERROR: Request name is NULL or empty.\n");
253 2 : return -2;
254 : }
255 :
256 70 : if (req_handler == NULL) {
257 1 : fprintf(stderr, "ERROR: Request handler is NULL.\n");
258 1 : return -3;
259 : }
260 :
261 69 : if (japi_get_request_handler(ctx, req_name) != NULL) {
262 1 : fprintf(stderr,
263 : "ERROR: A request handler called '%s' was already registered.\n",
264 : req_name);
265 1 : return -4;
266 : }
267 :
268 68 : if (ctx->init && strncmp(req_name, bad_req_name, strlen(bad_req_name)) == 0) {
269 1 : fprintf(stderr, "ERROR: Request name is not allowed.\n");
270 1 : return -6;
271 : }
272 :
273 67 : req = (japi_request *)malloc(sizeof(japi_request));
274 67 : if (req == NULL) {
275 0 : perror("ERROR: malloc() failed");
276 0 : return -5;
277 : }
278 :
279 67 : req->name = req_name;
280 67 : req->func = req_handler;
281 67 : req->next = ctx->requests;
282 :
283 67 : ctx->requests = req;
284 :
285 67 : return 0;
286 : }
287 :
288 12 : japi_context *japi_init(void *userptr)
289 : {
290 : japi_context *ctx;
291 :
292 12 : ctx = (japi_context *)malloc(sizeof(japi_context));
293 12 : if (ctx == NULL) {
294 0 : perror("ERROR: malloc() failed");
295 0 : return NULL;
296 : }
297 :
298 12 : ctx->init = false;
299 12 : ctx->userptr = userptr;
300 12 : ctx->requests = NULL;
301 12 : ctx->push_services = NULL;
302 12 : ctx->clients = NULL;
303 12 : ctx->num_clients = 0;
304 12 : ctx->max_clients = 0;
305 12 : ctx->include_args_in_response = false;
306 12 : ctx->shutdown = false;
307 :
308 : /* Initialize mutex */
309 12 : if (pthread_mutex_init(&(ctx->lock), NULL) != 0) {
310 0 : fprintf(stderr, "ERROR: mutex initialization has failed\n");
311 0 : return NULL;
312 : }
313 :
314 : /* Ignore SIGPIPE Signal */
315 12 : signal(SIGPIPE, SIG_IGN);
316 :
317 : /* Register the default fallback handler */
318 12 : japi_register_request(ctx, "japi_request_not_found_handler",
319 : &japi_request_not_found_handler);
320 : /* Register subscribe/unsubscribe service function */
321 12 : japi_register_request(ctx, "japi_pushsrv_subscribe", &japi_pushsrv_subscribe);
322 12 : japi_register_request(ctx, "japi_pushsrv_unsubscribe", &japi_pushsrv_unsubscribe);
323 : /* Register list_push_service function */
324 12 : japi_register_request(ctx, "japi_pushsrv_list", &japi_pushsrv_list);
325 12 : japi_register_request(ctx, "japi_cmd_list", &japi_cmd_list);
326 :
327 12 : ctx->init = true;
328 :
329 12 : return ctx;
330 : }
331 :
332 : /*
333 : * Set maximal allowed number of clients.
334 : * 0 is interpreted as unlimited number of allowed clients.
335 : */
336 0 : int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num)
337 : {
338 : /* Error handling */
339 0 : if (ctx == NULL) {
340 0 : fprintf(stderr, "ERROR: JAPI context is NULL.\n");
341 0 : return -1;
342 : }
343 :
344 0 : ctx->max_clients = num;
345 :
346 0 : return 0;
347 : }
348 :
349 : /*
350 : * Include request arguments in response.
351 : */
352 3 : int japi_include_args_in_response(japi_context *ctx, bool include_args)
353 : {
354 : /* Error handling */
355 3 : if (ctx == NULL) {
356 1 : fprintf(stderr, "ERROR: JAPI context is NULL.\n");
357 1 : return -1;
358 : }
359 :
360 2 : ctx->include_args_in_response = include_args;
361 :
362 2 : return 0;
363 : }
364 :
365 : /*
366 : * Add new client element to list
367 : */
368 6 : int japi_add_client(japi_context *ctx, int socket)
369 : {
370 : japi_client *client;
371 :
372 : /* Error handling */
373 6 : assert(ctx != NULL);
374 6 : assert(socket >= 0);
375 :
376 : /* Create new client list element */
377 6 : client = (japi_client *)malloc(sizeof(japi_client));
378 6 : if (client == NULL) {
379 0 : perror("ERROR: malloc() failed");
380 0 : return -1;
381 : }
382 :
383 : /* Reset the clients buffer used by creadline_r */
384 6 : client->crl_buffer.nbytes = 0;
385 :
386 6 : pthread_mutex_lock(&(ctx->lock));
387 6 : prntdbg("adding client %d to japi context\n", socket);
388 : /* Add socket */
389 6 : client->socket = socket;
390 :
391 : /* Link list */
392 6 : client->next = ctx->clients;
393 6 : ctx->clients = client;
394 : /* Increment number of connected clients */
395 6 : ctx->num_clients++;
396 6 : pthread_mutex_unlock(&(ctx->lock));
397 :
398 6 : return 0;
399 : }
400 :
401 : /*
402 : * Remove client from client list
403 : */
404 4 : int japi_remove_client(japi_context *ctx, int socket)
405 : {
406 : japi_client *client, *prev;
407 : int ret;
408 :
409 : /* Error Handling */
410 4 : assert(ctx != NULL);
411 4 : assert(socket >= 0);
412 :
413 4 : client = ctx->clients;
414 4 : prev = NULL;
415 4 : ret = -1;
416 :
417 4 : japi_pushsrv_remove_client_from_all_pushsrv(ctx, socket);
418 :
419 4 : pthread_mutex_lock(&(ctx->lock));
420 : /* Remove client from list */
421 21 : while (client != NULL) {
422 : /* If first element */
423 15 : if ((client->socket == socket) && (prev == NULL)) {
424 1 : ctx->clients = client->next;
425 1 : prntdbg("removing client %d from japi context and close socket\n",
426 : client->socket);
427 1 : close(client->socket);
428 1 : free(client);
429 1 : ctx->num_clients--;
430 1 : ret = 0;
431 1 : break;
432 : }
433 : /* If last element */
434 14 : if ((client->socket == socket) && (client->next == NULL)) {
435 1 : prev->next = NULL;
436 1 : prntdbg("removing client %d from japi context and close socket\n",
437 : client->socket);
438 1 : close(client->socket);
439 1 : free(client);
440 1 : ctx->num_clients--;
441 1 : ret = 0;
442 1 : break;
443 : }
444 13 : if (client->socket == socket) {
445 0 : prev->next = client->next;
446 0 : prntdbg("removing client %d from japi context and close socket\n",
447 : client->socket);
448 0 : close(client->socket);
449 0 : free(client);
450 0 : ctx->num_clients--;
451 0 : ret = 0;
452 0 : break;
453 : }
454 :
455 13 : prev = client;
456 13 : client = client->next;
457 : }
458 4 : pthread_mutex_unlock(&(ctx->lock));
459 :
460 4 : return ret;
461 : }
462 :
463 0 : int japi_remove_all_clients(japi_context *ctx)
464 : {
465 : japi_client *client, *following_client;
466 :
467 : /* Error Handling */
468 0 : assert(ctx != NULL);
469 :
470 0 : client = ctx->clients;
471 0 : while (client != NULL) {
472 0 : following_client = client->next;
473 0 : if (japi_remove_client(ctx, client->socket) != 0) {
474 0 : return -1;
475 : }
476 0 : client = following_client;
477 : }
478 :
479 0 : return 0;
480 : }
481 :
482 0 : int japi_start_server(japi_context *ctx, const char *port)
483 : {
484 : int server_socket;
485 :
486 0 : server_socket = tcp_start_server(port);
487 0 : if (server_socket < 0) {
488 0 : fprintf(stderr, "ERROR: Failed to start tcp server on port %s\n", port);
489 0 : return -1;
490 : }
491 :
492 : int ret;
493 : int nfds;
494 : fd_set fdrd;
495 : struct timeval timeout;
496 : japi_client *client, *following_client;
497 :
498 0 : if (listen(server_socket, 1) != 0) {
499 0 : perror("ERROR: listen() failed\n");
500 0 : return -1;
501 : }
502 :
503 : while (1) {
504 :
505 0 : FD_ZERO(&fdrd);
506 :
507 : /* Add server socket to set */
508 0 : FD_SET(server_socket, &fdrd);
509 :
510 0 : nfds = server_socket + 1;
511 :
512 : /* Add all file descriptors to set */
513 0 : client = ctx->clients;
514 0 : while (client != NULL) {
515 0 : FD_SET(client->socket, &fdrd);
516 0 : if (client->socket >= nfds) {
517 0 : nfds = client->socket + 1;
518 : }
519 0 : client = client->next;
520 : }
521 :
522 0 : timeout.tv_sec = 0;
523 0 : timeout.tv_usec = 200000;
524 0 : ret = select(nfds, &fdrd, NULL, NULL, &timeout);
525 0 : if (ret == -1) {
526 0 : perror("ERROR: select() failed\n");
527 0 : return -1;
528 : }
529 :
530 : /* Check if there is a request to shutdown the server */
531 0 : if (ctx->shutdown == true) {
532 0 : break;
533 0 : } else if (ret == 0) {
534 0 : continue;
535 : }
536 :
537 0 : client = ctx->clients; // Reset pointer to list
538 :
539 0 : while (client != NULL) {
540 :
541 0 : following_client = client->next;
542 :
543 : /* Check whether there is data to process */
544 0 : if (FD_ISSET(client->socket, &fdrd)) {
545 :
546 : int ret;
547 : char *request;
548 : char *response;
549 :
550 : do {
551 :
552 0 : ret = creadline_r(client->socket, (void **)&request,
553 : &(client->crl_buffer));
554 0 : if (ret > 0) {
555 :
556 0 : response = NULL;
557 :
558 : /* Received a line, process it... */
559 0 : japi_process_message(ctx, request, &response, client->socket);
560 :
561 : /* After the request buffer is processed, the memory
562 : *is not needed anymore and can be freed at this point. */
563 0 : free(request);
564 :
565 : /* Send response (if provided) */
566 0 : if (response != NULL) {
567 0 : ret = write_n(client->socket, response, strlen(response));
568 0 : free(response);
569 :
570 0 : if (ret <= 0) {
571 : /* Write failed */
572 0 : fprintf(stderr,
573 : "ERROR: Failed to send response to client %i "
574 : "(write returned %i)\n",
575 : client->socket, ret);
576 0 : japi_remove_client(ctx, client->socket);
577 0 : break;
578 : }
579 : }
580 0 : } else if (ret == 0) {
581 0 : if (request == NULL) {
582 : /* Received EOF (client disconnected) */
583 0 : prntdbg("client %d disconnected\n", client->socket);
584 0 : japi_remove_client(ctx, client->socket);
585 0 : break;
586 : } else {
587 : /* Received an empty line */
588 0 : free(request);
589 : }
590 : } else {
591 0 : fprintf(stderr, "ERROR: creadline() failed (ret = %i)\n", ret);
592 0 : japi_remove_client(ctx, client->socket);
593 0 : break;
594 : }
595 :
596 0 : } while (client->crl_buffer.nbytes != 0);
597 : }
598 0 : client = following_client;
599 : }
600 :
601 : /* Check whether there are new clients */
602 0 : if (FD_ISSET(server_socket, &fdrd)) {
603 0 : int client_socket = 0;
604 :
605 0 : client_socket = accept(server_socket, NULL, NULL);
606 0 : if (client_socket < 0) {
607 0 : perror("ERROR: accept() failed\n");
608 0 : return -1;
609 : }
610 0 : if (ctx->max_clients == 0 || ctx->num_clients < ctx->max_clients) {
611 0 : japi_add_client(ctx, client_socket);
612 0 : prntdbg("client %d added\n", client_socket);
613 : } else {
614 0 : close(client_socket);
615 : }
616 : }
617 0 : }
618 :
619 : /* Clean up */
620 0 : japi_remove_all_clients(ctx);
621 :
622 0 : close(server_socket);
623 :
624 0 : return 0;
625 : }
626 :
627 : /*
628 : * Provide the names of all registered commands as a JAPI response.
629 : */
630 1 : void japi_cmd_list(japi_context *ctx, json_object *request, json_object *response)
631 : {
632 : japi_request *req;
633 : json_object *jstring;
634 : json_object *jarray;
635 :
636 1 : assert(ctx != NULL);
637 1 : assert(response != NULL);
638 :
639 1 : jarray = json_object_new_array();
640 1 : req = ctx->requests;
641 :
642 : /* Iterate through push service list and return JSON object */
643 10 : while (req != NULL) {
644 8 : jstring = json_object_new_string(req->name); /* Create JSON-string */
645 8 : json_object_array_add(jarray, jstring); /* Add string to JSON array */
646 8 : req = req->next;
647 : }
648 :
649 : /* Add array to JSON-object */
650 1 : json_object_object_add(response, "commands", jarray);
651 1 : }
652 :
653 : /*
654 : * Default handler for reacting to unknown requests.
655 : */
656 1 : void japi_request_not_found_handler(japi_context *ctx, json_object *request,
657 : json_object *response)
658 : {
659 1 : json_object_object_add(response, "error",
660 : json_object_new_string("no request handler found"));
661 1 : }
|