#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ninac.h" using namespace std; DEBUG_KEY debug_ninac ("ninac","Operation generale"); void ninac_debug (const char *ctl, ...) { char tmp[10000]; va_list list; va_start (list,ctl); vsnprintf (tmp,sizeof(tmp)-1,ctl,list); va_end (list); debug_printf (debug_ninac,"%s",tmp); } class SSTREAM_TCP: public SSTREAM_BUF{ _F_TCPSERVER_V1 *c; public: SSTREAM_TCP(_F_TCPSERVER_V1 *_c){ c = _c; } ~SSTREAM_TCP(){ c->send (getbuf()); } //void puts(const char *s){ // SSTREAM_BUF::puts (s); //} }; static void ninac_lock_unlock( TCPSERVER_V1 *c, bool newstate, // New state of the setlisten() int except) // Handle un-affected { for (int i=c->iter_init(); i != -1; i=c->iter_next()){ if (i != except) c->setlisten(i,newstate); } } static void ninac_lock_unlock( TCPSERVER_V1 *c1, TCPSERVER_V1 *c2, bool newstate, // New state of the setlisten() int except) // Handle un-affected { ninac_lock_unlock (c1,newstate,except); ninac_lock_unlock (c2,newstate,except); } static void ninac_process( int no, const char *line, _F_TCPSERVER_V1 *c, TCPSERVER_V1 *c1, TCPSERVER_V1 *c2, bool privileged, bool &endclient, int &locked_by) // Will hold the handle of the locker { int ret = 0; ninac_debug("process: %s\n", line) ; SSTRINGS words; int nb = str_splitlineq (line,words); endclient = false; static const char *NO_PRIV = "No privilege to exec\n"; bool sendend = true; if (nb >= 1){ SSTREAM_TCP ss (c); const char *verb = words.getitem(0)->get(); if (strcmp(verb,"setvar")==0 && (nb == 3 || nb == 4)){ const char *varname = words.getitem(1)->get(); const char *val = words.getitem(2)->get(); if (nb == 3){ ninac_setvart (varname,val,time(NULL)); }else{ time_t t = words.getitem(3)->getval(); ninac_setvart (varname,val,t); } }else if (strcmp(verb,"unsetvar")==0 && nb == 2){ ninac_unsetvar (words.getitem(1)->get()); }else if (strcmp(verb,"clearvars")==0 && nb == 1){ if (privileged){ ninac_clearvars (); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"cleartasks")==0 && nb == 1){ if (privileged){ ninac_cleartasks (); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"checkpoint")==0 && nb == 1){ if (privileged){ ninac_journal_close() ; ninac_checkpoint (); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"lock")==0 && nb == 1){ if (privileged){ ninac_lock_unlock (c1,c2,false,no); locked_by = no; }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"unlock")==0 && nb == 1){ if (privileged){ ninac_lock_unlock (c1,c2,true,no); locked_by = -1; }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"done")==0 && nb == 4){ const char *var = words.getitem(1)->get(); const char *subvar = words.getitem(2)->get(); const char *status = words.getitem(3)->get(); // Il faut rouler les post avant toutes choses au cas ou le post veut // manipuler les compteurs. ninac_runpost (var,subvar,ss); const char *master = ninac_getval ("master",subvar); if (master != NULL){ string tmp = string ("ninac.nb_tasks.") + var; int nb = ninac_getvalnum(tmp.c_str(),master,0); if (nb == 0){ tlmp_error ("Variable %s.%s below 0" ,tmp.c_str(),master); }else{ ninac_setvarnum (tmp.c_str(),master,nb-1); } tmp = string ("ninac.nb_tasks"); nb = ninac_getvalnum(tmp.c_str(),master,0); if (nb == 0){ tlmp_error ("Variable %s.%s below 0" ,tmp.c_str(),master); }else{ ninac_setvarnum (tmp.c_str(),master,nb-1); } } string tmp = string ("ninac.is_running.") + var; ninac_setvarnumt (tmp.c_str(),subvar,0,time(NULL)); // Avant de setter le retstatus, il faut sauver l'ancien dans le prev. tmp = string ("ninac.retstatus.") + var; int prev_status = ninac_getvalnum (tmp.c_str(),subvar,0); int prev_time = ninac_getvmtime (tmp.c_str(),subvar,time(NULL)); // Ici il faut passer time(NULL) car on veut que le timestamp // soit update. ninac_setvart (tmp.c_str(),subvar,status,time(NULL)); tmp = string ("ninac.retstatus_prev.") + var; ninac_setvarnumt (tmp.c_str(),subvar,prev_status,prev_time); }else if (strcmp(verb,"setstate")==0 && nb == 2){ ninac_setcondt (words.getitem(1)->get(),time(NULL)); }else if (strcmp(verb,"settask")==0){ words.remove_del(0); SSTRING errors; if (ninac_settask (words,errors)==-1){ c->sendf ("%s",errors.get()); ret = 1; } }else if (strcmp(verb,"unsettask")==0){ words.remove_del(0); SSTRING errors; if (ninac_unsettask (words,errors)==-1){ c->sendf ("%s",errors.get()); ret = 1; } }else if (strcmp(verb,"runtask")==0){ words.remove_del(0); SSTRING errors; if (ninac_runtask (words,errors)==-1){ c->sendf ("%s",errors.get()); ret = 1; } }else if (strcmp(verb,"unsetstate")==0 && nb == 2){ ninac_unsetcond (words.getitem(1)->get()); }else if (strcmp(verb,"setpriority")==0 && nb == 4){ ninac_setpriority (words.getitem(1)->get() ,words.getitem(2)->get(),words.getitem(3)->getval()); }else if (strcmp(verb,"dumpstate")==0 && nb == 1){ ninac_dumpcond (ss); }else if (strcmp(verb,"liststate")==0 && nb == 1){ ninac_listcond (ss); }else if (strcmp(verb,"listtask")==0 && nb == 1){ ninac_listtask (ss); }else if (strcmp(verb,"listtarget")==0 && nb == 1){ ninac_listtarget (ss); }else if (strcmp(verb,"listfunc")==0 && nb == 1){ ninac_listfunc (ss); }else if (strcmp(verb,"dumpvars")==0 && nb == 1){ ninac_dumpvars (ss); if (c->is_blocked()){ sendend = false; // We will send the last line // when the buffer is flushed } }else if (strcmp(verb,"getvar")==0 && nb == 2){ const char *var = words.getitem(1)->c_str(); const char *val = ninac_getval(var); if (val == NULL) val = ""; c->sendf ("%s\n",val); }else if (strcmp(verb,"getvar_ex")==0 && nb == 2){ const char *var = words.getitem(1)->c_str(); const char *val = ninac_getval_ex(var); if (val == NULL) val = ""; c->sendf ("%s\n",val); }else if (strcmp(verb,"stop")==0 && nb == 1){ if (privileged){ ninac_setvarnum ("ninac","running",0); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"run")==0 && nb == 1){ if (privileged){ ninac_setvarnum ("ninac","running",1); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"quit")==0 && nb == 1){ if (privileged){ exit(0); }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"solver")==0 && nb == 1){ if (privileged){ ninac_solver(); ninac_checkwait() ; ninac_journal_close() ; }else{ c->send (NO_PRIV); ret = 1; } }else if (strcmp(verb,"wait_ex")==0 && nb == 3){ const char *var = words.getitem(1)->c_str(); int timestamp = words.getitem(2)->getval(); ninac_addwait (no,var,timestamp); sendend = false; }else{ c->sendf ("Invalid command '%s'\n", verb); ret = 1; } }else{ c->send ("???\n"); ret = 1; } if (sendend) c->sendf ("## %d\n",ret); } struct CLIENT_DATA: public ARRAY_OBJ{ string id; string challenge; bool auth_ok; unsigned long from; CLIENT_DATA(){ auth_ok = false; from = 0; } }; /* Create the challenge used by ninac to encrypt its secret Return -1 if we can't create the challenge (resources ?) */ static int ninac_formatkey (string &challenge) { int ret = -1; // We create the challenge by combining the current time // and a random number. This create a unique solution // We are keeping the file opened for all the session static int fd = -1; if (fd == -1){ fd = open ("/dev/urandom",O_RDONLY); } if (fd == -1){ tlmp_error ("Can't open /dev/urandom (%s)",strerror(errno)); }else{ char tmp[8]; if (read (fd,tmp,8)!=8){ tlmp_error ("Can't read 8 bytes from /dev/urandom"); }else{ char buf[30]; for (int i=0; i<8; i++){ sprintf (buf+i*2,"%02x",tmp[i]); } time_t ti = time(NULL); sprintf (buf+16,"%lu",ti); challenge = buf; ret = 0; } } return ret; } /* Check the secret of the provider Return != -1 if the secret was valid */ static int ninac_auth(const char *challenge, const char *buf, string &id) { int ret = -1; SSTRING client,digest; const char *pt = str_copyword (client,buf); str_copyword (digest,pt); if (digest.is_empty()){ tlmp_error ("Invalid authentication"); }else{ FILE *fin = fopen ("/etc/ninac/clients.conf","r"); if (fin == NULL){ tlmp_error ("No /etc/ninac/clients.conf file"); }else{ char line[100]; bool client_found = false; while (fgets(line,sizeof(line)-1,fin)!=NULL){ char *pt = strchr(line,':'); if (pt == NULL){ tlmp_error ("Invalid line in suppliers.conf: %s",line); break; }else{ *pt++ = '\0'; if (client.cmp(line)==0){ client_found = true; strip_end(pt); SSTRING tmp; tmp.setfromf ("%s%s",challenge,pt); misc_sha (tmp.get(),tmp); if (tmp.cmp(digest)!=0){ tlmp_error ("Invalid challenge response from supplier %s",client.get()); }else{ // Ok, the supplier is authentified // we accept its requests id = client.c_str(); ret = 0; } break; } } } fclose (fin); if (!client_found){ tlmp_error ("Unknown supplier %s",client.get()); } } } return ret; } static bool solver_needed = false; static void ninac_solver_if ( CLIENT_DATA *data) { if (data->auth_ok && ninac_getvalnum("ninac","running",0)>0){ solver_needed = true; } } static void ninac_process( CLIENT_DATA *data, int no, const char *line, _F_TCPSERVER_V1 *c, TCPSERVER_V1 *c1, TCPSERVER_V1 *c2, bool privileged, bool &endclient, int &locked_by) { if (data->id.size()==0){ if (ninac_auth (data->challenge.c_str(),line,data->id)==-1){ endclient = true; }else{ data->auth_ok = true; } }else{ ninac_process (no,line,c,c1,c2,privileged,endclient,locked_by); if (endclient) ninac_solver_if (data); } } // On reveille ninac a tous les 2 secondes. static void ninac_setwakeup(TCPSERVER_V1 &tim) { int tb[2]; if (pipe(tb)!=-1){ tim.inject (tb[0]); if (fork()==0){ close (tb[0]); while (1){ sleep(2); if (write (tb[1]," ",1) != 1) break; } _exit(0); } close (tb[1]); }else{ tlmp_error ("Can't setup control pipe, ending (%s)" ,strerror(errno)); exit (-1); } } int main (int argc, char *argv[]) { glocal int ret = -1; glocal const char *tcpport = "8100"; glocal const char *tcpbind = "0.0.0.0"; glocal const char *unixpath = "/var/run/ninac.sock"; glocal const char *ninacdir = "/usr/bin"; glocal const char *taskdir = "/var/lib/ninac/tasks"; glocal const char *outputdir = "/var/log/ninac"; glocal const char *logfile = "/var/log/ninac/ninac.log"; glocal const char *functions = NULL; glocal const char *journal = NULL; glocal bool running = false; glocal bool background = false; glocal.ret = (argc,argv); setproginfo ("ninac",VERSION,"Ninac is not a cron"); setarg ('b',"tcpbind","Bind TCP port to specific address",glocal.tcpbind,false); setarg ('f',"functions","Function file",glocal.functions,false); setarg ('g',"background","Run in background",glocal.background,false); setarg ('j',"journal","Path of the journal file",glocal.journal,false); setarg ('l',"logfile","Log file",glocal.logfile,false); setarg ('n',"ninacdir","Directory where ninac utilies are located" ,glocal.ninacdir,false); setarg ('o',"outputdir","Resultat des commandes",glocal.outputdir,false); setarg ('p',"tcpport","TCP port for remote connection",glocal.tcpport,false); setarg ('r',"running","Enable the command solver immediatly" ,glocal.running,false); setarg ('t',"taskdir","Directory where ninac tasks are located" ,glocal.taskdir,false); setarg ('u',"unixpath","Unix domain socket for local connection",glocal.unixpath,false); syslog (LOG_ERR,"%s",msg); fprintf (stderr,"%s\n",msg); glocal int ret = -1; glocal int locked_by = -1; glocal TCPSERVER_V1 *prem; glocal TCPSERVER_V1 *ploc; signal (SIGPIPE,SIG_IGN); ninac_journal_setpath (glocal.journal); if (glocal.functions != NULL){ void *p = dlopen (glocal.functions,RTLD_NOW); if (p == NULL){ tlmp_error ("Can't open function file %s (%s), aborting" ,glocal.functions,dlerror()); exit (-1); } } ninac_setcmddirs (glocal.ninacdir,glocal.taskdir ,glocal.outputdir,glocal.logfile,glocal.unixpath); (glocal.tcpbind,glocal.tcpport,10); CLIENT_DATA *data = new CLIENT_DATA; info.data = data; data->from = from; if (ninac_formatkey(data->challenge)!=-1){ sendf ("%s %s\n",PROTO_VERSION,data->challenge.c_str()); if (glocal.locked_by != -1){ setlisten (no,false); } }else{ endclient = true; } if (no == glocal.locked_by){ ninac_lock_unlock (glocal.ploc,glocal.prem,true,-1); glocal.locked_by = -1; } CLIENT_DATA *data = (CLIENT_DATA*)info.data; ninac_solver_if (data); ninac_delwait (no); CLIENT_DATA *data = (CLIENT_DATA*)info.data; ninac_process (data,no,line,this,glocal.prem ,glocal.ploc,false,endclient ,glocal.locked_by); CLIENT_DATA *d = (CLIENT_DATA*)data; char buf[20]; ipnum_ip2a (d->from,buf); if (ev == TCPSERVER_OUTFULL){ //endclient = true; unsigned long size; long long lastwrite; is_blocked(no,size,lastwrite); tlmp_error ("OUTFULL for remote client %s (%lu pending)",buf,size); }else if (ev == TCPSERVER_OUTFLUSHED){ tlmp_error ("OUTFLUSHED for remote client %s",buf); send ("## 0\n"); } if (!rem.is_ok()){ tlmp_error ("Can't setup TCP socket on %s/%s, aborting" ,glocal.tcpbind,glocal.tcpport); }else{ glocal.prem = &rem; string tmp = string("unix:") + glocal.unixpath; (tmp.c_str(),10); CLIENT_DATA *data = new CLIENT_DATA; info.data = data; if (ninac_formatkey(data->challenge)!=-1){ sendf ("%s %s\n",PROTO_VERSION,data->challenge.c_str()); if (glocal.locked_by != -1){ setlisten (no,false); } }else{ endclient = true; } if (no == glocal.locked_by){ ninac_lock_unlock (glocal.ploc,glocal.prem,true,-1); glocal.locked_by = -1; } ninac_debug ("LOCAL endclient force_end=%d\n",info.force_end); CLIENT_DATA *data = (CLIENT_DATA*)info.data; ninac_solver_if (data); ninac_delwait (no); CLIENT_DATA *data = (CLIENT_DATA*)info.data; ninac_process (data,no,line,this,glocal.ploc ,glocal.prem,true,endclient,glocal.locked_by); ninac_debug ("LOCAL receive endclient=%d blocked=%d\n",endclient,is_blocked()); ninac_debug ("LOCAL event %d\n",ev); if (ev == TCPSERVER_OUTFULL){ //endclient = true; unsigned long size; long long lastwrite; is_blocked(no,size,lastwrite); tlmp_error ("OUTFULL for local client (%lu pending)",size); }else if (ev == TCPSERVER_OUTFLUSHED){ tlmp_error ("OUTFLUSHED for local client"); send ("## 0\n"); } if (!loc.is_ok()){ tlmp_error ("Can't setup unix socket on %s, aborting" ,glocal.unixpath); }else{ glocal.ploc = &loc; struct passwd *user = getpwnam("ninac") ; if (user == NULL){ tlmp_error ("Error retrieving ninac uid/gid") ; }else{ chown(glocal.unixpath, user->pw_uid, user->pw_gid) ; chmod(glocal.unixpath, 0770) ; } (); ninac_debug("tim %d\n",solver_needed); if (solver_needed){ if (ninac_getvalnum("ninac","running",0)>0){ ninac_solver(); ninac_checkwait() ; ninac_journal_close() ; } solver_needed = false; } tim.setrawmode(true); (); solver_needed = true; rem.setnonblock(true,5000000); loc.setnonblock(true,5000000); n.add (rem); n.add (loc); n.add (tim); debug_printf (debug_ninac,"Debut de loop\n"); glocal.ret = 0; if (user != NULL){ setgid (user->pw_gid); setuid (user->pw_uid); } ninac_setvarnum ("ninac","running",glocal.running ? 1 : 0); ninac_journal_close() ; if (glocal.background){ int fd = open ("/dev/null",O_RDONLY); if (fd == -1){ tlmp_error ("Can't open /dev/null, can't detach"); }else{ dup2 (fd,0); dup2 (fd,1); dup2 (fd,2); } setsid(); if (fork() == 0){ ninac_setwakeup(tim); n.loop(60); } }else{ ninac_setwakeup(tim); n.loop(60); } } } return glocal.ret; return glocal.ret; }