/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 plugins.d/mandos-client.c

  • Committer: Teddy Hogeborn
  • Date: 2015-05-23 20:18:34 UTC
  • mto: This revision was merged to the branch mainline in revision 756.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

Show diffs side-by-side

added added

removed removed

Lines of Context:
234
234
                          .af = af };
235
235
  if(new_server->ip == NULL){
236
236
    perror_plus("strdup");
 
237
    free(new_server);
237
238
    return false;
238
239
  }
239
240
  ret = clock_gettime(CLOCK_MONOTONIC, &(new_server->last_seen));
240
241
  if(ret == -1){
241
242
    perror_plus("clock_gettime");
 
243
#ifdef __GNUC__
 
244
#pragma GCC diagnostic push
 
245
#pragma GCC diagnostic ignored "-Wcast-qual"
 
246
#endif
 
247
    free((char *)(new_server->ip));
 
248
#ifdef __GNUC__
 
249
#pragma GCC diagnostic pop
 
250
#endif
 
251
    free(new_server);
242
252
    return false;
243
253
  }
244
254
  /* Special case of first server */
483
493
  return plaintext_length;
484
494
}
485
495
 
 
496
__attribute__((warn_unused_result, const))
 
497
static const char *safe_string(const char *str){
 
498
  if(str == NULL)
 
499
    return "(unknown)";
 
500
  return str;
 
501
}
 
502
 
486
503
__attribute__((warn_unused_result))
487
504
static const char *safer_gnutls_strerror(int value){
488
505
  const char *ret = gnutls_strerror(value);
489
 
  if(ret == NULL)
490
 
    ret = "(unknown)";
491
 
  return ret;
 
506
  return safe_string(ret);
492
507
}
493
508
 
494
509
/* GnuTLS log function callback */
503
518
                              const char *seckeyfilename,
504
519
                              mandos_context *mc){
505
520
  int ret;
 
521
  unsigned int uret;
506
522
  
507
523
  if(debug){
508
524
    fprintf_plus(stderr, "Initializing GnuTLS\n");
559
575
                 safer_gnutls_strerror(ret));
560
576
    goto globalfail;
561
577
  }
 
578
  if(mc->dh_bits == 0){
 
579
    /* Find out the optimal number of DH bits */
 
580
    /* Try to read the private key file */
 
581
    gnutls_datum_t buffer = { .data = NULL, .size = 0 };
 
582
    {
 
583
      int secfile = open(seckeyfilename, O_RDONLY);
 
584
      size_t buffer_capacity = 0;
 
585
      while(true){
 
586
        buffer_capacity = incbuffer((char **)&buffer.data,
 
587
                                    (size_t)buffer.size,
 
588
                                    (size_t)buffer_capacity);
 
589
        if(buffer_capacity == 0){
 
590
          perror_plus("incbuffer");
 
591
          free(buffer.data);
 
592
          buffer.data = NULL;
 
593
          break;
 
594
        }
 
595
        ssize_t bytes_read = read(secfile, buffer.data + buffer.size,
 
596
                                  BUFFER_SIZE);
 
597
        /* EOF */
 
598
        if(bytes_read == 0){
 
599
          break;
 
600
        }
 
601
        /* check bytes_read for failure */
 
602
        if(bytes_read < 0){
 
603
          perror_plus("read");
 
604
          free(buffer.data);
 
605
          buffer.data = NULL;
 
606
          break;
 
607
        }
 
608
        buffer.size += (unsigned int)bytes_read;
 
609
      }
 
610
      close(secfile);
 
611
    }
 
612
    /* If successful, use buffer to parse private key */
 
613
    gnutls_sec_param_t sec_param = GNUTLS_SEC_PARAM_ULTRA;
 
614
    if(buffer.data != NULL){
 
615
      {
 
616
        gnutls_openpgp_privkey_t privkey = NULL;
 
617
        ret = gnutls_openpgp_privkey_init(&privkey);
 
618
        if(ret != GNUTLS_E_SUCCESS){
 
619
          fprintf_plus(stderr, "Error initializing OpenPGP key"
 
620
                       " structure: %s", safer_gnutls_strerror(ret));
 
621
          free(buffer.data);
 
622
          buffer.data = NULL;
 
623
        } else {
 
624
          ret = gnutls_openpgp_privkey_import(privkey, &buffer,
 
625
                                            GNUTLS_OPENPGP_FMT_BASE64,
 
626
                                              "", 0);
 
627
          if(ret != GNUTLS_E_SUCCESS){
 
628
            fprintf_plus(stderr, "Error importing OpenPGP key : %s",
 
629
                         safer_gnutls_strerror(ret));
 
630
            privkey = NULL;
 
631
          }
 
632
          free(buffer.data);
 
633
          buffer.data = NULL;
 
634
          if(privkey != NULL){
 
635
            /* Use private key to suggest an appropriate sec_param */
 
636
            sec_param = gnutls_openpgp_privkey_sec_param(privkey);
 
637
            gnutls_openpgp_privkey_deinit(privkey);
 
638
            if(debug){
 
639
              fprintf_plus(stderr, "This OpenPGP key implies using a"
 
640
                           " GnuTLS security parameter \"%s\".\n",
 
641
                           safe_string(gnutls_sec_param_get_name
 
642
                                       (sec_param)));
 
643
            }
 
644
          }
 
645
        }
 
646
      }
 
647
      if(sec_param == GNUTLS_SEC_PARAM_UNKNOWN){
 
648
        /* Err on the side of caution */
 
649
        sec_param = GNUTLS_SEC_PARAM_ULTRA;
 
650
        if(debug){
 
651
          fprintf_plus(stderr, "Falling back to security parameter"
 
652
                       " \"%s\"\n",
 
653
                       safe_string(gnutls_sec_param_get_name
 
654
                                   (sec_param)));
 
655
        }
 
656
      }
 
657
    }
 
658
    uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param);
 
