splitting rest-api in two parts
[src/app-framework-binder.git] / src / main.c
1 /* 
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author José Bollo <jose.bollo@iot.bzh>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <syslog.h>
20 #include <setjmp.h>
21 #include <signal.h>
22 #include <getopt.h>
23 #include <pwd.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #include "local-def.h"
28 #include "afb-plugins.h"
29
30 #if !defined(PLUGIN_INSTALL_DIR)
31 #error "you should define PLUGIN_INSTALL_DIR"
32 #endif
33
34 #define AFB_VERSION    "0.1"
35
36 // Define command line option
37 #define SET_VERBOSE        1
38 #define SET_BACKGROUND     2
39 #define SET_FORGROUND      3
40 #define SET_FAKE_MOD       4
41
42 #define SET_TCP_PORT       5
43 #define SET_ROOT_DIR       6
44 #define SET_ROOT_BASE      7
45 #define SET_ROOT_API       8
46 #define SET_ALIAS          9
47
48 #define SET_CACHE_TIMEOUT  10
49 #define SET_SESSION_DIR    11
50
51 #define SET_AUTH_TOKEN     12
52 #define SET_LDPATH         13
53 #define SET_APITIMEOUT     14
54 #define SET_CNTXTIMEOUT    15
55
56 #define DISPLAY_VERSION    16
57 #define DISPLAY_HELP       17
58
59 #define SET_MODE           18
60 #define SET_READYFD        19
61
62 // Command line structure hold cli --command + help text
63 typedef struct {
64   int  val;        // command number within application
65   int  has_arg;    // command number within application
66   char *name;      // command as used in --xxxx cli
67   char *help;      // help text
68 } AFB_options;
69
70
71 // Supported option
72 static  AFB_options cliOptions [] = {
73   {SET_VERBOSE      ,0,"verbose"         , "Verbose Mode"},
74
75   {SET_FORGROUND    ,0,"foreground"      , "Get all in foreground mode"},
76   {SET_BACKGROUND   ,0,"daemon"          , "Get all in background mode"},
77
78   {SET_TCP_PORT     ,1,"port"            , "HTTP listening TCP port  [default 1234]"},
79   {SET_ROOT_DIR     ,1,"rootdir"         , "HTTP Root Directory [default $HOME/.AFB]"},
80   {SET_ROOT_BASE    ,1,"rootbase"        , "Angular Base Root URL [default /opa]"},
81   {SET_ROOT_API     ,1,"rootapi"         , "HTML Root API URL [default /api]"},
82   {SET_ALIAS        ,1,"alias"           , "Muliple url map outside of rootdir [eg: --alias=/icons:/usr/share/icons]"},
83   
84   {SET_APITIMEOUT   ,1,"apitimeout"      , "Plugin API timeout in seconds [default 10]"},
85   {SET_CNTXTIMEOUT  ,1,"cntxtimeout"     , "Client Session Context Timeout [default 900]"},
86   {SET_CACHE_TIMEOUT,1,"cache-eol"       , "Client cache end of live [default 3600s]"},
87   
88   {SET_SESSION_DIR  ,1,"sessiondir"      , "Sessions file path [default rootdir/sessions]"},
89
90   {SET_LDPATH       ,1,"ldpaths"         , "Load Plugins from dir1:dir2:... [default = PLUGIN_INSTALL_DIR"},
91   {SET_AUTH_TOKEN   ,1,"token"           , "Initial Secret [default=no-session, --token="" for session without authentication]"},
92   
93   {DISPLAY_VERSION  ,0,"version"         , "Display version and copyright"},
94   {DISPLAY_HELP     ,0,"help"            , "Display this help"},
95
96   {SET_MODE         ,1,"mode"            , "set the mode: either local, remote or global"},
97   {SET_READYFD      ,1,"readyfd"         , "set the #fd to signal when ready"},
98   {0, 0, NULL, NULL}
99  };
100
101 static AFB_aliasdir aliasdir[MAX_ALIAS];
102 static int aliascount = 0;
103
104
105 /*----------------------------------------------------------
106  | printversion
107  |   print version and copyright
108  +--------------------------------------------------------- */
109 static void printVersion (void)
110 {
111    fprintf (stderr,"\n----------------------------------------- \n");
112    fprintf (stderr,"|  AFB [Application Framework Binder] version=%s |\n", AFB_VERSION);
113    fprintf (stderr,"----------------------------------------- \n");
114    fprintf (stderr,"|  Copyright(C) 2016 /IoT.bzh [fulup -at- iot.bzh]\n");
115    fprintf (stderr,"|  AFB comes with ABSOLUTELY NO WARRANTY.\n");
116    fprintf (stderr,"|  Licence Apache 2\n\n");
117    exit (0);
118 }
119
120 // load config from disk and merge with CLI option
121 static AFB_error config_set_default (AFB_session * session)
122 {
123    static char cacheTimeout [10];
124    
125    // default HTTP port
126    if (session->config->httpdPort == 0) session->config->httpdPort=1234;
127    
128    // default Plugin API timeout
129    if (session->config->apiTimeout == 0) session->config->apiTimeout=DEFLT_API_TIMEOUT;
130    
131    // default AUTH_TOKEN
132    if (session->config->token == NULL) session->config->token= DEFLT_AUTH_TOKEN;
133
134    // cache timeout default one hour
135    if (session->config->cacheTimeout == 0) session->config->cacheTimeout=DEFLT_CACHE_TIMEOUT;
136
137    // cache timeout default one hour
138    if (session->config->cntxTimeout == 0) session->config->cntxTimeout=DEFLT_CNTX_TIMEOUT;
139
140    if (session->config->rootdir == NULL) {
141        session->config->rootdir = getenv("AFBDIR");
142        if (session->config->rootdir == NULL) {
143            session->config->rootdir = malloc (512);
144            strncpy  (session->config->rootdir, getenv("HOME"),512);
145            strncat (session->config->rootdir, "/.AFB",512);
146        }
147        // if directory does not exist createit
148        mkdir (session->config->rootdir,  O_RDWR | S_IRWXU | S_IRGRP);
149    }
150    
151    // if no Angular/HTML5 rootbase let's try '/' as default
152    if  (session->config->rootbase == NULL) {
153        session->config->rootbase = "/opa";
154    }
155    
156    if  (session->config->rootapi == NULL) {
157        session->config->rootapi = "/api";
158    }
159
160    if  (session->config->ldpaths == NULL) {
161        session->config->ldpaths = PLUGIN_INSTALL_DIR;
162    }
163
164    // if no session dir create a default path from rootdir
165    if  (session->config->sessiondir == NULL) {
166        session->config->sessiondir = malloc (512);
167        strncpy (session->config->sessiondir, session->config->rootdir, 512);
168        strncat (session->config->sessiondir, "/sessions",512);
169    }
170
171    // if no config dir create a default path from sessiondir
172    if  (session->config->console == NULL) {
173        session->config->console = malloc (512);
174        strncpy (session->config->console, session->config->sessiondir, 512);
175        strncat (session->config->console, "/AFB-console.out",512);
176    }
177
178    // cacheTimeout is an integer but HTTPd wants it as a string
179    snprintf (cacheTimeout, sizeof (cacheTimeout),"%d", session->config->cacheTimeout);
180    session->cacheTimeout = cacheTimeout; // httpd uses cacheTimeout string version
181
182    return AFB_SUCCESS;
183 }
184
185
186 /*----------------------------------------------------------
187  | printHelp
188  |   print information from long option array
189  +--------------------------------------------------------- */
190
191  static void printHelp(char *name) {
192     int ind;
193     char command[20];
194
195     fprintf (stderr,"%s:\nallowed options\n", name);
196     for (ind=0; cliOptions [ind].name != NULL;ind++)
197     {
198       // display options
199       if (cliOptions [ind].has_arg == 0 )
200       {
201              fprintf (stderr,"  --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help);
202       } else {
203          sprintf(command,"%s=xxxx", cliOptions [ind].name);
204          fprintf (stderr,"  --%-15s %s\n", command, cliOptions[ind].help);
205       }
206     }
207     fprintf (stderr,"Example:\n  %s\\\n  --verbose --port=1234 --token='azerty' --ldpaths=build/plugins:/usr/lib64/agl/plugins\n", name);
208 } // end printHelp
209
210 /*---------------------------------------------------------
211  | main
212  |   Parse option and launch action
213  +--------------------------------------------------------- */
214
215 static void parse_arguments(int argc, char *argv[], AFB_session *session)
216 {
217   char*          programName = argv [0];
218   int            optionIndex = 0;
219   int            optc, ind;
220   int            nbcmd;
221   struct option *gnuOptions;
222
223   // ------------- Build session handler & init config -------
224   memset(&aliasdir  ,0,sizeof(aliasdir));
225   session->config->aliasdir = aliasdir;
226
227   // ------------------ Process Command Line -----------------------
228
229   // if no argument print help and return
230   if (argc < 2) {
231        printHelp(programName);
232        exit(1);
233   }
234
235   // build GNU getopt info from cliOptions
236   nbcmd = sizeof (cliOptions) / sizeof (AFB_options);
237   gnuOptions = malloc (sizeof (*gnuOptions) * (unsigned)nbcmd);
238   for (ind=0; ind < nbcmd;ind++) {
239     gnuOptions [ind].name    = cliOptions[ind].name;
240     gnuOptions [ind].has_arg = cliOptions[ind].has_arg;
241     gnuOptions [ind].flag    = 0;
242     gnuOptions [ind].val     = cliOptions[ind].val;
243   }
244
245   // get all options from command line
246   while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex))
247         != EOF)
248   {
249     switch (optc)
250     {
251      case SET_VERBOSE:
252        verbose = 1;
253        break;
254
255     case SET_TCP_PORT:
256        if (optarg == 0) goto needValueForOption;
257        if (!sscanf (optarg, "%d", &session->config->httpdPort)) goto notAnInteger;
258        break;
259        
260     case SET_APITIMEOUT:
261        if (optarg == 0) goto needValueForOption;
262        if (!sscanf (optarg, "%d", &session->config->apiTimeout)) goto notAnInteger;
263        break;
264
265     case SET_CNTXTIMEOUT:
266        if (optarg == 0) goto needValueForOption;
267        if (!sscanf (optarg, "%d", &session->config->cntxTimeout)) goto notAnInteger;
268        break;
269
270     case SET_ROOT_DIR:
271        if (optarg == 0) goto needValueForOption;
272        session->config->rootdir   = optarg;
273        if (verbose) fprintf(stderr, "Forcing Rootdir=%s\n",session->config->rootdir);
274        break;       
275        
276     case SET_ROOT_BASE:
277        if (optarg == 0) goto needValueForOption;
278        session->config->rootbase   = optarg;
279        if (verbose) fprintf(stderr, "Forcing Rootbase=%s\n",session->config->rootbase);
280        break;
281
282     case SET_ROOT_API:
283        if (optarg == 0) goto needValueForOption;
284        session->config->rootapi   = optarg;
285        if (verbose) fprintf(stderr, "Forcing Rootapi=%s\n",session->config->rootapi);
286        break;
287        
288     case SET_ALIAS:
289        if (optarg == 0) goto needValueForOption;
290        if (aliascount < MAX_ALIAS) {
291             aliasdir[aliascount].url  = strsep(&optarg,":");
292             if (optarg == NULL) {
293               fprintf(stderr, "missing ':' in alias %s, ignored\n", aliasdir[aliascount].url);
294             } else {
295               aliasdir[aliascount].path = optarg;
296               aliasdir[aliascount].len  = strlen(aliasdir[aliascount].url);
297               if (verbose) fprintf(stderr, "Alias url=%s path=%s\n", aliasdir[aliascount].url, aliasdir[aliascount].path);
298               aliascount++;
299             }
300        } else {
301            fprintf(stderr, "Too many aliases [max:%d] %s ignored\n", MAX_ALIAS, optarg);
302        }     
303        break;
304        
305     case SET_AUTH_TOKEN:
306        if (optarg == 0) goto needValueForOption;
307        session->config->token   = optarg;
308        break;
309
310     case SET_LDPATH:
311        if (optarg == 0) goto needValueForOption;
312        session->config->ldpaths = optarg;
313        break;
314
315     case SET_SESSION_DIR:
316        if (optarg == 0) goto needValueForOption;
317        session->config->sessiondir   = optarg;
318        break;
319
320     case  SET_CACHE_TIMEOUT:
321        if (optarg == 0) goto needValueForOption;
322        if (!sscanf (optarg, "%d", &session->config->cacheTimeout)) goto notAnInteger;
323        break;
324
325     case SET_FAKE_MOD:
326        if (optarg != 0) goto noValueForOption;
327        session->fakemod  = 1;
328        break;
329
330     case SET_FORGROUND:
331        if (optarg != 0) goto noValueForOption;
332        session->foreground  = 1;
333        break;
334
335     case SET_BACKGROUND:
336        if (optarg != 0) goto noValueForOption;
337        session->background  = 1;
338        break;
339
340     case SET_MODE:
341        if (optarg == 0) goto needValueForOption;
342        if (!strcmp(optarg, "local")) session->config->mode = AFB_MODE_LOCAL;
343        else if (!strcmp(optarg, "remote")) session->config->mode = AFB_MODE_REMOTE;
344        else if (!strcmp(optarg, "global")) session->config->mode = AFB_MODE_GLOBAL;
345        else goto badMode;
346        break;
347
348     case SET_READYFD:
349        if (optarg == 0) goto needValueForOption;
350        if (!sscanf (optarg, "%u", &session->readyfd)) goto notAnInteger;
351        break;
352
353     case DISPLAY_VERSION:
354        if (optarg != 0) goto noValueForOption;
355        printVersion();
356        exit(0);
357
358     case DISPLAY_HELP:
359      default:
360        printHelp(programName);
361        exit(0);
362     }
363   }
364   free(gnuOptions);
365  
366   config_set_default  (session);
367   return;
368
369
370 needValueForOption:
371   fprintf (stderr,"\nERR: AFB-daemon option [--%s] need a value i.e. --%s=xxx\n\n"
372           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
373   exit (1);
374
375 notAnInteger:
376   fprintf (stderr,"\nERR: AFB-daemon option [--%s] requirer an interger i.e. --%s=9\n\n"
377           ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name);
378   exit (1);
379
380 noValueForOption:
381   fprintf (stderr,"\nERR: AFB-daemon option [--%s] don't take value\n\n"
382           ,gnuOptions[optionIndex].name);
383   exit (1);
384
385 badMode:
386   fprintf (stderr,"\nERR: AFB-daemon option [--%s] only accepts local, global or remote.\n\n"
387           ,gnuOptions[optionIndex].name);
388   exit (1);
389 }
390
391 /*----------------------------------------------------------
392  | closeSession
393  |   try to close everything before leaving
394  +--------------------------------------------------------- */
395 static void closeSession (int status, void *data) {
396         /* AFB_session *session = data; */
397 }
398
399 /*----------------------------------------------------------
400  | timeout signalQuit
401  |
402  +--------------------------------------------------------- */
403 void signalQuit (int signum) {
404
405   sigset_t sigset;
406
407   // unlock timeout signal to allow a new signal to come
408   sigemptyset (&sigset);
409   sigaddset   (&sigset, SIGABRT);
410   sigprocmask (SIG_UNBLOCK, &sigset, 0);
411
412   fprintf (stderr, "ERR: Received signal quit\n");
413   syslog (LOG_ERR, "Daemon got kill3 & quit [please report bug]");
414   exit(1);
415 }
416
417
418 /*----------------------------------------------------------
419  | listenLoop
420  |   Main listening HTTP loop
421  +--------------------------------------------------------- */
422 static void listenLoop (AFB_session *session) {
423   AFB_error  err;
424
425   // ------ Start httpd server
426
427    err = httpdStart (session);
428    if (err != AFB_SUCCESS) return;
429
430         if (session->readyfd != 0) {
431                 static const char readystr[] = "READY=1";
432                 write(session->readyfd, readystr, sizeof(readystr) - 1);
433                 close(session->readyfd);
434         }
435
436    // infinite loop
437    httpdLoop(session);
438
439    fprintf (stderr, "hoops returned from infinite loop [report bug]\n");
440 }
441   
442 /*----------------------------------------------------------
443  | daemonize
444  |   set the process in background
445  +--------------------------------------------------------- */
446 static void daemonize(AFB_session *session)
447 {
448   int            consoleFD;
449   int            pid;
450
451       // open /dev/console to redirect output messAFBes
452       consoleFD = open(session->config->console, O_WRONLY | O_APPEND | O_CREAT , 0640);
453       if (consoleFD < 0) {
454                 fprintf (stderr,"\nERR: AFB-daemon cannot open /dev/console (use --foreground)\n\n");
455                 exit (1);
456       }
457
458       // fork process when running background mode
459       pid = fork ();
460
461       // if fail nothing much to do
462       if (pid == -1) {
463                 fprintf (stderr,"\nERR: AFB-daemon Failed to fork son process\n\n");
464                 exit (1);
465         }
466
467       // if in father process, just leave
468       if (pid != 0) _exit (0);
469
470       // son process get all data in standalone mode
471      printf ("\nAFB: background mode [pid:%d console:%s]\n", getpid(),session->config->console);
472
473       // redirect default I/O on console
474       close (2); dup(consoleFD);  // redirect stderr
475       close (1); dup(consoleFD);  // redirect stdout
476       close (0);           // no need for stdin
477       close (consoleFD);
478
479 #if 0
480          setsid();   // allow father process to fully exit
481      sleep (2);  // allow main to leave and release port
482 #endif
483
484          fprintf (stderr, "----------------------------\n");
485          fprintf (stderr, "INF: main background pid=%d\n", getpid());
486          fflush  (stderr);
487 }
488
489 /*---------------------------------------------------------
490  | main
491  |   Parse option and launch action
492  +--------------------------------------------------------- */
493
494 int main(int argc, char *argv[])  {
495   AFB_session    *session;
496
497   // open syslog if ever needed
498   openlog("afb-daemon", 0, LOG_DAEMON);
499
500   // ------------- Build session handler & init config -------
501   session = calloc (1, sizeof (AFB_session));
502   session->config = calloc (1, sizeof (AFB_config));
503
504   on_exit(closeSession, session);
505   parse_arguments(argc, argv, session);
506
507   // ------------------ sanity check ----------------------------------------
508   if  ((session->background) && (session->foreground)) {
509     fprintf (stderr, "ERR: cannot select foreground & background at the same time\n");
510      exit (1);
511   }
512   if (session->config->httpdPort <= 0) {
513     fprintf (stderr, "ERR: no port is defined\n");
514      exit (1);
515   }
516
517   initPlugins(session);
518   ctxStoreInit(CTX_NBCLIENTS);
519
520   // ------------------ Some useful default values -------------------------
521   if  ((session->background == 0) && (session->foreground == 0)) session->foreground=1;
522
523   // ------------------ clean exit on CTR-C signal ------------------------
524   if (signal (SIGINT, signalQuit) == SIG_ERR || signal (SIGABRT, signalQuit) == SIG_ERR) {
525      fprintf (stderr, "ERR: main fail to install Signal handler\n");
526      return 1;
527   }
528
529
530   // let's run this program with a low priority
531   nice (20);
532
533   // ------------------ Finaly Process Commands -----------------------------
534   // let's not take the risk to run as ROOT
535   //if (getuid() == 0)  goto errorNoRoot;
536
537 #if defined(ALLOWS_SESSION_FILES)
538   // check session dir and create if it does not exist
539   if (sessionCheckdir (session) != AFB_SUCCESS) {
540         fprintf (stderr,"\nERR: AFB-daemon cannot read/write session dir\n\n");
541         exit (1);
542   }
543 #endif
544   if (verbose) fprintf (stderr, "AFB: notice Init config done\n");
545
546   // ---- run in foreground mode --------------------
547   if (session->foreground) {
548
549         if (verbose) fprintf (stderr,"AFB: notice Foreground mode\n");
550
551   } // end foreground
552
553   // --------- run in background mode -----------
554   if (session->background) {
555
556       if (verbose) printf ("AFB: Entering background mode\n");
557
558       daemonize(session);
559
560          // if everything look OK then look forever
561          syslog (LOG_ERR, "AFB: Entering infinite loop in background mode");
562
563
564   } // end background-foreground
565
566
567   listenLoop(session);
568   if (verbose) printf ("\n---- Application Framework Binder Normal End ------\n");
569   exit(0);
570
571 }
572
573