/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 dracut-module/password-agent.c

  • Committer: Teddy Hogeborn
  • Date: 2019-11-03 19:17:57 UTC
  • Revision ID: teddy@recompile.se-20191103191757-1hdpp0u5fxa8iumo
INSTALL: Add "-" argument to "su" invocations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos password agent - Simple password agent to run Mandos client
4
4
 *
5
 
 * Copyright © 2019-2020 Teddy Hogeborn
6
 
 * Copyright © 2019-2020 Björn Påhlsson
 
5
 * Copyright © 2019 Teddy Hogeborn
 
6
 * Copyright © 2019 Björn Påhlsson
7
7
 * 
8
8
 * This file is part of Mandos.
9
9
 * 
49
49
#include <sysexits.h>           /* EX_USAGE, EX_OSERR, EX_OSFILE */
50
50
#include <errno.h>              /* errno, error_t, EACCES,
51
51
                                   ENAMETOOLONG, ENOENT, ENOTDIR,
52
 
                                   ENOMEM, EEXIST, ECHILD, EPERM,
 
52
                                   EEXIST, ECHILD, EPERM, ENOMEM,
53
53
                                   EAGAIN, EINTR, ENOBUFS, EADDRINUSE,
54
54
                                   ECONNREFUSED, ECONNRESET,
55
55
                                   ETOOMANYREFS, EMSGSIZE, EBADF,
73
73
                                   ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS,
74
74
                                   struct argp, argp_parse(),
75
75
                                   ARGP_NO_EXIT */
76
 
#include <stdint.h>             /* SIZE_MAX */
77
76
#include <unistd.h>             /* uid_t, gid_t, close(), pipe2(),
78
77
                                   fork(), _exit(), dup2(),
79
78
                                   STDOUT_FILENO, setresgid(),
96
95
                                   IN_EXCL_UNLINK, IN_ONLYDIR,
97
96
                                   struct inotify_event */
98
97
#include <fnmatch.h>            /* fnmatch(), FNM_FILE_NAME */
99
 
#include <stdio.h>              /* asprintf(), FILE, stderr, fopen(),
100
 
                                   fclose(), getline(), sscanf(),
101
 
                                   feof(), ferror(), rename(),
102
 
                                   fdopen(), fprintf(), fscanf() */
 
98
#include <stdio.h>              /* asprintf(), FILE, fopen(),
 
99
                                   getline(), sscanf(), feof(),
 
100
                                   ferror(), fclose(), stderr,
 
101
                                   rename(), fdopen(), fprintf(),
 
102
                                   fscanf() */
103
103
#include <glib.h>    /* GKeyFile, g_key_file_free(), g_key_file_new(),
104
104
                        GError, g_key_file_load_from_file(),
105
105
                        G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT,
651
651
 