659
    if(uret != 0){
 
660
      mc->dh_bits = uret;
 
661
      if(debug){
 
662
        fprintf_plus(stderr, "A \"%s\" GnuTLS security parameter"
 
663
                     " implies %u DH bits; using that.\n",
 
664
                     safe_string(gnutls_sec_param_get_name
 
665
                                 (sec_param)),
 
666
                     mc->dh_bits);
 
667
      }
 
668
    } else {
 
669
      fprintf_plus(stderr, "Failed to get implied number of DH"
 
670
                   " bits for security parameter \"%s\"): %s\n",
 
671
                   safe_string(gnutls_sec_param_get_name(sec_param)),
 
672
                   safer_gnutls_strerror(ret));
 
673
      goto globalfail;
 
674
    }
 
675
  } else if(debug){
 
676
    fprintf_plus(stderr, "DH bits explicitly set to %u\n",
 
677
                 mc->dh_bits);
 
678
  }
562
679
  ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits);
563
680
  if(ret != GNUTLS_E_SUCCESS){
564
 
    fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n",
565
 
                 safer_gnutls_strerror(ret));
 
681
    fprintf_plus(stderr, "Error in GnuTLS prime generation (%u bits):"
 
682
                 " %s\n", mc->dh_bits, safer_gnutls_strerror(ret));
566
683
    goto globalfail;
567
684
  }
568
685
  
1066
1183
     timed out */
1067
1184
  
1068
1185
  if(quit_now){
 
1186
    avahi_s_service_resolver_free(r);
1069
1187
    return;
1070
1188
  }
1071
1189
  
1458
1576
  error_t ret_errno = 0;
1459
1577
  if(seteuid(0) == -1){
1460
1578
    ret_errno = errno;
1461
 
    perror_plus("seteuid");
1462
1579
  }
1463
1580
  errno = old_errno;
1464
1581
  return ret_errno;
1475
1592
  }
1476
1593
  if(setuid(0) == -1){
1477
1594
    ret_errno = errno;
1478
 
    perror_plus("seteuid");
1479
1595
  }
1480
1596
  errno = old_errno;
1481
1597
  return ret_errno;
1488
1604
  error_t ret_errno = 0;
1489
1605
  if(seteuid(uid) == -1){
1490
1606
    ret_errno = errno;
1491
 
    perror_plus("seteuid");
1492
1607
  }
1493
1608
  errno = old_errno;
1494
1609
  return ret_errno;
1501
1616
  error_t ret_errno = 0;
1502
1617
  if(setuid(uid) == -1){
1503
1618
    ret_errno = errno;
1504
 
    perror_plus("setuid");
1505
1619
  }
1506
1620
  errno = old_errno;
1507
1621
  return ret_errno;
1554
1668
    if(hook_pid == 0){
1555
1669
      /* Child */
1556
1670
      /* Raise privileges */
1557
 
      if(raise_privileges_permanently() != 0){
 
1671
      errno = raise_privileges_permanently();
 
1672
      if(errno != 0){
1558
1673
        perror_plus("Failed to raise privileges");
1559
1674
        _exit(EX_NOPERM);
1560
1675
      }
1642
1757
        _exit(EXIT_FAILURE);
1643
1758
      }
1644
1759
    } else {
 
1760
      if(hook_pid == -1){
 
1761
        perror_plus("fork");
 
1762
        free(direntry);
 
1763
        continue;
 
1764
      }
1645
1765
      int status;
1646
1766
      if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){
1647
1767
        perror_plus("waitpid");
 
1768
        free(direntry);
1648
1769
        continue;
1649
1770
      }
