/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugbasedclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-01 06:56:27 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080801065627-2c7k4ydefjgacgnq
* plugins.d/plugbasedclient.c (set_cloexec_flag): New function.
  (main): Use the set_cloexec_flag function.  Move inserting of global
  arguments to before the fork.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
22
22
 */
23
23
 
24
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY() */
25
 
 
26
24
#include <stdio.h>              /* popen(), fileno(), fprintf(),
27
25
                                   stderr, STDOUT_FILENO */
28
26
#include <iso646.h>             /* and, or, not */
29
 
#include <sys/types.h>          /* DIR, opendir(), stat(),
30
 
                                   struct stat, waitpid(),
31
 
                                   WIFEXITED(), WEXITSTATUS(),
32
 
                                   wait() */
 
27
#include <sys/types.h>         /* DIR, opendir(), stat(), struct stat,
 
28
                                  waitpid(), WIFEXITED(),
 
29
                                  WEXITSTATUS(), wait() */
33
30
#include <sys/wait.h>           /* wait() */
34
31
#include <dirent.h>             /* DIR, struct dirent, opendir(),
35
32
                                   readdir(), closedir() */
58
55
  char *buffer;
59
56
  size_t buffer_size;
60
57
  size_t buffer_length;
61
 
  bool eof;
62
 
  bool completed;
63
 
  int status;
64
58
  struct process *next;
65
59
} process;
66
60
 
128
122
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
129
123
}
130
124
 
 
125
 
131
126
#define BUFFER_SIZE 256
132
127
 
133
 
const char *argp_program_version = "plugbasedclient 0.9";
134
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
135
 
 
136
 
process *process_list = NULL;
137
 
 
138
 
/* Mark a process as completed when it exits, and save its exit
139
 
   status. */
140
 
void handle_sigchld(__attribute__((unused)) int sig){
141
 
  process *proc = process_list;
142
 
  int status;
143
 
  pid_t pid = wait(&status);
144
 
  while(proc != NULL and proc->pid != pid){
145
 
    proc = proc->next;    
146
 
  }
147
 
  if(proc == NULL){
148
 
    /* Process not found in process list */
149
 
    return;
150
 
  }
151
 
  proc->status = status;
152
 
  proc->completed = true;
153
 
}
 
128
const char *argp_program_version =
 
129
  "plugbasedclient 0.9";
 
130
const char *argp_program_bug_address =
 
131
  "<mandos@fukt.bsnet.se>";
154
132
 
