#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static long long getnow () { struct timeval tv; gettimeofday (&tv,NULL); return tv.tv_sec *(long long)1000000 + tv.tv_usec; } enum CLIENTTYPE { CLIENT_NONE, CLIENT_HTTP, CLIENT_CGI, CLIENT_CONTROL }; struct CLIENTINFO: public ARRAY_OBJ{ CLIENTTYPE type; int talkto; bool header_seen; string page; // Which page is requested string subpage; string width; string height; string parm; CLIENTINFO(){ type = CLIENT_NONE; talkto = -1; } }; static void nocracks_formatdate (time_t date, string &s) { char tmp[100]; struct tm *t = gmtime (&date); static const char *days[]={ "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static const char *months[]={ "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; snprintf (tmp,sizeof(tmp),"%s, %d %s %d %02d:%02d:%02d GMT" ,days[t->tm_wday] ,t->tm_mday ,months[t->tm_mon] ,t->tm_year+1900 ,t->tm_hour,t->tm_min,t->tm_sec); s = tmp; } static string nocracks_replace (const char *line, const char *token, const char *newval) { string ret; const char *pt = strstr(line,token); if (pt != NULL){ ret = string(line,pt-line) + newval + (pt + strlen(token)); }else{ ret = line; } return ret; } static void nocracks_sendhead ( _F_TCPSERVER_V1 *c, int no, unsigned size, time_t date) { c->sendto (no,"HTTP/1.1 200 OK\r\n"); string tmp; nocracks_formatdate (date,tmp); c->sendtof (no,"Date: %s\r\n",tmp.c_str()); c->sendtof (no,"Server: nocracks %s\r\n",VERSION); c->sendtof (no,"Content-Length: %u\r\n",size); c->sendto (no,"Connection: close\r\n"); } static int nocracks_finddoc (const vector &docdirs, const char *&page, string &pagepath, struct stat &st) { int ret = -1; for (unsigned i=0; i &docdirs, const char *&page, string &pagepath) { struct stat st; return nocracks_finddoc (docdirs,page,pagepath,st); } static void nocracks_sendpage ( _F_TCPSERVER_V1 *c, int no, const vector &docdirs, const char *page, const string &subpage, const string &width, const string &height) { glocal _F_TCPSERVER_V1 *c = c; glocal int fd = no; glocal const char *subpage = subpage.c_str(); glocal const char *width = width.c_str(); glocal const char *height = height.c_str(); struct stat st; string pagepath; if (nocracks_finddoc (docdirs,page,pagepath,st)==-1){ tlmp_error ("Page %s missing (%s)\n",page,strerror(errno)); c->sendto (glocal.fd,"HTTP/1.1 404 File not found\r\n"); }else{ glocal unsigned size = 0; { // We must compute the exact size of the file // sent, after performing replacement (page,false); string tmp = nocracks_replace (line,"==PAGE==",glocal.subpage); tmp = nocracks_replace (tmp.c_str(),"==WIDTH==",glocal.width); tmp = nocracks_replace (tmp.c_str(),"==HEIGHT==",glocal.height); glocal.size += tmp.size(); return 0; } debug_printf ("Send page %s to handle %d replace %s (st_size=%u size=%u)\n" ,page,glocal.fd,glocal.subpage,st.st_size,glocal.size); nocracks_sendhead (c,no,glocal.size,st.st_mtime); c->sendto (glocal.fd,"Content-Type: text/html\r\n"); c->sendto (glocal.fd,"\r\n"); (page,false); string tmp = nocracks_replace (line,"==PAGE==",glocal.subpage); tmp = nocracks_replace (tmp.c_str(),"==WIDTH==",glocal.width); tmp = nocracks_replace (tmp.c_str(),"==HEIGHT==",glocal.height); glocal.c->sendto (glocal.fd,tmp.c_str()); return 0; } } struct BUF{ int len; char buf[1024*12]; BUF(int _len, char _buf[1024*12]){ len = _len; memcpy (buf,_buf,_len); } }; static void nocracks_send ( _F_TCPSERVER_V1 *c, int no, const vector &docdirs, const char *page, const char *ext) { string newpage; string newext; const char *geometry = strchr(ext,':'); if (geometry != NULL){ newpage = string(page,geometry-page); page = newpage.c_str(); ext = strchr(page,'.'); if (ext == NULL) ext = ""; geometry++; } struct stat st; string pagepath; debug_printf ("send page=%s ext=%s\n",page,ext); if (nocracks_finddoc (docdirs,page,pagepath,st)==-1){ tlmp_error ("Page %s missing (%s)\n",page,strerror(errno)); c->sendto (no,"HTTP/1.1 404 File not found\r\n"); }else{ vector bufs; unsigned newsize = st.st_size; if (geometry != NULL){ char tmp[200]; snprintf (tmp,sizeof(tmp),"convert -resize %s %s -",geometry,page); debug_printf ("Converting image: %s\n",tmp); FILE *fin = popen (tmp,"r"); if (fin == NULL){ tlmp_error ("Can't convert image: %s (%s)\n",tmp,strerror(errno)); }else{ newsize = 0; char buf[1024*12]; int n; while ((n=fread(buf,1,sizeof(buf),fin))>0){ newsize += n; bufs.push_back (BUF(n,buf)); } pclose (fin); debug_printf ("convert size %u -> %u\n",st.st_size,newsize); } } nocracks_sendhead (c,no,newsize,st.st_mtime); if (strcmp(ext,".png")==0){ c->sendto (no,"Content-Type: image/png\r\n"); }else if (strcmp(ext,".jpg")==0){ c->sendto (no,"Content-Type: image/jpeg\r\n"); } c->sendto (no,"\r\n"); if (geometry != NULL){ FILE *fout = fopen ("/tmp/toto.JPG","w"); for (unsigned i=0; isendto (no,buf.buf,buf.len); fwrite (buf.buf,1,buf.len,fout); } fclose (fout); }else{ FILE *fin = fopen (page,"r"); if (fin != NULL){ char buf[1024*10]; int n; while ((n=fread(buf,1,sizeof(buf),fin))>0){ c->sendto (no,buf,n); } fclose (fin); } } } } static void nocracks_send (_F_TCPSERVER_V1 *c, int no, const vector &lines) { unsigned size = 0; for (unsigned i=0; isendto (no,"Content-Type: text/html\r\n"); c->sendto (no,"\r\n"); for (unsigned i=0; isendtof (no,"%s\r\n",lines[i].c_str()); } } struct PAGEINFO { int no; string width; string height; PAGEINFO (int _no, string &_width, string &_height){ no = _no; width = _width; height = _height; } }; /* Remove reference to a page assigned to a handle */ static void nocracks_removeref ( map > &pages, const string &page, int handle) { map >::iterator it = pages.find(page); if (it != pages.end()){ debug_printf ("Remove handle %d from page %s\n",handle,page.c_str()); for (vector::iterator iit = it->second.begin(); iit != it->second.end(); iit++){ if (iit->no == handle){ it->second.erase(iit); if (it->second.size()==0) pages.erase(it); break; } } } } static int nocracks_exec ( _F_TCPSERVER_V1 *c, const char *cgidir, const char *cgi, const string &parm) { int ret = -1; int tb[2]; const char *special = "?:;'\"*"; while (*special != '\0'){ if (strchr(parm.c_str(),*special)!=NULL){ tlmp_error ("Invalid character in request: %s %s\n",cgi,parm.c_str()); c->send ("HTTP/1.1 400 Bad request\r\n\r\n"); return -1; } special++; } if (pipe(tb)==-1){ tlmp_error ("Can't setup pipe (%s)\n",strerror(errno)); }else{ pid_t pid = fork(); if (pid == (pid_t)-1){ close (tb[1]); close (tb[0]); }else if (pid == (pid_t)0){ close (tb[0]); dup2 (tb[1],1); // Close all other handle for (int i=3; i<1024; i++){ close(i); } string tmp = string(cgidir) + "/" + string(cgi) + " " + parm; system (tmp.c_str()); _exit (-1); }else{ close (tb[1]); ret = tb[0]; } } return ret; } static void nocracks_senditems ( _F_TCPSERVER_V1 *c, int fd, const char *utildir, const char *command, const char *page, vector &args, const string &width, const string &height, bool debugtool) { glocal vector lines; char bufcmd[200]; int len = snprintf (bufcmd,sizeof(bufcmd),"%s/%s --viewwidth %s --viewheight %s" ,utildir,command,width.c_str(),height.c_str()); if (debugtool){ snprintf (bufcmd+len,sizeof(bufcmd)-len," --debug --debugfile /tmp/%s.log",command); } string cmd = bufcmd; for (unsigned j=0; j(cmd.c_str(),20,true); glocal.lines.push_back(line); return 0; debug_printf ("Command return %u lines\n",glocal.lines.size()); nocracks_send (c,fd,glocal.lines); } static void nocracks_accesslog (FILE *flog, long long start, const char *type, const char *request) { if (flog != NULL){ long long end = getnow(); time_t t = time(NULL); struct tm *tt = localtime(&t); fprintf (flog,"%04d/%02d/%02d-%02d:%02d:%02d %6.3lf %s %s\n" ,tt->tm_year+1900,tt->tm_mon+1,tt->tm_mday ,tt->tm_hour,tt->tm_min,tt->tm_sec ,(end-start)/1000000.0,type,request); fflush (flog); } } int main (int argc, char *argv[]) { glocal int ret = -1; glocal const char *unixport = "/tmp/nocracks.sock"; glocal const char *httpport = "8080"; glocal const char *cgidir = "."; glocal const char *utildir = "/usr/lib/nocracks"; glocal const char *logfile = NULL; glocal vector docdirs; glocal bool debugtool = false; glocal.ret = (argc,argv); setproginfo ("nocracks",VERSION ,"http server used to synchronise several web pages\n"); setarg ('c',"control","Control unix socket",glocal.unixport,false); setarg ('h',"httpport","HTTP port",glocal.httpport,false); setarg (' ',"docdir","Document root(s)",glocal.docdirs,true); setarg (' ',"cgidir","CGI directory",glocal.cgidir,false); setarg (' ',"utildir","Utility directory",glocal.utildir,false); setarg (' ',"debugtool","Debug for formatting utilities",glocal.debugtool,false); setarg (' ',"logfile","log request and stats",glocal.logfile,false); int ret = -1; string tmp = string("unix:") + glocal.unixport; glocal map > pages; // All http request waiting for a given logical page glocal FILE *flog = NULL; if (glocal.logfile != NULL){ glocal.flog = fopen (glocal.logfile,"a"); if (glocal.flog == NULL){ tlmp_error ("Can't open log file %s (%s)\n",glocal.logfile,strerror(errno)); exit (-1); } } signal (SIGCHLD,SIG_IGN); signal (SIGPIPE,SIG_IGN); // Turn docdirs into absolute path. Useful for cgis { char cwd[PATH_MAX]; if (getcwd(cwd,sizeof(cwd))==NULL){ tlmp_error ("Can't get the current working directory, aborting\n"); exit (-1); } string scwd = string(cwd) + "/"; for (unsigned i=0; i 0) doc += ":"; doc += glocal.docdirs[i]; } putenv ((char*)sock.c_str()); putenv ((char*)path.c_str()); putenv ((char*)doc.c_str()); (tmp.c_str(),10); CLIENTINFO *c = new CLIENTINFO; info.data = c; if (strncmp(info.port,"unix:",5)==0){ c->type = CLIENT_CONTROL; }else{ debug_printf ("New client http %d\n",no); c->type = CLIENT_HTTP; long long start = getnow(); nocracks_accesslog (glocal.flog,start,"CLIENT_HTTP",""); } CLIENTINFO *c = (CLIENTINFO*)info.data; debug_printf ("endclient %d %d %s\n",no,c->type,c->page.c_str()); nocracks_removeref (glocal.pages,c->page,no); if (c->talkto != -1){ nocracks_removeref (glocal.pages,c->page,c->talkto); closeclient (c->talkto); } CLIENTINFO *c = (CLIENTINFO*)info.data; debug_printf ("Receive %d %s\n",no,line); if (c->type == CLIENT_HTTP){ if (info.linelen > 200){ send ("HTTP/1.1 400 Bad request\r\n"); endclient = true; }else if (line[0] == '\0'){ const char *page = c->page.c_str(); if (*page == '/') page++; if (*page == '\0') page = "index.cgi"; if (page[0] != '='){ // Not a logical page (=....html) // so we can send it long long start = getnow(); const char *ext = strchr(page,'.'); if (ext != NULL && strcmp(ext,".html")==0){ nocracks_sendpage (this,no,glocal.docdirs,page,c->subpage,c->width,c->height); endclient = true; }else if (ext != NULL && strcmp(ext,".cgi")==0){ int fd = nocracks_exec (this,glocal.cgidir,page,c->parm); if (fd != -1){ c->talkto = fd; CLIENTINFO *inf = new CLIENTINFO; inf->type = CLIENT_CGI; inf->talkto = no; inf->page = c->page; inject (fd,inf); }else{ endclient = true; } }else if (ext != NULL){ nocracks_send (this,no,glocal.docdirs,page,ext); endclient = true; }else{ send ("HTTP/1.1 404 Not found\r\n"); endclient = true; } nocracks_accesslog (glocal.flog,start,"get",page); } }else{ vector words; int n = str_splitline(line,' ',words); if (n == 3 && words[0] == "GET"){ const char *page = words[1].c_str(); const char *pt = strchr(page,'?'); if (pt == NULL){ c->page = page; c->subpage = ""; }else{ c->page = string(page,pt-page); pt++; vector parms; int p = str_splitline(pt,'&',parms); for (int i=0; ipage.c_str(),c->subpage.c_str()); glocal.pages[c->page].push_back(PAGEINFO(no,c->width,c->height)); } } }else if (c->type == CLIENT_CONTROL){ vector words; int n = str_splitline(line,' ',words); if (n >= 3 && words[0] == "show"){ map >::iterator it = glocal.pages.find(words[1]); if (it == glocal.pages.end()){ sendf ("No browser waiting for page %s\n",words[1].c_str()); }else{ const char *page = words[2].c_str(); string pagepath; if (nocracks_finddoc (glocal.docdirs,page,pagepath)==-1){ sendf ("No %s file\n",page); }else{ vector &tmp = it->second; for (unsigned i=0; i args(words.begin()+3,words.end()); if (strstr(page,".items")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"bullet",page,args,tmp[i].width,tmp[i].height,glocal.debugtool); }else if (strstr(page,".graph")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"graph",page,args,tmp[i].width,tmp[i].height,glocal.debugtool); }else if (strstr(page,".title")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"title",page,args,tmp[i].width,tmp[i].height,glocal.debugtool); }else if (strstr(page,".gr")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"graph-svg",page,args,tmp[i].width,tmp[i].height,glocal.debugtool); }else if (strstr(page,".table")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"table",page,args,tmp[i].width,tmp[i].height,glocal.debugtool); }else if (strstr(page,".sh")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"runsh",page,args,tmp[i].width,tmp[i].height,false); }else if (strstr(page,".html")!=NULL){ nocracks_senditems (this,fd,glocal.utildir ,"cathtml",page,args,tmp[i].width,tmp[i].height,false); } closeclient(fd); nocracks_accesslog (glocal.flog,start,"push",page); } // We have sent the page to all listeners. // We forget about them and they will come back glocal.pages.erase(it); } } send ("Ok\n"); }else if (n==1 && words[0] == "status"){ void *data; int fd = iter_init(data); while (fd != -1){ CLIENTINFO *n = (CLIENTINFO*)data; if (n->type == CLIENT_HTTP){ sendf ("Connection %d HTTP page=%s sub=%s\n",fd,n->page.c_str(),n->subpage.c_str()); }else if (n->type == CLIENT_CONTROL){ sendf ("Connection %d CONTROL\n",fd); }else if (n->type == CLIENT_CGI){ sendf ("Connection %d CGI page=%s parm=%s\n",fd,n->page.c_str(),n->parm.c_str()); } fd = iter_next(data); } for (map >::iterator it = glocal.pages.begin(); it != glocal.pages.end(); it++){ sendf ("page %s handle",it->first.c_str()); for (unsigned i=0; isecond.size(); i++){ PAGEINFO &p = it->second[i]; sendf (" %d:%sx%s",p.no,p.width.c_str(),p.height.c_str()); } sendf ("\n"); } send ("Ok\n"); }else if (n==2 && words[0] == "debug"){ if (words[1] == "on"){ glocal.debugtool = true; send ("Turning debugging on\n"); }else{ glocal.debugtool = false; send ("Turning debugging off\n"); } } endclient = true; }else if (c->type == CLIENT_CGI){ sendto (c->talkto,line); sendto (c->talkto,"\r\n"); }else{ tlmp_error ("Unknown client type\n"); } if (oo.is_ok()){ if (oo.listen(glocal.httpport) != -1){ oo.loop(); ret = 0; debug_printf ("End loop\n"); } } return ret; return glocal.ret; }