/mandos/release

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

« back to all changes in this revision

Viewing changes to plugbasedclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-02 10:48:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080802104824-fx0miwp9o4g9r31e
* plugbasedclient.c (struct process): New fields "eof", "completed",
                                      and "status".
  (handle_sigchld): New function.
  (main): Initialize "dir" to NULL to only closedir() it if necessary.
          Move "process_list" to be a global variable to be accessible
          by "handle_sigchld".  Make "handle_sigchld" handle SIGCHLD.
          Remove redundant check for NULL "dir".  Free "filename" when
          no longer used.  Block SIGCHLD around fork()/exec().
          Restore normal signals in child.  Only loop while running
          processes exist.  Print process buffer when the process is
          done and it has emitted EOF, not when it only emits EOF.
          Remove processes from list which exit non-cleanly.  In
          cleaning up, closedir() if necessary.  Bug fix: set next
          pointer correctly when freeing process list.

* plugins.d/passprompt.c (main): Do not ignore SIGQUIT.

Show diffs side-by-side

added added

removed removed

Lines of Context:
58
58
  char *buffer;
59
59
  size_t buffer_size;
60
60
  size_t buffer_length;
 
61
  bool eof;
 
62
  bool completed;
 
63
  int status;
61
64
  struct process *next;
62
65
} process;
63
66
 
125
128
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
126
129
}
127
130
 
128
 
 
129
131
#define BUFFER_SIZE 256
130
132
 
131
 
const char *argp_program_version =
132
 
  "plugbasedclient 0.9";
133
 
const char *argp_program_bug_address =
134
 
  "<mandos@fukt.bsnet.se>";
 
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
}
135
154
 
136
155
int main(int argc, char *argv[]){
137
156
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
138
157
  size_t d_name_len;
139
 
  DIR *dir;
 
158
  DIR *dir = NULL;
140
159
  struct dirent *dirst;
141
160
  struct stat st;
142
161
  fd_set rfds_all;
143
162
  int ret, maxfd = 0;
144
 
  process *process_list = NULL;
145
163
  bool debug = false;
146
164
  int exitstatus = EXIT_SUCCESS;
147
165
  
 
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
  
148
182
  /* The options we understand. */
149
183
  struct argp_option options[] = {
150
184
    { .name = "global-options", .key = 'g',
165
199
  };
166
200
  
167
201
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
168
 
       /* Get the INPUT argument from `argp_parse', which we
169
 
          know is a pointer to our plugin list pointer. */
 
202
    /* Get the INPUT argument from `argp_parse', which we know is a
 
203
       pointer to our plugin list pointer. */
170
204
    plugin **plugins = state->input;
171
205
    switch (key) {
172
206
    case 'g':
237
271
    exitstatus = EXIT_FAILURE;
238
272
    goto end;
239
273
  }
 
274
  
240
275
  /* Set the FD_CLOEXEC flag on the directory, if possible */
241
276
  {
242
277
    int dir_fd = dirfd(dir);
250
285
    }
251
286
  }
252
287
  
253
 
  if(dir == NULL){
254
 
    fprintf(stderr, "Can not open directory\n");
255
 
    return EXIT_FAILURE;
256
 
  }
257
 
  
258
288
  FD_ZERO(&rfds_all);
259
289
  
260
290
  while(true){
318
348
      exitstatus = EXIT_FAILURE;
319
349
      goto end;
320
350
    }
321
 
    strcpy(filename, plugindir);
322
 
    strcat(filename, "/");
323
 
    strcat(filename, dirst->d_name);    
324
 
 
 
351
    strcpy(filename, plugindir); /* Spurious warning */
 
352
    strcat(filename, "/");      /* Spurious warning */
 
353
    strcat(filename, dirst->d_name); /* Spurious warning */
 
354
    
325
355
    stat(filename, &st);
326
 
 
 
356
    
327
357
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
328
358
      if(debug){
329
359
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
330
360
                " with bad type or mode\n", filename);
331
361
      }
 
362
      free(filename);
332
363
      continue;
333
364
    }
334
365
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
336
367
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
337
368
                dirst->d_name);
338
369
      }
 
370
      free(filename);
339
371
      continue;
340
372
    }
341
373
    plugin *p = getplugin(dirst->d_name, &plugin_list);
365
397
      exitstatus = EXIT_FAILURE;
366
398
      goto end;
367
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
    }
368
407
    // Starting a new process to be watched
369
408
    pid_t pid = fork();
370
409
    if(pid == 0){
371
410
      /* 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
      }
372
421
      dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
373
422
      
374
423
      if(dirfd(dir) < 0){
375
424
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
376
 
           and must close it manually  */
 
425
           above and must now close it manually here. */
377
426
        closedir(dir);
378
427
      }