155
133
int main(int argc, char *argv[]){
156
134
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
157
135
  size_t d_name_len;
158
 
  DIR *dir = NULL;
 
136
  DIR *dir;
159
137
  struct dirent *dirst;
160
138
  struct stat st;
161
139
  fd_set rfds_all;
162
140
  int ret, maxfd = 0;
 
141
  process *process_list = NULL;
163
142
  bool debug = false;
164
143
  int exitstatus = EXIT_SUCCESS;
165
144
  
166
 
  /* Establish a signal handler */
167
 
  struct sigaction old_sigchld_action,
168
 
    sigchld_action = { .sa_handler = handle_sigchld,
169
 
                       .sa_flags = SA_NOCLDSTOP };
170
 
  sigemptyset(&sigchld_action.sa_mask);
171
 
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
172
 
  if(ret < 0){
173
 
    perror("sigaddset");
174
 
    exit(EXIT_FAILURE);
175
 
  }
176
 
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
177
 
  if(ret < 0){
178
 
    perror("sigaction");
179
 
    exit(EXIT_FAILURE);
180
 
  }
181
 
  
182
145
  /* The options we understand. */
183
146
  struct argp_option options[] = {
184
147
    { .name = "global-options", .key = 'g',
199
162
  };
200
163
  
201
164
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
202
 
    /* Get the INPUT argument from `argp_parse', which we know is a
203
 
       pointer to our plugin list pointer. */
 
165
       /* Get the INPUT argument from `argp_parse', which we
 
166
          know is a pointer to our plugin list pointer. */
204
167
    plugin **plugins = state->input;
205
168
    switch (key) {
206
169
    case 'g':
266
229
  }
267
230
  
268
231
  dir = opendir(plugindir);
 
232
  /* Set the FD_CLOEXEC flag on the directory */
 
233
  ret = set_cloexec_flag(dirfd(dir));
 
234
  if(ret < 0){
 
235
    perror("set_cloexec_flag");
 
236
    goto end;
 
237
  }
 
238
  
269
239
  if(dir == NULL){
270
 
    perror("Could not open plugin dir");
271
 
    exitstatus = EXIT_FAILURE;
272
 
    goto end;
273
 
  }
274
 
  
275
 
  /* Set the FD_CLOEXEC flag on the directory, if possible */
276
 
  {
277
 
    int dir_fd = dirfd(dir);
278
 
    if(dir_fd >= 0){
279
 
      ret = set_cloexec_flag(dir_fd);
280
 
      if(ret < 0){
281
 
        perror("set_cloexec_flag");
282
 
        exitstatus = EXIT_FAILURE;
283
 
        goto end;
284
 
      }
285
 
    }
 
240
    fprintf(stderr, "Can not open directory\n");
 
241
    return EXIT_FAILURE;
286
242
  }
287
243
  
288
244
  FD_ZERO(&rfds_all);
311
267
        if((d_name_len >= pre_len)
312
268
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
313
269
          if(debug){
314
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
270
            fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
315
271
                    " with bad prefix %s\n", dirst->d_name, *pre);
316
272
          }
317
273
          bad_name = true;
329
285
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
330
286
                == 0)){
331
287
          if(debug){
332
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
288
            fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
333
289
                    " with bad suffix %s\n", dirst->d_name, *suf);
334
290
          }
335
291
          bad_name = true;
345
301
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
346
302
    if (filename == NULL){
347
303
      perror("malloc");
348
 
      exitstatus = EXIT_FAILURE;
 
304
      exitstatus =EXIT_FAILURE;
349
305
      goto end;
350
306
    }
351
 
    strcpy(filename, plugindir); /* Spurious warning */
352
 
    strcat(filename, "/");      /* Spurious warning */
353
 
    strcat(filename, dirst->d_name); /* Spurious warning */
354
 
    
 
307
    strcpy(filename, plugindir);
 
308
    strcat(filename, "/");
 
309
    strcat(filename, dirst->d_name);    
 
310
 
355
311
    stat(filename, &st);
356
 
    
 
312
 
357
313
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
358
314
      if(debug){
359
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
315
        fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
360
316
                " with bad type or mode\n", filename);
361
317
      }
362
 
      free(filename);
363
318
      continue;
364
319
    }
365
320
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
366
321
      if(debug){
367
 
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
 
322
        fprintf(stderr, "Ignoring disabled plugin \"%s\"",
368
323
                dirst->d_name);
369
324
      }
370
 
      free(filename);
371
325
      continue;
372
326
    }
 
327
    // Starting a new process to be watched
 
328
    int pipefd[2]; 
 
329
    ret = pipe(pipefd);
 
330
    if (ret == -1){
 
331
      perror(argv[0]);
 
332
      goto end;
 
333
    }
373
334
    plugin *p = getplugin(dirst->d_name, &plugin_list);
374
335
    {
375
336
      /* Add global arguments to argument list for this plugin */
378
339
        addargument(p, *a);
379
340
      }
380
341
    }
381
 
    int pipefd[2]; 
382
 
    ret = pipe(pipefd);
383
 
    if (ret == -1){
384
 
      perror("pipe");
385
 
      exitstatus = EXIT_FAILURE;
386
 
      goto end;
387
 
    }
388
 
    ret = set_cloexec_flag(pipefd[0]);
389
 
    if(ret < 0){
390
 
      perror("set_cloexec_flag");
391
 
      exitstatus = EXIT_FAILURE;
392
 
      goto end;
393
 
    }
394
 
    ret = set_cloexec_flag(pipefd[1]);