1650
1771
      if(WIFEXITED(status)){
1652
1773
          fprintf_plus(stderr, "Warning: network hook \"%s\" exited"
1653
1774
                       " with status %d\n", direntry->d_name,
1654
1775
                       WEXITSTATUS(status));
 
1776
          free(direntry);
1655
1777
          continue;
1656
1778
        }
1657
1779
      } else if(WIFSIGNALED(status)){
1658
1780
        fprintf_plus(stderr, "Warning: network hook \"%s\" died by"
1659
1781
                     " signal %d\n", direntry->d_name,
1660
1782
                     WTERMSIG(status));
 
1783
        free(direntry);
1661
1784
        continue;
1662
1785
      } else {
1663
1786
        fprintf_plus(stderr, "Warning: network hook \"%s\""
1664
1787
                     " crashed\n", direntry->d_name);
 
1788
        free(direntry);
1665
1789
        continue;
1666
1790
      }
1667
1791
    }
1669
1793
      fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n",
1670
1794
                   direntry->d_name);
1671
1795
    }
 
1796
    free(direntry);
1672
1797
  }
1673
1798
  free(direntries);
1674
1799
  if((int)TEMP_FAILURE_RETRY(close(hookdir_fd)) == -1){
1733
1858
    /* Raise privileges */
1734
1859
    ret_errno = raise_privileges();
1735
1860
    if(ret_errno != 0){
 
1861
      errno = ret_errno;
1736
1862
      perror_plus("Failed to raise privileges");
1737
1863
    }
1738
1864
    
1842
1968
    /* Raise privileges */
1843
1969
    ret_errno = raise_privileges();
1844
1970
    if(ret_errno != 0){
 
1971
      errno = ret_errno;
1845
1972
      perror_plus("Failed to raise privileges");
1846
1973
    }
1847
1974
    
1880
2007
}
1881
2008
 
1882
2009
int main(int argc, char *argv[]){
1883
 
  mandos_context mc = { .server = NULL, .dh_bits = 1024,
 
2010
  mandos_context mc = { .server = NULL, .dh_bits = 0,
1884
2011
                        .priority = "SECURE256:!CTYPE-X.509:"
1885
 
                        "+CTYPE-OPENPGP", .current_server = NULL,
 
2012
                        "+CTYPE-OPENPGP:!RSA", .current_server = NULL,
1886
2013
                        .interfaces = NULL, .interfaces_size = 0 };
1887
2014
  AvahiSServiceBrowser *sb = NULL;
1888
2015
  error_t ret_errno;
2266
2393
        if(ret_errno != 0){
2267
2394
          errno = ret_errno;
2268
2395
          perror_plus("argz_add");
 
2396
          free(direntries[i]);
2269
2397
          continue;
2270
2398
        }
2271
2399
        if(debug){
2272
2400
          fprintf_plus(stderr, "Will use interface \"%s\"\n",
2273
2401
                       direntries[i]->d_name);
2274
2402
        }
 
2403
        free(direntries[i]);
2275
2404
      }
2276
2405
      free(direntries);
2277
2406
    } else {
2547
2676
    mc.current_server->prev->next = NULL;
2548
2677
    while(mc.current_server != NULL){
2549
2678
      server *next = mc.current_server->next;
 
2679
#ifdef __GNUC__
 
2680
#pragma GCC diagnostic push
 
2681
#pragma GCC diagnostic ignored "-Wcast-qual"
 
2682
#endif
 
2683
      free((char *)(mc.current_server->ip));
 
2684
#ifdef __GNUC__
 
2685
#pragma GCC diagnostic pop
 
2686
#endif
2550
2687
      free(mc.current_server);
2551
2688
      mc.current_server = next;
2552
2689
    }
2556
2693
  {
2557
2694
    ret_errno = raise_privileges();
2558
2695
    if(ret_errno != 0){
 
2696
      errno = ret_errno;
2559
2697
      perror_plus("Failed to raise privileges");
2560
2698
    } else {
2561
2699
      
2584
2722
    
2585
2723
    ret_errno = lower_privileges_permanently();
2586
2724
    if(ret_errno != 0){
 
2725
      errno = ret_errno;
2587
2726
      perror_plus("Failed to lower privileges permanently");
2588
2727
    }
2589
2728
  }
2619
2758
                         " \"%s\", 0): %s\n", tempdir,
2620
2759
                         direntries[i]->d_name, strerror(errno));
2621
2760
          }
 
2761
          free(direntries[i]);
2622
2762
        }
2623
2763
        
2624
2764
        /* need to clean even if 0 because man page doesn't specify */