removed private api and fix some few warnings
[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->privatebuf) free(postHandle->privatebuf);
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 = NULL;
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                             goto ExitOnDone;
111                        }
112                     }
113                     // Trigger a timer to protect from unacceptable long time execution
114                     alarm ((unsigned)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                         goto ExitOnDone;
128                     };
129                     
130                     if (verbose) fprintf(stderr, "Plugin=[%s] Api=[%s] Middleware=[%d] Client=[%p] 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' && request->config->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                                 goto ExitOnDone;
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                                 goto ExitOnDone;
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                                 goto ExitOnDone;
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                                 goto ExitOnDone;
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                                 goto ExitOnDone;
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                 // Store context in case it was updated by plugins
202                 if (request->context != NULL) clientCtx->contexts[plugidx] = request->context;               
203                 
204                 // handle intermediary Post Iterates out of band
205                 if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK)) return (AFB_SUCCESS);
206
207                 // Session close is done after the API call so API can still use session in closing API
208                 if (AFB_SESSION_CLOSE == plugin->apis[idx].session) ctxTokenReset (clientCtx, request);                    
209                 
210                 // API should return NULL of a valid Json Object
211                 if (jresp == NULL) {
212                     json_object_object_add(jcall, "status", json_object_new_string ("null"));
213                     json_object_object_add(jreqt, "request", jcall);
214                     request->errcode = MHD_HTTP_NO_RESPONSE;
215                     
216                 } else {
217                     json_object_object_add(jcall, "status", json_object_new_string ("processed"));
218                     json_object_object_add(jreqt, "request", jcall);
219                     json_object_object_add(jreqt, "response", jresp);
220                 }
221                 // cancel timeout and plugin signal handle before next call
222                 if (request->config->apiTimeout > 0) {
223                     alarm (0);
224                     for (sig=0; signals[sig] != 0; sig++) {
225                        signal (signals[sig], SIG_DFL);
226                     }
227                 }              
228             }       
229             goto ExitOnDone; 
230         }
231     }   
232     return (AFB_FAIL);
233     
234 ExitOnDone:
235     request->jresp = jreqt;
236     return (AFB_DONE);                        
237 }
238
239 STATIC AFB_error findAndCallApi (AFB_request *request, void *context) {
240     int idx;
241     AFB_error status;
242     
243     if (!request->api || !request->prefix) return (AFB_FAIL);
244    
245     // Search for a plugin with this urlpath
246     for (idx = 0; request->plugins[idx] != NULL; idx++) {
247         if (!strcmp(request->plugins[idx]->prefix, request->prefix)) {
248             status =callPluginApi(request, idx, context);
249             break;
250         }
251     }
252     // No plugin was found
253     if (request->plugins[idx] == NULL) {
254         request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s] Url=%s", request->prefix, request->url);
255         goto ExitOnError;
256     }  
257     
258     // plugin callback did not return a valid Json Object
259     if (status == AFB_FAIL) {
260         request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->api, request->prefix, request->url);
261         goto ExitOnError;
262     }
263     
264     // Everything look OK
265     return (status);
266     
267 ExitOnError:
268     request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
269     return (AFB_FAIL);
270 }
271
272 // This CB is call for every item with a form post it reformat iterator values
273 // and callback Plugin API for each Item within PostForm.
274 STATIC int doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
275               const char *filename, const char *mimetype,
276               const char *encoding, const char *data, uint64_t offset,
277               size_t size) {
278   
279   AFB_error    status;
280   AFB_PostItem item;
281     
282   // retrieve API request from Post iterator handle  
283   AFB_PostHandle *postHandle  = (AFB_PostHandle*)cls;
284   AFB_request *request = (AFB_request*)postHandle->privatebuf;
285   AFB_PostRequest postRequest;
286   
287   if (verbose)
288     fprintf (stderr, "postHandle key=%s filename=%s len=%zu mime=%s\n", key, filename, size, mimetype);
289    
290   // Create and Item value for Plugin API
291   item.kind     = kind;
292   item.key      = key;
293   item.filename = filename;
294   item.mimetype = mimetype;
295   item.encoding = encoding;
296   item.len      = size;
297   item.data     = data;
298   item.offset   = offset;
299   
300   // Reformat Request to make it somehow similar to GET/PostJson case
301   postRequest.data= (char*) postHandle;
302   postRequest.len = size;
303   postRequest.type= AFB_POST_FORM;;
304   request->post = &postRequest;
305   
306   // effectively call plugin API                 
307   status = findAndCallApi (request, &item);
308   // when returning no processing of postform stop
309   if (status != AFB_SUCCESS) return MHD_NO;
310   
311   // let's allow iterator to move to next item
312   return MHD_YES;
313 }
314
315 STATIC void freeRequest (AFB_request *request) {
316
317  free (request->prefix);    
318  free (request->api);    
319  free (request);    
320 }
321
322 STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_session *session, const char* url) {
323     
324     AFB_request *request;
325
326     // Start with a clean request
327     request = calloc (1, sizeof (AFB_request));
328     char *urlcpy1, *urlcpy2;
329     char *baseapi, *baseurl;
330       
331     // Extract plugin urlpath from request and make two copy because strsep overload copy
332     urlcpy1 = urlcpy2 = strdup(url);
333     baseurl = strsep(&urlcpy2, "/");
334     if (baseurl == NULL) {
335         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
336         request->errcode = MHD_HTTP_BAD_REQUEST;
337         goto Done;
338     }
339
340     // let's compute URL and call API
341     baseapi = strsep(&urlcpy2, "/");
342     if (baseapi == NULL) {
343         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url);
344         request->errcode = MHD_HTTP_BAD_REQUEST;
345         goto Done;
346     }
347     
348     // build request structure
349     request->connection = connection;
350     request->config = session->config;
351     request->url    = url;
352     request->prefix = strdup (baseurl);
353     request->api    = strdup (baseapi);
354     request->plugins= session->plugins;
355     // note request->handle is fed with request->context in ctxClientGet
356
357 Done:    
358     free(urlcpy1);
359     return (request);
360 }
361
362 // process rest API query
363 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
364     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
365     
366     static int postcount = 0; // static counter to debug POST protocol
367     json_object *errMessage;
368     AFB_error status;
369     struct MHD_Response *webResponse;
370     const char *serialized;
371     AFB_request *request = NULL;
372     AFB_PostHandle *postHandle;
373     AFB_PostRequest postRequest;
374     int ret;
375     
376     // fprintf (stderr, "doRestAPI method=%s posthandle=%p\n", method, con_cls);
377     
378     // if post data may come in multiple calls
379     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
380         const char *encoding, *param;
381         int contentlen = -1;
382         postHandle = *con_cls;
383
384         // This is the initial post event let's create form post structure POST data come in multiple events
385         if (postHandle == NULL) {
386
387             // allocate application POST processor handle to zero
388             postHandle = calloc(1, sizeof (AFB_PostHandle));
389             postHandle->uid = postcount++; // build a UID for DEBUG
390             *con_cls = postHandle;  // update context with posthandle
391             
392             // Let make sure we have the right encoding and a valid length
393             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
394             
395             // We are facing an empty post let's process it as a get
396             if (encoding == NULL) {
397                 postHandle->type   = AFB_POST_EMPTY;
398                 return MHD_YES;
399             }
400         
401             // Form post is handle through a PostProcessor and call API once per form key
402             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
403                 if (verbose) fprintf(stderr, "Create doPostIterate[uid=%d posthandle=%p]\n", postHandle->uid, postHandle);
404
405                 request = createRequest (connection, session, url);
406                 if (request->jresp != NULL) goto ProcessApiCall;
407                 postHandle->type   = AFB_POST_FORM;
408                 postHandle->privatebuf = (void*)request;
409                 postHandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, &doPostIterate, postHandle);
410                 
411                 if (NULL == postHandle->pp) {
412                     fprintf(stderr,"OOPS: Internal error fail to allocate MHD_create_post_processor\n");
413                     free (postHandle);
414                     return MHD_NO;
415                 }
416                 return MHD_YES;
417             }           
418         
419             // POST json is store into a buffer and present in one piece to API
420             if (strcasestr(encoding, JSON_CONTENT) != NULL) {
421
422                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
423                 if (param) sscanf(param, "%i", &contentlen);
424
425                 // Because PostJson are build in RAM size is constrained
426                 if (contentlen > MAX_POST_SIZE) {
427                     errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
428                     goto ExitOnError;
429                 }
430
431                 // Size is OK, let's allocate a buffer to hold post data
432                 postHandle->type = AFB_POST_JSON;
433                 postHandle->privatebuf = malloc((unsigned)contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
434
435                 // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
436                 return MHD_YES;
437
438             } else {
439                 // We only support Json and Form Post format
440                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
441                 goto ExitOnError;                
442             }   
443         }
444
445         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
446         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
447         // 'Internal application error, closing connection'.            
448         if (*upload_data_size) {
449     
450             if (postHandle->type == AFB_POST_FORM) {
451                 // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
452                 MHD_post_process (postHandle->pp, upload_data, *upload_data_size);
453             }
454             
455             // Process JsonPost request when buffer is completed let's call API    
456             if (postHandle->type == AFB_POST_JSON) {
457                 // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
458                 memcpy(&postHandle->privatebuf[postHandle->len], upload_data, *upload_data_size);
459                 postHandle->len = postHandle->len + *upload_data_size;
460             }
461             
462             *upload_data_size = 0;
463             return MHD_YES;
464             
465         } else {  // we have finish with Post reception let's finish the work
466             
467             // Create a request structure to finalise the request
468             request= createRequest (connection, session, url);
469             if (request->jresp != NULL) {
470                 errMessage = request->jresp;
471                 goto ExitOnError;
472             }
473             postRequest.type = postHandle->type;
474             
475             // Postform add application context handle to request
476             if (postHandle->type == AFB_POST_FORM) {
477                postRequest.data = (char*) postHandle;
478                request->post = &postRequest;
479             }
480             
481             if (postHandle->type == AFB_POST_JSON) {
482                 // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
483
484                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
485                 if (param) sscanf(param, "%i", &contentlen);
486
487                 // At this level we're may verify that we got everything and process DATA
488                 if (postHandle->len != contentlen) {
489                     errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len);
490                     goto ExitOnError;
491                 }
492
493                 // Before processing data, make sure buffer string is properly ended
494                 postHandle->privatebuf[postHandle->len] = '\0';
495                 postRequest.data = postHandle->privatebuf;
496                 request->post = &postRequest;
497
498                 // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
499             }
500         }
501     } else {
502         // this is a get we only need a request
503         request= createRequest (connection, session, url);
504     };
505
506 ProcessApiCall:    
507     // Request is ready let's call API without any extra handle
508     status = findAndCallApi (request, NULL);
509
510     serialized = json_object_to_json_string(request->jresp);
511     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
512     
513     // client did not pass token on URI let's use cookies 
514     if ((!request->restfull) && (request->context != NULL)) {
515        char cookie[256]; 
516        snprintf (cookie, sizeof (cookie), "%s-%d=%s; Path=%s; Max-Age=%d; HttpOnly", COOKIE_NAME, request->config->httpdPort, request->uuid, request->config->rootapi,request->config->cntxTimeout); 
517        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
518     }
519     
520     // if requested add an error status
521     if (request->errcode != 0)  ret=MHD_queue_response (connection, request->errcode, webResponse);
522     else MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
523     
524     MHD_destroy_response(webResponse);
525     json_object_put(request->jresp); // decrease reference rqtcount to free the json object
526     freeRequest (request);
527     return MHD_YES;
528
529 ExitOnError:
530     freeRequest (request);
531     serialized = json_object_to_json_string(errMessage);
532     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
533     MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
534     MHD_destroy_response(webResponse);
535     json_object_put(errMessage); // decrease reference rqtcount to free the json object
536     return MHD_YES;
537 }
538
539
540 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
541 STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
542     int idx;
543
544     for (idx = 0; plugins[idx] != NULL; idx++) {
545         if (plugins[idx]->type != AFB_PLUGIN_JSON) {
546             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN_JSON, plugins[idx]->type);
547         } else {
548             // some sanity controls
549             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
550                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
551                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
552                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
553                 return NULL;
554             }
555
556             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
557             
558             // Prebuild plugin jtype to boost API response
559             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
560             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
561             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
562         }
563     }
564     return (plugins);
565 }
566
567 STATIC void scanDirectory(char *dirpath, int dirfd, AFB_plugin **plugins, int *count) {
568     DIR *dir;
569     void *libso;
570     struct dirent pluginDir, *result;
571     AFB_plugin* (*pluginRegisterFct)(void);
572     char pluginPath[255];   
573
574     // Open Directory to scan over it
575     dir = fdopendir (dirfd);
576     if (dir == NULL) {
577         fprintf(stderr, "ERROR in scanning directory\n");
578         return; 
579     }
580     if (verbose) fprintf (stderr, "Scanning dir=[%s] for plugins\n", dirpath);
581
582     for (;;) {
583          readdir_r(dir, &pluginDir, &result);
584          if (result == NULL) break;
585
586         // Loop on any contained directory
587         if ((pluginDir.d_type == DT_DIR) && (pluginDir.d_name[0] != '.')) {
588            int fd = openat (dirfd, pluginDir.d_name, O_DIRECTORY);
589            char newpath[255];
590            strncpy (newpath, dirpath, sizeof(newpath));
591            strncat (newpath, "/", sizeof(newpath));
592            strncat (newpath, pluginDir.d_name, sizeof(newpath));
593            
594            scanDirectory (newpath, fd, plugins, count);
595            close (fd);
596
597         } else {
598
599             // This is a file but not a plugin let's move to next directory element
600             if (!strstr (pluginDir.d_name, ".so")) continue;
601
602             // This is a loadable library let's check if it's a plugin
603             snprintf (pluginPath, sizeof(pluginPath), "%s/%s", dirpath, pluginDir.d_name);
604             libso = dlopen (pluginPath, RTLD_NOW | RTLD_LOCAL);
605
606             // Load fail we ignore this .so file            
607             if (!libso) {
608                 fprintf(stderr, "[%s] is not loadable, continuing...\n", pluginDir.d_name);
609                 continue;
610             }
611
612             pluginRegisterFct = dlsym (libso, "pluginRegister");
613
614             if (!pluginRegisterFct) {
615                 fprintf(stderr, "[%s] is not an AFB plugin, continuing...\n", pluginDir.d_name);
616                 continue;
617             }
618
619             // if max plugin is reached let's stop searching
620             if (*count == AFB_MAX_PLUGINS) {
621                 fprintf(stderr, "[%s] is not loaded [Max Count=%d reached]\n", pluginDir.d_name, *count);
622                 continue;
623             }
624
625             if (verbose) fprintf(stderr, "[%s] is a valid AFB plugin, loading pos[%d]\n", pluginDir.d_name, *count);
626             plugins[*count] = pluginRegisterFct();
627             if (!plugins[*count]) {
628                 if (verbose) fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", pluginDir.d_name);
629             } else
630                 *count = *count +1;
631         }
632     }
633     closedir (dir);
634 }
635
636 void initPlugins(AFB_session *session) {
637     AFB_plugin **plugins;
638     
639     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
640     int count = 0;
641     char *dirpath;
642     int dirfd;
643
644     /* pre-allocate for AFB_MAX_PLUGINS plugins, we will downsize later */
645     plugins = (AFB_plugin **) malloc (AFB_MAX_PLUGINS *sizeof(AFB_plugin*));
646     
647     // Loop on every directory passed in --plugins=xxx
648     while ((dirpath = strsep(&session->config->ldpaths, ":"))) {
649             // Ignore any directory we fail to open
650         if ((dirfd = open(dirpath, O_DIRECTORY)) <= 0) {
651             fprintf(stderr, "Invalid directory path=[%s]\n", dirpath);
652             continue;
653         }
654         scanDirectory (dirpath, dirfd, plugins, &count);
655         close (dirfd);
656     }
657
658     if (count == 0) {
659         fprintf(stderr, "No plugins found, afb-daemon is unlikely to work in this configuration, exiting...\n");
660         exit (-1);
661     }
662     
663     // downsize structure to effective number of loaded plugins
664     plugins = (AFB_plugin **)realloc (plugins, (unsigned)(count+1)*sizeof(AFB_plugin*));
665     plugins[count] = NULL;
666
667     // complete plugins and save them within current sessions    
668     session->plugins = RegisterJsonPlugins(plugins);
669     session->config->pluginCount = count;
670 }