395
 
    if(ret < 0){
396
 
      perror("set_cloexec_flag");
397
 
      exitstatus = EXIT_FAILURE;
398
 
      goto end;
399
 
    }
400
 
    /* Block SIGCHLD until process is safely in process list */
401
 
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
402
 
    if(ret < 0){
403
 
      perror("sigprocmask");
404
 
      exitstatus = EXIT_FAILURE;
405
 
      goto end;
406
 
    }
407
 
    // Starting a new process to be watched
408
342
    pid_t pid = fork();
409
343
    if(pid == 0){
410
344
      /* this is the child process */
411
 
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
412
 
      if(ret < 0){
413
 
        perror("sigaction");
414
 
        _exit(EXIT_FAILURE);
415
 
      }
416
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
417
 
      if(ret < 0){
418
 
        perror("sigprocmask");
419
 
        _exit(EXIT_FAILURE);
420
 
      }
 
345
      closedir(dir);
 
346
      close(pipefd[0]); /* close unused read end of pipe */
421
347
      dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
422
348
      
423
 
      if(dirfd(dir) < 0){
424
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
425
 
           above and must now close it manually here. */
426
 
        closedir(dir);
427
 
      }
428
349
      if(execv(filename, p->argv) < 0){
429
 
        perror("execv");
 
350
        perror(argv[0]);
 
351
        close(pipefd[1]);
430
352
        _exit(EXIT_FAILURE);
431
353
      }
432
354
      /* no return */
433
355
    }
434
 
    /* parent process */
435
 
    free(filename);
436
356
    close(pipefd[1]);           /* close unused write end of pipe */
437
357
    process *new_process = malloc(sizeof(process));
438
358
    if (new_process == NULL){
439
359
      perror("malloc");
440
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
441
 
      if(ret < 0){
442
 
        perror("sigprocmask");
443
 
      }
444
 
      exitstatus = EXIT_FAILURE;
445
 
      goto end;
446
 
    }
447
 
    
448
 
    *new_process = (struct process){ .pid = pid,
449
 
                                     .fd = pipefd[0],
450
 
                                     .next = process_list };
451
 
    // List handling
452
 
    process_list = new_process;
453
 
    /* Unblock SIGCHLD so signal handler can be run if this process
454
 
       has already completed */
455
 
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
456
 
    if(ret < 0){
457
 
      perror("sigprocmask");
458
 
      exitstatus = EXIT_FAILURE;
459
 
      goto end;
460
 
    }
461
 
    
 
360
      exitstatus = EXIT_FAILURE;
 
361
      goto end;
 
362
    }
 
363
    
 
364
    new_process->fd = pipefd[0];
 
365
    new_process->buffer = malloc(BUFFER_SIZE);
 
366
    if (new_process->buffer == NULL){
 
367
      perror("malloc");
 
368
      exitstatus = EXIT_FAILURE;
 
369
      goto end;
 
370
    }
 
371
    new_process->buffer_size = BUFFER_SIZE;
 
372
    new_process->buffer_length = 0;
462
373
    FD_SET(new_process->fd, &rfds_all);
463
 
    
 
374
      
464
375
    if (maxfd < new_process->fd){
465
376
      maxfd = new_process->fd;
466
377
    }
467
378
    
468
 
  }
469
 
  
470
 
  /* Free the plugin list */
471
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
472
 
    next = plugin_list->next;
473
 
    free(plugin_list->argv);
474
 
    free(plugin_list);
 
379
    //List handling
 
380
    new_process->next = process_list;
 
381
    process_list = new_process;
475
382
  }
476
383
  
477
384
  closedir(dir);
478
 
  dir = NULL;
479
385
  
480
 
  if (process_list == NULL){
481
 
    fprintf(stderr, "No plugin processes started, exiting\n");
482
 
    exitstatus = EXIT_FAILURE;
483
 
    goto end;
484
 
  }
