rest-api: catch error from pluginRegisterFct()
[src/app-framework-binder.git] / src / rest-api.c
1 /*
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  * 
18  * Contain all generic part to handle REST/API
19  * 
20  *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c']
21  */
22
23 #include "../include/local-def.h"
24
25 #include <dirent.h>
26 #include <dlfcn.h>
27 #include <setjmp.h>
28 #include <signal.h>
29
30 #define AFB_MSG_JTYPE "AJB_reply"
31
32
33
34 static json_object     *afbJsonType;
35
36
37 // Because of POST call multiple time requestApi we need to free POST handle here
38 // Note this method is called from http-svc just before closing session
39 PUBLIC void endPostRequest(AFB_PostHandle *postHandle) {
40
41     if (postHandle->type == AFB_POST_JSON) {
42         // if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid);
43     }
44
45     if (postHandle->type == AFB_POST_FORM) {
46          if (verbose) fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid);
47     }
48     if (postHandle->private) free(postHandle->private);
49     free(postHandle);
50 }
51
52 // Check of apiurl is declare in this plugin and call it
53 STATIC AFB_error callPluginApi(AFB_request *request, int plugidx, void *context) {
54     json_object *jresp, *jcall, *jreqt;
55     int idx, status, sig;
56     AFB_clientCtx *clientCtx;
57     AFB_plugin *plugin = request->plugins[plugidx];
58     int signals[]= {SIGALRM, SIGSEGV, SIGFPE, 0};
59     
60     /*---------------------------------------------------------------
61     | Signal handler defined inside CallPluginApi to access Request
62     +---------------------------------------------------------------- */
63     void pluginError (int signum) {
64       sigset_t sigset;
65    
66       
67       // unlock signal to allow a new signal to come
68       sigemptyset (&sigset);
69       sigaddset   (&sigset, signum);
70       sigprocmask (SIG_UNBLOCK, &sigset, 0);
71
72       fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
73       longjmp (request->checkPluginCall, signum);
74     }
75
76     
77     // If a plugin hold this urlpath call its callback
78     for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
79         if (!strcmp(plugin->apis[idx].name, request->api)) {
80             
81             // Request was found and at least partially executed
82             jreqt  = json_object_new_object();
83             json_object_get (afbJsonType);  // increate jsontype reference count
84             json_object_object_add (jreqt, "jtype", afbJsonType);
85             
86             // prepare an object to store calling values
87             jcall=json_object_new_object();
88             json_object_object_add(jcall, "prefix", json_object_new_string (plugin->prefix));
89             json_object_object_add(jcall, "api"   , json_object_new_string (plugin->apis[idx].name));
90             
91             // save context before calling the API
92             status = setjmp (request->checkPluginCall);
93             if (status != 0) {    
94                 
95                 // Plugin aborted somewhere during its execution
96                 json_object_object_add(jcall, "status", json_object_new_string ("abort"));
97                 json_object_object_add(jcall, "info" ,  json_object_new_string ("Plugin broke during execution"));
98                 json_object_object_add(jreqt, "request", jcall);
99                 
100             } else {
101                 
102                 // If timeout protection==0 we are in debug and we do not apply signal protection
103                 if (request->config->apiTimeout > 0) {
104                     for (sig=0; signals[sig] != 0; sig++) {
105                        if (signal (signals[sig], pluginError) == SIG_ERR) {
106                             request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
107                             json_object_object_add(jcall, "status", json_object_new_string ("fail"));
108                             json_object_object_add(jcall, "info", json_object_new_string ("Setting Timeout Handler Failed"));
109                             json_object_object_add(jreqt, "request", jcall);
110                             return AFB_DONE;
111                        }
112                     }
113                     // Trigger a timer to protect from unacceptable long time execution
114                     alarm (request->config->apiTimeout);
115                 }
116
117                 // Out of SessionNone every call get a client context session
118                 if (AFB_SESSION_NONE != plugin->apis[idx].session) {
119                     
120                     // add client context to request
121                     clientCtx = ctxClientGet(request, plugidx);
122                     if (clientCtx == NULL) {
123                         request->errcode=MHD_HTTP_INSUFFICIENT_STORAGE;
124                         json_object_object_add(jcall, "status", json_object_new_string ("fail"));
125                         json_object_object_add(jcall, "info", json_object_new_string ("Client Session Context Full !!!"));
126                         json_object_object_add(jreqt, "request", jcall);
127                         return (AFB_DONE);                              
128                     };
129                     
130                     if (verbose) fprintf(stderr, "Plugin=[%s] Api=[%s] Middleware=[%d] Client=[0x%x] Uuid=[%s] Token=[%s]\n"
131                            , request->prefix, request->api, plugin->apis[idx].session, clientCtx, clientCtx->uuid, clientCtx->token);                        
132                     
133                     switch(plugin->apis[idx].session) {
134
135                         case AFB_SESSION_CREATE:
136                             if (clientCtx->token[0] != '\0') {
137                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
138                                 json_object_object_add(jcall, "status", json_object_new_string ("exist"));
139                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CREATE Session already exist"));
140                                 json_object_object_add(jreqt, "request", jcall);
141                                 return (AFB_DONE);                              
142                             }
143                         
144                             if (AFB_SUCCESS != ctxTokenCreate (clientCtx, request)) {
145                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
146                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
147                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CREATE Invalid Initial Token"));
148                                 json_object_object_add(jreqt, "request", jcall);
149                                 return (AFB_DONE);
150                             } else {
151                                 json_object_object_add(jcall, "uuid", json_object_new_string (clientCtx->uuid));                                
152                                 json_object_object_add(jcall, "token", json_object_new_string (clientCtx->token));                                
153                                 json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
154                             }
155                             break;
156
157
158                         case AFB_SESSION_RENEW:
159                             if (AFB_SUCCESS != ctxTokenRefresh (clientCtx, request)) {
160                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
161                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
162                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_REFRESH Broken Exchange Token Chain"));
163                                 json_object_object_add(jreqt, "request", jcall);
164                                 return (AFB_DONE);
165                             } else {
166                                 json_object_object_add(jcall, "uuid", json_object_new_string (clientCtx->uuid));                                
167                                 json_object_object_add(jcall, "token", json_object_new_string (clientCtx->token));                                
168                                 json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
169                             }
170                             break;
171
172                         case AFB_SESSION_CLOSE:
173                             if (AFB_SUCCESS != ctxTokenCheck (clientCtx, request)) {
174                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
175                                 json_object_object_add(jcall, "status", json_object_new_string ("empty"));
176                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CLOSE Not a Valid Access Token"));
177                                 json_object_object_add(jreqt, "request", jcall);
178                                 return (AFB_DONE);
179                             } else {
180                                 json_object_object_add(jcall, "uuid", json_object_new_string (clientCtx->uuid));                                
181                             }
182                             break;
183                         
184                         case AFB_SESSION_CHECK:
185                         default: 
186                             // default action is check
187                             if (AFB_SUCCESS != ctxTokenCheck (clientCtx, request)) {
188                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
189                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
190                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CHECK Invalid Active Token"));
191                                 json_object_object_add(jreqt, "request", jcall);
192                                 return (AFB_DONE);
193                             }
194                             break;
195                     }
196                 }
197                 
198                 // Effectively CALL PLUGIN API with a subset of the context
199                 jresp = plugin->apis[idx].callback(request, context);
200                 
201                 // prefix response with request object;
202                 request->jresp = jreqt;
203                 
204                 // Store context in case it was updated by plugins
205                 if (request->context != NULL) clientCtx->contexts[plugidx] = request->context;               
206                 
207                 // handle intermediary Post Iterates out of band
208                 if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK)) return (AFB_SUCCESS);
209
210                 // Session close is done after the API call so API can still use session in closing API
211                 if (AFB_SESSION_CLOSE == plugin->apis[idx].session) ctxTokenReset (clientCtx, request);                    
212                 
213                 // API should return NULL of a valid Json Object
214                 if (jresp == NULL) {
215                     json_object_object_add(jcall, "status", json_object_new_string ("null"));
216                     json_object_object_add(request->jresp, "request", jcall);
217                     request->errcode = MHD_HTTP_NO_RESPONSE;
218                     
219                 } else {
220                     json_object_object_add(jcall, "status", json_object_new_string ("processed"));
221                     json_object_object_add(request->jresp, "request", jcall);
222                     json_object_object_add(request->jresp, "response", jresp);
223                 }
224                 // cancel timeout and plugin signal handle before next call
225                 if (request->config->apiTimeout > 0) {
226                     alarm (0);
227                     for (sig=0; signals[sig] != 0; sig++) {
228                        signal (signals[sig], SIG_DFL);
229                     }
230                 }              
231             }       
232             return (AFB_DONE);
233         }
234     }   
235     return (AFB_FAIL);
236 }
237
238 STATIC AFB_error findAndCallApi (AFB_request *request, void *context) {
239     int idx;
240     AFB_error status;
241     
242     if (!request->api || !request->prefix) return (AFB_FAIL);
243    
244     // Search for a plugin with this urlpath
245     for (idx = 0; request->plugins[idx] != NULL; idx++) {
246         if (!strcmp(request->plugins[idx]->prefix, request->prefix)) {
247             status =callPluginApi(request, idx, context);
248             break;
249         }
250     }
251     // No plugin was found
252     if (request->plugins[idx] == NULL) {
253         request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s] Url=%s", request->prefix, request->url);
254         goto ExitOnError;
255     }  
256     
257     // plugin callback did not return a valid Json Object
258     if (status == AFB_FAIL) {
259         request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->api, request->prefix, request->url);
260         goto ExitOnError;
261     }
262     
263     // Everything look OK
264     return (status);
265     
266 ExitOnError:
267     request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
268     return (AFB_FAIL);
269 }
270
271 // This CB is call for every item with a form post it reformat iterator values
272 // and callback Plugin API for each Item within PostForm.
273 STATIC int doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
274               const char *filename, const char *mimetype,
275               const char *encoding, const char *data, uint64_t offset,
276               size_t size) {
277   
278   AFB_error    status;
279   AFB_PostItem item;
280     
281   // retrieve API request from Post iterator handle  
282   AFB_PostHandle *postHandle  = (AFB_PostHandle*)cls;
283   AFB_request *request = (AFB_request*)postHandle->private;
284   AFB_PostRequest postRequest;
285   
286   fprintf (stderr, "postHandle key=%s filename=%s len=%d mime=%s\n", key, filename, size, mimetype);
287    
288   // Create and Item value for Plugin API
289   item.kind     = kind;
290   item.key      = key;
291   item.filename = filename;
292   item.mimetype = mimetype;
293   item.encoding = encoding;
294   item.len      = size;
295   item.data     = data;
296   item.offset   = offset;
297   
298   // Reformat Request to make it somehow similar to GET/PostJson case
299   postRequest.data= (char*) postHandle;
300   postRequest.len = size;
301   postRequest.type= AFB_POST_FORM;;
302   request->post = &postRequest;
303   
304   // effectively call plugin API                 
305   status = findAndCallApi (request, &item);
306   // when returning no processing of postform stop
307   if (status != AFB_SUCCESS) return MHD_NO;
308   
309   // let's allow iterator to move to next item
310   return MHD_YES;
311 }
312
313 STATIC void freeRequest (AFB_request *request) {
314
315  free (request->prefix);    
316  free (request->api);    
317  free (request);    
318 }
319
320 STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_session *session, const char* url) {
321     
322     AFB_request *request;
323     int idx;
324
325     // Start with a clean request
326     request = calloc (1, sizeof (AFB_request));
327     char *urlcpy1, *urlcpy2;
328     char *baseapi, *baseurl;
329       
330     // Extract plugin urlpath from request and make two copy because strsep overload copy
331     urlcpy1 = urlcpy2 = strdup(url);
332     baseurl = strsep(&urlcpy2, "/");
333     if (baseurl == NULL) {
334         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
335         request->errcode = MHD_HTTP_BAD_REQUEST;
336         goto Done;
337     }
338
339     // let's compute URL and call API
340     baseapi = strsep(&urlcpy2, "/");
341     if (baseapi == NULL) {
342         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url);
343         request->errcode = MHD_HTTP_BAD_REQUEST;
344         goto Done;
345     }
346     
347     // build request structure
348     request->connection = connection;
349     request->config = session->config;
350     request->url    = url;
351     request->prefix = strdup (baseurl);
352     request->api    = strdup (baseapi);
353     request->plugins= session->plugins;
354     // note request->handle is fed with request->context in ctxClientGet
355
356 Done:    
357     free(urlcpy1);
358     return (request);
359 }
360
361 // process rest API query
362 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
363     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
364     
365     static int postcount = 0; // static counter to debug POST protocol
366     json_object *errMessage;
367     AFB_error status;
368     struct MHD_Response *webResponse;
369     const char *serialized;
370     AFB_request *request;
371     AFB_PostHandle *postHandle;
372     AFB_PostRequest postRequest;
373     int ret;
374     
375     // fprintf (stderr, "doRestAPI method=%s posthandle=0x%x\n", method, con_cls);
376     
377     // if post data may come in multiple calls
378     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
379         const char *encoding, *param;
380         int contentlen = -1;
381         postHandle = *con_cls;
382
383         // This is the initial post event let's create form post structure POST data come in multiple events
384         if (postHandle == NULL) {
385
386             // allocate application POST processor handle to zero
387             postHandle = calloc(1, sizeof (AFB_PostHandle));
388             postHandle->uid = postcount++; // build a UID for DEBUG
389             *con_cls = postHandle;  // update context with posthandle
390             
391             // Let make sure we have the right encoding and a valid length
392             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
393             
394             // We are facing an empty post let's process it as a get
395             if (encoding == NULL) {
396                 postHandle->type   = AFB_POST_EMPTY;
397                 return MHD_YES;
398             }
399         
400             // Form post is handle through a PostProcessor and call API once per form key
401             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
402                 if (verbose) fprintf(stderr, "Create doPostIterate[uid=%d posthandle=0x%x]\n", postHandle->uid, postHandle);
403
404                 request = createRequest (connection, session, url);
405                 if (request->jresp != NULL) goto ProcessApiCall;
406                 postHandle->type   = AFB_POST_FORM;
407                 postHandle->private= (void*)request;
408                 postHandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, &doPostIterate, postHandle);
409                 
410                 if (NULL == postHandle->pp) {
411                     fprintf(stderr,"OOPS: Internal error fail to allocate MHD_create_post_processor\n");
412                     free (postHandle);
413                     return MHD_NO;
414                 }
415                 return MHD_YES;
416             }           
417         
418             // POST json is store into a buffer and present in one piece to API
419             if (strcasestr(encoding, JSON_CONTENT) != NULL) {
420
421                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
422                 if (param) sscanf(param, "%i", &contentlen);
423
424                 // Because PostJson are build in RAM size is constrained
425                 if (contentlen > MAX_POST_SIZE) {
426                     errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
427                     goto ExitOnError;
428                 }
429
430                 // Size is OK, let's allocate a buffer to hold post data
431                 postHandle->type = AFB_POST_JSON;
432                 postHandle->private = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
433
434                 // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
435                 return MHD_YES;
436
437             } else {
438                 // We only support Json and Form Post format
439                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
440                 goto ExitOnError;                
441             }   
442         }
443
444         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
445         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
446         // 'Internal application error, closing connection'.            
447         if (*upload_data_size) {
448     
449             if (postHandle->type == AFB_POST_FORM) {
450                 // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
451                 MHD_post_process (postHandle->pp, upload_data, *upload_data_size);
452             }
453             
454             // Process JsonPost request when buffer is completed let's call API    
455             if (postHandle->type == AFB_POST_JSON) {
456                 // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
457                 memcpy(&postHandle->private[postHandle->len], upload_data, *upload_data_size);
458                 postHandle->len = postHandle->len + *upload_data_size;
459             }
460             
461             *upload_data_size = 0;
462             return MHD_YES;
463             
464         } else {  // we have finish with Post reception let's finish the work
465             
466             // Create a request structure to finalise the request
467             request= createRequest (connection, session, url);
468             if (request->jresp != NULL) {
469                 errMessage = request->jresp;
470                 goto ExitOnError;
471             }
472             postRequest.type = postHandle->type;
473             
474             // Postform add application context handle to request
475             if (postHandle->type == AFB_POST_FORM) {
476                postRequest.data = (char*) postHandle;
477                request->post = &postRequest;
478             }
479             
480             if (postHandle->type == AFB_POST_JSON) {
481                 // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
482
483                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
484                 if (param) sscanf(param, "%i", &contentlen);
485
486                 // At this level we're may verify that we got everything and process DATA
487                 if (postHandle->len != contentlen) {
488                     errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len);
489                     goto ExitOnError;
490                 }
491
492                 // Before processing data, make sure buffer string is properly ended
493                 postHandle->private[postHandle->len] = '\0';
494                 postRequest.data = postHandle->private;
495                 request->post = &postRequest;
496
497                 // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
498             }
499         }
500     } else {
501         // this is a get we only need a request
502         request= createRequest (connection, session, url);
503     };
504
505 ProcessApiCall:    
506     // Request is ready let's call API without any extra handle
507     status = findAndCallApi (request, NULL);
508
509     serialized = json_object_to_json_string(request->jresp);
510     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
511     
512     // client did not pass token on URI let's use cookies 
513     if ((!request->restfull) && (request->context != NULL)) {
514        char cookie[256]; 
515        snprintf (cookie, sizeof (cookie), "%s=%s;path=%s;max-age=%d", COOKIE_NAME, request->uuid, request->config->rootapi,request->config->cntxTimeout); 
516        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
517     }
518     
519     // if requested add an error status
520     if (request->errcode != 0)  ret=MHD_queue_response (connection, request->errcode, webResponse);
521     else MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
522     
523     MHD_destroy_response(webResponse);
524     json_object_put(request->jresp); // decrease reference rqtcount to free the json object
525     freeRequest (request);
526     return MHD_YES;
527
528 ExitOnError:
529     freeRequest (request);
530     serialized = json_object_to_json_string(errMessage);
531     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
532     MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
533     MHD_destroy_response(webResponse);
534     json_object_put(errMessage); // decrease reference rqtcount to free the json object
535     return MHD_YES;
536 }
537
538
539 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
540 STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
541     int idx, jdx;
542
543     for (idx = 0; plugins[idx] != NULL; idx++) {
544         if (plugins[idx]->type != AFB_PLUGIN_JSON) {
545             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN_JSON, plugins[idx]->type);
546         } else {
547             // some sanity controls
548             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
549                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
550                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
551                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
552                 return NULL;
553             }
554
555             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
556             
557             // Prebuild plugin jtype to boost API response
558             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
559             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
560             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
561             
562               
563             // Prebuild each API jtype to boost API json response
564             for (jdx = 0; plugins[idx]->apis[jdx].name != NULL; jdx++) {
565                 AFB_privateApi *private = malloc (sizeof (AFB_privateApi));
566                 if (plugins[idx]->apis[jdx].private != NULL) {
567                     fprintf (stderr, "WARNING: plugin=%s api=%s private handle should be NULL=0x%x\n"
568                             ,plugins[idx]->prefix,plugins[idx]->apis[jdx].name, plugins[idx]->apis[jdx].private);
569                 }
570                 private->len = strlen (plugins[idx]->apis[jdx].name);
571                 private->jtype=json_object_new_string(plugins[idx]->apis[jdx].name);
572                 json_object_get(private->jtype); // increase reference count to make it permanent
573                 plugins[idx]->apis[jdx].private = private;
574             }
575         }
576     }
577     return (plugins);
578 }
579
580 STATIC void scanDirectory(char *dirpath, int dirfd, AFB_plugin **plugins, int *count) {
581     DIR *dir;
582     void *libso;
583     struct dirent pluginDir, *result;
584     AFB_plugin* (*pluginRegisterFct)(void);
585     char pluginPath[255];   
586
587     // Open Directory to scan over it
588     dir = fdopendir (dirfd);
589     if (dir == NULL) {
590         fprintf(stderr, "ERROR in scanning directory\n");
591         return; 
592     }
593     if (verbose) fprintf (stderr, "Scanning dir=[%s] for plugins\n", dirpath);
594
595     for (;;) {
596          readdir_r(dir, &pluginDir, &result);
597          if (result == NULL) break;
598
599         // Loop on any contained directory
600         if ((pluginDir.d_type == DT_DIR) && (pluginDir.d_name[0] != '.')) {
601            int fd = openat (dirfd, pluginDir.d_name, O_DIRECTORY);
602            char newpath[255];
603            strncpy (newpath, dirpath, sizeof(newpath));
604            strncat (newpath, "/", sizeof(newpath));
605            strncat (newpath, pluginDir.d_name, sizeof(newpath));
606            
607            scanDirectory (newpath, fd, plugins, count);
608            close (fd);
609
610         } else {
611
612             // This is a file but not a plugin let's move to next directory element
613             if (!strstr (pluginDir.d_name, ".so")) continue;
614
615             // This is a loadable library let's check if it's a plugin
616             snprintf (pluginPath, sizeof(pluginPath), "%s/%s", dirpath, pluginDir.d_name);
617             libso = dlopen (pluginPath, RTLD_NOW | RTLD_LOCAL);
618
619             // Load fail we ignore this .so file            
620             if (!libso) {
621                 fprintf(stderr, "[%s] is not loadable, continuing...\n", pluginDir.d_name);
622                 continue;
623             }
624
625             pluginRegisterFct = dlsym (libso, "pluginRegister");
626
627             if (!pluginRegisterFct) {
628                 fprintf(stderr, "[%s] is not an AFB plugin, continuing...\n", pluginDir.d_name);
629                 continue;
630             }
631
632             // if max plugin is reached let's stop searching
633             if (*count == AFB_MAX_PLUGINS) {
634                 fprintf(stderr, "[%s] is not loaded [Max Count=%d reached]\n", *count);
635                 continue;
636             }
637
638             if (verbose) fprintf(stderr, "[%s] is a valid AFB plugin, loading pos[%d]\n", pluginDir.d_name, *count);
639             plugins[*count] = pluginRegisterFct();
640             if (!plugins[*count]) {
641                 if (verbose) fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", pluginDir.d_name);
642             } else
643                 *count = *count +1;
644         }
645     }
646     closedir (dir);
647 }
648
649 void initPlugins(AFB_session *session) {
650     AFB_plugin **plugins;
651     
652     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
653     int count = 0;
654     char *dirpath;
655     int dirfd;
656
657     /* pre-allocate for AFB_MAX_PLUGINS plugins, we will downsize later */
658     plugins = (AFB_plugin **) malloc (AFB_MAX_PLUGINS *sizeof(AFB_plugin*));
659     
660     // Loop on every directory passed in --plugins=xxx
661     while (dirpath = strsep(&session->config->ldpaths, ":")) {
662             // Ignore any directory we fail to open
663         if ((dirfd = open(dirpath, O_DIRECTORY)) <= 0) {
664             fprintf(stderr, "Invalid directory path=[%s]\n", dirpath);
665             continue;
666         }
667         scanDirectory (dirpath, dirfd, plugins, &count);
668         close (dirfd);
669     }
670
671     if (count == 0) {
672         fprintf(stderr, "No plugins found, afb-daemon is unlikely to work in this configuration, exiting...\n");
673         exit (-1);
674     }
675     
676     // downsize structure to effective number of loaded plugins
677     plugins = (AFB_plugin **)realloc (plugins, (count+1)*sizeof(AFB_plugin*));
678     plugins[count] = NULL;
679
680     // complete plugins and save them within current sessions    
681     session->plugins = RegisterJsonPlugins(plugins);
682     session->config->pluginCount = count;
683 }