/* * newslettersan.c * * Small newsletter web server * * Author: Dario Rodriguez dario@softhome.net * This program is licensed under the terms of the Affero GPL v1+ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include "gen_res.h" #include "socklib.h" #include "loglib.h" #include "webkernel.h" #include "newslettersan.h" #include "newsl_config.h" #define CONFIGFILE "newslettersan.cfg" #define CFLOGFILE "newslettersan.log" #define CFROOTDIR "subscribe" static int signal_init(int signum, void (*fn)(int)); static void sigint(int signum); volatile int sigint_flag=0; wk_action callback_post(wk *web, int connid, wk_uri *uri, void *userptr); wk_action callback_http(wk *web, int connid, wk_uri *uri, void *userptr); int newsl_serve_buffer_with_rootdir(newslettersan *newsl, wk *web, int connid, char *data, int datalen, const char *mime); int main(int argc, char *argv[]) { int timeout=500; int serverfd; newslettersan *newsl,newslstore; char *hostnameport; char hostname[128]; char *host; long hostsize; char *ptr,*sep; memset(&newslstore,0,sizeof(newslstore)); newsl=&newslstore; if(argc!=2) { printf("Syntax: %s [ip:]port\n",argv[0]); return(1); } hostnameport=argv[1]; if(newslconfig_exists(CONFIGFILE)!=0) { log_setlogfile(CFLOGFILE); log_write("INIT","Config file not found, writing default file %s",CONFIGFILE); newslconfig_write(CONFIGFILE,CFLOGFILE,CFROOTDIR); } if((newsl->config=newslconfig_init(CONFIGFILE,CFLOGFILE,CFROOTDIR))==NULL) { log_setlogfile(CFLOGFILE); log_write("INIT","ERROR: insufficient memory or config file error"); return(1); } log_setlogfile((newsl->config->logfile!=NULL)?newsl->config->logfile:CFLOGFILE); if((newsl->ssel=sselect_init())==NULL) { log_write("INIT","ERROR: insufficient memory"); return(1); } if((sep=strchr(hostnameport,':'))!=NULL) { serverfd=-1; strncpy(hostname,hostnameport,sizeof(hostname)); hostname[sizeof(hostname)-1]='\0'; if((ptr=strchr(hostname,':'))!=NULL) *ptr='\0'; if((host=ipv4_genip(hostname,&hostsize))!=NULL) { serverfd=ipv4_serverbinded(host,hostsize,atoi(sep+1)); free(host),host=NULL; } } else { serverfd=ipv4_server(atoi(hostnameport)); } if(serverfd==-1) { sselect_free(newsl->ssel),newsl->ssel=NULL; log_write("INIT","ERROR: couldn't listen on port"); return(2); } sock_setunsafe(serverfd); if((newsl->web=wk_init(serverfd,newsl->ssel,NULL,callback_http,callback_post,NULL,newsl))==NULL) { sselect_free(newsl->ssel),newsl->ssel=NULL; log_write("INIT","ERROR: couldn't init web server"); return(2); } sigint_flag=0; signal_init(SIGINT,sigint); log_write("INIT","Server initialized, waiting connections..."); while(!sigint_flag) { sselect_wait(newsl->ssel,timeout); if(sigint_flag) break; wk_service(newsl->web); } wk_free(newsl->web),newsl->web=NULL; close(serverfd),serverfd=-1; sselect_free(newsl->ssel),newsl->ssel=NULL; newslconfig_free(newsl->config),newsl->config=NULL; log_write("FINI","SIGINT detected, exiting..."); return(0); } static int signal_init(int signum, void (*fn)(int)) { struct sigaction sa; sa.sa_handler=fn; sigemptyset(&sa.sa_mask); sa.sa_flags=0; return(sigaction(signum,&sa,0)); } static void sigint(int signum) { sigint_flag=1; } wk_action callback_post(wk *web, int connid, wk_uri *uri, void *userptr) { newslettersan *newsl=(newslettersan *)userptr; char partialpath[1024]; int l; if(newsl==NULL) return(wkact_finished); /* check that URI starts with /$rootdir/ */ l=strlen(newsl->config->rootdir); if(uri->path[0]!='/' || memcmp(uri->path+1,newsl->config->rootdir,l)!=0 || uri->path[l+1]!='/') { return(wkact_finished); } /* copy the path below $rootdir into partialpath */ strncpy(partialpath,uri->path+1+l,sizeof(partialpath)-1); partialpath[sizeof(partialpath)-1]='\0'; /* set push variables for the specified URI */ /* to be able to retrieve them in callback_http */ if(strcmp(partialpath,"/addemail")==0) { wk_post_addvalid(web,connid,"email",NULL); wk_post_addvalid(web,connid,"language",NULL); } return(wkact_finished); } wk_action callback_http(wk *web, int connid, wk_uri *uri, void *userptr) { newslettersan *newsl=(newslettersan *)userptr; resindex *res; int l; char partialpath[1024]; if(newsl==NULL) return(wkact_finished); /* redirect to the real root URI if request is "/" */ if(strcmp(uri->path,"/")==0) { partialpath[0]='/'; strncpy(partialpath+1,newsl->config->rootdir,sizeof(partialpath)-1); partialpath[sizeof(partialpath)-1]='\0'; if((l=strlen(partialpath))<(sizeof(partialpath)-2)) strcpy(partialpath+l,"/"); wk_serve_redirect(web,connid,partialpath); return(wkact_finished); } /* check that URI starts with /$rootdir/ */ l=strlen(newsl->config->rootdir); if(uri->path[0]!='/' || memcmp(uri->path+1,newsl->config->rootdir,l)!=0 || uri->path[l+1]!='/') { wk_serve_error(web,connid,wkerr_notfound); return(wkact_finished); } /* copy the path below $rootdir into partialpath */ strncpy(partialpath,uri->path+1+l,sizeof(partialpath)-1); partialpath[sizeof(partialpath)-1]='\0'; /* change the root url to index.html if neccessary */ if(strcmp(partialpath,"/")==0) { strncpy(partialpath,"/index.html",sizeof(partialpath)); partialpath[sizeof(partialpath)-1]='\0'; } /* serve the requested page */ if(partialpath[0]=='/' && (res=res_find(resindexdata,partialpath+1))!=NULL) { log_write("HTTP","Serving in-memory file %s",partialpath+1); wk_serve_etagset(web,connid,res->etag); newsl_serve_buffer_with_rootdir(newsl,web,connid,(char *) res->data,res->len,mime_getdefault(res->name,"application/octet-stream")); return(wkact_finished); } else if(strcmp(partialpath,"/addemail")==0) { char *email,*language; if((email=wk_post_get(web,connid,"email",NULL))==NULL || (language=wk_post_get(web,connid,"language",NULL))==NULL) { wk_serve_error(web,connid,wkerr_internal); return(wkact_finished); } #warning TODO: do something with the email { FILE *f; if((f=fopen("emails.txt","a"))==NULL) { wk_serve_error(web,connid,wkerr_internal); return(wkact_finished); } fprintf(f,"%s %s\n",language,email); fclose(f); } if((res=res_find(resindexdata,"success.html"))!=NULL) { wk_serve_buffer_as_file(web,connid,(char *) res->data,res->len,mime_getdefault(res->name,"application/octet-stream")); } else { char success[]={"The email has been saved.\n"}; wk_serve_buffer_as_file(web,connid,success,strlen(success),"text/plain"); } return(wkact_finished); } wk_serve_error(web,connid,wkerr_notfound); return(wkact_finished); } /* newsl_serve_buffer_with_rootdir() substitutes /~/ with /$rootdir/ */ /* NOTE: it is limited to webkernel's MAXOUTBUF * BUFSIZE; normally MAXOUTBUF is 128 and BUFSIZE is 8192 (that is, 1MB) */ int newsl_serve_buffer_with_rootdir(newslettersan *newsl, wk *web, int connid, char *data, int datalen, const char *mime) { int res; int newdatalen; int newstrlen; char *last,*next,*end; int etagnotmodified; if(newsl==NULL || web==NULL || data==NULL || datalen<0) return(-1); /* sanity check failed */ /* we calculate the new datalen */ end=data+datalen; newstrlen=1+strlen(newsl->config->rootdir)+1; for(last=data,next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL;next!=NULL;next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL) { if(memcmp(next,"/~/",3)==0) { newdatalen+=next-last+newstrlen; last=next+3; } else { newdatalen+=next-last+1; last=next+1; } } newdatalen+=end-last; /* serve the headers */ res=wk_serve_generic_headers(web,connid,newdatalen,mime,&etagnotmodified); if(res!=0) return(res); /* error in headers */ /* serve the contents */ if(!etagnotmodified) { /* we copy the buffer substituting /~/ for /$rootdir/ */ end=data+datalen; for(last=data,next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL;next!=NULL;next=((last+3)<=end)?memchr(last,'/',end-last-3):NULL) { if(memcmp(next,"/~/",3)==0) { wk_write(web,connid,last,next-last); wk_write(web,connid,"/",1); wk_writestr(web,connid,newsl->config->rootdir); wk_write(web,connid,"/",1); last=next+3; } else { wk_write(web,connid,last,next-last+1); last=next+1; } } wk_write(web,connid,last,end-last); } return(0); }