485
 
  while(process_list){
486
 
    fd_set rfds = rfds_all;
487
 
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
488
 
    if (select_ret == -1){
489
 
      perror("select");
490
 
      exitstatus = EXIT_FAILURE;
491
 
      goto end;
492
 
    }
493
 
    /* OK, now either a process completed, or something can be read
494
 
       from one of them */
495
 
    for(process *proc = process_list; proc ; proc = proc->next){
496
 
      /* Is this process completely done? */
497
 
      if(proc->eof and proc->completed){
498
 
        /* Only accept the plugin output if it exited cleanly */
499
 
        if(not WIFEXITED(proc->status)
500
 
           or WEXITSTATUS(proc->status) != 0){
501
 
          /* Bad exit by plugin */
502
 
          if(debug){
503
 
            if(WIFEXITED(proc->status)){
504
 
              fprintf(stderr, "Plugin %d exited with status %d\n",
505
 
                      proc->pid, WEXITSTATUS(proc->status));
506
 
            } else if(WIFSIGNALED(proc->status)) {
507
 
              fprintf(stderr, "Plugin %d killed by signal %d\n",
508
 
                      proc->pid, WTERMSIG(proc->status));
509
 
            } else if(WCOREDUMP(proc->status)){
510
 
              fprintf(stderr, "Plugin %d dumped core\n", proc->pid);
511
 
            }
512
 
          }
513
 
          /* Remove the plugin */
514
 
          FD_CLR(proc->fd, &rfds_all);
515
 
          /* Block signal while modifying process_list */
516
 
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
517
 
          if(ret < 0){
518
 
            perror("sigprocmask");
519
 
            exitstatus = EXIT_FAILURE;
520
 
            goto end;
521
 
          }
522
 
          /* Delete this process entry from the list */
523
 
          if(process_list == proc){
524
 
            /* First one - simple */
525
 
            process_list = proc->next;
526
 
          } else {
527
 
            /* Second one or later */
528
 
            for(process *p = process_list; p != NULL; p = p->next){
529
 
              if(p->next == proc){
530
 
                p->next = proc->next;
531
 
                break;
 
386
  if (process_list != NULL){
 
387
    while(true){
 
388
      fd_set rfds = rfds_all;
 
389
      int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
 
390
      if (select_ret == -1){
 
391
        perror(argv[0]);
 
392
        goto end;
 
393
      }else{    
 
394
        for(process *process_itr = process_list; process_itr != NULL;
 
395
            process_itr = process_itr->next){
 
396
          if(FD_ISSET(process_itr->fd, &rfds)){
 
397
            if(process_itr->buffer_length + BUFFER_SIZE
 
398
               > process_itr->buffer_size){
 
399
                process_itr->buffer = realloc(process_itr->buffer,
 
400
                                              process_itr->buffer_size
 
401
                                              + (size_t) BUFFER_SIZE);
 
402
                if (process_itr->buffer == NULL){
 
403
                  perror(argv[0]);
 
404
                  goto end;
 
405
                }
 
406
                process_itr->buffer_size += BUFFER_SIZE;
 
407
            }
 
408
            ret = read(process_itr->fd, process_itr->buffer
 
409
                       + process_itr->buffer_length, BUFFER_SIZE);
 
410
            if(ret < 0){
 
411
              /* Read error from this process; ignore it */
 
412
              continue;
 
413
            }
 
414
            process_itr->buffer_length += (size_t) ret;
 
415
            if(ret == 0){
 
416
              /* got EOF */
 
417
              /* wait for process exit */
 
418
              int status;
 
419
              waitpid(process_itr->pid, &status, 0);
 
420
              if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
 
421
                for(size_t written = 0;
 
422
                    written < process_itr->buffer_length;){
 
423
                  ret = write(STDOUT_FILENO,
 
424
                              process_itr->buffer + written,
 
425
                              process_itr->buffer_length - written);
 
426
                  if(ret < 0){
 
427
                    perror(argv[0]);
 
428
                    goto end;
 
429
                  }
 
430
                  written += (size_t)ret;
 
431
                }
 
432
                goto end;
 
433
              } else {
 
434
                FD_CLR(process_itr->fd, &rfds_all);
532
435
              }
533
436
            }
534
437
          }
535
 
          /* We are done modifying process list, so unblock signal */
536
 
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
537
 
                             NULL);
538
 
          if(ret < 0){
539
 
            perror("sigprocmask");
540
 
          }
541
 
          free(proc->buffer);
542
 
          free(proc);
543
 
          /* We deleted this process from the list, so we can't go
544
 
             proc->next.  Therefore, start over from the beginning of
545
 
             the process list */
546
 
          break;
547
 
        }
548
 
        /* This process exited nicely, so print its buffer */
549
 
        for(size_t written = 0; written < proc->buffer_length;
550
 
            written += (size_t)ret){
551
 
          ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
552
 
                                         proc->buffer + written,
553
 
                                         proc->buffer_length
554
 
                                         - written));
555
 
          if(ret < 0){
556
 
            perror("write");
557
 
            exitstatus = EXIT_FAILURE;
558
 
            goto end;
559
 
          }
560
 
        }
561
 
        goto end;
562
 
      }
563
 
      /* This process has not completed.  Does it have any output? */
564
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
565
 
        /* This process had nothing to say at this time */
566
 
        continue;
567
 
      }
568
 
      /* Before reading, make the process' data buffer large enough */
569
 
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
570
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
571
 
                               + (size_t) BUFFER_SIZE);
572
 
        if (proc->buffer == NULL){
573
 
          perror("malloc");
574
 
          exitstatus = EXIT_FAILURE;
575
 
          goto end;
576
 
        }
577
 
        proc->buffer_size += BUFFER_SIZE;
578
 
      }
579
 
      /* Read from the process */
580
 
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
581
 
                 BUFFER_SIZE);
582
 
      if(ret < 0){
583
 
        /* Read error from this process; ignore the error */
584
 
        continue;
585
 
      }
586
 
      if(ret == 0){
587
 
        /* got EOF */
588
 
        proc->eof = true;
589
 
      } else {
590
 
        proc->buffer_length += (size_t) ret;
 
438
        }
591
439
      }
