1 /* pperl - run perl scripts persistently */
2 
3 #include <stdio.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdarg.h>
7 #include <memory.h>
8 #include <errno.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <sys/socket.h>
12 #include <sys/un.h>
13 #include <sys/stat.h>
14 #include <sys/time.h>
15 #include <fcntl.h>
16 #include <limits.h>
17 #include <signal.h>
18 #include <unistd.h>
19 #include "pperl.h"
20 
21 #include "pass_fd.h" /* the stuff borrowed from stevens */
22 
23 #define DEBUG 1
24 
25 /* must never be less than 3 */
26 #define BUF_SIZE 4096
27 
28 
29 #ifdef ENOBUFS
30 #   define NO_BUFSPC(e) ((e) == ENOBUFS || (e) == ENOMEM)
31 #else
32 #   define NO_BUFSPC(e) ((e) == ENOMEM)
33 #endif
34 
35 static void Usage( char *pName );
36 static void DecodeParm( char *pArg );
37 static int  DispatchCall( char *scriptname, int argc, char **argv );
38 
39 char *pVersion = PPERL_VERSION;
40 char perl_options[1024];
41 extern char **environ;
42 pid_t connected_to;
43 int kill_script = 0;
44 int any_user = 0;
45 int prefork = 5;
46 int maxclients = 100;
47 int path_max;
48 int no_cleanup = 0;
49 FILE *log_fd = NULL;
50 
51 #if DEBUG
52 #define Dx(x) (x)
53 #else
54 #define Dx(x)
55 #endif
56 
57 static
Debug(const char * format,...)58 void Debug( const char * format, ...)
59 {
60     va_list args;
61 
62     if (!log_fd)
63       return;
64 
65     va_start(args, format);
66     vfprintf(log_fd, format, args);
67     va_end(args);
68     fflush(log_fd);
69 }
70 
71 
main(int argc,char ** argv)72 int main( int argc, char **argv )
73 {
74     int i;
75     char *pArg;
76     int pperl_section = 0;
77     int return_code = 0;
78 
79     if( argc < 2 )
80         Usage( argv[0] );
81 
82 #ifdef PATH_MAX
83     path_max = PATH_MAX;
84 #else
85     path_max = pathconf (path, _PC_PATH_MAX);
86     if (path_max <= 0) {
87         path_max = 4096;
88     }
89 #endif
90 
91     pperl_section = 0;
92     for ( i = 1; i < argc; i++ ) {
93         pArg = argv[i];
94         /* fprintf(stderr, "Parsing arg: %s\n", pArg); */
95         if (*pArg != '-') break;
96         if ( !strcmp(pArg, "-k") || !strcmp(pArg, "--kill") )
97             kill_script = 1;
98         else if (!strncmp(pArg, "--prefork", 9) ) {
99             int newval;
100             if (pArg[9] == '=') /* "--prefork=20" */
101                 pArg += 10;
102             else                /* "--prefork" "20" */
103                 pArg = argv[++i];
104 
105             newval = atoi(pArg);
106             if (newval > 0) prefork = newval;
107         }
108         else if (!strncmp(pArg, "--logfile", 9) ) {
109             int newval;
110             char *filename;
111             if (pArg[9] == '=') /* --logfile=.... */
112               pArg += 10;
113             else
114               pArg = argv[++i];
115 
116             filename = pArg;
117             newval = atoi(pArg);
118             if (newval == 0) {
119               fprintf(stderr, "opening log_fd: %s\n", filename);
120               log_fd = fopen(filename, "a");
121               if (!log_fd) {
122                 perror("Cannot open logfile");
123                 exit(1);
124               }
125             }
126             else {
127               log_fd = fdopen(newval, "a");
128               if (!log_fd) {
129                 perror("fd for --logfile error");
130                 exit(1);
131               }
132             }
133         }
134         else if (!strncmp(pArg, "--no-cleanup", 12) ) {
135             no_cleanup = 1;
136         }
137         else if (!strncmp(pArg, "--maxclients", 12) ) {
138             int newval;
139             if (pArg[12] == '=') /* "--maxclients=20" */
140                 pArg += 13;
141             else                /* "--maxclients" "20" */
142                 pArg = argv[++i];
143 
144             newval = atoi(pArg);
145             if (newval > 0) maxclients = newval;
146         }
147         else if ( !strcmp(pArg, "-z") || !strcmp(pArg, "--anyuser") )
148             any_user = 1;
149         else if ( !strcmp(pArg, "-h") || !strcmp(pArg, "--help") )
150             Usage( NULL );
151         else if ( !strncmp(pArg, "--", 2) )
152             ; /* do nothing - backward compatibility */
153         else {
154             DecodeParm( pArg );
155         }
156     }
157 
158     i++;
159     return_code = DispatchCall( pArg, argc - i, (char**)(argv + i) );
160     Dx(Debug("done, returning %d\n", return_code));
161     if (log_fd) fclose(log_fd);
162     return return_code;
163 }
164 
DecodeParm(char * pArg)165 static void DecodeParm( char *pArg )
166 {
167     if ( (strlen(perl_options) + strlen(pArg) + 1) > 1000 ) {
168         fprintf(stderr, "param list too long. Sorry.");
169         exit(1);
170     }
171     strcat(perl_options, pArg);
172     strcat(perl_options, " ");
173 }
174 
Usage(char * pName)175 static void Usage( char *pName )
176 {
177     printf( "pperl version %s\n", pVersion );
178 
179     if( pName == NULL )
180     {
181         printf( "Usage: pperl [options] filename\n" );
182     }
183     else
184     {
185         printf( "Usage: %.255s [options] filename\n", pName );
186     }
187     printf("perl options are passed to your perl executable (see the perlrun man page).\n"
188            "pperl options control the persistent perl behaviour\n"
189            "\n"
190            "PPerl Options:\n"
191            "  -k  or --kill      Kill the currently running pperl for that script\n"
192            "  -h  or --help      This page\n"
193 	   "  --prefork          The number of child processes to prefork (default=5)\n"
194 	   "  --maxclients       The number of client connections each child\n"
195 	   "                       will process (default=100)\n"
196            "  -z  or --anyuser   Allow any user (after the first) to access the socket\n"
197            "                       WARNING: This has severe security implications. Use\n"
198 	   "                       at your own risk\n"
199            "  --no-cleanup       Skip the cleanup stage at the end of running your script\n"
200            "                       this may make your code run faster, but if you forget\n"
201            "                       to close files then they will remain unflushed and unclosed\n"
202     );
203     exit( 1 );
204 }
205 
206 static void *
my_malloc(size_t size)207 my_malloc(size_t size)
208 {
209     void *mem = malloc(size);
210     if (mem == NULL) {
211         perror("malloc failed");
212         exit(-1);
213     }
214     return mem;
215 }
216 
217 /* make socket name from scriptname, switching / for _ */
218 static char *
MakeSockName(char * scriptname)219 MakeSockName(char * scriptname )
220 {
221     char * sockname;
222     char * save;
223     /* strict C compilers can't/won't do char foo[variant]; */
224     char *fullpath = my_malloc(path_max);
225     int i = 0;
226     char euid[sizeof(uid_t)+10+2];
227     sprintf(euid, "_%d", geteuid());
228 
229     if (realpath(scriptname, fullpath) == NULL) {
230         perror("pperl: resolving full pathname to script failed");
231         exit(1);
232     }
233     Dx(Debug("realpath returned: %s\n", fullpath));
234     /* Ugh. I am a terrible C programmer! */
235     sockname = my_malloc(strlen(P_tmpdir) + strlen(fullpath) + 3 + strlen(euid));
236     save = sockname;
237     sprintf(sockname, "%s/", P_tmpdir);
238     sockname += strlen(P_tmpdir) + 1;
239     while (fullpath[i] != '\0') {
240         if (fullpath[i] == '/') {
241             *sockname = '_';
242         }
243         else if (fullpath[i] == '.') {
244             *sockname = '_';
245         }
246         else {
247             *sockname = fullpath[i];
248         }
249         sockname++; i++;
250     }
251     strcat(sockname, euid);
252     free(fullpath);
253     return save;
254 }
255 
256 
257 static void
sig_handler(int sig)258 sig_handler(int sig)
259 {
260     kill(connected_to, sig);
261     signal(sig, sig_handler);
262     /* skreech_to_a_halt++; */
263 }
264 
265 static int handle_socket(int sd, int argc, char **argv );
DispatchCall(char * scriptname,int argc,char ** argv)266 static int DispatchCall( char *scriptname, int argc, char **argv )
267 {
268     register int i, sd, len;
269     int error_number;
270     ssize_t readlen;
271     struct sockaddr_un saun;
272     struct stat stat_buf;
273     struct stat sock_stat;
274     char *sock_name;
275     char buf[BUF_SIZE];
276     int respawn_script = 0;
277 	sd = 0;
278 
279     /* create socket name */
280     Dx(Debug("pperl: %s\n", scriptname));
281     sock_name = MakeSockName(scriptname);
282     Dx(Debug("got socket: %s\n", sock_name));
283 
284     if (!stat(sock_name, &sock_stat) && !stat(scriptname, &stat_buf)) {
285         if (stat_buf.st_mtime >= sock_stat.st_mtime) {
286             respawn_script = 1;
287             Dx(Debug("respawning slave - top level script changed\n"));
288         }
289     }
290 
291     if (kill_script || respawn_script) {
292         int pid_fd, sock_name_len;
293         char *pid_file;
294         pid_t pid = 0;
295 
296         respawn_script = 0; /* reset so we can use it later :-) */
297 
298         sock_name_len = strlen(sock_name);
299         pid_file = my_malloc(sock_name_len + 5);
300         strncpy(pid_file, sock_name, sock_name_len);
301         pid_file[sock_name_len] = '.';
302         pid_file[sock_name_len+1] = 'p';
303         pid_file[sock_name_len+2] = 'i';
304         pid_file[sock_name_len+3] = 'd';
305         pid_file[sock_name_len+4] = '\0';
306 
307         Dx(Debug("opening pid_file: %s\n", pid_file));
308         pid_fd = open(pid_file, O_RDONLY);
309         if (pid_fd == -1) {
310             Dx(Debug("Cannot open pid file (perhaps PPerl wasn't running for that script?)\n"));
311             write(1, "No process killed - no pid file\n", 32);
312             goto killed;
313         }
314 
315         readlen = read(pid_fd, buf, BUF_SIZE);
316         if (readlen == -1) {
317             perror("pperl: nothing in pid file?");
318             goto killed;
319         }
320         buf[readlen] = '\0';
321 
322         close(pid_fd);
323 
324         pid = atoi(buf);
325         Dx(Debug("got pid %d (%s)\n", pid, buf));
326         if (kill(pid, SIGINT) == -1) {
327             if (errno == ESRCH) {
328                 perror("pperl kill");
329                 Dx(Debug("Process didn't exist. Unlinking %s and %s\n", pid_file, sock_name));
330                 unlink(pid_file);
331                 unlink(sock_name);
332             }
333             else {
334                 perror("pperl: could not kill process");
335             }
336         }
337 
338         free(pid_file);
339 
340     killed:
341 
342         if (kill_script) {
343             free(sock_name); /* Hmm, should probably do this everywhere else we return too */
344             return 0;
345         }
346 
347         if (pid != 0) {
348             /* cheesy - let the child go away proper */
349             while (!kill(pid, 0)) {}
350         }
351     }
352 
353     for (i = 0; i < 10; i++) {
354         sd = socket(PF_UNIX, SOCK_STREAM, PF_UNSPEC);
355         if (sd != -1) {
356             break;
357         }
358         else if (NO_BUFSPC(errno)) {
359             sleep(1);
360         }
361         else {
362             perror("pperl: Couldn't create socket");
363             return 1;
364         }
365     }
366 
367     saun.sun_family = PF_UNIX;
368     strcpy(saun.sun_path, sock_name);
369 
370     len = sizeof(saun.sun_family) + strlen(saun.sun_path) + 1;
371 
372     Dx(Debug("%d connecting\n", getpid()));
373 
374     if (stat((const char*)sock_name, &stat_buf)) {
375         if (errno == ENOENT) {
376             /* socket doesn't exist. good */
377             Dx(Debug("socket doesn't exist yet (good)\n"));
378         }
379         else {
380             perror("Socket stat error");
381             exit(1);
382         }
383     }
384 
385     /* is there a race between stat() and connect() here? Or is it irrelevant? */
386 
387     if (connect(sd, (struct sockaddr *)&saun, len) < 0) {
388         /* Consider spawning Perl here and try again */
389         FILE *source;
390         int tmp_fd;
391         char temp_file[BUF_SIZE];
392         char *lock_file;
393         int sock_name_len;
394         int lock_fd;
395         int start_checked = 0;
396         int wrote_footer = 0; /* we may encounter __END__ or __DATA__ */
397         int line;
398         int retry_connect = 0;
399         int exit_code = 0;
400 
401         int pid, itmp, exitstatus;
402         sigset_t mask, omask;
403 
404         Dx(Debug("Couldn't connect, spawning new server: %s\n", strerror(errno)));
405 
406         sock_name_len = strlen(sock_name);
407         lock_file = my_malloc(sock_name_len + 6);
408         strncpy(lock_file, sock_name, sock_name_len);
409         lock_file[sock_name_len] = '.';
410         lock_file[sock_name_len+1] = 'l';
411         lock_file[sock_name_len+2] = 'o';
412         lock_file[sock_name_len+3] = 'c';
413         lock_file[sock_name_len+4] = 'k';
414         lock_file[sock_name_len+5] = '\0';
415 
416         Dx(Debug("opening lock_file: %s\n", lock_file));
417         lock_fd = open(lock_file, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR);
418         if (lock_fd == -1) {
419             perror("Cannot open lock file");
420             exit_code = 1;
421             goto cleanup;
422         }
423         while (flock(lock_fd, LOCK_EX|LOCK_NB) == -1) {
424             Dx(Debug("flock failed - someone else is probably waiting to spawn - sleeping\n"));
425             retry_connect = 1;
426             sleep(1);
427         }
428 
429         if (retry_connect) {
430             if (connect(sd, (struct sockaddr *)&saun, len) >= 0) {
431                 goto cleanup; /* everything is now OK! */
432             }
433             /* otherwise we try ourselves to re-spawn */
434         }
435 
436         /*
437         if (unlink(sock_name) != 0 && errno != ENOENT) {
438             perror("pperl: removal of old socket failed");
439             exit_code = 1;
440             goto cleanup;
441         }
442         */
443 
444         /* Create temp file with adjusted script... */
445         if (!(source = fopen(scriptname, "r"))) {
446             perror("pperl: Cannot open perl script");
447             exit_code = 1;
448             goto cleanup;
449         }
450 
451         snprintf(temp_file, BUF_SIZE, "%s/%s", P_tmpdir, "pperlXXXXXX");
452         tmp_fd = mkstemp(temp_file);
453         if (tmp_fd == -1) {
454             perror("pperl: Cannot create temporary file");
455             exit_code = 1;
456             goto cleanup;
457         }
458 
459         write(tmp_fd, "### Temp File ###\n", 18);
460         write(tmp_fd, perl_header, strlen(perl_header));
461 
462         /* rewrite the perl script with pperl.h.header contents wrapper
463            and do some other fixups in the process */
464         line = 0;
465         while ( fgets( buf, BUF_SIZE, source ) ) {
466             readlen = strlen(buf);
467             Dx(Debug("read '%s' %d \n", buf, readlen));
468 
469             if (!start_checked) { /* first line */
470                 start_checked = 1;
471 
472                 if (buf[0] == '#' && buf[1] == '!') {
473                     char *args;
474                     /* solaris sometimes doesn't propogate all the
475                      * shebang line  - so we do that here */
476                     if ( (args = strstr(buf, " ")) ) {
477                         strncat(perl_options, args, strlen(args) - 1);
478                     }
479 
480                     write(tmp_fd, "\n#line 2 ", 9);
481                     write(tmp_fd, scriptname, strlen(scriptname));
482                     write(tmp_fd, "\n", 1);
483 
484                     line = 2;
485                     continue;
486                 }
487                 else {
488                     write(tmp_fd, "\n#line 1 ", 9);
489                     write(tmp_fd, scriptname, strlen(scriptname));
490                     write(tmp_fd, "\n", 1);
491                 }
492             }
493             if ((!strcmp(buf, "__END__\n") ||
494                  !strcmp(buf, "__DATA__\n")) &&
495                 !wrote_footer) {
496                 char text_line[BUF_SIZE];
497                 wrote_footer = 1;
498                 write(tmp_fd, perl_footer, strlen(perl_footer));
499                 snprintf(text_line, BUF_SIZE, "package main;\n#line %d %s\n", line, scriptname);
500                 write(tmp_fd, text_line, strlen(text_line));
501             }
502             write(tmp_fd, buf, readlen);
503             if (buf[readlen] == '\n') ++line;
504         }
505 
506         if (fclose(source)) {
507             perror("pperl: Error reading perl script");
508             exit_code = 1;
509             goto cleanup;
510         }
511 
512         if (!wrote_footer)
513             write(tmp_fd, perl_footer, strlen(perl_footer));
514 
515         Dx(Debug("wrote file %s\n", temp_file));
516 
517         close(tmp_fd);
518 
519         /*** Temp file creation done ***/
520 
521         snprintf(buf, BUF_SIZE, "%s %s %s %s %d %d %d %d %s",
522                  PERL_INTERP, perl_options, temp_file,
523                  sock_name, prefork, maxclients,
524                  any_user, no_cleanup, scriptname);
525         Dx(Debug("syscall: %s\n", buf));
526 
527         /* block SIGCHLD so noone else can wait() on the child before we do */
528         sigemptyset(&mask);
529         sigaddset(&mask, SIGCHLD);
530         sigprocmask(SIG_BLOCK, &mask, &omask);
531 
532         if ((pid = system(buf)) != 0) {
533             unlink(temp_file);
534             if (stat((const char*)sock_name, &stat_buf) == 0) {
535                 /* socket exists - perhaps we should just try and connect to it? */
536                 /* possible cause is a race condition. So ignore this and just try
537                    the connect() call again. */
538                 perror("pperl: perl script failed to start, but lets be gung-ho and try and connect again anyway!");
539             }
540             perror("pperl: perl script failed to start");
541             exit_code = 1;
542             goto cleanup;
543         }
544         else {
545           Dx(Debug("waiting for perl to return...\n"));
546           while ((itmp = waitpid(0, &exitstatus, 0)) == -1 && errno == EINTR)
547               ;
548           sigprocmask(SIG_SETMASK, &omask, NULL);
549           Dx(Debug("returned.\n"));
550 
551           /* now remove the perl script */
552           unlink(temp_file);
553         }
554 
555         /* try and connect to the new socket */
556         while ((i++ <= 30) && (connect(sd, (struct sockaddr *)&saun, len) < 0))
557         {
558             Dx(Debug("."));
559             sleep(1);
560         }
561         if (i >= 30) {
562             /* If we really *really* couldn't connect, try and delete the socket if it exists */
563             if (unlink(sock_name) != 0 && errno != ENOENT) {
564                 perror("pperl: removal of old socket failed");
565             }
566             perror("pperl: persistent perl process failed to start after 30 seconds");
567             exit_code = 1;
568             goto cleanup;
569         }
570 
571         Dx(Debug("Connected\n"));
572 
573     cleanup:
574         flock(lock_fd, LOCK_UN);
575         close(lock_fd);
576         free(lock_file);
577         if (exit_code > 0) {
578             free(sock_name);
579             exit(exit_code);
580         }
581     }
582 
583     free(sock_name);
584     return handle_socket(sd, argc, argv);
585 }
586 
587 static
588 int
handle_socket(int sd,int argc,char ** argv)589 handle_socket(int sd, int argc, char **argv) {
590     long max_fd;
591     char **env;
592     int i;
593     char buf[BUF_SIZE];
594 
595     Dx(Debug("connected over %d\n", sd));
596 
597     read(sd, buf, 10);
598     buf[10] = '\0';
599     connected_to = atoi(buf);
600     Dx(Debug("chatting to %d, hooking signals\n", connected_to));
601 
602     /* bad magic number, there only seem to be 30 signals on a linux
603      * box -- richardc*/
604     for (i = 1; i < 32; i++)
605         signal(i, sig_handler);
606 
607 
608     Dx(Debug("sending fds\n"));
609     if ((max_fd = sysconf(_SC_OPEN_MAX)) < 0) {
610         perror("pperl: dunno how many fds to check");
611         exit(1);
612     }
613 
614     for (i = 0; i < max_fd; i++) {
615         if (fcntl(i, F_GETFL, -1) >= 0 && i != sd) {
616             int ret;
617             write(sd, &i, sizeof(int));
618             ret = send_fd(sd, i);
619             Dx(Debug("send_fd %d %d\n", i, ret));
620         }
621     }
622     i = -1;
623     write(sd, &i, sizeof(int));
624     Dx(Debug("fds sent\n"));
625 
626     write(sd, "[PID]", 6);
627     snprintf(buf, BUF_SIZE, "%d", getpid());
628     write(sd, buf, strlen(buf) + 1);
629 
630 
631     /* print to socket... */
632     write(sd, "[ENV]", 6);
633     for (i= 0, env = environ; *env; i++, env++);
634     snprintf(buf, BUF_SIZE, "%d", i);
635     write(sd, buf, strlen(buf) + 1);
636 
637     while ( *environ != NULL ) {
638         size_t len = strlen(*environ) + 1;
639         /* Dx(Debug("sending environ: %s\n", *environ)); */
640         write(sd, *environ, len);
641         environ++;
642     }
643 
644     write(sd, "[CWD]", 6);
645     if (getcwd(buf, BUF_SIZE) == NULL) {
646         perror("pperl: getcwd");
647         exit (1);
648     }
649     write(sd, buf, strlen(buf) + 1);
650 
651     Dx(Debug("sending %d args\n", argc));
652     write(sd, "[ARGV]", 7);
653     snprintf(buf, BUF_SIZE, "%d", argc);
654     write(sd, buf, strlen(buf) + 1);
655     for (i = 0; i < argc; i++) {
656         size_t len = strlen(argv[i]) + 1;
657         Dx(Debug("sending argv[%d]: '%s'\n", i, argv[i]));
658         write(sd, argv[i], len);
659     }
660 
661     write(sd, "[DONE]", 7);
662 
663     Dx(Debug("waiting for OK message from %d\n", sd));
664     if (read(sd, buf, 3) != 3) {
665         perror("pperl: failed to read 3 bytes for an OK message");
666         exit(1);
667     }
668     if (strncmp(buf, "OK\n", 3)) {
669         i = read(sd, buf, BUF_SIZE - 1);
670         buf[i] = '\0';
671         fprintf(stderr, "pperl: expected 'OK\\n', got: '%s'\n", buf);
672         exit(1);
673     }
674     Dx(Debug("got it\n"));
675 
676     Dx(Debug("reading return code\n"));
677     i = read(sd, buf, BUF_SIZE - 1);
678     if (i == -1) {
679       perror("Nothing read back from socket!");
680     }
681     buf[i] = '\0';
682     Dx(Debug("socket read '%s'\n", buf));
683 
684     for (i = 0; i < max_fd; i++) {
685         close(i);
686     }
687 
688     exit (atoi(buf));
689 }
690 
691 
692 
693 /*
694 Local Variables:
695 mode: C
696 c-basic-offset: 4
697 tab-width: 4
698 indent-tabs-mode: nil
699 End:
700 */
701