// crm_expr_syscall.c - Controllable Regex Mutilator, version v1.0 // Copyright 2001-2006 William S. Yerazunis, all rights reserved. // // This software is licensed to the public under the Free Software // Foundation's GNU GPL, version 2. You may obtain a copy of the // GPL by visiting the Free Software Foundations web site at // www.fsf.org, and a copy is included in this distribution. // // Other licenses may be negotiated; contact the // author for details. // // include some standard files #include "crm114_sysincludes.h" // include any local crm114 configuration file #include "crm114_config.h" // include the crm114 data structures file #include "crm114_structs.h" // and include the routine declarations file #include "crm114.h" // the command line argc, argv extern int prog_argc; extern char **prog_argv; // the auxilliary input buffer (for WINDOW input) extern char *newinputbuf; // the globals used when we need a big buffer - allocated once, used // wherever needed. These are sized to the same size as the data window. extern char *inbuf; extern char *outbuf; extern char *tempbuf; #ifdef WIN32 typedef struct { HANDLE to_minion; char *inbuf; long inlen; long internal_trace; long keep_proc; } pusherparams; typedef struct { HANDLE from_minion; int timeout; } suckerparams; DWORD WINAPI pusher_proc(LPVOID lpParameter) { DWORD bytesWritten; pusherparams *p = (pusherparams *)lpParameter; WriteFile(p->to_minion, p->inbuf, p->inlen, &bytesWritten, NULL); free(p->inbuf); if (p->internal_trace) fprintf (stderr, "pusher: input sent to minion.\n"); // if we don't want to keep this proc, we close it's input, and // wait for it to exit. if (! p->keep_proc) { CloseHandle (p->to_minion); if (internal_trace) fprintf (stderr, "minion input pipe closed\n"); } if (p->internal_trace) fprintf (stderr, "pusher: exiting pusher\n"); return 0; } DWORD WINAPI sucker_proc(LPVOID lpParameter) { DWORD bytesRead; suckerparams *p = (suckerparams *)lpParameter; char *outbuf = malloc(sizeof(char) * 8192); // we're in the sucker process here- just throw away // everything till we get EOF, then exit. while (1) { Sleep (p->timeout); ReadFile(p->from_minion, outbuf, 8192, &bytesRead, NULL); if (bytesRead == 0) break; }; return 0; } #endif int crm_expr_syscall ( CSL_CELL *csl, ARGPARSE_BLOCK *apb) { // Go off and fork a process, sending that process // one pattern evaluated as input, and then accepting // all the returns from that process as the new value // for a variable. // // syntax is: // exec (:to:) (:from:) (:ctl:) /commandline/ long inlen; long outlen; char from_var [MAX_VARNAME]; char sys_cmd [MAX_PATTERN]; long cmd_len; char keep_buf [MAX_PATTERN]; long keep_len; char exp_keep_buf[MAX_PATTERN]; long exp_keep_len; long vstart; long vlen; long done, charsread; int keep_proc; int async_mode; int to_minion[2]; int from_minion[2]; pid_t minion; int minion_exit_status; pid_t pusher; pid_t sucker; pid_t random_child; int status; long timeout; #ifdef WIN32 SECURITY_ATTRIBUTES pipeSecAttr; HANDLE hminion; #endif #ifdef POSIX if (user_trace) fprintf (stderr, "executing an SYSCALL statement"); timeout = MINION_SLEEP_USEC; // clean up any prior processes - note that // we don't keep track of these. For that matter, we have // no context to keep track of 'em. // while ( (random_child = waitpid ( 0, &status, WNOHANG)) > 0 ); #endif #ifdef WIN32 timeout = MINION_SLEEP_USEC / 1000; // need milliseconds for Sleep() if (MINION_SLEEP_USEC > 0 && timeout == 0) { timeout = 1; } #endif // get the flags // keep_proc = 0; if (apb->sflags & CRM_KEEP) { if (user_trace) fprintf (stderr, "Keeping the process around if possible\n"); keep_proc = 1; }; async_mode = 0; if (apb->sflags & CRM_ASYNC) { if (user_trace) fprintf (stderr, "Letting the process go off on it's own"); async_mode = 1; }; // Sanity check - is incompatible with // if (keep_proc && async_mode) { nonfatalerror ("This syscall uses both async and keep, but async is " "incompatible with keep. Since keep is safer" "we will use that.\n", "You need to fix this program."); async_mode = 0; }; // get the input variable(s) // crm_get_pgm_arg (inbuf, data_window_size, apb->p1start, apb->p1len); inlen = crm_nexpandvar (inbuf, apb->p1len, data_window_size); if (user_trace) fprintf (stderr, " command's input wil be: ***%s***\n", inbuf); // now get the name of the variable where the return will be // placed... this is a crock and should be fixed someday. // the output goes only into a single var (the first one) // so we extract that // crm_get_pgm_arg (from_var, MAX_PATTERN, apb->p2start, apb->p2len); outlen = crm_nexpandvar (from_var, apb->p2len, MAX_PATTERN); done = 0; vstart = 0; while (from_var[vstart] < 0x021 && from_var[vstart] > 0x0 ) vstart++; vlen = 0; while (from_var[vstart+vlen] >= 0x021) vlen++; memmove (from_var, &from_var[vstart], vlen); from_var[vlen] = '\000'; if (user_trace) fprintf (stderr, " command output will overwrite var ***%s***\n", from_var); // now get the name of the variable (if it exists) where // the kept-around minion process's pipes and pid are stored. crm_get_pgm_arg (keep_buf, MAX_PATTERN, apb->p3start, apb->p3len); keep_len = crm_nexpandvar (keep_buf, apb->p3len, MAX_PATTERN); if (user_trace) fprintf (stderr, " command status kept in var ***%s***\n", keep_buf); // get the command to execute // crm_get_pgm_arg (sys_cmd, MAX_PATTERN, apb->s1start, apb->s1len); cmd_len = crm_nexpandvar (sys_cmd, apb->s1len, MAX_PATTERN); if (user_trace) fprintf (stderr, " command will be ***%s***\n", sys_cmd); // Do we reuse an already-existing process? Check to see if the // keeper variable has it... note that we have to :* prefix it // and expand it again. minion = 0; to_minion[0] = 0; from_minion[1] = 0; exp_keep_buf [0] = '\000'; // this is 8-bit-safe because vars are never wchars. strcat (exp_keep_buf, ":*"); strncat (exp_keep_buf, keep_buf, keep_len); exp_keep_len = crm_nexpandvar (exp_keep_buf, keep_len+2, MAX_PATTERN); sscanf (exp_keep_buf, "MINION PROC PID: %d from-pipe: %d to-pipe: %d", &minion, &from_minion[0], &to_minion[1]); #ifdef POSIX // if, no minion already existing, we create // communications pipes and launch the subprocess. This // code borrows concepts from both liblaunch and from // netcat (thanks, *Hobbit*!) // if (minion == 0) { long status1, status2; if (user_trace) fprintf (stderr, " Must start a new minion.\n"); status1 = pipe (to_minion); status2 = pipe (from_minion); if (status1 > 0 || status2 > 0) { nonfatalerror ("Problem setting up the to/from pipes to a minion. ", "Perhaps the system file descriptor table is full?"); return (1); }; minion = fork(); if (minion < 0) { nonfatalerror ("Tried to fork your minion, but it failed.", "Your system may have run out of process slots"); return (1); }; if (minion == 0) { // START OF IN THE MINION // // if minion == 0, then We're in the minion here int retcode; long vstart, vlen; long varline; // close the ends of the pipes we don't need. // // NOTE: if this gets messed up, you end up with a race // condition, because both master and minion processes // can both read and write both pipes (effectively a // process could write something out, then read it again // right back out of the pipe)! So, it's REALLY REALLY // IMPORTANT that you use two pipe structures, (one for // each direction) and you keep track of which process // should write to which pipe!!! // close (to_minion[1]); close (from_minion[0]); dup2 (to_minion[0], fileno(stdin)); dup2 (from_minion[1], fileno(stdout)); // Are we a syscall to a :label:, or should we invoke the // shell on an external command? // crm_nextword (sys_cmd, strlen (sys_cmd), 0, &vstart, &vlen); varline = crm_lookupvarline (vht, sys_cmd, vstart, vlen); if (varline > 0) { // sys_cmd[vstart+vlen] = '\0'; if (user_trace) fprintf (stderr, "FORK transferring control to line %s\n", &sys_cmd[vstart]); // set the current pid and parent pid. { char pidstr [32]; long pid; pid = (long) getpid(); sprintf (pidstr, "%ld", pid); crm_set_temp_var (":_pid:", pidstr); if (user_trace) fprintf (stderr, "My new PID is %s\n", pidstr); pid = (long) getppid(); sprintf (pidstr, "%ld", pid); crm_set_temp_var (":_ppid:", pidstr); } // See if we have redirection of stdin and stdout while (crm_nextword (sys_cmd, strlen (sys_cmd), vstart+vlen, &vstart, &vlen)) { char filename[MAX_PATTERN]; if (sys_cmd[vstart] == '<') { strncpy (filename, &sys_cmd[vstart+1], vlen); filename[vlen-1] = '\0'; if (user_trace) fprintf (stderr, "Redirecting minion stdin to %s\n", filename); freopen (filename, "rb", stdin); }; if (sys_cmd[vstart] == '>') { if (sys_cmd[vstart+1] != '>') { strncpy (filename, &sys_cmd[vstart+1], vlen); filename[vlen-1] = '\0'; if (user_trace) fprintf (stderr, "Redirecting minion stdout to %s\n", filename); freopen (filename, "wb", stdout); } else { strncpy (filename, &sys_cmd[vstart+2], vlen); filename[vlen-2] = '\0'; if (user_trace) fprintf (stderr, "Appending minion stdout to %s\n", filename); freopen (filename, "a+", stdout); } }; } csl->cstmt = varline; // and note that this isn't a failure. csl->aliusstk [ csl->mct[csl->cstmt]->nest_level ] = 1; // The minion's real work should now start; get out of // the syscall code and go run something real. :) return (0); } else { if (user_trace) fprintf (stderr, "Systemcalling on shell command %s\n", sys_cmd); retcode = system (sys_cmd); // // This code only ever happens if an error occurs... // if (retcode == -1 ) { char errstr [4096]; sprintf (errstr, "The command was >%s< and returned exit code %d .", sys_cmd, WEXITSTATUS (retcode)); nonfatalerror ("This program tried a shell command that " "didn't run correctly. ", errstr); if (engine_exit_base != 0) { exit (engine_exit_base + 11); } else exit (WEXITSTATUS (retcode )); }; exit ( WEXITSTATUS (retcode) ); }; }; // END OF IN THE MINION } else { if (user_trace) fprintf (stderr, " reusing old minion PID: %d\n", minion); }; // Now, we're out of the minion for sure. // so we close the pipe ends we know we won't be using. if (to_minion[0] != 0) { close (to_minion[0]); close (from_minion[1]); }; // // launch "pusher" process to send the buffer to the minion // (this hint from Dave Soderberg). This avoids the deadly // embrace situation where both processes are waiting to read // (or, equally, both processes have written and filled up // their buffers, and are now held up waiting for the other // process to empty some space in the output buffer) // if (strlen (inbuf) > 0) { pusher = fork (); // we're in the "input pusher" process if we got here. // shove the input buffer out to the minion if (pusher == 0) { write (to_minion[1], inbuf, inlen ); if (internal_trace) fprintf (stderr, "pusher: input sent to minion.\n"); close (to_minion[1]); if (internal_trace) fprintf (stderr, "pusher: minion input pipe closed\n"); if (internal_trace) fprintf (stderr, "pusher: exiting pusher\n"); // The pusher always exits with success, so do NOT // do not use the engine_exit_base value exit ( EXIT_SUCCESS ); }; }; // now we're out of the pusher process. // if we don't want to keep this proc, we close it's input, and // wait for it to exit. if (! keep_proc) { close (to_minion[1]); if (internal_trace) fprintf (stderr, "minion input pipe closed\n"); } // and see what is in the pipe for us. outbuf[0] = '\000'; done = 0; outlen = 0; // grot grot grot this only works if varnames are not widechars if (strlen (from_var) > 0) { if (async_mode == 0 && keep_proc == 0) { usleep (timeout); // synchronous read- read till we hit EOF, which is read // returning a char count of zero. readloop: if (internal_trace) fprintf (stderr, "SYNCH READ "); usleep (timeout); charsread = read (from_minion[0], &outbuf[done], (data_window_size >> SYSCALL_WINDOW_RATIO) - done - 2); done = done + charsread; if ( charsread > 0 && done + 2 < (data_window_size >> SYSCALL_WINDOW_RATIO)) goto readloop; if (done < 0) done = 0; outbuf [done] = '\000'; outlen = done ; }; if (keep_proc == 1 || async_mode == 1) { // we're in either 'keep' 'async' mode. Set nonblocking mode, then // read it once; then put it back in regular mode. //fcntl (from_minion[0], F_SETFL, O_NONBLOCK); // usleep (timeout); charsread = read (from_minion[0], &outbuf[done], (data_window_size >> SYSCALL_WINDOW_RATIO)); done = charsread; if (done < 0) done = 0; outbuf [done] = '\000'; outlen = done ; //fcntl (from_minion[0], F_SETFL, 0); }; // If the minion process managed to fill our buffer, and we // aren't "keep"ing it around, OR if the process is "async", // then we should also launch a sucker process to // asynchronously eat all of the stuff we couldn't get into // the buffer. The sucker proc just reads stuff and throws it // away asynchronously... and exits when it gets EOF. // if ( async_mode || (outlen >= ((data_window_size >> SYSCALL_WINDOW_RATIO) - 2 ) && keep_proc == 0)) { sucker = fork (); if (sucker == 0) { // we're in the sucker process here- just throw away // everything till we get EOF, then exit. while (1) { usleep (timeout); charsread = read (from_minion[0], &outbuf[0], data_window_size >> SYSCALL_WINDOW_RATIO ); // in the sucker here, don't use engine_exit_base exit if (charsread == 0) exit (EXIT_SUCCESS); }; }; }; // and set the returned value into from_var. if (user_trace) fprintf (stderr, "SYSCALL output: %ld chars ---%s---.\n ", outlen, outbuf); if (internal_trace) fprintf (stderr, " storing return str in var %s\n", from_var); crm_destructive_alter_nvariable ( from_var, vlen, outbuf, outlen); }; // Record useful minion data, if possible. if (strlen (keep_buf) > 0) { sprintf (exp_keep_buf, "MINION PROC PID: %d from-pipe: %d to-pipe: %d", minion, from_minion[0], to_minion[1]); if (internal_trace) fprintf (stderr, " saving minion state: %s \n", exp_keep_buf); crm_destructive_alter_nvariable (keep_buf, keep_len, exp_keep_buf, strlen (exp_keep_buf)); }; // If we're keeping this minion process around, record the useful // information, like pid, in and out pipes, etc. if (keep_proc || async_mode) { } else { if (internal_trace) fprintf (stderr, "No keep, no async, so not keeping minion, closing everything.\n"); // de-zombify any dead minions; waitpid ( minion, &minion_exit_status, 0); // we're not keeping it around, so close the pipe. // close (from_minion [0]); if ( crm_vht_lookup (vht, keep_buf, strlen (keep_buf))) { char exit_value_string[MAX_VARNAME]; if (internal_trace) fprintf (stderr, "minion waitpid result :%d; whacking %s\n", minion_exit_status, keep_buf); sprintf (exit_value_string, "DEAD MINION, EXIT CODE: %d", WEXITSTATUS (minion_exit_status)); if (keep_len > 0) crm_destructive_alter_nvariable (keep_buf, keep_len, exit_value_string, strlen (exit_value_string)); }; }; #endif #ifdef WIN32 // if, no minion already existing, we create // communications pipes and launch the subprocess. This // code borrows concepts from both liblaunch and from // netcat (thanks, *Hobbit*!) // if (minion == 0) { int retcode; long vstart, vlen; long varline; if (user_trace) fprintf (stderr, " Must start a new minion.\n"); pipeSecAttr.nLength = sizeof(SECURITY_ATTRIBUTES); pipeSecAttr.bInheritHandle = TRUE; pipeSecAttr.lpSecurityDescriptor = NULL; status = CreatePipe(&to_minion[0], &to_minion[1], &pipeSecAttr, 2^10 * 32); status = CreatePipe(&from_minion[0], &from_minion[1], &pipeSecAttr, 2^10 * 32); crm_nextword (sys_cmd, strlen (sys_cmd), 0, &vstart, &vlen); varline = crm_lookupvarline (vht, sys_cmd, vstart, vlen); if (varline > 0) { fatalerror (" Sorry, syscall to a label isn't implemented in this version", ""); } else { STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE stdout_save, stdin_save; HANDLE to_minion_write, from_minion_read; stdout_save = GetStdHandle(STD_OUTPUT_HANDLE); SetStdHandle(STD_OUTPUT_HANDLE, from_minion[1]); stdin_save = GetStdHandle(STD_INPUT_HANDLE); SetStdHandle(STD_INPUT_HANDLE, to_minion[0]); DuplicateHandle(GetCurrentProcess(), from_minion[0], GetCurrentProcess(), &from_minion_read , 0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(from_minion[0]); from_minion[0] = from_minion_read; DuplicateHandle(GetCurrentProcess(), to_minion[1], GetCurrentProcess(), &to_minion_write , 0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(to_minion[1]); to_minion[1] = to_minion_write; if (user_trace) fprintf (stderr, "systemcalling on shell command %s\n", sys_cmd); ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); retcode = CreateProcess(NULL, sys_cmd, NULL, NULL, TRUE , NULL, NULL, NULL, &si, &pi); if (!retcode) { char errstr [4096]; sprintf (errstr, "The command was >>%s<< and returned exit code %d .", sys_cmd, retcode); fatalerror ("This program tried a shell command that " "didn't run correctly. ", errstr); { if (engine_exit_base != 0) { exit (engine_exit_base + 13); } else exit ( EXIT_FAILURE ); } } else { minion = pi.dwProcessId; hminion = pi.hProcess; SetStdHandle(STD_OUTPUT_HANDLE, stdout_save); SetStdHandle(STD_INPUT_HANDLE, stdin_save); CloseHandle(pi.hThread); } }; } else { if (user_trace) fprintf (stderr, " reusing old minion PID: %d\n", minion); hminion = OpenProcess(PROCESS_ALL_ACCESS, 0, minion); if (hminion == NULL) fatalerror("Couldn't open the existing minion process", ""); }; // Now, we're out of the minion for sure. // so we close the pipe ends we know we won't be using. if (to_minion[0] != 0) { CloseHandle (to_minion[0]); CloseHandle (from_minion[1]); }; // // launch "pusher" process to send the buffer to the minion // (this hint from Dave Soderberg). This avoids the deadly // embrace situation where both processes are waiting to read // (or, equally, both processes have written and filled up // their buffers, and are now held up waiting for the other // process to empty some space in the output buffer) // if (strlen (inbuf) > 0) { HANDLE hThread; pusherparams pp; char *inbuf_copy = malloc(sizeof(char) * inlen+1); int i; //Since the pusher thread may continue executing after the //syscall statement has finished, we need to make a copy of //inbuf for the pusher thread to use. The pusher process will //free the memory. for (i=0; i 0) { if (async_mode == 0) { Sleep (timeout); // synchronous read- read till we hit EOF, which is read // returning a char count of zero. readloop: if (internal_trace) fprintf (stderr, "SYNCH READ "); Sleep (timeout); charsread = 0; ReadFile(from_minion[0], outbuf + done, (data_window_size >> SYSCALL_WINDOW_RATIO) - done - 2, &charsread, NULL); done = done + charsread; if (charsread > 0 && done + 2 < (data_window_size >> SYSCALL_WINDOW_RATIO)) goto readloop; if (done < 0) done = 0; outbuf [done] = '\000'; outlen = done ; } else { // we're in 'async' mode. Just grab what we can ReadFile(from_minion[0], &outbuf[done], (data_window_size >> SYSCALL_WINDOW_RATIO), &charsread, NULL); done = charsread; if (done < 0) done = 0; outbuf [done] = '\000'; outlen = done ; } // If the minion process managed to fill our buffer, and we // aren't "keep"ing it around, OR if the process is "async", // then we should also launch a sucker process to // asynchronously eat all of the stuff we couldn't get into // the buffer. The sucker proc just reads stuff and throws it // away asynchronously... and exits when it gets EOF. // if ( async_mode || (outlen >= ((data_window_size >> SYSCALL_WINDOW_RATIO) - 2 ) && keep_proc == 0)) { HANDLE hThread; suckerparams sp; sp.from_minion = from_minion[0]; sp.timeout = timeout; CreateThread(NULL, 0, sucker_proc , &sp , NULL, &hThread); } // and set the returned value into from_var. if (user_trace) fprintf (stderr, "SYSCALL output: %ld chars ---%s---.\n ", outlen, outbuf); if (internal_trace) fprintf (stderr, " storing return str in var %s\n", from_var); crm_destructive_alter_nvariable ( from_var, vlen, outbuf, outlen); } // Record useful minion data, if possible. if (strlen (keep_buf) > 0) { sprintf (exp_keep_buf, "MINION PROC PID: %d from-pipe: %d to-pipe: %d", minion, from_minion[0], to_minion[1]); if (internal_trace) fprintf (stderr, " saving minion state: %s \n", exp_keep_buf); crm_destructive_alter_nvariable (keep_buf, keep_len, exp_keep_buf, strlen (exp_keep_buf)); }; // If we're keeping this minion process around, record the useful // information, like pid, in and out pipes, etc. if (!keep_proc && !async_mode) { DWORD exit_code; if (internal_trace) fprintf (stderr, "No keep, no async, so not keeping minion, closing everything.\n"); // no, we're not keeping it around, so close the pipe. // CloseHandle(from_minion [0]); WaitForSingleObject(hminion, INFINITE); if (!GetExitCodeProcess(hminion, &exit_code)) { DWORD error = GetLastError(); } if ( crm_vht_lookup (vht, keep_buf, strlen (keep_buf))) { char exit_value_string[MAX_VARNAME]; if (internal_trace) fprintf (stderr, "minion exit code :%d; whacking %s\n", exit_code, keep_buf); sprintf (exit_value_string, "DEAD MINION, EXIT CODE: %d", exit_code); if (keep_len > 0) crm_destructive_alter_nvariable (keep_buf, keep_len, exit_value_string, strlen (exit_value_string)); }; CloseHandle(hminion); }; #endif return (0); };