592
440
    }
593
441
  }
594
 
  if(process_list == NULL){
595
 
    fprintf(stderr, "All plugin processes failed, exiting\n");
596
 
    exitstatus = EXIT_FAILURE;
597
 
  }
598
442
  
599
443
 end:
600
 
  /* Restore old signal handler */
601
 
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
602
 
  
603
 
  /* Free the plugin list */
604
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
605
 
    next = plugin_list->next;
606
 
    free(plugin_list->argv);
607
 
    free(plugin_list);
608
 
  }
609
 
  
610
 
  if(dir != NULL){
611
 
    closedir(dir);
612
 
  }
613
 
  
614
 
  /* Free the process list and kill the processes */
615
 
  for(process *next; process_list != NULL; process_list = next){
616
 
    next = process_list->next;
617
 
    close(process_list->fd);
618
 
    kill(process_list->pid, SIGTERM);
619
 
    free(process_list->buffer);
620
 
    free(process_list);
621
 
  }
622
 
  
623
 
  /* Wait for any remaining child processes to terminate */
624
 
  do{
625
 
    ret = wait(NULL);
626
 
  } while(ret >= 0);
627
 
  if(errno != ECHILD){
628
 
    perror("wait");
629
 
  }
630
 
  
 
444
  for(process *process_itr = process_list; process_itr != NULL;
 
445
      process_itr = process_itr->next){
 
446
    close(process_itr->fd);
 
447
    kill(process_itr->pid, SIGTERM);
 
448
    free(process_itr->buffer);
 
449
  }
 
450
  
 
451
  while(true){
 
452
    int status;
 
453
    ret = wait(&status);
 
454
    if (ret == -1){
 
455
      if(errno != ECHILD){
 
456
        perror("wait");
 
457
      }
 
458
      break;
 
459
    }
 
460
  }  
631
461
  return exitstatus;
632
462
}