379
428
      if(execv(filename, p->argv) < 0){
383
432
      /* no return */
384
433
    }
385
434
    /* parent process */
 
435
    free(filename);
386
436
    close(pipefd[1]);           /* close unused write end of pipe */
387
437
    process *new_process = malloc(sizeof(process));
388
438
    if (new_process == NULL){
389
439
      perror("malloc");
 
440
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
441
      if(ret < 0){
 
442
        perror("sigprocmask");
 
443
      }
390
444
      exitstatus = EXIT_FAILURE;
391
445
      goto end;
392
446
    }
394
448
    *new_process = (struct process){ .pid = pid,
395
449
                                     .fd = pipefd[0],
396
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
    
397
462
    FD_SET(new_process->fd, &rfds_all);
398
463
    
399
464
    if (maxfd < new_process->fd){
400
465
      maxfd = new_process->fd;
401
466
    }
402
467
    
403
 
    //List handling
404
 
    process_list = new_process;
405
468
  }
406
469
  
407
470
  /* Free the plugin list */
412
475
  }
413
476
  
414
477
  closedir(dir);
 
478
  dir = NULL;
415
479
  
416
480
  if (process_list == NULL){
417
481
    fprintf(stderr, "No plugin processes started, exiting\n");
418
 
    return EXIT_FAILURE;
 
482
    exitstatus = EXIT_FAILURE;
 
483
    goto end;
419
484
  }
420
 
  while(true){
 
485
  while(process_list){
421
486
    fd_set rfds = rfds_all;
422
487
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
423
488
    if (select_ret == -1){
425
490
      exitstatus = EXIT_FAILURE;
426
491
      goto end;
427
492
    }
 
493
    /* OK, now either a process completed, or something can be read
 
494
       from one of them */
428
495
    for(process *proc = process_list; proc ; proc = proc->next){
429
 
      if(not FD_ISSET(proc->fd, &rfds)){
 
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;
 
532
              }
 
533
            }
 
534
          }
 
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 */
430
566
        continue;
431
567
      }
 
568
      /* Before reading, make the process' data buffer large enough */
432
569
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
433
570
        proc->buffer = realloc(proc->buffer, proc->buffer_size
434
571
                               + (size_t) BUFFER_SIZE);
439
576
        }
440
577
        proc->buffer_size += BUFFER_SIZE;
441
578
      }
 
579
      /* Read from the process */
442
580
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
443
581
                 BUFFER_SIZE);
444
582
      if(ret < 0){
445
 
        /* Read error from this process; ignore it */
 
583
        /* Read error from this process; ignore the error */
446
584
        continue;
447
585
      }
448
 
      proc->buffer_length += (size_t) ret;
449
586
      if(ret == 0){
450
587
        /* got EOF */
451
 
        /* wait for process exit */
452
 
        int status;
453
 
        waitpid(proc->pid, &status, 0);
454
 
        if(not WIFEXITED(status) or WEXITSTATUS(status) != 0){
455
 
          FD_CLR(proc->fd, &rfds_all);
456
 
          continue;
457
 
        }
458
 
        for(size_t written = 0;
459
 
            written < proc->buffer_length; written += (size_t)ret){
460
 
          ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
461
 
                                         proc->buffer + written,
462
 
                                         proc->buffer_length
463
 
                                         - written));
464
 
          if(ret < 0){
465
 
            perror("write");
466
 
            exitstatus = EXIT_FAILURE;
467
 
            goto end;
468
 
          }
469
 
        }
470
 
        goto end;
 
588
        proc->eof = true;
 
589
      } else {
 
590
        proc->buffer_length += (size_t) ret;
471
591
      }
472
592
    }
473
593
  }
 
594
  if(process_list == NULL){
 
595
    fprintf(stderr, "All plugin processes failed, exiting\n");
 
596
    exitstatus = EXIT_FAILURE;
 
597
  }
474
598
  
475
599
 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 */
476
615
  for(process *next; process_list != NULL; process_list = next){
 
616
    next = process_list->next;
477
617
    close(process_list->fd);
478
618
    kill(process_list->pid, SIGTERM);
479
619
    free(process_list->buffer);
480
620
    free(process_list);
481
621
  }
482
622
  
483
 
  while(true){
484
 
    int status;
485
 
    ret = wait(&status);
486
 
    if (ret == -1){
487
 
      if(errno != ECHILD){
488
 
        perror("wait");
489
 
      }
490
 
      break;
491
 
    }
492
 
  }  
 
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
  
493
631
  return exitstatus;
494
632
}