Merge origin/master
[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;
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 (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=[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' && 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=%d 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     int idx;
326
327     // Start with a clean request
328     request = calloc (1, sizeof (AFB_request));
329     char *urlcpy1, *urlcpy2;
330     char *baseapi, *baseurl;
331       
332     // Extract plugin urlpath from request and make two copy because strsep overload copy
333     urlcpy1 = urlcpy2 = strdup(url);
334     baseurl = strsep(&urlcpy2, "/");
335     if (baseurl == NULL) {
336         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
337         request->errcode = MHD_HTTP_BAD_REQUEST;
338         goto Done;
339     }
340
341     // let's compute URL and call API
342     baseapi = strsep(&urlcpy2, "/");
343     if (baseapi == NULL) {
344         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url);
345         request->errcode = MHD_HTTP_BAD_REQUEST;
346         goto Done;
347     }
348     
349     // build request structure
350     request->connection = connection;
351     request->config = session->config;
352     request->url    = url;
353     request->prefix = strdup (baseurl);
354     request->api    = strdup (baseapi);
355     request->plugins= session->plugins;
356     // note request->handle is fed with request->context in ctxClientGet
357
358 Done:    
359     free(urlcpy1);
360     return (request);
361 }
362
363 // process rest API query
364 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
365     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
366     
367     static int postcount = 0; // static counter to debug POST protocol
368     json_object *errMessage;
369     AFB_error status;
370     struct MHD_Response *webResponse;
371     const char *serialized;
372     AFB_request *request;
373     AFB_PostHandle *postHandle;
374     AFB_PostRequest postRequest;
375     int ret;
376     
377     // fprintf (stderr, "doRestAPI method=%s posthandle=0x%x\n", method, con_cls);
378     
379     // if post data may come in multiple calls
380     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
381         const char *encoding, *param;
382         int contentlen = -1;
383         postHandle = *con_cls;
384
385         // This is the initial post event let's create form post structure POST data come in multiple events
386         if (postHandle == NULL) {
387
388             // allocate application POST processor handle to zero
389             postHandle = calloc(1, sizeof (AFB_PostHandle));
390             postHandle->uid = postcount++; // build a UID for DEBUG
391             *con_cls = postHandle;  // update context with posthandle
392             
393             // Let make sure we have the right encoding and a valid length
394             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
395             
396             // We are facing an empty post let's process it as a get
397             if (encoding == NULL) {
398                 postHandle->type   = AFB_POST_EMPTY;
399                 return MHD_YES;
400             }
401         
402             // Form post is handle through a PostProcessor and call API once per form key
403             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
404                 if (verbose) fprintf(stderr, "Create doPostIterate[uid=%d posthandle=0x%x]\n", postHandle->uid, postHandle);
405
406                 request = createRequest (connection, session, url);
407                 if (request->jresp != NULL) goto ProcessApiCall;
408                 postHandle->type   = AFB_POST_FORM;
409                 postHandle->privatebuf = (void*)request;
410                 postHandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, &doPostIterate, postHandle);
411                 
412                 if (NULL == postHandle->pp) {
413                     fprintf(stderr,"OOPS: Internal error fail to allocate MHD_create_post_processor\n");
414                     free (postHandle);
415                     return MHD_NO;
416                 }
417                 return MHD_YES;
418             }           
419         
420             // POST json is store into a buffer and present in one piece to API
421             if (strcasestr(encoding, JSON_CONTENT) != NULL) {
422
423                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
424                 if (param) sscanf(param, "%i", &contentlen);
425
426                 // Because PostJson are build in RAM size is constrained
427                 if (contentlen > MAX_POST_SIZE) {
428                     errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
429                     goto ExitOnError;
430                 }
431
432                 // Size is OK, let's allocate a buffer to hold post data
433                 postHandle->type = AFB_POST_JSON;
434                 postHandle->privatebuf = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
435
436                 // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
437                 return MHD_YES;
438
439             } else {
440                 // We only support Json and Form Post format
441                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
442                 goto ExitOnError;                
443             }   
444         }
445
446         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
447         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
448         // 'Internal application error, closing connection'.            
449         if (*upload_data_size) {
450     
451             if (postHandle->type == AFB_POST_FORM) {
452                 // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
453                 MHD_post_process (postHandle->pp, upload_data, *upload_data_size);
454             }
455             
456             // Process JsonPost request when buffer is completed let's call API    
457             if (postHandle->type == AFB_POST_JSON) {
458                 // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
459                 memcpy(&postHandle->privatebuf[postHandle->len], upload_data, *upload_data_size);
460                 postHandle->len = postHandle->len + *upload_data_size;
461             }
462             
463             *upload_data_size = 0;
464             return MHD_YES;
465             
466         } else {  // we have finish with Post reception let's finish the work
467             
468             // Create a request structure to finalise the request
469             request= createRequest (connection, session, url);
470             if (request->jresp != NULL) {
471                 errMessage = request->jresp;
472                 goto ExitOnError;
473             }
474             postRequest.type = postHandle->type;
475             
476             // Postform add application context handle to request
477             if (postHandle->type == AFB_POST_FORM) {
478                postRequest.data = (char*) postHandle;
479                request->post = &postRequest;
480             }
481             
482             if (postHandle->type == AFB_POST_JSON) {
483                 // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
484
485                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
486                 if (param) sscanf(param, "%i", &contentlen);
487
488                 // At this level we're may verify that we got everything and process DATA
489                 if (postHandle->len != contentlen) {
490                     errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len);
491                     goto ExitOnError;
492                 }
493
494                 // Before processing data, make sure buffer string is properly ended
495                 postHandle->privatebuf[postHandle->len] = '\0';
496                 postRequest.data = postHandle->privatebuf;
497                 request->post = &postRequest;
498
499                 // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
500             }
501         }
502     } else {
503         // this is a get we only need a request
504         request= createRequest (connection, session, url);
505     };
506
507 ProcessApiCall:    
508     // Request is ready let's call API without any extra handle
509     status = findAndCallApi (request, NULL);
510
511     serialized = json_object_to_json_string(request->jresp);
512     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
513     
514     // client did not pass token on URI let's use cookies 
515     if ((!request->restfull) && (request->context != NULL)) {
516        char cookie[256]; 
517        snprintf (cookie, sizeof (cookie), "%s=%s;path=%s;max-age=%d", COOKIE_NAME, request->uuid, request->config->rootapi,request->config->cntxTimeout); 
518        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
519     }
520     
521     // if requested add an error status
522     if (request->errcode != 0)  ret=MHD_queue_response (connection, request->errcode, webResponse);
523     else MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
524     
525     MHD_destroy_response(webResponse);
526     json_object_put(request->jresp); // decrease reference rqtcount to free the json object
527     freeRequest (request);
528     return MHD_YES;
529
530 ExitOnError:
531     freeRequest (request);
532     serialized = json_object_to_json_string(errMessage);
533     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
534     MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
535     MHD_destroy_response(webResponse);
536     json_object_put(errMessage); // decrease reference rqtcount to free the json object
537     return MHD_YES;
538 }
539
540
541 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
542 STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
543     int idx, jdx;
544
545     for (idx = 0; plugins[idx] != NULL; idx++) {
546         if (plugins[idx]->type != AFB_PLUGIN_JSON) {
547             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN_JSON, plugins[idx]->type);
548         } else {
549             // some sanity controls
550             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
551                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
552                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
553                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
554                 return NULL;
555             }
556
557             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
558             
559             // Prebuild plugin jtype to boost API response
560             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
561             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
562             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
563             
564               
565             // Prebuild each API jtype to boost API json response
566             for (jdx = 0; plugins[idx]->apis[jdx].name != NULL; jdx++) {
567                 AFB_privateApi *privateapi = malloc (sizeof (AFB_privateApi));
568                 if (plugins[idx]->apis[jdx].privateapi != NULL) {
569                     fprintf (stderr, "WARNING: plugin=%s api=%s private handle should be NULL=0x%x\n"
570                             ,plugins[idx]->prefix,plugins[idx]->apis[jdx].name, plugins[idx]->apis[jdx].privateapi);
571                 }
572                 privateapi->len = strlen (plugins[idx]->apis[jdx].name);
573                 privateapi->jtype=json_object_new_string(plugins[idx]->apis[jdx].name);
574                 json_object_get(privateapi->jtype); // increase reference count to make it permanent
575                 plugins[idx]->apis[jdx].privateapi = privateapi;
576             }
577         }
578     }
579     return (plugins);
580 }
581
582 STATIC void scanDirectory(char *dirpath, int dirfd, AFB_plugin **plugins, int *count) {
583     DIR *dir;
584     void *libso;
585     struct dirent pluginDir, *result;
586     AFB_plugin* (*pluginRegisterFct)(void);
587     char pluginPath[255];   
588
589     // Open Directory to scan over it
590     dir = fdopendir (dirfd);
591     if (dir == NULL) {
592         fprintf(stderr, "ERROR in scanning directory\n");
593         return; 
594     }
595     if (verbose) fprintf (stderr, "Scanning dir=[%s] for plugins\n", dirpath);
596
597     for (;;) {
598          readdir_r(dir, &pluginDir, &result);
599          if (result == NULL) break;
600
601         // Loop on any contained directory
602         if ((pluginDir.d_type == DT_DIR) && (pluginDir.d_name[0] != '.')) {
603            int fd = openat (dirfd, pluginDir.d_name, O_DIRECTORY);
604            char newpath[255];
605            strncpy (newpath, dirpath, sizeof(newpath));
606            strncat (newpath, "/", sizeof(newpath));
607            strncat (newpath, pluginDir.d_name, sizeof(newpath));
608            
609            scanDirectory (newpath, fd, plugins, count);
610            close (fd);
611
612         } else {
613
614             // This is a file but not a plugin let's move to next directory element
615             if (!strstr (pluginDir.d_name, ".so")) continue;
616
617             // This is a loadable library let's check if it's a plugin
618             snprintf (pluginPath, sizeof(pluginPath), "%s/%s", dirpath, pluginDir.d_name);
619             libso = dlopen (pluginPath, RTLD_NOW | RTLD_LOCAL);
620
621             // Load fail we ignore this .so file            
622             if (!libso) {
623                 fprintf(stderr, "[%s] is not loadable, continuing...\n", pluginDir.d_name);
624                 continue;
625             }
626
627             pluginRegisterFct = dlsym (libso, "pluginRegister");
628
629             if (!pluginRegisterFct) {
630                 fprintf(stderr, "[%s] is not an AFB plugin, continuing...\n", pluginDir.d_name);
631                 continue;
632             }
633
634             // if max plugin is reached let's stop searching
635             if (*count == AFB_MAX_PLUGINS) {
636                 fprintf(stderr, "[%s] is not loaded [Max Count=%d reached]\n", *count);
637                 continue;
638             }
639
640             if (verbose) fprintf(stderr, "[%s] is a valid AFB plugin, loading pos[%d]\n", pluginDir.d_name, *count);
641             plugins[*count] = pluginRegisterFct();
642             if (!plugins[*count]) {
643                 if (verbose) fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", pluginDir.d_name);
644             } else
645                 *count = *count +1;
646         }
647     }
648     closedir (dir);
649 }
650
651 void initPlugins(AFB_session *session) {
652     AFB_plugin **plugins;
653     
654     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
655     int count = 0;
656     char *dirpath;
657     int dirfd;
658
659     /* pre-allocate for AFB_MAX_PLUGINS plugins, we will downsize later */
660     plugins = (AFB_plugin **) malloc (AFB_MAX_PLUGINS *sizeof(AFB_plugin*));
661     
662     // Loop on every directory passed in --plugins=xxx
663     while (dirpath = strsep(&session->config->ldpaths, ":")) {
664             // Ignore any directory we fail to open
665         if ((dirfd = open(dirpath, O_DIRECTORY)) <= 0) {
666             fprintf(stderr, "Invalid directory path=[%s]\n", dirpath);
667             continue;
668         }
669         scanDirectory (dirpath, dirfd, plugins, &count);
670         close (dirfd);
671     }
672
673     if (count == 0) {
674         fprintf(stderr, "No plugins found, afb-daemon is unlikely to work in this configuration, exiting...\n");
675         exit (-1);
676     }
677     
678     // downsize structure to effective number of loaded plugins
679     plugins = (AFB_plugin **)realloc (plugins, (count+1)*sizeof(AFB_plugin*));
680     plugins[count] = NULL;
681
682     // complete plugins and save them within current sessions    
683     session->plugins = RegisterJsonPlugins(plugins);
684     session->config->pluginCount = count;
685 }