652
652
__attribute__((nonnull, warn_unused_result))
653
653
bool add_to_queue(task_queue *const queue, const task_context task){
654
 
  if((queue->length + 1) > (SIZE_MAX / sizeof(task_context))){
655
 
    /* overflow */
656
 
    error(0, ENOMEM, "Failed to allocate %" PRIuMAX
657
 
          " tasks for queue->tasks", (uintmax_t)(queue->length + 1));
658
 
    errno = ENOMEM;
659
 
    return false;
660
 
  }
661
654
  const size_t needed_size = sizeof(task_context)*(queue->length + 1);
662
655
  if(needed_size > (queue->allocated)){
663
656
    task_context *const new_tasks = realloc(queue->tasks,
868
861
  }
869
862
  close(pipefds[1]);
870
863
 
871
 
  if(pid == -1){
872
 
    error(0, errno, "Failed to fork()");
873
 
    close(pipefds[0]);
874
 
    return false;
875
 
  }
876
 
 
877
864
  if(not add_to_queue(queue, (task_context){
878
865
        .func=wait_for_mandos_client_exit,
879
866
        .pid=pid,
1193
1180
  bool *const password_is_read = task.password_is_read;
1194
1181
 
1195
1182
  /* We use the GLib "Key-value file parser" functions to parse the
1196
 
     question file.  See <https://systemd.io/PASSWORD_AGENTS/> for
1197
 
     specification of contents */
 
1183
     question file.  See <https://www.freedesktop.org/wiki/Software
 
1184
     /systemd/PasswordAgents/> for specification of contents */
1198
1185
  __attribute__((nonnull))
1199
1186
    void cleanup_g_key_file(GKeyFile **key_file){
1200
1187
    if(*key_file != NULL){
1490
1477
         not. You may but don't have to include a final NUL byte in
1491
1478
         your message.
1492
1479
 
1493
 
         — <https://systemd.io/PASSWORD_AGENTS/> (Tue, 15 Sep 2020
1494
 
         14:24:20 GMT)
 
1480
         — <https://www.freedesktop.org/wiki/Software/systemd/
 
1481
         PasswordAgents/> (Wed 08 Oct 2014 02:14:28 AM UTC)
1495
1482
      */
1496
1483
      send_buffer[0] = '+';     /* Prefix with "+" */
1497
1484
      /* Always add an extra NUL */
1502
1489
      errno = 0;
1503
1490
      ssize_t ssret = send(fd, send_buffer, send_buffer_length,
1504
1491
                           MSG_NOSIGNAL);
1505
 
      const error_t saved_errno = (ssret < 0) ? errno : 0;
 
1492
      const error_t saved_errno = errno;
1506
1493
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25)
1507
1494
      explicit_bzero(send_buffer, send_buffer_length);
1508
1495
#else
1526
1513
          /* Retry, below */
1527
1514
          break;
1528
1515
        case EMSGSIZE:
1529
 
          error(0, saved_errno, "Password of size %" PRIuMAX
1530
 
                " is too big", (uintmax_t)password->length);
 
1516
          error(0, 0, "Password of size %" PRIuMAX " is too big",
 
1517
                (uintmax_t)password->length);
1531
1518
#if __GNUC__ < 7
1532
1519
          /* FALLTHROUGH */
1533
1520
#else
1535
1522
#endif
1536
1523
        case 0:
1537
1524
          if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){
1538
 
            error(0, 0, "Password only partially sent to socket %s: %"
1539
 
                  PRIuMAX " out of %" PRIuMAX " bytes sent", filename,
1540
 
                  (uintmax_t)ssret, (uintmax_t)send_buffer_length);
 
1525
            error(0, 0, "Password only partially sent to socket");
1541
1526
          }
1542
1527
#if __GNUC__ < 7
1543
1528
          /* FALLTHROUGH */
1899
1884
  g_assert_true(queue->tasks[0].func == dummy_func);
1900
1885
}
1901
1886
 
1902
 
static void test_add_to_queue_overflow(__attribute__((unused))
1903
 
                                       test_fixture *fixture,
1904
 
                                       __attribute__((unused))
1905
 
                                       gconstpointer user_data){
1906
 
  __attribute__((cleanup(cleanup_queue)))
1907
 
    task_queue *queue = create_queue();
1908
 
  g_assert_nonnull(queue);
1909
 
  g_assert_true(queue->length == 0);
1910
 
  queue->length = SIZE_MAX / sizeof(task_context); /* fake max size */
1911
 
 
1912
 
  FILE *real_stderr = stderr;
1913
 
  FILE *devnull = fopen("/dev/null", "we");
1914
 
  g_assert_nonnull(devnull);
1915
 
  stderr = devnull;
1916
 
  const bool ret = add_to_queue(queue,
1917
 
                                (task_context){ .func=dummy_func });
1918
 
  g_assert_true(errno == ENOMEM);
1919
 
  g_assert_false(ret);
1920
 
  stderr = real_stderr;
1921
 
  g_assert_cmpint(fclose(devnull), ==, 0);
1922
 
  queue->length = 0;            /* Restore real size */
1923
 
}
1924
 
 
1925
1887
static void dummy_func(__attribute__((unused))
1926
1888
                       const task_context task,
1927
1889
                       __attribute__((unused))
2198
2160
    }
2199
2161
    exit(EXIT_SUCCESS);
2200
2162
  }
2201
 
  if(pid == -1){
2202
 
    error(EXIT_FAILURE, errno, "Failed to fork()");
2203
 
  }
2204
 
 
2205
2163
  int status;
2206
2164
  waitpid(pid, &status, 0);
2207
2165
  if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){
5809
5767
  char write_data[PIPE_BUF];
5810
5768
  {
5811
5769
    /* Construct test password buffer */
5812
 
    /* Start with + since that is what the real protocol uses */
 
5770
    /* Start with + since that is what the real procotol uses */
5813
5771
    write_data[0] = '+';
5814
5772
    /* Set a special character at string end just to mark the end */
5815
5773
    write_data[sizeof(write_data)-2] = 'y';
5967
5925
  char *const filename = strdup("/nonexistent/socket");
5968
5926
  __attribute__((cleanup(string_set_clear)))
5969
5927
    string_set cancelled_filenames = {};
5970
 
  int socketfds[2];
5971
 
 
5972
 
  /* Find a message size which triggers EMSGSIZE */
5973
 
  __attribute__((cleanup(cleanup_string)))
5974
 
    char *message_buffer = NULL;
5975
 
  size_t message_size = PIPE_BUF + 1;
5976
 
  for(ssize_t ssret = 0; ssret >= 0; message_size += 1024){
5977
 
    if(message_size >= 1024*1024*1024){ /* 1 GiB */
5978
 
      g_test_skip("Skipping EMSGSIZE test: Will not try 1GiB");
5979
 
      return;
5980
 
    }
5981
 
    message_buffer = realloc(message_buffer, message_size);
5982
 
    if(message_buffer == NULL){
5983
 
      g_test_skip("Skipping EMSGSIZE test");
5984
 
      g_test_message("Failed to malloc() %" PRIuMAX " bytes",
5985
 
                     (uintmax_t)message_size);
5986
 
      return;
5987
 
    }
5988
 
    /* Fill buffer with 'x' */
5989
 
    memset(message_buffer, 'x', message_size);
5990
 
    /* Create a new socketpair for each message size to avoid having
5991
 
       to empty the pipe by reading the message to a separate buffer
5992
 
    */
5993
 
    g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
5994
 
                               | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
5995
 
                               socketfds), ==, 0);
5996
 
    ssret = send(socketfds[1], message_buffer, message_size,
5997
 
                 MSG_NOSIGNAL);
5998
 
    error_t saved_errno = errno;
5999
 
    g_assert_cmpint(close(socketfds[0]), ==, 0);
6000
 
    g_assert_cmpint(close(socketfds[1]), ==, 0);
6001
 
 
6002
 
    if(ssret < 0){
6003
 
      if(saved_errno != EMSGSIZE) {
6004
 
        g_test_skip("Skipping EMSGSIZE test");
6005
 
        g_test_message("Error on send(): %s", strerror(saved_errno));
6006
 
        return;
6007
 
      }
6008
 
      break;
6009
 
    } else if(ssret != (ssize_t)message_size){
6010
 
      g_test_skip("Skipping EMSGSIZE test");
6011
 
      g_test_message("Partial send(): %" PRIuMAX " of %" PRIdMAX
6012
 
                     " bytes", (uintmax_t)ssret,
6013
 
                     (intmax_t)message_size);
6014
 
      return;
6015
 
    }
6016
 
  }
6017
 
  g_test_message("EMSGSIZE triggered by %" PRIdMAX " bytes",
6018
 
                 (intmax_t)message_size);
6019
 
 
6020
 
  buffer password = {
6021
 
    .data=message_buffer,
6022
 
    .length=message_size - 2,   /* Compensate for added '+' and NUL */
6023
 
    .allocated=message_size,
 
5928
  const size_t oversized = 1024*1024; /* Limit seems to be 212960 */
 
5929
  __attribute__((cleanup(cleanup_buffer)))
 
5930
    buffer password = {
 
5931
    .data=malloc(oversized),
 
5932
    .length=oversized,
 
5933
    .allocated=oversized,
6024
5934
  };
 
5935
  g_assert_nonnull(password.data);
6025
5936
  if(mlock(password.data, password.allocated) != 0){
6026
5937
    g_assert_true(errno == EPERM or errno == ENOMEM);
6027
5938
  }
 
5939
  /* Construct test password buffer */
 
5940
  /* Start with + since that is what the real procotol uses */
 
5941
  password.data[0] = '+';
 
5942
  /* Set a special character at string end just to mark the end */
 
5943
  password.data[oversized-3] = 'y';
 
5944
  /* Set NUL at buffer end, as suggested by the protocol */
 
5945
  password.data[oversized-2] = '\0';
 
5946
  /* Fill rest of password with 'x' */
 
5947
  memset(password.data+1, 'x', oversized-3);
6028
5948
 
6029
5949
  __attribute__((cleanup(cleanup_queue)))
6030
5950
    task_queue *queue = create_queue();
6031
5951
  g_assert_nonnull(queue);
 
5952
  int socketfds[2];
6032
5953
  g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM
6033
5954
                             | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
6034
5955
                             socketfds), ==, 0);
