void SwitchProgram(const char *CommandLine, const char *User,const char *Group, const char *Dir) { char **argv, *ptr; char *Token=NULL, *SafeStr=NULL; int i; SafeStr=MakeShellSafeString(SafeStr,CommandLine,0); argv=(char **) calloc(101,sizeof(char *)); ptr=SafeStr; for (i=0; i < 100; i++) { ptr=GetToken(ptr,"\\S",&Token,GETTOKEN_QUOTES); if (! ptr) break; argv[i]=CopyStr(argv[i],Token); } if (StrLen(Dir)) chdir(Dir); if (StrLen(Group)) SwitchGroup(Group); if (StrLen(User)) SwitchUser(User); DestroyString(Token); DestroyString(SafeStr); /* we are the child so we continue */ execv(argv[0],argv); //no point trying to free stuff here, we will no longer //be the main program }
int HTTPServerExecCGI(STREAM *ClientCon, HTTPSession *Session, const char *ScriptPath) { char *Tempstr=NULL; int i; if (StrLen(Session->Group) && (! SwitchGroup(Session->Group))) { LogToFile(Settings.LogPath,"WARN: Failed to switch to group '%s' to execute script: %s",Session->Group, ScriptPath); } //Switch user. ALAYA WILL NOT RUN SCRIPTS AS ROOT! if ((geteuid()==0) && (! SwitchUser(Session->RealUser))) { LogToFile(Settings.LogPath,"ERROR: Failed to switch to user '%s' to execute script: %s",Session->RealUser, ScriptPath); return(FALSE); } if (geteuid()==0) { HTTPServerSendHTML(ClientCon, NULL, "403 Forbidden","Alaya will not run .cgi programs as 'root'.<br>\r\nTry setting 'Default User' in config file or command line."); LogToFile(Settings.LogPath, "Failed to switch user to '%s' for running a .cgi program. Will not run programs as 'root'. Set 'DefaultUser' in config file or command line.",Session->RealUser); } else { Session->ResponseCode=CopyStr(Session->ResponseCode,"200 OK"); HTTPServerSendHeaders(ClientCon, Session, HEADERS_CGI); STREAMFlush(ClientCon); SetupEnvironment(Session, ScriptPath); Tempstr=FindScriptHandlerForScript(Tempstr,ScriptPath); if (Tempstr) LogToFile(Settings.LogPath,"Execute script: %s using handler '%s'",ScriptPath,Tempstr); else LogToFile(Settings.LogPath,"Execute script: %s QUERY_STRING= '%s'",ScriptPath,getenv("QUERY_STRING")); //Only do this late! Otherwise logging won't work. for (i=3; i < 1000; i++) close(i); if (StrLen(Tempstr)) execl(Tempstr, Tempstr, ScriptPath,NULL); else execl(ScriptPath,ScriptPath,NULL); /*If this code gets executed, then 'execl' failed*/ HTTPServerSendHTML(ClientCon, Session, "403 Forbidden","You don't have permission for that."); //Logging won't work after we've closed all the file descriptors! LogToFile(Settings.LogPath,"Cannot execute script: %s",ScriptPath); } //if we get there then, for whatever reason, our script didn't run DestroyString(Tempstr); return(FALSE); }
pid_t ForkWithContext(const char *User, const char *Dir, const char *Group) { pid_t pid; LogFileFlushAll(TRUE); pid=fork(); if (pid==0) { if (StrLen(Dir)) chdir(Dir); if (StrLen(Group)) SwitchGroup(Group); if (StrLen(User)) SwitchUser(User); } return(pid); }
pid_t HandlePostFileRequest(STREAM *ClientCon, char *Data) { HTTPSession *Response; STREAM *S; char *Tempstr=NULL; pid_t pid; pid=fork(); if (pid==0) { Response=ParseSessionInfo(Data); Tempstr=FindFileInPath(Tempstr,Response->Path,Response->SearchPath); Response->Path=CopyStr(Response->Path, Tempstr); if (! SwitchGroup(Response->Group)) { LogToFile(Settings.LogPath,"WARN: Failed to switch to group '%s' when posting '%s'", Response->RealUser, Tempstr); } if (! SwitchUser(Response->RealUser)) { LogToFile(Settings.LogPath,"ERROR: Failed to switch to user '%s' when posting to document '%s'", Tempstr); LogFileFlushAll(TRUE); _exit(0); } LogToFile(Settings.LogPath,"SWITCH USER: '******' posting document '%s'", Response->RealUser, Response->Group, Tempstr); STREAMWriteLine("READY\n",ClientCon); STREAMFlush(ClientCon); HTTPServerHandlePost(ClientCon, Response); //exit 1 means that we can keep connection alive for re-use LogFileFlushAll(TRUE); if (Response->Flags & SESSION_KEEPALIVE) _exit(1); _exit(0); } else ClientCon->State |= SS_EMBARGOED; DestroyString(Tempstr); return(pid); }
pid_t HandleGetFileRequest(STREAM *ClientCon, char *Data) { HTTPSession *Response; char *Tempstr=NULL; pid_t pid; pid=fork(); if (pid==0) { Response=ParseSessionInfo(Data); Tempstr=FindFileInPath(Tempstr,Response->Path,Response->SearchPath); if (! SwitchGroup(Response->Group)) { LogToFile(Settings.LogPath,"WARN: Failed to switch to group '%s' when getting document '%s'", Response->RealUser, Tempstr); } if (! SwitchUser(Response->RealUser)) { LogToFile(Settings.LogPath,"ERROR: Failed to switch to user '%s' when getting document '%s'", Tempstr); LogFileFlushAll(TRUE); _exit(0); } STREAMWriteLine("READY\n",ClientCon); STREAMFlush(ClientCon); HTTPServerSendDocument(ClientCon, Response, Tempstr, HEADERS_SENDFILE|HEADERS_USECACHE|HEADERS_KEEPALIVE); //exit 1 means that we can keep connection alive for re-use LogFileFlushAll(TRUE); if (Response->Flags & SESSION_KEEPALIVE) _exit(1); _exit(0); } DestroyString(Tempstr); return(pid); }
pid_t PseudoTTYSpawnFunction(int *ret_pty, BASIC_FUNC Func, void *Data, const char *User, const char *Group, int TTYFlags) { pid_t pid=-1; int tty, pty, i; if (GrabPseudoTTY(&pty, &tty, TTYFlags)) { pid=ForkWithContext(NULL,NULL,NULL); if (pid==0) { for (i=0; i < 4; i++) close(i); close(pty); setsid(); ioctl(tty,TIOCSCTTY,0); dup(tty); dup(tty); dup(tty); ///now that we've dupped it, we don't need to keep it open //as it will be open on stdin/stdout close(tty); if (StrLen(Group)) SwitchGroup(Group); if (StrLen(User)) SwitchUser(User); Func((char *) Data); _exit(0); } close(tty); } *ret_pty=pty; return(pid); }
pid_t HandleIconRequest(STREAM *ClientCon, char *Data) { HTTPSession *Response; char *Name=NULL, *Value=NULL, *ptr, *tptr; char *Tempstr=NULL; ListNode *Vars; pid_t pid; pid=fork(); if (pid==0) { Response=ParseSessionInfo(Data); Vars=ListCreate(); ptr=GetNameValuePair(Response->Arguments,"&","=",&Name,&Tempstr); while (ptr) { Value=HTTPUnQuote(Value,Tempstr); SetVar(Vars,Name,Value); if (strcasecmp(Name,"MimeType")==0) { tptr=GetToken(Value,"/",&Tempstr,0); SetVar(Vars,"MimeClass",Tempstr); SetVar(Vars,"MimeSub",tptr); } ptr=GetNameValuePair(ptr,"&","=",&Name,&Tempstr); } if (! SwitchGroup(Response->Group)) { LogToFile(Settings.LogPath,"WARN: Failed to switch to group '%s' for mimeicons '%s'", Response->RealUser, Tempstr); } if (! SwitchUser(Response->RealUser)) { LogToFile(Settings.LogPath,"ERROR: Failed to switch to user '%s' when getting icons from '%s'", Response->SearchPath); LogFileFlushAll(TRUE); _exit(0); } STREAMWriteLine("READY\n",ClientCon); STREAMFlush(ClientCon); ptr=GetToken(Response->SearchPath,":",&Value,0); while (ptr) { Tempstr=SubstituteVarsInString(Tempstr,Value,Vars,0); if (access(Tempstr,R_OK)==0) break; ptr=GetToken(ptr,":",&Value,0); } //HTTPServerSendDocument(ClientCon, Response, Tempstr, HEADERS_SENDFILE|HEADERS_USECACHE|HEADERS_KEEPALIVE); HTTPServerSendDocument(ClientCon, Response, Tempstr, HEADERS_SENDFILE|HEADERS_USECACHE); //exit 1 means we can keep connection alive for reuse LogFileFlushAll(TRUE); _exit(1); } DestroyString(Name); DestroyString(Value); DestroyString(Tempstr); return(pid); }
pid_t HandleWebsocketExecRequest(STREAM *ClientCon, char *Data) { char *Tempstr=NULL, *Name=NULL, *Value=NULL; char *ScriptPath=NULL; int result, i; HTTPSession *Response; //We will never read from this stream again. Any further data will be read //by the process we spawn off ClientCon->State |= SS_EMBARGOED; Response=ParseSessionInfo(Data); CleanStr(Response->Path); CleanStr(Response->SearchPath); CleanStr(Response->StartDir); ScriptPath=FindFileInPath(ScriptPath, Response->Path, Response->SearchPath); LogToFile(Settings.LogPath,"Script: Found=[%s] SearchPath=[%s] ScriptName=[%s] Arguments=[%s]", ScriptPath, Response->SearchPath, Response->Path, Response->Arguments); if (access(ScriptPath,F_OK) !=0) { LogToFile(Settings.LogPath,"No such script: %s in path %s = %s",Response->Path, Response->SearchPath, ScriptPath); } else if ( (access(ScriptPath,X_OK) !=0) || (! CheckScriptIntegrity(ScriptPath)) ) { LogToFile(Settings.LogPath,"Cannot execute script: %s", ScriptPath); } else { STREAMFlush(ClientCon); result=fork(); if (result==0) { //do this so that when we exec the script, anything output goes to the client close(0); dup(ClientCon->in_fd); close(1); dup(ClientCon->out_fd); if (! SwitchGroup(Response->Group)) { LogToFile(Settings.LogPath,"WARN: Failed to switch to group '%s' to execute script: %s using handler '%s'", Response->RealUser, ScriptPath, Tempstr); } //Switch user. ALAYA WILL NOT RUN SCRIPTS AS ROOT! if (! SwitchUser(Response->RealUser)) { LogToFile(Settings.LogPath,"ERROR: Failed to switch to user '%s' to execute script: %s using handler '%s'", Response->RealUser, ScriptPath, Tempstr); LogFileFlushAll(TRUE); _exit(0); } if (geteuid()==0) { LogToFile(Settings.LogPath, "Failed to switch user to '%s' for running a .cgi program. Will not run programs as 'root'. Set 'DefaultUser' in config file or command line.", Response->RealUser); } else { SetupEnvironment(Response, ScriptPath); Tempstr=FindScriptHandlerForScript(Tempstr,ScriptPath); if (Tempstr) LogToFile(Settings.LogPath,"Execute script: %s using handler '%s'",ScriptPath,Tempstr); else LogToFile(Settings.LogPath,"Execute script: %s QUERY_STRING= '%s'",ScriptPath,getenv("QUERY_STRING")); //Only do this late! Otherwise logging won't work. for (i=3; i < 1000; i++) close(i); if (StrLen(Tempstr)) execl(Tempstr, Tempstr, ScriptPath,NULL); else execl(ScriptPath,ScriptPath,NULL); //Logging won't work after we've closed all the file descriptors! LogToFile(Settings.LogPath,"Cannot execute script: %s",ScriptPath); } LogFileFlushAll(TRUE); _exit(0); } else { } } HTTPSessionDestroy(Response); DestroyString(ScriptPath); DestroyString(Tempstr); DestroyString(Name); DestroyString(Value); //Always return STREAM_CLOSED, so that pipe gets closed regardless of exit status of //forked helper process return(STREAM_CLOSED); }
/* This creates a child process that we can talk to using a couple of pipes*/ pid_t PipeSpawnFunction(int *infd,int *outfd,int *errfd, BASIC_FUNC Func, void *Data, const char *User, const char *Group) { pid_t pid; int channel1[2], channel2[2], channel3[2], DevNull=-1; if (infd) pipe(channel1); if (outfd) pipe(channel2); if (errfd) pipe(channel3); pid=ForkWithContext(NULL,NULL,NULL); if (pid==0) { /* we are the child */ if (infd) close(channel1[1]); else if (DevNull==-1) DevNull=open("/dev/null",O_RDWR); if (outfd) close(channel2[0]); else if (DevNull==-1) DevNull=open("/dev/null",O_RDWR); if (errfd) close(channel3[0]); else if (DevNull==-1) DevNull=open("/dev/null",O_RDWR); /*close stdin, stdout and stderr*/ close(0); close(1); close(2); /*channel 1 is going to be our stdin, so we close the writing side of it*/ if (infd) dup(channel1[0]); else dup(DevNull); /* channel 2 is stdout */ if (outfd) dup(channel2[1]); else dup(DevNull); /* channel 3 is stderr */ if (errfd) { //Yes, we can pass an integer value as errfd, even though it's an int *. //This is probably a bad idea, and will likely be changed in future releases if (errfd==(int) COMMS_COMBINE_STDERR) dup(channel2[1]); else dup(channel3[1]); } else dup(DevNull); if (StrLen(Group)) SwitchGroup(Group); if (StrLen(User)) SwitchUser(User); Func(Data); exit(0); } else { /* we close the appropriate halves of the link */ if (infd) { close(channel1[0]); *infd=channel1[1]; } if (outfd) { close(channel2[1]); *outfd=channel2[0]; } if (errfd) { close(channel3[1]); //Yes, we can pass an integer value as errfd, even though it's an int *. //This is probably a bad idea, and will likely be changed in future releases if (errfd != (int) COMMS_COMBINE_STDERR) *errfd=channel3[0]; } } return(pid); }
void SpawnApplyConfig(const char *Config, int Flags) { char *User=NULL, *Group=NULL, *Dir=NULL; char *Name=NULL, *Value=NULL; const char *ptr; struct rlimit limit; rlim_t val; int i; //set all signal handlers to default if (Flags & SPAWN_SIGDEF) { for (i =0; i < _NSIG; i++) signal(i,SIG_DFL); } //Set controlling tty to be stdin. This means that CTRL-C, SIGWINCH etc is handled for the //stdin file descriptor, not for any oher if (Flags & SPAWN_DAEMON) demonize(); else { if (Flags & SPAWN_SETSID) setsid(); if (Flags & SPAWN_CTRL_TTY) tcsetpgrp(0, getpgrp()); } User=CopyStr(User,""); Group=CopyStr(Group,""); ptr=GetNameValuePair(Config,"\\S","=",&Name,&Value); while (ptr) { if (strcasecmp(Name,"User")==0) User=CopyStr(User, Value); else if (strcasecmp(Name,"Group")==0) Group=CopyStr(Group, Value); else if (strcasecmp(Name,"Dir")==0) Dir=CopyStr(Dir, Value); else if (strcasecmp(Name,"PidFile")==0) WritePidFile(Value); else if (strcasecmp(Name,"prio")==0) setpriority(PRIO_PROCESS, 0, atoi(Value)); else if (strcasecmp(Name,"nice")==0) setpriority(PRIO_PROCESS, 0, atoi(Value)); else if (strcasecmp(Name,"priority")==0) setpriority(PRIO_PROCESS, 0, atoi(Value)); else if (strcasecmp(Name,"mem")==0) { val=(rlim_t) ParseHumanReadableDataQty(Value, 0); limit.rlim_cur=val; limit.rlim_max=val; setrlimit(RLIMIT_DATA, &limit); } else if (strcasecmp(Name,"fsize")==0) { val=(rlim_t) ParseHumanReadableDataQty(Value, 0); limit.rlim_cur=val; limit.rlim_max=val; setrlimit(RLIMIT_FSIZE, &limit); } else if (strcasecmp(Name,"files")==0) { val=(rlim_t) ParseHumanReadableDataQty(Value, 0); limit.rlim_cur=val; limit.rlim_max=val; setrlimit(RLIMIT_NOFILE, &limit); } else if (strcasecmp(Name,"coredumps")==0) { val=(rlim_t) ParseHumanReadableDataQty(Value, 0); limit.rlim_cur=val; limit.rlim_max=val; setrlimit(RLIMIT_CORE, &limit); } else if ( (strcasecmp(Name,"procs")==0) || (strcasecmp(Name,"nproc")==0) ) { val=(rlim_t) ParseHumanReadableDataQty(Value, 0); limit.rlim_cur=val; limit.rlim_max=val; setrlimit(RLIMIT_NPROC, &limit); } ptr=GetNameValuePair(ptr,"\\S","=",&Name,&Value); } // This allows us to chroot into a whole different unix directory tree, with its own // password file etc if (Flags & SPAWN_CHROOT) chroot("."); if (StrLen(Dir)) chdir(Dir); //Always do group first, otherwise we'll lose ability to switch user/group if (StrLen(Group)) SwitchGroup(Group); if (StrLen(User)) SwitchUser(User); //Must do this last! After parsing Config, and also after functions like //SwitchUser that will need access to /etc/passwd if (Flags & SPAWN_JAIL) chroot("."); DestroyString(Name); DestroyString(Value); DestroyString(User); DestroyString(Group); DestroyString(Dir); }