=== modified file 'Makefile' --- Makefile 2008-07-22 01:59:47 +0000 +++ Makefile 2008-07-29 03:35:39 +0000 @@ -1,11 +1,14 @@ WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs -Wswitch-default -Wswitch-enum -Wunused-parameter -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow -Wunsafe-loop-optimizations -Wpointer-arith -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion -Wstrict-prototypes -Wold-style-definition -Wpacked -Wnested-externs -Wunreachable-code -Winline -Wvolatile-register-var DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see +# +FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all #COVERAGE=--coverage OPTIMIZE=-Os LANGUAGE=-std=gnu99 # Do not change these two -CFLAGS=$(WARN) $(COVERAGE) $(DEBUG) $(OPTIMIZE) $(LANGUAGE) +CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) $(LANGUAGE) LDFLAGS=$(COVERAGE) PROGS=plugbasedclient plugins.d/mandosclient plugins.d/passprompt === modified file 'TODO' --- TODO 2008-07-22 06:23:29 +0000 +++ TODO 2008-08-02 14:33:47 +0000 @@ -1,25 +1,35 @@ -[Mandos client] -configuration for OpenPGP key -header files/symbols tally -check exit codes of all system calls -IPv4 support - -[Pluginbasedclient] -disable certain plugins -header files/symbols tally -check exit codes of all system calls -change uid to nobody:nogroup - other drop privs stuff? - -[Server] -config file for: - TXT record -run-time communication with server - -[Mandos-tools/utilities] - List clients - Enable client - Disable client - -[Installer] -... +-*- org -*- + +* Mandos client +** header files/symbols tally +** check exit codes of all system calls +** IPv4 support +** protocol version header +** use strsep instead of strtok? + +* Pluginbasedclient +** header files/symbols tally +** check exit codes of all system calls +** change uid to nobody:nogroup + other drop privs stuff? +** pass things in environment, like device name, etc + Does cryptsetup already do this? +** use strsep instead of strtok? + +* Server +** config for: +*** TXT record +** protocol version header +** Run-time communication with server + probably using D-Bus + +* Mandos-tools/utilities + All of this probably using D-Bus +** List clients +** Enable client +** Disable client + +* Installer + + +#+STARTUP: showall === renamed file 'mandos-clients.conf' => 'clients.conf' === modified file 'plugbasedclient.c' --- plugbasedclient.c 2008-08-02 14:48:45 +0000 +++ plugbasedclient.c 2008-08-02 15:47:42 +0000 @@ -2,7 +2,7 @@ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2007-2008 Teddy Hogeborn and Björn Påhlsson. + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,28 +18,37 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ -#define _FORTIFY_SOURCE 2 +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ -#include /* popen, fileno */ -#include /* and, or, not */ -#include /* DIR, opendir, stat, struct stat, waitpid, - WIFEXITED, WEXITSTATUS, wait */ -#include /* wait */ -#include /* DIR, opendir */ -#include /* stat, struct stat */ -#include /* stat, struct stat, chdir */ -#include /* EXIT_FAILURE */ -#include /* fd_set, select, FD_ZERO, FD_SET, - FD_ISSET */ -#include /* strlen, strcpy, strcat */ -#include /* true */ -#include /* waitpid, WIFEXITED, WEXITSTATUS */ -#include /* errno */ -#include /* argp */ +#include /* popen(), fileno(), fprintf(), + stderr, STDOUT_FILENO */ +#include /* and, or, not */ +#include /* DIR, opendir(), stat(), + struct stat, waitpid(), + WIFEXITED(), WEXITSTATUS(), + wait() */ +#include /* wait() */ +#include /* DIR, struct dirent, opendir(), + readdir(), closedir() */ +#include /* struct stat, stat(), S_ISREG() */ +#include /* struct stat, stat(), S_ISREG(), + fcntl() */ +#include /* fcntl() */ +#include /* NULL */ +#include /* EXIT_FAILURE */ +#include /* fd_set, select(), FD_ZERO(), + FD_SET(), FD_ISSET() */ +#include /* strlen(), strcpy(), strcat() */ +#include /* true */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS() */ +#include /* errno */ +#include /* struct argp_option, + struct argp_state, struct argp, + argp_parse() */ struct process; @@ -49,14 +58,17 @@ char *buffer; size_t buffer_size; size_t buffer_length; + bool eof; + bool completed; + int status; struct process *next; } process; typedef struct plugin{ - char *name; /* can be "global" and any plugin name */ + char *name; /* can be NULL or any plugin name */ char **argv; int argc; - bool disable; + bool disabled; struct plugin *next; } plugin; @@ -82,14 +94,14 @@ new_plugin->argv[0] = name; new_plugin->argv[1] = NULL; new_plugin->argc = 1; - new_plugin->disable = false; + new_plugin->disabled = false; new_plugin->next = *plugin_list; /* Append the new plugin to the list */ *plugin_list = new_plugin; return new_plugin; } -void addarguments(plugin *p, char *arg){ +void addargument(plugin *p, char *arg){ p->argv[p->argc] = arg; p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2)); if (p->argv == NULL){ @@ -99,62 +111,111 @@ p->argc++; p->argv[p->argc] = NULL; } - + +/* + * Based on the example in the GNU LibC manual chapter 13.13 "File + * Descriptor Flags". + * *Note File Descriptor Flags:(libc)Descriptor Flags. + */ +int set_cloexec_flag(int fd) +{ + int ret = fcntl(fd, F_GETFD, 0); + /* If reading the flags failed, return error indication now. */ + if(ret < 0){ + return ret; + } + /* Store modified flag word in the descriptor. */ + return fcntl(fd, F_SETFD, ret | FD_CLOEXEC); +} + #define BUFFER_SIZE 256 -const char *argp_program_version = - "plugbasedclient 0.9"; -const char *argp_program_bug_address = - ""; -static char doc[] = - "Mandos plugin runner -- Run Mandos plugins"; -/* A description of the arguments we accept. */ -static char args_doc[] = ""; +const char *argp_program_version = "plugbasedclient 0.9"; +const char *argp_program_bug_address = ""; + +process *process_list = NULL; + +/* Mark a process as completed when it exits, and save its exit + status. */ +void handle_sigchld(__attribute__((unused)) int sig){ + process *proc = process_list; + int status; + pid_t pid = wait(&status); + while(proc != NULL and proc->pid != pid){ + proc = proc->next; + } + if(proc == NULL){ + /* Process not found in process list */ + return; + } + proc->status = status; + proc->completed = true; +} int main(int argc, char *argv[]){ - const char *plugindir = "plugins.d"; + const char *plugindir = "/conf/conf.d/mandos/plugins.d"; size_t d_name_len; - DIR *dir; + DIR *dir = NULL; struct dirent *dirst; struct stat st; - fd_set rfds_orig; + fd_set rfds_all; int ret, maxfd = 0; - process *process_list = NULL; uid_t uid = 65534; gid_t gid = 65534; + bool debug = false; + int exitstatus = EXIT_SUCCESS; + struct sigaction old_sigchld_action; + struct sigaction sigchld_action = { .sa_handler = handle_sigchld, + .sa_flags = SA_NOCLDSTOP }; + + /* Establish a signal handler */ + sigemptyset(&sigchld_action.sa_mask); + ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD); + if(ret < 0){ + perror("sigaddset"); + exit(EXIT_FAILURE); + } + ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action); + if(ret < 0){ + perror("sigaction"); + exit(EXIT_FAILURE); + } + /* The options we understand. */ struct argp_option options[] = { { .name = "global-options", .key = 'g', - .arg = "option[,option[,...]]", .flags = 0, - .doc = "Options effecting all plugins" }, + .arg = "OPTION[,OPTION[,...]]", + .doc = "Options passed to all plugins" }, { .name = "options-for", .key = 'o', - .arg = "plugin:option[,option[,...]]", .flags = 0, - .doc = "Options effecting only specified plugins" }, - { .name = "disable-plugin", .key = 'd', - .arg = "Plugin[,Plugin[,...]]", .flags = 0, - .doc = "Option to disable specififed plugins" }, + .arg = "PLUGIN:OPTION[,OPTION[,...]]", + .doc = "Options passed only to specified plugin" }, + { .name = "disable", .key = 'd', + .arg = "PLUGIN", + .doc = "Disable a specific plugin", .group = 1 }, { .name = "plugin-dir", .key = 128, - .arg = "Directory", .flags = 0, - .doc = "Option to change directory to search for plugins" }, + .arg = "DIRECTORY", + .doc = "Specify a different plugin directory", .group = 2 }, { .name = "userid", .key = 129, - .arg = "Id", .flags = 0, - .doc = "Option to change which user id the plugins will run as" }, + .arg = "ID", .flags = 0, + .doc = "User ID the plugins will run as", .group = 2 }, { .name = "groupid", .key = 130, - .arg = "Id", .flags = 0, - .doc = "Option to change which group id the plugins will run as" }, + .arg = "ID", .flags = 0, + .doc = "Group ID the plugins will run as", .group = 2 }, + { .name = "debug", .key = 131, + .doc = "Debug mode", .group = 3 }, { .name = NULL } }; error_t parse_opt (int key, char *arg, struct argp_state *state) { - /* Get the INPUT argument from `argp_parse', which we - know is a pointer to our arguments structure. */ + /* Get the INPUT argument from `argp_parse', which we know is a + pointer to our plugin list pointer. */ plugin **plugins = state->input; switch (key) { case 'g': if (arg != NULL){ char *p = strtok(arg, ","); do{ - addarguments(getplugin(NULL, plugins), p); + addargument(getplugin(NULL, plugins), p); p = strtok(NULL, ","); } while (p); } @@ -163,20 +224,18 @@ if (arg != NULL){ char *name = strtok(arg, ":"); char *p = strtok(NULL, ":"); - p = strtok(p, ","); - do{ - addarguments(getplugin(name, plugins), p); - p = strtok(NULL, ","); - } while (p); + if(p){ + p = strtok(p, ","); + do{ + addargument(getplugin(name, plugins), p); + p = strtok(NULL, ","); + } while (p); + } } break; case 'd': if (arg != NULL){ - char *p = strtok(arg, ","); - do{ - getplugin(p, plugins)->disable = true; - p = strtok(NULL, ","); - } while (p); + getplugin(arg, plugins)->disabled = true; } break; case 128: @@ -188,6 +247,9 @@ case 130: gid = (gid_t)strtol(arg, NULL, 10); break; + case 131: + debug = true; + break; case ARGP_KEY_ARG: argp_usage (state); break; @@ -198,41 +260,56 @@ } return 0; } - + plugin *plugin_list = NULL; struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = args_doc, .doc = doc }; - - argp_parse (&argp, argc, argv, 0, 0, &plugin_list); - -/* for(plugin *p = plugin_list; p != NULL; p=p->next){ */ -/* fprintf(stderr, "Plugin: %s has %d arguments\n", p->name ? p->name : "Global", p->argc); */ -/* for(char **a = p->argv + 1; *a != NULL; a++){ */ -/* fprintf(stderr, "\tArg: %s\n", *a); */ -/* } */ -/* } */ + .args_doc = "", + .doc = "Mandos plugin runner -- Run plugins" }; -/* return 0; */ + argp_parse (&argp, argc, argv, 0, 0, &plugin_list); + + if(debug){ + for(plugin *p = plugin_list; p != NULL; p=p->next){ + fprintf(stderr, "Plugin: %s has %d arguments\n", + p->name ? p->name : "Global", p->argc - 1); + for(char **a = p->argv; *a != NULL; a++){ + fprintf(stderr, "\tArg: %s\n", *a); + } + } + } ret = setuid(uid); if (ret == -1){ perror("setuid"); } - + setgid(gid); if (ret == -1){ perror("setuid"); } dir = opendir(plugindir); - if(dir == NULL){ - fprintf(stderr, "Can not open directory\n"); - return EXIT_FAILURE; - } - - FD_ZERO(&rfds_orig); + perror("Could not open plugin dir"); + exitstatus = EXIT_FAILURE; + goto end; + } + + /* Set the FD_CLOEXEC flag on the directory, if possible */ + { + int dir_fd = dirfd(dir); + if(dir_fd >= 0){ + ret = set_cloexec_flag(dir_fd); + if(ret < 0){ + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; + goto end; + } + } + } + + FD_ZERO(&rfds_all); while(true){ dirst = readdir(dir); @@ -244,146 +321,336 @@ d_name_len = strlen(dirst->d_name); - // Ignore dotfiles and backup files - if (dirst->d_name[0] == '.' - or dirst->d_name[d_name_len - 1] == '~'){ - continue; + // Ignore dotfiles, backup files and other junk + { + bool bad_name = false; + + const char const *bad_prefixes[] = { ".", "#", NULL }; + + const char const *bad_suffixes[] = { "~", "#", ".dpkg-new", + ".dpkg-old", + ".dpkg-divert", NULL }; + for(const char **pre = bad_prefixes; *pre != NULL; pre++){ + size_t pre_len = strlen(*pre); + if((d_name_len >= pre_len) + and strncmp((dirst->d_name), *pre, pre_len) == 0){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad prefix %s\n", dirst->d_name, *pre); + } + bad_name = true; + break; + } + } + + if(bad_name){ + continue; + } + + for(const char **suf = bad_suffixes; *suf != NULL; suf++){ + size_t suf_len = strlen(*suf); + if((d_name_len >= suf_len) + and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf) + == 0)){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad suffix %s\n", dirst->d_name, *suf); + } + bad_name = true; + break; + } + } + + if(bad_name){ + continue; + } } - + char *filename = malloc(d_name_len + strlen(plugindir) + 2); - strcpy(filename, plugindir); - strcat(filename, "/"); - strcat(filename, dirst->d_name); - + if (filename == NULL){ + perror("malloc"); + exitstatus = EXIT_FAILURE; + goto end; + } + strcpy(filename, plugindir); /* Spurious warning */ + strcat(filename, "/"); /* Spurious warning */ + strcat(filename, dirst->d_name); /* Spurious warning */ + stat(filename, &st); - - if (S_ISREG(st.st_mode) - and (access(filename, X_OK) == 0) - and not (getplugin(dirst->d_name, &plugin_list)->disable)){ - // Starting a new process to be watched - process *new_process = malloc(sizeof(process)); - int pipefd[2]; - ret = pipe(pipefd); - if (ret == -1){ - perror(argv[0]); - goto end; - } - new_process->pid = fork(); - if(new_process->pid == 0){ - /* this is the child process */ + + if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad type or mode\n", filename); + } + free(filename); + continue; + } + if(getplugin(dirst->d_name, &plugin_list)->disabled){ + if(debug){ + fprintf(stderr, "Ignoring disabled plugin \"%s\"\n", + dirst->d_name); + } + free(filename); + continue; + } + plugin *p = getplugin(dirst->d_name, &plugin_list); + { + /* Add global arguments to argument list for this plugin */ + plugin *g = getplugin(NULL, &plugin_list); + for(char **a = g->argv + 1; *a != NULL; a++){ + addargument(p, *a); + } + } + int pipefd[2]; + ret = pipe(pipefd); + if (ret == -1){ + perror("pipe"); + exitstatus = EXIT_FAILURE; + goto end; + } + ret = set_cloexec_flag(pipefd[0]); + if(ret < 0){ + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; + goto end; + } + ret = set_cloexec_flag(pipefd[1]); + if(ret < 0){ + perror("set_cloexec_flag"); + exitstatus = EXIT_FAILURE; + goto end; + } + /* Block SIGCHLD until process is safely in process list */ + ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL); + if(ret < 0){ + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; + goto end; + } + // Starting a new process to be watched + pid_t pid = fork(); + if(pid == 0){ + /* this is the child process */ + ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); + if(ret < 0){ + perror("sigaction"); + _exit(EXIT_FAILURE); + } + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + if(ret < 0){ + perror("sigprocmask"); + _exit(EXIT_FAILURE); + } + dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ + + if(dirfd(dir) < 0){ + /* If dir has no file descriptor, we could not set FD_CLOEXEC + above and must now close it manually here. */ closedir(dir); - close(pipefd[0]); /* close unused read end of pipe */ - dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ - - plugin *p = getplugin(dirst->d_name, &plugin_list); - plugin *g = getplugin(NULL, &plugin_list); - for(char **a = g->argv + 1; *a != NULL; a++){ - addarguments(p, *a); - } - if(execv(filename, p->argv) < 0){ - perror(argv[0]); - close(pipefd[1]); - exit(EXIT_FAILURE); - } - /* no return */ - } - close(pipefd[1]); /* close unused write end of pipe */ - new_process->fd = pipefd[0]; - new_process->buffer = malloc(BUFFER_SIZE); - if (new_process->buffer == NULL){ - perror(argv[0]); - goto end; - } - new_process->buffer_size = BUFFER_SIZE; - new_process->buffer_length = 0; - FD_SET(new_process->fd, &rfds_orig); - - if (maxfd < new_process->fd){ - maxfd = new_process->fd; - } - - //List handling - new_process->next = process_list; - process_list = new_process; - } + } + if(execv(filename, p->argv) < 0){ + perror("execv"); + _exit(EXIT_FAILURE); + } + /* no return */ + } + /* parent process */ + free(filename); + close(pipefd[1]); /* close unused write end of pipe */ + process *new_process = malloc(sizeof(process)); + if (new_process == NULL){ + perror("malloc"); + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + if(ret < 0){ + perror("sigprocmask"); + } + exitstatus = EXIT_FAILURE; + goto end; + } + + *new_process = (struct process){ .pid = pid, + .fd = pipefd[0], + .next = process_list }; + // List handling + process_list = new_process; + /* Unblock SIGCHLD so signal handler can be run if this process + has already completed */ + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); + if(ret < 0){ + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; + goto end; + } + + FD_SET(new_process->fd, &rfds_all); + + if (maxfd < new_process->fd){ + maxfd = new_process->fd; + } + + } + + /* Free the plugin list */ + for(plugin *next; plugin_list != NULL; plugin_list = next){ + next = plugin_list->next; + free(plugin_list->argv); + free(plugin_list); } closedir(dir); + dir = NULL; - if (process_list != NULL){ - while(true){ - fd_set rfds = rfds_orig; - int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); - if (select_ret == -1){ - perror(argv[0]); - goto end; - }else{ - for(process *process_itr = process_list; process_itr != NULL; - process_itr = process_itr->next){ - if(FD_ISSET(process_itr->fd, &rfds)){ - if(process_itr->buffer_length + BUFFER_SIZE - > process_itr->buffer_size){ - process_itr->buffer = realloc(process_itr->buffer, - process_itr->buffer_size - + (size_t) BUFFER_SIZE); - if (process_itr->buffer == NULL){ - perror(argv[0]); - goto end; - } - process_itr->buffer_size += BUFFER_SIZE; - } - ret = read(process_itr->fd, process_itr->buffer - + process_itr->buffer_length, BUFFER_SIZE); - if(ret < 0){ - /* Read error from this process; ignore it */ - continue; - } - process_itr->buffer_length += (size_t) ret; - if(ret == 0){ - /* got EOF */ - /* wait for process exit */ - int status; - waitpid(process_itr->pid, &status, 0); - if(WIFEXITED(status) and WEXITSTATUS(status) == 0){ - for(size_t written = 0; - written < process_itr->buffer_length;){ - ret = write(STDOUT_FILENO, - process_itr->buffer + written, - process_itr->buffer_length - written); - if(ret < 0){ - perror(argv[0]); - goto end; - } - written += (size_t)ret; - } - goto end; - } else { - FD_CLR(process_itr->fd, &rfds_orig); + if (process_list == NULL){ + fprintf(stderr, "No plugin processes started, exiting\n"); + exitstatus = EXIT_FAILURE; + goto end; + } + while(process_list){ + fd_set rfds = rfds_all; + int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); + if (select_ret == -1){ + perror("select"); + exitstatus = EXIT_FAILURE; + goto end; + } + /* OK, now either a process completed, or something can be read + from one of them */ + for(process *proc = process_list; proc ; proc = proc->next){ + /* Is this process completely done? */ + if(proc->eof and proc->completed){ + /* Only accept the plugin output if it exited cleanly */ + if(not WIFEXITED(proc->status) + or WEXITSTATUS(proc->status) != 0){ + /* Bad exit by plugin */ + if(debug){ + if(WIFEXITED(proc->status)){ + fprintf(stderr, "Plugin %d exited with status %d\n", + proc->pid, WEXITSTATUS(proc->status)); + } else if(WIFSIGNALED(proc->status)) { + fprintf(stderr, "Plugin %d killed by signal %d\n", + proc->pid, WTERMSIG(proc->status)); + } else if(WCOREDUMP(proc->status)){ + fprintf(stderr, "Plugin %d dumped core\n", proc->pid); + } + } + /* Remove the plugin */ + FD_CLR(proc->fd, &rfds_all); + /* Block signal while modifying process_list */ + ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL); + if(ret < 0){ + perror("sigprocmask"); + exitstatus = EXIT_FAILURE; + goto end; + } + /* Delete this process entry from the list */ + if(process_list == proc){ + /* First one - simple */ + process_list = proc->next; + } else { + /* Second one or later */ + for(process *p = process_list; p != NULL; p = p->next){ + if(p->next == proc){ + p->next = proc->next; + break; } } } - } + /* We are done modifying process list, so unblock signal */ + ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, + NULL); + if(ret < 0){ + perror("sigprocmask"); + } + free(proc->buffer); + free(proc); + /* We deleted this process from the list, so we can't go + proc->next. Therefore, start over from the beginning of + the process list */ + break; + } + /* This process exited nicely, so print its buffer */ + for(size_t written = 0; written < proc->buffer_length; + written += (size_t)ret){ + ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, + proc->buffer + written, + proc->buffer_length + - written)); + if(ret < 0){ + perror("write"); + exitstatus = EXIT_FAILURE; + goto end; + } + } + goto end; + } + /* This process has not completed. Does it have any output? */ + if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ + /* This process had nothing to say at this time */ + continue; + } + /* Before reading, make the process' data buffer large enough */ + if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){ + proc->buffer = realloc(proc->buffer, proc->buffer_size + + (size_t) BUFFER_SIZE); + if (proc->buffer == NULL){ + perror("malloc"); + exitstatus = EXIT_FAILURE; + goto end; + } + proc->buffer_size += BUFFER_SIZE; + } + /* Read from the process */ + ret = read(proc->fd, proc->buffer + proc->buffer_length, + BUFFER_SIZE); + if(ret < 0){ + /* Read error from this process; ignore the error */ + continue; + } + if(ret == 0){ + /* got EOF */ + proc->eof = true; + } else { + proc->buffer_length += (size_t) ret; } } } + if(process_list == NULL){ + fprintf(stderr, "All plugin processes failed, exiting\n"); + exitstatus = EXIT_FAILURE; + } end: - for(process *process_itr = process_list; process_itr != NULL; - process_itr = process_itr->next){ - close(process_itr->fd); - kill(process_itr->pid, SIGTERM); - free(process_itr->buffer); - } - - while(true){ - int status; - ret = wait(&status); - if (ret == -1){ - if(errno != ECHILD){ - perror("wait"); - } - break; - } - } - return EXIT_SUCCESS; + /* Restore old signal handler */ + sigaction(SIGCHLD, &old_sigchld_action, NULL); + + /* Free the plugin list */ + for(plugin *next; plugin_list != NULL; plugin_list = next){ + next = plugin_list->next; + free(plugin_list->argv); + free(plugin_list); + } + + if(dir != NULL){ + closedir(dir); + } + + /* Free the process list and kill the processes */ + for(process *next; process_list != NULL; process_list = next){ + next = process_list->next; + close(process_list->fd); + kill(process_list->pid, SIGTERM); + free(process_list->buffer); + free(process_list); + } + + /* Wait for any remaining child processes to terminate */ + do{ + ret = wait(NULL); + } while(ret >= 0); + if(errno != ECHILD){ + perror("wait"); + } + + return exitstatus; } === modified file 'plugins.d/mandosclient.c' --- plugins.d/mandosclient.c 2008-08-02 14:48:45 +0000 +++ plugins.d/mandosclient.c 2008-08-02 15:47:42 +0000 @@ -8,8 +8,8 @@ * includes the following functions: "resolve_callback", * "browse_callback", and parts of "main". * - * Everything else is Copyright © 2007-2008 Teddy Hogeborn and Björn - * Påhlsson. + * Everything else is + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -25,12 +25,10 @@ * along with this program. If not, see * . * - * Contact the authors at and - * . + * Contact the authors at . */ -#define _FORTIFY_SOURCE 2 - +/* Needed by GPGME, specifically gpgme_data_seek() */ #define _LARGEFILE_SOURCE #define _FILE_OFFSET_BITS 64 @@ -73,9 +71,9 @@ #define BUFFER_SIZE 256 #define DH_BITS 1024 -const char *certdir = "/conf/conf.d/cryptkeyreq/"; -const char *certfile = "openpgp-client.txt"; -const char *certkey = "openpgp-client-key.txt"; +static const char *certdir = "/conf/conf.d/mandos"; +static const char *certfile = "openpgp-client.txt"; +static const char *certkey = "openpgp-client-key.txt"; bool debug = false; @@ -86,8 +84,9 @@ } encrypted_session; -ssize_t pgp_packet_decrypt (char *packet, size_t packet_size, - char **new_packet, const char *homedir){ +static ssize_t pgp_packet_decrypt (char *packet, size_t packet_size, + char **new_packet, + const char *homedir){ gpgme_data_t dh_crypto, dh_plain; gpgme_ctx_t ctx; gpgme_error_t rc; @@ -249,12 +248,12 @@ return ret; } -void debuggnutls(__attribute__((unused)) int level, - const char* string){ +static void debuggnutls(__attribute__((unused)) int level, + const char* string){ fprintf(stderr, "%s", string); } -int initgnutls(encrypted_session *es){ +static int initgnutls(encrypted_session *es){ const char *err; int ret; @@ -348,11 +347,11 @@ return 0; } -void empty_log(__attribute__((unused)) AvahiLogLevel level, - __attribute__((unused)) const char *txt){} +static void empty_log(__attribute__((unused)) AvahiLogLevel level, + __attribute__((unused)) const char *txt){} -int start_mandos_communication(const char *ip, uint16_t port, - unsigned int if_index){ +static int start_mandos_communication(const char *ip, uint16_t port, + AvahiIfIndex if_index){ int ret, tcp_sd; struct sockaddr_in6 to; encrypted_session es; @@ -366,7 +365,8 @@ char interface[IF_NAMESIZE]; if(debug){ - fprintf(stderr, "Setting up a tcp connection to %s\n", ip); + fprintf(stderr, "Setting up a tcp connection to %s, port %d\n", + ip, port); } tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); @@ -376,7 +376,7 @@ } if(debug){ - if(if_indextoname(if_index, interface) == NULL){ + if(if_indextoname((unsigned int)if_index, interface) == NULL){ if(debug){ perror("if_indextoname"); } @@ -402,7 +402,15 @@ to.sin6_scope_id = (uint32_t)if_index; if(debug){ - fprintf(stderr, "Connection to: %s\n", ip); + fprintf(stderr, "Connection to: %s, port %d\n", ip, port); +/* char addrstr[INET6_ADDRSTRLEN]; */ +/* if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */ +/* sizeof(addrstr)) == NULL){ */ +/* perror("inet_ntop"); */ +/* } else { */ +/* fprintf(stderr, "Really connecting to: %s, port %d\n", */ +/* addrstr, ntohs(to.sin6_port)); */ +/* } */ } ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to)); @@ -489,7 +497,7 @@ &decrypted_buffer, certdir); if (decrypted_buffer_size >= 0){ - while(written < (size_t)decrypted_buffer_size){ + while(written < (size_t) decrypted_buffer_size){ ret = (int)fwrite (decrypted_buffer + written, 1, (size_t)decrypted_buffer_size - written, stdout); @@ -564,8 +572,7 @@ fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on" " port %d\n", name, host_name, ip, port); } - int ret = start_mandos_communication(ip, port, - (unsigned int) interface); + int ret = start_mandos_communication(ip, port, interface); if (ret == 0){ exit(EXIT_SUCCESS); } @@ -623,19 +630,23 @@ } } -/* combinds file name and path and returns the malloced new string. som sane checks could/should be added */ -const char *combinepath(const char *first, const char *second){ - char *tmp; - tmp = malloc(strlen(first) + strlen(second) + 2); +/* Combines file name and path and returns the malloced new + string. some sane checks could/should be added */ +static const char *combinepath(const char *first, const char *second){ + size_t f_len = strlen(first); + size_t s_len = strlen(second); + char *tmp = malloc(f_len + s_len + 2); if (tmp == NULL){ - perror("malloc"); return NULL; } - strcpy(tmp, first); - if (first[0] != '\0' and first[strlen(first) - 1] != '/'){ - strcat(tmp, "/"); - } - strcat(tmp, second); + if(f_len > 0){ + memcpy(tmp, first, f_len); + } + tmp[f_len] = '/'; + if(s_len > 0){ + memcpy(tmp + f_len + 1, second, s_len); + } + tmp[f_len + 1 + s_len] = '\0'; return tmp; } @@ -649,10 +660,13 @@ const char *interface = "eth0"; struct ifreq network; int sd; + char *connect_to = NULL; + AvahiIfIndex if_index = AVAHI_IF_UNSPEC; while (true){ static struct option long_options[] = { {"debug", no_argument, (int *)&debug, 1}, + {"connect", required_argument, 0, 'C'}, {"interface", required_argument, 0, 'i'}, {"certdir", required_argument, 0, 'd'}, {"certkey", required_argument, 0, 'c'}, @@ -673,6 +687,9 @@ case 'i': interface = optarg; break; + case 'C': + connect_to = optarg; + break; case 'd': certdir = optarg; break; @@ -686,19 +703,51 @@ exit(EXIT_FAILURE); } } - + certfile = combinepath(certdir, certfile); if (certfile == NULL){ + perror("combinepath"); returncode = EXIT_FAILURE; goto exit; } - + certkey = combinepath(certdir, certkey); if (certkey == NULL){ + perror("combinepath"); returncode = EXIT_FAILURE; goto exit; } - + + if_index = (AvahiIfIndex) if_nametoindex(interface); + if(if_index == 0){ + fprintf(stderr, "No such interface: \"%s\"\n", interface); + exit(EXIT_FAILURE); + } + + if(connect_to != NULL){ + /* Connect directly, do not use Zeroconf */ + /* (Mainly meant for debugging) */ + char *address = strrchr(connect_to, ':'); + if(address == NULL){ + fprintf(stderr, "No colon in address\n"); + exit(EXIT_FAILURE); + } + errno = 0; + uint16_t port = (uint16_t) strtol(address+1, NULL, 10); + if(errno){ + perror("Bad port number"); + exit(EXIT_FAILURE); + } + *address = '\0'; + address = connect_to; + ret = start_mandos_communication(address, port, if_index); + if(ret < 0){ + exit(EXIT_FAILURE); + } else { + exit(EXIT_SUCCESS); + } + } + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); if(sd < 0) { perror("socket"); @@ -761,9 +810,7 @@ } /* Create the service browser */ - sb = avahi_s_service_browser_new(server, - (AvahiIfIndex) - if_nametoindex(interface), + sb = avahi_s_service_browser_new(server, if_index, AVAHI_PROTO_INET6, "_mandos._tcp", NULL, 0, browse_callback, server); === modified file 'plugins.d/passprompt.c' --- plugins.d/passprompt.c 2008-07-21 15:34:44 +0000 +++ plugins.d/passprompt.c 2008-08-02 10:48:24 +0000 @@ -2,7 +2,7 @@ /* * Passprompt - Read a password from the terminal and print it * - * Copyright © 2007-2008 Teddy Hogeborn and Björn Påhlsson. + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -23,7 +23,6 @@ */ #define _GNU_SOURCE /* getline() */ -#define _FORTIFY_SOURCE 2 #include /* struct termios, tcsetattr(), TCSAFLUSH, tcgetattr(), ECHO */ @@ -103,15 +102,11 @@ sigemptyset(&new_action.sa_mask); sigaddset(&new_action.sa_mask, SIGINT); - sigaddset(&new_action.sa_mask, SIGQUIT); sigaddset(&new_action.sa_mask, SIGHUP); sigaddset(&new_action.sa_mask, SIGTERM); sigaction(SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction(SIGINT, &new_action, NULL); - sigaction(SIGQUIT, NULL, &old_action); - if (old_action.sa_handler != SIG_IGN) - sigaction(SIGQUIT, &new_action, NULL); sigaction(SIGHUP, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction(SIGHUP, &new_action, NULL); === added file 'server.conf' --- server.conf 1970-01-01 00:00:00 +0000 +++ server.conf 2008-07-29 03:35:39 +0000 @@ -0,0 +1,7 @@ +[server] +#interface = +#address = +#port = +#debug = +#priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP +#servicename = Mandos === modified file 'server.py' --- server.py 2008-07-22 06:23:29 +0000 +++ server.py 2008-07-31 19:48:05 +0000 @@ -6,12 +6,12 @@ # This program is partly derived from an example program for an Avahi # service publisher, downloaded from # . This includes the -# following functions: "add_service", "remove_service", +# following functions: "AvahiService.add", "AvahiService.remove", # "server_state_changed", "entry_group_state_changed", and some lines # in "main". # -# Everything else is Copyright © 2007-2008 Teddy Hogeborn and Björn -# Påhlsson. +# Everything else is +# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,8 +26,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# Contact the authors at and -# . +# Contact the authors at . # from __future__ import division @@ -66,8 +65,8 @@ # # This server announces itself as a Zeroconf service. Connecting # clients use the TLS protocol, with the unusual quirk that this -# server program acts as a TLS "client" while the connecting clients -# acts as a TLS "server". The clients (acting as a TLS "server") must +# server program acts as a TLS "client" while a connecting client acts +# as a TLS "server". The client (acting as a TLS "server") must # supply an OpenPGP certificate, and the fingerprint of this # certificate is used by this server to look up (in a list read from a # file at start time) which binary blob to give the client. No other @@ -82,20 +81,90 @@ logger.addHandler(syslogger) del syslogger -# This variable is used to optionally bind to a specified interface. -# It is a global variable to fit in with the other variables from the -# Avahi example code. -serviceInterface = avahi.IF_UNSPEC + +class AvahiError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class AvahiServiceError(AvahiError): + pass + +class AvahiGroupError(AvahiError): + pass + + +class AvahiService(object): + """ + interface: integer; avahi.IF_UNSPEC or an interface index. + Used to optionally bind to the specified interface. + name = string; Example: "Mandos" + type = string; Example: "_mandos._tcp". + See + port = integer; what port to announce + TXT = list of strings; TXT record for the service + domain = string; Domain to publish on, default to .local if empty. + host = string; Host to publish records for, default to localhost + if empty. + max_renames = integer; maximum number of renames + rename_count = integer; counter so we only rename after collisions + a sensible number of times + """ + def __init__(self, interface = avahi.IF_UNSPEC, name = None, + type = None, port = None, TXT = None, domain = "", + host = "", max_renames = 12): + """An Avahi (Zeroconf) service. """ + self.interface = interface + self.name = name + self.type = type + self.port = port + if TXT is None: + self.TXT = [] + else: + self.TXT = TXT + self.domain = domain + self.host = host + self.rename_count = 0 + def rename(self): + """Derived from the Avahi example code""" + if self.rename_count >= self.max_renames: + logger.critical(u"No suitable service name found after %i" + u" retries, exiting.", rename_count) + raise AvahiServiceError("Too many renames") + name = server.GetAlternativeServiceName(name) + logger.notice(u"Changing name to %r ...", name) + self.remove() + self.add() + self.rename_count += 1 + def remove(self): + """Derived from the Avahi example code""" + if group is not None: + group.Reset() + def add(self): + """Derived from the Avahi example code""" + global group + if group is None: + group = dbus.Interface\ + (bus.get_object(avahi.DBUS_NAME, + server.EntryGroupNew()), + avahi.DBUS_INTERFACE_ENTRY_GROUP) + group.connect_to_signal('StateChanged', + entry_group_state_changed) + logger.debug(u"Adding service '%s' of type '%s' ...", + service.name, service.type) + group.AddService( + self.interface, # interface + avahi.PROTO_INET6, # protocol + dbus.UInt32(0), # flags + self.name, self.type, + self.domain, self.host, + dbus.UInt16(self.port), + avahi.string_array_to_txt_array(self.TXT)) + group.Commit() + # From the Avahi example code: -serviceName = None -serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html -servicePort = None # Not known at startup -serviceTXT = [] # TXT record for the service -domain = "" # Domain to publish on, default to .local -host = "" # Host to publish records for, default to localhost -group = None #our entry group -rename_count = 12 # Counter so we only rename after collisions a - # sensible number of times +group = None # our entry group # End of Avahi example code @@ -107,26 +176,26 @@ uniquely identify the client secret: bytestring; sent verbatim (over TLS) to client fqdn: string (FQDN); available for use by the checker command - created: datetime.datetime() - last_seen: datetime.datetime() or None if not yet seen - timeout: datetime.timedelta(); How long from last_seen until - this client is invalid + created: datetime.datetime(); object creation, not client host + last_checked_ok: datetime.datetime() or None if not yet checked OK + timeout: datetime.timedelta(); How long from last_checked_ok + until this client is invalid interval: datetime.timedelta(); How often to start a new checker stop_hook: If set, called by stop() as stop_hook(self) checker: subprocess.Popen(); a running checker process used to see if the client lives. - Is None if no process is running. + 'None' if no process is running. checker_initiator_tag: a gobject event source tag, or None stop_initiator_tag: - '' - checker_callback_tag: - '' - checker_command: string; External command which is run to check if - client lives. %()s expansions are done at + client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. Private attibutes: _timeout: Real variable for 'timeout' _interval: Real variable for 'interval' - _timeout_milliseconds: Used by gobject.timeout_add() + _timeout_milliseconds: Used when calling gobject.timeout_add() _interval_milliseconds: - '' - """ def _set_timeout(self, timeout): @@ -171,11 +240,11 @@ self.secret = sf.read() sf.close() else: - raise RuntimeError(u"No secret or secfile for client %s" - % self.name) - self.fqdn = fqdn # string + raise TypeError(u"No secret or secfile for client %s" + % self.name) + self.fqdn = fqdn self.created = datetime.datetime.now() - self.last_seen = None + self.last_checked_ok = None self.timeout = string_to_delta(timeout) self.interval = string_to_delta(interval) self.stop_hook = stop_hook @@ -199,20 +268,18 @@ self.stop) def stop(self): """Stop this client. - The possibility that this client might be restarted is left - open, but not currently used.""" + The possibility that a client might be restarted is left open, + but not currently used.""" # If this client doesn't have a secret, it is already stopped. if self.secret: logger.debug(u"Stopping client %s", self.name) self.secret = None else: return False - if hasattr(self, "stop_initiator_tag") \ - and self.stop_initiator_tag: + if getattr(self, "stop_initiator_tag", False): gobject.source_remove(self.stop_initiator_tag) self.stop_initiator_tag = None - if hasattr(self, "checker_initiator_tag") \ - and self.checker_initiator_tag: + if getattr(self, "checker_initiator_tag", False): gobject.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = None self.stop_checker() @@ -232,7 +299,7 @@ and (os.WEXITSTATUS(condition) == 0): logger.debug(u"Checker for %(name)s succeeded", vars(self)) - self.last_seen = now + self.last_checked_ok = now gobject.source_remove(self.stop_initiator_tag) self.stop_initiator_tag = gobject.timeout_add\ (self._timeout_milliseconds, @@ -257,24 +324,25 @@ # is as it should be. if self.checker is None: try: + # In case check_command has exactly one % operator command = self.check_command % self.fqdn except TypeError: + # Escape attributes for the shell escaped_attrs = dict((key, re.escape(str(val))) for key, val in vars(self).iteritems()) try: command = self.check_command % escaped_attrs except TypeError, error: - logger.critical(u'Could not format string "%s":' - u' %s', self.check_command, error) + logger.error(u'Could not format string "%s":' + u' %s', self.check_command, error) return True # Try again later try: logger.debug(u"Starting checker %r for %s", command, self.name) - self.checker = subprocess.\ - Popen(command, - close_fds=True, shell=True, - cwd="/") + self.checker = subprocess.Popen(command, + close_fds=True, + shell=True, cwd="/") self.checker_callback_tag = gobject.child_watch_add\ (self.checker.pid, self.checker_callback) @@ -288,7 +356,7 @@ if self.checker_callback_tag: gobject.source_remove(self.checker_callback_tag) self.checker_callback_tag = None - if not hasattr(self, "checker") or self.checker is None: + if getattr(self, "checker", None) is None: return logger.debug("Stopping checker for %(name)s", vars(self)) try: @@ -297,17 +365,16 @@ #if self.checker.poll() is None: # os.kill(self.checker.pid, signal.SIGKILL) except OSError, error: - if error.errno != errno.ESRCH: + if error.errno != errno.ESRCH: # No such process raise self.checker = None - def still_valid(self, now=None): + def still_valid(self): """Has the timeout not yet passed for this client?""" - if now is None: - now = datetime.datetime.now() - if self.last_seen is None: + now = datetime.datetime.now() + if self.last_checked_ok is None: return now < (self.created + self.timeout) else: - return now < (self.last_seen + self.timeout) + return now < (self.last_checked_ok + self.timeout) def peer_certificate(session): @@ -366,16 +433,20 @@ def handle(self): logger.debug(u"TCP connection from: %s", unicode(self.client_address)) - session = gnutls.connection.ClientSession(self.request, - gnutls.connection.\ - X509Credentials()) + session = gnutls.connection.ClientSession\ + (self.request, gnutls.connection.X509Credentials()) + # Note: gnutls.connection.X509Credentials is really a generic + # GnuTLS certificate credentials object so long as no X.509 + # keys are added to it. Therefore, we can use it here despite + # using OpenPGP certificates. #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", # "+DHE-DSS")) - priority = "NORMAL" - if self.server.options.priority: - priority = self.server.options.priority + priority = "NORMAL" # Fallback default, since this + # MUST be set. + if self.server.settings["priority"]: + priority = self.server.settings["priority"] gnutls.library.functions.gnutls_priority_set_direct\ (session._c_object, priority, None); @@ -398,16 +469,15 @@ if c.fingerprint == fpr: client = c break + if not client: + logger.debug(u"Client not found for fingerprint: %s", fpr) + session.bye() + return # Have to check if client.still_valid(), since it is possible # that the client timed out while establishing the GnuTLS # session. - if (not client) or (not client.still_valid()): - if client: - logger.debug(u"Client %(name)s is invalid", - vars(client)) - else: - logger.debug(u"Client not found for fingerprint: %s", - fpr) + if not client.still_valid(): + logger.debug(u"Client %(name)s is invalid", vars(client)) session.bye() return sent_size = 0 @@ -423,14 +493,14 @@ class IPv6_TCPServer(SocketServer.ForkingTCPServer, object): """IPv6 TCP server. Accepts 'None' as address and/or port. Attributes: - options: Command line options + settings: Server settings clients: Set() of Client objects """ address_family = socket.AF_INET6 def __init__(self, *args, **kwargs): - if "options" in kwargs: - self.options = kwargs["options"] - del kwargs["options"] + if "settings" in kwargs: + self.settings = kwargs["settings"] + del kwargs["settings"] if "clients" in kwargs: self.clients = kwargs["clients"] del kwargs["clients"] @@ -439,19 +509,18 @@ """This overrides the normal server_bind() function to bind to an interface if one was specified, and also NOT to bind to an address or port if they were not specified.""" - if self.options.interface: - if not hasattr(socket, "SO_BINDTODEVICE"): - # From /usr/include/asm-i486/socket.h - socket.SO_BINDTODEVICE = 25 + if self.settings["interface"]: + # 25 is from /usr/include/asm-i486/socket.h + SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25) try: self.socket.setsockopt(socket.SOL_SOCKET, - socket.SO_BINDTODEVICE, - self.options.interface) + SO_BINDTODEVICE, + self.settings["interface"]) except socket.error, error: if error[0] == errno.EPERM: logger.warning(u"No permission to" u" bind to interface %s", - self.options.interface) + self.settings["interface"]) else: raise error # Only bind(2) the socket if we really need to. @@ -500,93 +569,61 @@ return delta -def add_service(): - """Derived from the Avahi example code""" - global group, serviceName, serviceType, servicePort, serviceTXT, \ - domain, host - if group is None: - group = dbus.Interface( - bus.get_object( avahi.DBUS_NAME, - server.EntryGroupNew()), - avahi.DBUS_INTERFACE_ENTRY_GROUP) - group.connect_to_signal('StateChanged', - entry_group_state_changed) - logger.debug(u"Adding service '%s' of type '%s' ...", - serviceName, serviceType) - - group.AddService( - serviceInterface, # interface - avahi.PROTO_INET6, # protocol - dbus.UInt32(0), # flags - serviceName, serviceType, - domain, host, - dbus.UInt16(servicePort), - avahi.string_array_to_txt_array(serviceTXT)) - group.Commit() - - -def remove_service(): - """From the Avahi example code""" - global group - - if not group is None: - group.Reset() - - def server_state_changed(state): """Derived from the Avahi example code""" if state == avahi.SERVER_COLLISION: logger.warning(u"Server name collision") - remove_service() + service.remove() elif state == avahi.SERVER_RUNNING: - add_service() + service.add() def entry_group_state_changed(state, error): """Derived from the Avahi example code""" - global serviceName, server, rename_count - logger.debug(u"state change: %i", state) if state == avahi.ENTRY_GROUP_ESTABLISHED: logger.debug(u"Service established.") elif state == avahi.ENTRY_GROUP_COLLISION: - - rename_count = rename_count - 1 - if rename_count > 0: - name = server.GetAlternativeServiceName(name) - logger.warning(u"Service name collision, " - u"changing name to '%s' ...", name) - remove_service() - add_service() - - else: - logger.error(u"No suitable service name found after %i" - u" retries, exiting.", n_rename) - killme(1) + logger.warning(u"Service name collision.") + service.rename() elif state == avahi.ENTRY_GROUP_FAILURE: - logger.error(u"Error in group state changed %s", - unicode(error)) - killme(1) - - -def if_nametoindex(interface): - """Call the C function if_nametoindex()""" + logger.critical(u"Error in group state changed %s", + unicode(error)) + raise AvahiGroupError("State changed: %s", str(error)) + +def if_nametoindex(interface, _func=[None]): + """Call the C function if_nametoindex(), or equivalent""" + if _func[0] is not None: + return _func[0](interface) try: - libc = ctypes.cdll.LoadLibrary("libc.so.6") - return libc.if_nametoindex(interface) + if "ctypes.util" not in sys.modules: + import ctypes.util + while True: + try: + libc = ctypes.cdll.LoadLibrary\ + (ctypes.util.find_library("c")) + _func[0] = libc.if_nametoindex + return _func[0](interface) + except IOError, e: + if e != errno.EINTR: + raise except (OSError, AttributeError): if "struct" not in sys.modules: import struct if "fcntl" not in sys.modules: import fcntl - SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h - s = socket.socket() - ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack("16s16x", interface)) - s.close() - interface_index = struct.unpack("I", ifreq[16:20])[0] - return interface_index + def the_hard_way(interface): + "Get an interface index the hard way, i.e. using fcntl()" + SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h + s = socket.socket() + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack("16s16x", interface)) + s.close() + interface_index = struct.unpack("I", ifreq[16:20])[0] + return interface_index + _func[0] = the_hard_way + return _func[0](interface) def daemon(nochdir, noclose): @@ -599,7 +636,7 @@ os.chdir("/") if not noclose: # Close all standard open file descriptors - null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR) + null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, "/dev/null not a character device") @@ -610,39 +647,30 @@ os.close(null) -def killme(status = 0): - logger.debug("Stopping server with exit status %d", status) - exitstatus = status - if main_loop_started: - main_loop.quit() - else: - sys.exit(status) - - def main(): - global exitstatus - exitstatus = 0 global main_loop_started main_loop_started = False parser = OptionParser() parser.add_option("-i", "--interface", type="string", - default=None, metavar="IF", - help="Bind to interface IF") - parser.add_option("-a", "--address", type="string", default=None, + metavar="IF", help="Bind to interface IF") + parser.add_option("-a", "--address", type="string", help="Address to listen for requests on") - parser.add_option("-p", "--port", type="int", default=None, + parser.add_option("-p", "--port", type="int", help="Port number to receive requests on") parser.add_option("--check", action="store_true", default=False, help="Run self-test") parser.add_option("--debug", action="store_true", default=False, - help="Debug mode") - parser.add_option("--priority", type="string", - default="SECURE256", - help="GnuTLS priority string" - " (see GnuTLS documentation)") - parser.add_option("--servicename", type="string", - default="Mandos", help="Zeroconf service name") + help="Debug mode; run in foreground and log to" + " terminal") + parser.add_option("--priority", type="string", help="GnuTLS" + " priority string (see GnuTLS documentation)") + parser.add_option("--servicename", type="string", metavar="NAME", + help="Zeroconf service name") + parser.add_option("--configdir", type="string", + default="/etc/mandos", metavar="DIR", + help="Directory to search for configuration" + " files") (options, args) = parser.parse_args() if options.check: @@ -650,17 +678,52 @@ doctest.testmod() sys.exit() - # Parse config file - defaults = { "timeout": "1h", - "interval": "5m", - "checker": "fping -q -- %%(fqdn)s", - } - client_config = ConfigParser.SafeConfigParser(defaults) - #client_config.readfp(open("global.conf"), "global.conf") - client_config.read("mandos-clients.conf") - - global serviceName - serviceName = options.servicename; + # Default values for config file for server-global settings + server_defaults = { "interface": "", + "address": "", + "port": "", + "debug": "False", + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", + "servicename": "Mandos", + } + + # Parse config file for server-global settings + server_config = ConfigParser.SafeConfigParser(server_defaults) + del server_defaults + server_config.read(os.path.join(options.configdir, "server.conf")) + server_section = "server" + # Convert the SafeConfigParser object to a dict + server_settings = dict(server_config.items(server_section)) + # Use getboolean on the boolean config option + server_settings["debug"] = server_config.getboolean\ + (server_section, "debug") + del server_config + + # Override the settings from the config file with command line + # options, if set. + for option in ("interface", "address", "port", "debug", + "priority", "servicename", "configdir"): + value = getattr(options, option) + if value is not None: + server_settings[option] = value + del options + # Now we have our good server settings in "server_settings" + + # Parse config file with clients + client_defaults = { "timeout": "1h", + "interval": "5m", + "checker": "fping -q -- %%(fqdn)s", + } + client_config = ConfigParser.SafeConfigParser(client_defaults) + client_config.read(os.path.join(server_settings["configdir"], + "clients.conf")) + + global service + service = AvahiService(name = server_settings["servicename"], + type = "_mandos._tcp", ); + if server_settings["interface"]: + service.interface = if_nametoindex(server_settings["interface"]) global main_loop global bus @@ -674,7 +737,7 @@ avahi.DBUS_INTERFACE_SERVER ) # End of Avahi example code - debug = options.debug + debug = server_settings["debug"] if debug: console = logging.StreamHandler() @@ -689,7 +752,7 @@ clients.remove(client) if not clients: logger.debug(u"No clients left, exiting") - killme() + sys.exit() clients.update(Set(Client(name=section, stop_hook = remove_from_clients, @@ -718,47 +781,48 @@ if not debug: signal.signal(signal.SIGINT, signal.SIG_IGN) - signal.signal(signal.SIGHUP, lambda signum, frame: killme()) - signal.signal(signal.SIGTERM, lambda signum, frame: killme()) + signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) + signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) for client in clients: client.start() - tcp_server = IPv6_TCPServer((options.address, options.port), + tcp_server = IPv6_TCPServer((server_settings["address"], + server_settings["port"]), tcp_handler, - options=options, + settings=server_settings, clients=clients) - # Find out what random port we got - global servicePort - servicePort = tcp_server.socket.getsockname()[1] - logger.debug(u"Now listening on port %d", servicePort) - - if options.interface is not None: - global serviceInterface - serviceInterface = if_nametoindex(options.interface) - - # From the Avahi example code - server.connect_to_signal("StateChanged", server_state_changed) - try: - server_state_changed(server.GetState()) - except dbus.exceptions.DBusException, error: - logger.critical(u"DBusException: %s", error) - killme(1) - # End of Avahi example code - - gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, - lambda *args, **kwargs: - tcp_server.handle_request(*args[2:], - **kwargs) or True) - try: + # Find out what port we got + service.port = tcp_server.socket.getsockname()[1] + logger.debug(u"Now listening on address %r, port %d, flowinfo %d," + u" scope_id %d" % tcp_server.socket.getsockname()) + + #service.interface = tcp_server.socket.getsockname()[3] + + try: + # From the Avahi example code + server.connect_to_signal("StateChanged", server_state_changed) + try: + server_state_changed(server.GetState()) + except dbus.exceptions.DBusException, error: + logger.critical(u"DBusException: %s", error) + sys.exit(1) + # End of Avahi example code + + gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, + lambda *args, **kwargs: + tcp_server.handle_request\ + (*args[2:], **kwargs) or True) + logger.debug("Starting main loop") main_loop_started = True main_loop.run() + except AvahiError, error: + logger.critical(u"AvahiError: %s" + unicode(error)) + sys.exit(1) except KeyboardInterrupt: if debug: print - - sys.exit(exitstatus) if __name__ == '__main__': main()