7941
7862
  test_add("/parse_arguments/mixed", test_parse_arguments_mixed);
7942
7863
  test_add("/queue/create", test_create_queue);
7943
7864
  test_add("/queue/add", test_add_to_queue);
7944
 
  test_add("/queue/add/overflow", test_add_to_queue_overflow);
7945
7865
  test_add("/queue/has_question/empty",
7946
7866
           test_queue_has_question_empty);
7947
7867
  test_add("/queue/has_question/false",
8172
8092
  g_option_context_set_help_enabled(context, FALSE);
8173
8093
  g_option_context_set_ignore_unknown_options(context, TRUE);
8174
8094
 
8175
 
  gboolean should_run_tests = FALSE;
 
8095
  gboolean run_tests = FALSE;
8176
8096
  GOptionEntry entries[] = {
8177
8097
    { "test", 0, 0, G_OPTION_ARG_NONE,
8178
 
      &should_run_tests, "Run tests", NULL },
 
8098
      &run_tests, "Run tests", NULL },
8179
8099
    { NULL }
8180
8100
  };
8181
8101
  g_option_context_add_main_entries(context, entries, NULL);
8188
8108
  }
8189
8109
 
8190
8110
  g_option_context_free(context);
8191
 
  return should_run_tests != FALSE;
 
8111
  return run_tests != FALSE;
8192
8112
}