Merge branch 'refs/heads/t/aft_fixes'
authorAndre Noll <maan@tuebingen.mpg.de>
Mon, 25 Jun 2018 18:12:27 +0000 (20:12 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Mon, 25 Jun 2018 18:17:56 +0000 (20:17 +0200)
Two fixes related to the audio file table.

Cooking for three weeks.

* refs/heads/t/aft_fixes:
  aft: Remember current audio file after SIGHUP.
  aft: Honor AUDIO_FILE_REMOVE events.
  server: Update status items on file renames.

61 files changed:
Makefile.real
NEWS.md
acl.c
afs.c
afs.h
aft.c
audioc.c
audiod.c
audiod_command.c
client.c
client_common.c
close_on_fork.c
close_on_fork.h
command.c
command.h
configure.ac
crypt.c [deleted file]
crypt.h
crypt_backend.h
crypt_common.c
daemon.c
daemon.h
dccp_send.c
error.h
fd.c
fd.h
filter.c
gcrypt.c
gui.c
http_send.c
lsu.c [new file with mode: 0644]
lsu.h [new file with mode: 0644]
m4/lls/audioc.suite.m4
m4/lls/audiod_cmd.suite.m4
m4/lls/filter.suite.m4
m4/lls/include/log-timing.m4
m4/lls/include/long-help.m4 [new file with mode: 0644]
m4/lls/play.suite.m4
m4/lls/play_cmd.suite.m4
m4/lls/server.suite.m4
m4/lls/server_cmd.suite.m4
mixer.c
mp.c
net.c
net.h
openssl.c [new file with mode: 0644]
play.c
sched.c
send.h
send_common.c
server.c
server.h
sideband.h
t/t0004-server.sh
udp_send.c
user_list.c
user_list.h
vss.c
vss.h
web/download.in.html
web/manual.md

index 7648767..dae48f0 100644 (file)
@@ -144,6 +144,7 @@ cc-option = $(shell \
 )
 
 STRICT_CFLAGS += $(call cc-option, -Wformat-signedness)
+STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers)
 
 # To put more focus on warnings, be less verbose as default
 # Use 'make V=1' to see the full commands
diff --git a/NEWS.md b/NEWS.md
index 4fd9dcb..8758814 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,11 +2,32 @@ NEWS
 ====
 
 -------------------------------------------
-0.6.2 (to be accounced) "elastic diversity"
+0.6.2 (to be announced) "elastic diversity"
 -------------------------------------------
 
 - para_gui no longer waits up to one second to update the screen when
-the geometry of the terminal changes.
+  the geometry of the terminal changes.
+- Minor documentation improvements.
+- Improvements to the crypto subsystem.
+- The server subcommand "task" has been deprecated. It still works,
+  but prints nothing. It will be removed in the next major release.
+- Server log output is now serialized, avoiding issues with partial
+  lines.
+- It is now possible to switch to a different afs database by changing
+  the server configuration and sending SIGHUP to the server process.
+- New server options: --listen--address, --http-listen-address and
+  --dccp-listen-address. These options restrict the set of listening
+  addresses of the TCP and DCCP sockets of the server process.
+- The help commands of server, audiod, play now support --long. By default,
+  the short help is shown.
+- The code which merges the command line options with the config file
+  options has been consolidated.
+- If the current audio file is renamed, the status items are now updated
+  accordingly.
+- After the server process received SIGHUP, changes to the current audio
+  file did not trigger an update of the status items. This has been fixed.
+
+Download: [tarball](./releases/paraslash-git.tar.xz)
 
 ----------------------------------------
 0.6.1 (2017-09-23) "segmented iteration"
diff --git a/acl.c b/acl.c
index 10f56bf..2c90052 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -31,10 +31,12 @@ struct access_info {
 /**
  * Return true if addr_1 matches addr_2 in the first `netmask' bits.
  */
-static int v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
+static bool v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
 {
        uint32_t mask = ~0U;
 
+       if (netmask == 0) /* avoid 32-bit shift, which is undefined in C. */
+               return true;
        if (netmask < 32)
                mask <<= (32 - netmask);
        return (htonl(addr_1) & mask) == (htonl(addr_2) & mask);
@@ -99,14 +101,18 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
        struct access_info *ai, *tmp;
        struct in_addr to_delete;
 
+       PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
        inet_pton(AF_INET, addr, &to_delete);
 
        list_for_each_entry_safe(ai, tmp, acl, node) {
-
                if (v4_addr_match(to_delete.s_addr, ai->addr.s_addr,
                                        PARA_MIN(netmask, ai->netmask))) {
-                       PARA_NOTICE_LOG("removing %s/%u from access list\n",
-                                       addr, ai->netmask);
+                       char dst[INET_ADDRSTRLEN + 1];
+                       const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
+                               dst, sizeof(dst));
+                       if (p)
+                               PARA_INFO_LOG("removing %s/%u\n", p,
+                                       ai->netmask);
                        list_del(&ai->node);
                        free(ai);
                }
diff --git a/afs.c b/afs.c
index 538606a..4fe2140 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -638,16 +638,18 @@ static int setup_command_socket_or_die(void)
        return socket_fd;
 }
 
+static char *database_dir;
+
 static void close_afs_tables(void)
 {
        int i;
        PARA_NOTICE_LOG("closing afs_tables\n");
        for (i = 0; i < NUM_AFS_TABLES; i++)
                afs_tables[i].close();
+       free(database_dir);
+       database_dir = NULL;
 }
 
-static char *database_dir;
-
 static void get_database_dir(void)
 {
        if (!database_dir) {
@@ -1002,9 +1004,14 @@ __noreturn void afs_init(int socket_fd)
        }
        ret = schedule(&s);
        sched_shutdown(&s);
+       close_current_mood();
 out_close:
        close_afs_tables();
 out:
+       signal_shutdown(signal_task);
+       free_status_items();
+       free(current_mop);
+       free_lpr();
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -1016,6 +1023,7 @@ static int com_init_callback(struct afs_callback_arg *aca)
        int i, ret;
 
        close_afs_tables();
+       get_database_dir();
        for (i = 0; i < NUM_AFS_TABLES; i++) {
                struct afs_table *t = &afs_tables[i];
 
diff --git a/afs.h b/afs.h
index 8beca5a..b0d283f 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -262,6 +262,7 @@ int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
 int get_audio_file_path_of_row(const struct osl_row *row, char **path);
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 int aft_check_callback(struct afs_callback_arg *aca);
+void free_status_items(void);
 
 /* playlist */
 int playlist_open(const char *name);
diff --git a/aft.c b/aft.c
index 944d904..5d4e647 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -983,6 +983,12 @@ out:
        WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024);
 }
 
+void free_status_items(void)
+{
+       freep(&status_items);
+       freep(&parser_friendly_status_items);
+}
+
 static int make_status_items(void)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
@@ -992,6 +998,7 @@ static int make_status_items(void)
        time_t current_time;
        int ret;
 
+       free_status_items();
        if (!status_item_ls_data.path) /* no audio file open */
                return 0;
        ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL);
@@ -1001,23 +1008,20 @@ static int make_status_items(void)
        if (ret < 0)
                goto out;
        make_inode_status_items(&pb);
-       free(status_items);
        status_items = pb.buf;
 
        memset(&pb, 0, sizeof(pb));
        pb.max_size = shm_get_shmmax() - 1;
        pb.flags = PBF_SIZE_PREFIX;
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
-       if (ret < 0) {
-               free(status_items);
-               status_items = NULL;
-               return ret;
-       }
+       if (ret < 0)
+               goto out;
        make_inode_status_items(&pb);
-       free(parser_friendly_status_items);
        parser_friendly_status_items = pb.buf;
        ret = 1;
 out:
+       if (ret < 0)
+               free_status_items();
        lls_free_parse_result(opts.lpr, cmd);
        return ret;
 }
index 77a0539..af67063 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -17,6 +17,7 @@
 
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
@@ -95,9 +96,15 @@ I9E_DUMMY_COMPLETER(tasks);
 I9E_DUMMY_COMPLETER(term);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, audiod_completers);
+       char *opts[] = {LSG_AUDIOD_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, audiod_completers);
 }
 
 static void version_completer(struct i9e_completion_info *ci,
@@ -284,19 +291,6 @@ __noreturn static void print_completions(void)
 
 #endif /* HAVE_READLINE */
 
-static char *configfile_exists(void)
-{
-       char *config_file;
-       char *home = para_homedir();
-
-       config_file = make_message("%s/.paraslash/audioc.conf", home);
-       free(home);
-       if (file_exists(config_file))
-               return config_file;
-       free(config_file);
-       return NULL;
-}
-
 static void handle_help_flag(void)
 {
        char *help;
@@ -330,54 +324,21 @@ static void handle_help_flag(void)
  */
 int main(int argc, char *argv[])
 {
-       const struct lls_command *cmd = CMD_PTR;
        int ret, fd;
-       char *cf = NULL, *buf, *args, *errctx = NULL;
+       char *buf, *args, *errctx = NULL;
        size_t bufsize;
-       struct lls_parse_result *lpr1, *lpr2, *lpr3;
        unsigned num_inputs;
 
-       ret = lls(lls_parse(argc, argv, cmd, &lpr1, &errctx));
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto fail;
-       lpr = lpr1;
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        version_handle_flag("audioc", OPT_GIVEN(VERSION));
        handle_help_flag();
-       cf = configfile_exists();
-       if (cf) {
-               void *map;
-               size_t sz;
-               int cf_argc;
-               char **cf_argv;
-               ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-               if (ret != -E_EMPTY) {
-                       if (ret < 0)
-                               goto out;
-                       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv,
-                               &errctx));
-                       para_munmap(map, sz);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("syntax error in %s\n", cf);
-                               goto out;
-                       }
-                       cf_argc = ret;
-                       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &lpr2,
-                               &errctx));
-                       lls_free_argv(cf_argv);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("parse error in %s\n", cf);
-                               goto out;
-                       }
-                       ret = lls(lls_merge(lpr1, lpr2, cmd, &lpr3, &errctx));
-                       lls_free_parse_result(lpr2, cmd);
-                       if (ret < 0)
-                               goto out;
-                       lls_free_parse_result(lpr1, cmd);
-                       lpr = lpr3;
-                       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-               }
-       }
+       ret = lsu_merge_config_file_options(NULL, "audioc.conf",
+               &lpr, CMD_PTR, audioc_suite, 0 /* default flags */);
+       if (ret < 0)
+               goto fail;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        if (OPT_GIVEN(COMPLETE))
                print_completions();
        if (OPT_GIVEN(SOCKET))
@@ -412,8 +373,7 @@ int main(int argc, char *argv[])
        } while (ret >= 0);
        free(buf);
 out:
-       lls_free_parse_result(lpr, cmd);
-       free(cf);
+       lls_free_parse_result(lpr, CMD_PTR);
 fail:
        if (errctx)
                PARA_ERROR_LOG("%s\n", errctx);
index 8d152fa..b93f29d 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -17,6 +17,7 @@
 #include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "list.h"
 #include "sched.h"
@@ -378,81 +379,36 @@ empty:
 
 static void parse_config_or_die(void)
 {
-       int ret;
-       char *cf, *errctx = NULL;
-       void *map;
-       size_t sz;
+       int i, ret;
+       uint32_t n;
 
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/audiod.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "audiod.conf", &lpr, CMD_PTR, audiod_suite, 0U /* flags */);
        if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-       } else {
-               int cf_argc;
-               char **cf_argv;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-               para_munmap(map, sz);
-               if (ret < 0)
-                       goto free_cf;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto free_cf;
-               ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-               lls_free_parse_result(cf_lpr, CMD_PTR);
-               if (ret < 0)
-                       goto free_cf;
-               lls_free_parse_result(lpr, CMD_PTR);
-               lpr = merged_lpr;
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
        daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
-       if (OPT_GIVEN(USER_ALLOW)) {
-               uint32_t n = OPT_GIVEN(USER_ALLOW);
-               int i;
-
-               uid_whitelist = para_malloc(n * sizeof(uid_t));
-               for (i = 0; i < n; i++) {
-                       const char *arg = lls_string_val(i,
-                               OPT_RESULT(USER_ALLOW));
-                       int32_t val;
-                       struct passwd *pw;
-                       ret = para_atoi32(arg, &val);
-                       if (ret >= 0) {
-                               uid_whitelist[i] = val;
-                               continue;
-                       }
-                       errno = 0; /* see getpwnam(3) */
-                       pw = getpwnam(arg);
-                       if (!pw) {
-                               PARA_EMERG_LOG("invalid username: %s\n", arg);
-                               free(uid_whitelist);
-                               goto free_cf;
-                       }
-                       uid_whitelist[i] = pw->pw_uid;
+       n = OPT_GIVEN(USER_ALLOW);
+       if (n == 0)
+               return;
+       uid_whitelist = para_malloc(n * sizeof(uid_t));
+       for (i = 0; i < n; i++) {
+               const char *arg = lls_string_val(i, OPT_RESULT(USER_ALLOW));
+               int32_t val;
+               struct passwd *pw;
+               ret = para_atoi32(arg, &val);
+               if (ret >= 0) {
+                       uid_whitelist[i] = val;
+                       continue;
                }
-       }
-       ret = 0;
-free_cf:
-       free(cf);
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               lls_free_parse_result(lpr, CMD_PTR);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+               pw = getpwnam(arg);
+               if (!pw) {
+                       PARA_EMERG_LOG("invalid username: %s\n", arg);
+                       exit(EXIT_FAILURE);
+               }
+               uid_whitelist[i] = pw->pw_uid;
        }
 }
 
@@ -1493,7 +1449,7 @@ int main(int argc, char *argv[])
        version_handle_flag("audiod", OPT_GIVEN(VERSION));
        handle_help_flags();
        parse_config_or_die();
-       init_random_seed_or_die();
+       crypt_init();
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
        recv_init();
        if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO,
@@ -1542,7 +1498,7 @@ int main(int argc, char *argv[])
        audiod_cleanup();
        sched_shutdown(&sched);
        signal_shutdown(signal_task);
-
+       crypt_shutdown();
 out:
        lls_free_parse_result(lpr, CMD_PTR);
        if (errctx)
index cb4363b..bb54dfa 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "audiod.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "audiod_cmd.lsg.h"
 #include "list.h"
 #include "sched.h"
@@ -224,59 +225,17 @@ __malloc static char *audiod_status_string(void)
        return para_strdup(status);
 }
 
-static int dump_commands(int fd)
-{
-       char *buf = para_strdup(""), *tmp = NULL;
-       int i;
-       ssize_t ret;
-       const struct lls_command *cmd;
-
-       for (i = 1; (cmd = lls_cmd(i, audiod_cmd_suite)); i++) {
-               tmp = make_message("%s%s\t%s\n", buf, lls_command_name(cmd),
-                       lls_purpose(cmd));
-               free(buf);
-               buf = tmp;
-       }
-       ret = client_write(fd, buf);
-       free(buf);
-       return ret;
-}
-
 static int com_help(int fd, struct lls_parse_result *lpr)
 {
+       char *buf;
        int ret;
-       char *buf, *errctx;
-       const struct lls_command *cmd;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_AUDIOD_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               if (errctx) {
-                       buf = make_message("%s\n", errctx);
-                       client_write(fd, buf);
-                       free(buf);
-                       free(errctx);
-               }
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0)
-               return dump_commands(fd);
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), audiod_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               buf = make_message("%s: %s\nAvailable commands:\n", errctx?
-                       errctx : lls_input(0, lpr), para_strerror(-ret));
-               if (client_write(fd, buf) >= 0)
-                       dump_commands(fd);
-               free(errctx);
-               free(buf);
-               goto out;
-       }
-       cmd = lls_cmd(ret, audiod_cmd_suite);
-       buf = lls_long_help(cmd);
-       assert(buf);
+       lsu_com_help(long_help, lpr, audiod_cmd_suite, NULL, &buf, NULL);
        ret = client_write(fd, buf);
        free(buf);
-out:
        return ret < 0? ret : 0;
 }
 EXPORT_AUDIOD_CMD_HANDLER(help)
index 3e9219f..f72719f 100644 (file)
--- a/client.c
+++ b/client.c
@@ -248,9 +248,15 @@ I9E_DUMMY_COMPLETER(tasks);
 static struct i9e_completer completers[];
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, completers);
+       char *opts[] = {LSG_SERVER_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, completers);
 }
 
 static void stat_completer(struct i9e_completion_info *ci,
@@ -618,7 +624,7 @@ int main(int argc, char *argv[])
 {
        int ret;
 
-       init_random_seed_or_die();
+       crypt_init();
        sched.default_timeout.tv_sec = 1;
 
        ret = client_parse_config(argc, argv, &ct, &client_loglevel);
@@ -664,6 +670,7 @@ int main(int argc, char *argv[])
                }
        }
        sched_shutdown(&sched);
+       crypt_shutdown();
 out:
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
index a8d4805..a7eee85 100644 (file)
@@ -15,6 +15,7 @@
 #include "para.h"
 #include "error.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
 #include "crypt.h"
 #include "net.h"
@@ -317,15 +318,15 @@ static int client_post_select(struct sched *s, void *context)
                }
                n = sbb.iov.iov_len;
                PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n);
-               ret = priv_decrypt(ct->key_file, crypt_buf,
+               ret = apc_priv_decrypt(ct->key_file, crypt_buf,
                        sbb.iov.iov_base, n);
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
                ct->challenge_hash = para_malloc(HASH_SIZE);
-               hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash);
-               ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
-               ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
+               hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+               ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
+               ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
                        SESSION_KEY_LEN);
                hash_to_asc(ct->challenge_hash, buf);
                PARA_INFO_LOG("--> %s\n", buf);
@@ -525,12 +526,10 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                int *loglevel)
 {
        const struct lls_command *cmd = CLIENT_CMD_PTR;
-       void *map;
-       size_t sz;
        struct lls_parse_result *lpr;
        int ret, ll;
        struct client_task *ct;
-       char *cf = NULL, *kf = NULL, *user, *errctx, *home = para_homedir();
+       char *kf = NULL, *user, *errctx, *home = para_homedir();
 
        ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
        if (ret < 0)
@@ -538,38 +537,10 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        version_handle_flag("client", CLIENT_OPT_GIVEN(VERSION, lpr));
        handle_help_flag(lpr);
 
-       if (CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-               cf = para_strdup(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr));
-       else
-               cf = make_message("%s/.paraslash/client.conf", home);
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto out;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
-                               CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-                       goto out;
-       } else {
-               int cf_argc;
-               char **cf_argv;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-               para_munmap(map, sz);
-               if (ret < 0)
-                       goto out;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto out;
-               ret = lls(lls_merge(lpr, cf_lpr, cmd, &merged_lpr,
-                       &errctx));
-               lls_free_parse_result(cf_lpr, cmd);
-               if (ret < 0)
-                       goto out;
-               lls_free_parse_result(lpr, cmd);
-               lpr = merged_lpr;
-       }
+       ret = lsu_merge_config_file_options(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr),
+               "client.conf", &lpr, cmd, client_suite, 0U /* default flags */);
+       if (ret < 0)
+               goto out;
        /* success */
        ll = CLIENT_OPT_UINT32_VAL(LOGLEVEL, lpr);
        if (loglevel)
@@ -587,7 +558,6 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                }
        }
        PARA_INFO_LOG("user: %s\n", user);
-       PARA_INFO_LOG("config file: %s\n", cf);
        PARA_INFO_LOG("key file: %s\n", kf);
        PARA_INFO_LOG("loglevel: %d\n", ll);
        ct = para_calloc(sizeof(*ct));
@@ -599,7 +569,6 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        ret = lls_num_inputs(lpr);
 out:
        free(home);
-       free(cf);
        if (ret < 0) {
                if (errctx)
                        PARA_ERROR_LOG("%s\n", errctx);
index 7e0c8e6..28c5eab 100644 (file)
@@ -62,13 +62,7 @@ void del_close_on_fork_list(int fd)
        }
 }
 
-/**
- * Close all fds in the list and destroy all list entries.
- *
- * This function calls close(3) for each fd in the close-on-fork list
- * and empties the list afterwards.
- */
-void close_listed_fds(void)
+static void deplete_cof_list(bool close_fds)
 {
        struct close_on_fork *cof, *tmp;
 
@@ -76,8 +70,32 @@ void close_listed_fds(void)
                return;
        list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) {
                PARA_DEBUG_LOG("closing fd %d\n", cof->fd);
-               close(cof->fd);
+               if (close_fds)
+                       close(cof->fd);
                list_del(&cof->node);
                free(cof);
        }
 }
+
+/**
+ * Close all fds in the list and destroy all list entries.
+ *
+ * This function calls close(3) for each fd in the close-on-fork list
+ * and empties the list afterwards.
+ *
+ * \sa \ref deplete_close_on_fork_list().
+ */
+void close_listed_fds(void)
+{
+       deplete_cof_list(true);
+}
+
+/**
+ * Remove all listed fds from the close on fork list.
+ *
+ * This is like \ref close_listed_fds() but does not close the fds.
+ */
+void deplete_close_on_fork_list(void)
+{
+       deplete_cof_list(false);
+}
index 1bd0cd1..0dcc2b6 100644 (file)
@@ -2,3 +2,4 @@
 void del_close_on_fork_list(int fd);
 void add_close_on_fork_list(int fd);
 void close_listed_fds(void);
+void deplete_close_on_fork_list(void);
index 6db0c00..6adb945 100644 (file)
--- a/command.c
+++ b/command.c
@@ -16,6 +16,7 @@
 #include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "sideband.h"
 #include "command.h"
@@ -48,11 +49,15 @@ static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS
 
 extern int mmd_mutex;
 extern struct misc_meta_data *mmd;
-extern struct sender senders[];
 int send_afs_status(struct command_context *cc, int parser_friendly);
+static bool subcmd_should_die;
 
-static void dummy(__a_unused int s)
+static void command_handler_sighandler(int s)
 {
+       if (s != SIGTERM)
+               return;
+       PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM);
+       subcmd_should_die = true;
 }
 
 /*
@@ -235,10 +240,10 @@ static int check_sender_args(struct command_context *cc,
                return ret;
        }
        arg = lls_input(0, lpr);
-       for (i = 0; senders[i].name; i++)
-               if (!strcmp(senders[i].name, arg))
+       FOR_EACH_SENDER(i)
+               if (strcmp(senders[i]->name, arg) == 0)
                        break;
-       if (!senders[i].name)
+       if (!senders[i])
                return -E_COMMAND_SYNTAX;
        scd->sender_num = i;
        arg = lls_input(1, lpr);
@@ -248,7 +253,7 @@ static int check_sender_args(struct command_context *cc,
        if (i == NUM_SENDER_CMDS)
                return -E_COMMAND_SYNTAX;
        scd->cmd_num = i;
-       if (!senders[scd->sender_num].client_cmds[scd->cmd_num])
+       if (!senders[scd->sender_num]->client_cmds[scd->cmd_num])
                return -E_SENDER_CMD;
        switch (scd->cmd_num) {
        case SENDER_on:
@@ -330,10 +335,10 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr)
        struct sender_command_data scd;
 
        if (lls_num_inputs(lpr) == 0) {
-               for (i = 0; senders[i].name; i++) {
+               FOR_EACH_SENDER(i) {
                        char *tmp;
                        ret = xasprintf(&tmp, "%s%s\n", msg? msg : "",
-                               senders[i].name);
+                               senders[i]->name);
                        free(msg);
                        msg = tmp;
                }
@@ -344,17 +349,17 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr)
                if (scd.sender_num < 0)
                        return ret;
                if (strcmp(lls_input(1, lpr), "status") == 0)
-                       msg = senders[scd.sender_num].status();
+                       msg = senders[scd.sender_num]->status();
                else
-                       msg = senders[scd.sender_num].help();
+                       msg = senders[scd.sender_num]->help();
                return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
        }
 
        switch (scd.cmd_num) {
        case SENDER_add:
        case SENDER_delete:
-               assert(senders[scd.sender_num].resolve_target);
-               ret = senders[scd.sender_num].resolve_target(lls_input(2, lpr),
+               assert(senders[scd.sender_num]->resolve_target);
+               ret = senders[scd.sender_num]->resolve_target(lls_input(2, lpr),
                        &scd);
                if (ret < 0)
                        return ret;
@@ -493,9 +498,21 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
        bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY,
                lpr) > 0;
        uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr);
+       const struct timespec ts = {.tv_sec = 50, .tv_nsec = 0};
 
-       para_sigaction(SIGUSR1, dummy);
+       para_sigaction(SIGINT, SIG_IGN);
+       para_sigaction(SIGUSR1, command_handler_sighandler);
+       para_sigaction(SIGTERM, command_handler_sighandler);
+       /*
+        * Simply checking subcmd_should_die is racy because a signal may
+        * arrive after the check but before the subsequent call to sleep(3).
+        * If this happens, sleep(3) would not be interrupted by the signal.
+        * To avoid this we block SIGTERM here and allow it to arrive only
+        * while we sleep.
+        */
+       para_block_signal(SIGTERM);
        for (;;) {
+               sigset_t set;
                /*
                 * Copy the mmd structure to minimize the time we hold the mmd
                 * lock.
@@ -518,7 +535,15 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
                ret = 1;
                if (num > 0 && !--num)
                        goto out;
-               sleep(50);
+               sigemptyset(&set); /* empty set means: unblock all signals */
+               /*
+                * pselect(2) allows to atomically unblock signals, then go to
+                * sleep. Calling sigprocmask(2) followed by sleep(3) would
+                * open a race window similar to the one described above.
+                */
+               pselect(1, NULL, NULL, NULL, &ts, &set);
+               if (subcmd_should_die)
+                       goto out;
                ret = -E_SERVER_CRASH;
                if (getppid() == 1)
                        goto out;
@@ -528,63 +553,35 @@ out:
 }
 EXPORT_SERVER_CMD_HANDLER(stat);
 
-/* fixed-length, human readable permission string */
-const char *server_cmd_perms_str(unsigned int perms)
+const char *aux_info_cb(unsigned cmd_num, bool verbose)
 {
-       static char result[5];
-
-       result[0] = perms & AFS_READ? 'a' : '-';
-       result[1] = perms & AFS_WRITE? 'A' : '-';
-       result[2] = perms & VSS_READ? 'v' : '-';
-       result[3] = perms & VSS_WRITE? 'V' : '-';
-       result[4] = '\0';
-       return result;
-}
+       static char result[80];
+       unsigned perms = server_command_perms[cmd_num];
 
-static int send_list_of_commands(struct command_context *cc)
-{
-       int i;
-       const struct lls_command *cmd;
-       char *msg = para_strdup("");
-
-       for (i = 1; (cmd = lls_cmd(i, server_cmd_suite)); i++) {
-               const char *perms = server_cmd_perms_str(server_command_perms[i]);
-               char *tmp = make_message("%s%s\t%s\t%s\n", msg,
-                       lls_command_name(cmd), perms, lls_purpose(cmd));
-               free(msg);
-               msg = tmp;
+       if (verbose) {
+               /* permissions: VSS_READ | VSS_WRITE */
+               sprintf(result, "permissions: %s",
+                       server_command_perms_txt[cmd_num]);
+       } else {
+               result[0] = perms & AFS_READ? 'a' : '-';
+               result[1] = perms & AFS_WRITE? 'A' : '-';
+               result[2] = perms & VSS_READ? 'v' : '-';
+               result[3] = perms & VSS_WRITE? 'V' : '-';
+               result[4] = '\0';
        }
-       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
+       return result;
 }
 
 static int com_help(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       const char *perms;
-       char *long_help, *buf, *errctx;
+       char *buf;
        int ret;
-       const struct lls_command *cmd;
+       unsigned n;
+       bool long_help = SERVER_CMD_OPT_GIVEN(HELP, LONG, lpr);
 
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0)
-               return send_list_of_commands(cc);
-       /* argument given for help */
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), server_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       cmd = lls_cmd(ret, server_cmd_suite);
-       perms = server_command_perms_txt[ret];
-       long_help = lls_long_help(cmd);
-       assert(long_help);
-       ret = xasprintf(&buf, "%spermissions: %s\n", long_help, perms);
-       free(long_help);
-       return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
+       lsu_com_help(long_help, lpr, server_cmd_suite, aux_info_cb, &buf, &n);
+       ret = send_sb(&cc->scc, buf, n, SBD_OUTPUT, false);
+       return ret;
 }
 EXPORT_SERVER_CMD_HANDLER(help);
 
@@ -758,7 +755,7 @@ struct connection_features {
        int dummy; /* none at the moment */
 };
 
-static int parse_auth_request(char *buf, int len, struct user **u,
+static int parse_auth_request(char *buf, int len, const struct user **u,
                struct connection_features *cf)
 {
        int ret;
@@ -792,7 +789,7 @@ static int parse_auth_request(char *buf, int len, struct user **u,
                }
        }
        PARA_DEBUG_LOG("received auth request for user %s\n", username);
-       *u = lookup_user(username);
+       *u = user_list_lookup(username);
        ret = 1;
 out:
        free_argv(features);
@@ -874,12 +871,14 @@ static int run_command(struct command_context *cc, struct iovec *iov)
  * the function if the connection was not authenticated when the timeout
  * expires.
  *
- * \sa alarm(2), \ref crypt.c, \ref crypt.h.
+ * \return Standard.
+ *
+ * \sa alarm(2), \ref openssl.c, \ref crypt.h.
  */
-__noreturn void handle_connect(int fd)
+int handle_connect(int fd)
 {
        int ret;
-       unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
+       unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
        unsigned char challenge_hash[HASH_SIZE];
        char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
@@ -909,7 +908,7 @@ __noreturn void handle_connect(int fd)
                goto net_err;
        if (cc->u) {
                get_random_bytes_or_die(rand_buf, sizeof(rand_buf));
-               ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
+               ret = apc_pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
                        (unsigned char *)buf);
                if (ret < 0)
                        goto net_err;
@@ -924,7 +923,7 @@ __noreturn void handle_connect(int fd)
                get_random_bytes_or_die((unsigned char *)buf, numbytes);
        }
        PARA_DEBUG_LOG("sending %d byte challenge + session key (%zu bytes)\n",
-               CHALLENGE_SIZE, numbytes);
+               APC_CHALLENGE_SIZE, numbytes);
        ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
        buf = NULL;
        if (ret < 0)
@@ -940,21 +939,21 @@ __noreturn void handle_connect(int fd)
        if (!cc->u)
                goto net_err;
        /*
-        * The correct response is the hash of the first CHALLENGE_SIZE bytes
+        * The correct response is the hash of the first APC_CHALLENGE_SIZE bytes
         * of the random data.
         */
        ret = -E_BAD_AUTH;
        if (numbytes != HASH_SIZE)
                goto net_err;
-       hash_function((char *)rand_buf, CHALLENGE_SIZE, challenge_hash);
+       hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
        if (memcmp(challenge_hash, buf, HASH_SIZE))
                goto net_err;
        /* auth successful */
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
        /* init stream cipher keys with the second part of the random buffer */
-       cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
-       cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
+       cc->scc.recv = sc_new(rand_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
+       cc->scc.send = sc_new(rand_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
                SESSION_KEY_LEN);
        ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
        if (ret < 0)
@@ -986,5 +985,5 @@ out:
        }
        sc_free(cc->scc.recv);
        sc_free(cc->scc.send);
-       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+       return ret;
 }
index 0265f05..a7fa467 100644 (file)
--- a/command.h
+++ b/command.h
@@ -3,7 +3,7 @@
 /** Per connection data available to command handlers. */
 struct command_context {
        /** The paraslash user that executes this command. */
-       struct user *u;
+       const struct user *u;
        /** File descriptor and crypto keys. */
        struct stream_cipher_context scc;
 };
index 499571a..053996d 100644 (file)
@@ -392,9 +392,10 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                wma_common
                sideband
                version
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
-               server_errlist_objs="$server_errlist_objs crypt"
+               server_errlist_objs="$server_errlist_objs openssl"
        else
                server_errlist_objs="$server_errlist_objs gcrypt"
        fi
@@ -420,6 +421,7 @@ if test -n "$CRYPTOLIB"; then
                net
                string
                fd
+               lsu
                sched
                stdin
                stdout
@@ -432,7 +434,7 @@ if test -n "$CRYPTOLIB"; then
                version
        "
        if test "$CRYPTOLIB" = openssl; then
-               client_errlist_objs="$client_errlist_objs crypt"
+               client_errlist_objs="$client_errlist_objs openssl"
        else
                client_errlist_objs="$client_errlist_objs gcrypt"
        fi
@@ -486,9 +488,10 @@ if test -n "$CRYPTOLIB"; then
                wmadec_filter
                buffer_tree
                sync_filter
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
-               audiod_errlist_objs="$audiod_errlist_objs crypt"
+               audiod_errlist_objs="$audiod_errlist_objs openssl"
        else
                audiod_errlist_objs="$audiod_errlist_objs gcrypt"
        fi
@@ -543,7 +546,7 @@ fi
 if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then
        build_mixer="yes"
        executables="$executables mixer"
-       mixer_errlist_objs="mixer exec string fd version"
+       mixer_errlist_objs="mixer exec string fd lsu version"
        if test $HAVE_OSS = yes; then
                mixer_errlist_objs="$mixer_errlist_objs oss_mix"
        fi
@@ -569,6 +572,7 @@ if test $HAVE_CURSES = yes; then
                fd
                gui
                gui_theme
+               lsu
                time
                sched
                version
@@ -593,6 +597,7 @@ filter_errlist_objs="
        amp_filter
        fecdec_filter
        fec
+       lsu
        version
        prebuffer_filter
        time
@@ -722,6 +727,7 @@ play_errlist_objs="
        file_write
        version
        sync_filter
+       lsu
 "
 NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common"
 NEED_VORBIS_OBJECTS && {
@@ -793,6 +799,7 @@ AC_SUBST(write_objs, add_dot_o($write_objs))
 audioc_errlist_objs="
        audioc
        string
+       lsu
        net
        fd
        version
diff --git a/crypt.c b/crypt.c
deleted file mode 100644 (file)
index b8a587c..0000000
--- a/crypt.c
+++ /dev/null
@@ -1,296 +0,0 @@
-/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file crypt.c Openssl-based encryption/decryption routines. */
-
-#include <regex.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <openssl/rand.h>
-#include <openssl/err.h>
-#include <openssl/pem.h>
-#include <openssl/sha.h>
-#include <openssl/bn.h>
-#include <openssl/aes.h>
-
-#include "para.h"
-#include "error.h"
-#include "string.h"
-#include "crypt.h"
-#include "fd.h"
-#include "crypt_backend.h"
-#include "base64.h"
-#include "portable_io.h"
-
-struct asymmetric_key {
-       RSA *rsa;
-};
-
-void get_random_bytes_or_die(unsigned char *buf, int num)
-{
-       unsigned long err;
-
-       /* RAND_bytes() returns 1 on success, 0 otherwise. */
-       if (RAND_bytes(buf, num) == 1)
-               return;
-       err = ERR_get_error();
-       PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err));
-       exit(EXIT_FAILURE);
-}
-
-/*
- * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG
- * used by random(3) with a random seed obtained from SSL. If /dev/urandom is
- * not readable, the function calls exit().
- *
- * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3),
- * random(3), \ref para_random().
- */
-void init_random_seed_or_die(void)
-{
-       int seed, ret = RAND_load_file("/dev/urandom", 64);
-
-       if (ret != 64) {
-               PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret);
-               exit(EXIT_FAILURE);
-       }
-       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
-       srandom(seed);
-}
-
-static int get_private_key(const char *path, RSA **rsa)
-{
-       EVP_PKEY *pkey;
-       BIO *bio = BIO_new(BIO_s_file());
-
-       *rsa = NULL;
-       if (!bio)
-               return -E_PRIVATE_KEY;
-       if (BIO_read_filename(bio, path) <= 0)
-               goto bio_free;
-       pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
-       if (!pkey)
-               goto bio_free;
-       *rsa = EVP_PKEY_get1_RSA(pkey);
-       EVP_PKEY_free(pkey);
-bio_free:
-       BIO_free(bio);
-       return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
-}
-
-/*
- * The public key loading functions below were inspired by corresponding code
- * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo,
- * Finland. However, not much of the original code remains.
- */
-
-static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
-{
-       const unsigned char *p = buf, *end = buf + len;
-       uint32_t bnsize;
-       BIGNUM *bn;
-
-       if (p + 4 < p)
-               return -E_BIGNUM;
-       if (p + 4 > end)
-               return -E_BIGNUM;
-       bnsize = read_u32_be(p);
-       PARA_DEBUG_LOG("bnsize: %u\n", bnsize);
-       p += 4;
-       if (p + bnsize < p)
-               return -E_BIGNUM;
-       if (p + bnsize > end)
-               return -E_BIGNUM;
-       if (bnsize > 8192)
-               return -E_BIGNUM;
-       bn = BN_bin2bn(p, bnsize, NULL);
-       if (!bn)
-               return -E_BIGNUM;
-       *result = bn;
-       return bnsize + 4;
-}
-
-static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
-{
-       int ret;
-       RSA *rsa;
-       BIGNUM *n, *e;
-       const unsigned char *p = blob, *end = blob + blen;
-
-       rsa = RSA_new();
-       if (!rsa)
-               return -E_BIGNUM;
-       ret = read_bignum(p, end - p, &e);
-       if (ret < 0)
-               goto fail;
-       p += ret;
-       ret = read_bignum(p, end - p, &n);
-       if (ret < 0)
-               goto fail;
-#ifdef HAVE_RSA_SET0_KEY
-       RSA_set0_key(rsa, n, e, NULL);
-#else
-       rsa->n = n;
-       rsa->e = e;
-#endif
-       *result = rsa;
-       return 1;
-fail:
-       RSA_free(rsa);
-       return ret;
-}
-
-int get_public_key(const char *key_file, struct asymmetric_key **result)
-{
-       struct asymmetric_key *key = NULL;
-       void *map = NULL;
-       unsigned char *blob = NULL;
-       size_t map_size, encoded_size, decoded_size;
-       int ret, ret2;
-       char *cp;
-
-       key = para_malloc(sizeof(*key));
-       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
-       if (ret < 0)
-               goto out;
-       ret = is_ssh_rsa_key(map, map_size);
-       if (!ret) {
-               ret = -E_SSH_PARSE;
-               goto out_unmap;
-       }
-       cp = map + ret;
-       encoded_size = map_size - ret;
-       PARA_INFO_LOG("decoding public rsa-ssh key %s\n", key_file);
-       ret = uudecode(cp, encoded_size, (char **)&blob, &decoded_size);
-       if (ret < 0)
-               goto out_unmap;
-       ret = check_ssh_key_header(blob, decoded_size);
-       if (ret < 0)
-               goto out_unmap;
-       ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
-       if (ret < 0)
-               goto out_unmap;
-       ret = RSA_size(key->rsa);
-out_unmap:
-       ret2 = para_munmap(map, map_size);
-       if (ret >= 0 && ret2 < 0)
-               ret = ret2;
-out:
-       if (ret < 0) {
-               free(key);
-               *result = NULL;
-               PARA_ERROR_LOG("key %s: %s\n", key_file, para_strerror(-ret));
-       } else
-               *result = key;
-       free(blob);
-       return ret;
-}
-
-void free_public_key(struct asymmetric_key *key)
-{
-       if (!key)
-               return;
-       RSA_free(key->rsa);
-       free(key);
-}
-
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
-               unsigned char *inbuf, int inlen)
-{
-       struct asymmetric_key *priv;
-       int ret;
-
-       ret = check_private_key_file(key_file);
-       if (ret < 0)
-               return ret;
-       if (inlen < 0)
-               return -E_RSA;
-       priv = para_malloc(sizeof(*priv));
-       ret = get_private_key(key_file, &priv->rsa);
-       if (ret < 0) {
-               free(priv);
-               return ret;
-       }
-       /*
-        * RSA is vulnerable to timing attacks. Generate a random blinding
-        * factor to protect against this kind of attack.
-        */
-       ret = -E_BLINDING;
-       if (RSA_blinding_on(priv->rsa, NULL) == 0)
-               goto out;
-       ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa,
-               RSA_PKCS1_OAEP_PADDING);
-       RSA_blinding_off(priv->rsa);
-       if (ret <= 0)
-               ret = -E_DECRYPT;
-out:
-       RSA_free(priv->rsa);
-       free(priv);
-       return ret;
-}
-
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
-               unsigned len, unsigned char *outbuf)
-{
-       int ret, flen = len; /* RSA_public_encrypt expects a signed int */
-
-       if (flen < 0)
-               return -E_ENCRYPT;
-       ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
-               RSA_PKCS1_OAEP_PADDING);
-       return ret < 0? -E_ENCRYPT : ret;
-}
-
-struct stream_cipher {
-       EVP_CIPHER_CTX *aes;
-};
-
-struct stream_cipher *sc_new(const unsigned char *data, int len)
-{
-       struct stream_cipher *sc = para_malloc(sizeof(*sc));
-
-       assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-       sc->aes = EVP_CIPHER_CTX_new();
-       EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data,
-               data + AES_CRT128_BLOCK_SIZE);
-       return sc;
-}
-
-void sc_free(struct stream_cipher *sc)
-{
-       if (!sc)
-               return;
-       EVP_CIPHER_CTX_free(sc->aes);
-       free(sc);
-}
-
-static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
-               struct iovec *dst)
-{
-       int ret, inlen = src->iov_len, outlen, tmplen;
-
-       *dst = (typeof(*dst)) {
-               /* Add one for the terminating zero byte. */
-               .iov_base = para_malloc(inlen + 1),
-               .iov_len = inlen
-       };
-       ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
-       assert(ret != 0);
-       ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen);
-       assert(ret != 0);
-       outlen += tmplen;
-       ((char *)dst->iov_base)[outlen] = '\0';
-       dst->iov_len = outlen;
-}
-
-void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
-{
-       return aes_ctr128_crypt(sc->aes, src, dst);
-}
-
-void hash_function(const char *data, unsigned long len, unsigned char *hash)
-{
-       SHA_CTX c;
-       SHA1_Init(&c);
-       SHA1_Update(&c, data, len);
-       SHA1_Final(hash, &c);
-}
diff --git a/crypt.h b/crypt.h
index 85623fb..8562959 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -2,15 +2,14 @@
 
 /** \file crypt.h Public crypto interface. */
 
+/*
+ * Asymmetric pubkey cryptosystem (apc).
+ *
+ * This is just RSA, but this fact is a hidden implementation detail.
+ */
 
-/* These are used to distinguish between loading of private/public key. */
-
-/** The key to load is a public key. */
-#define LOAD_PUBLIC_KEY 0
-/** The key to load is a private key. */
-#define LOAD_PRIVATE_KEY 1
 /** The size of the challenge sent to the client. */
-#define CHALLENGE_SIZE 64
+#define APC_CHALLENGE_SIZE 64
 
 /** Opaque structure for public and private keys. */
 struct asymmetric_key;
@@ -25,7 +24,7 @@ struct asymmetric_key;
  *
  * \return The size of the encrypted data on success, negative on errors.
  */
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf);
 
 /**
@@ -40,7 +39,7 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
  *
  * \return The size of the recovered plaintext on success, negative on errors.
  */
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen);
 
 /**
@@ -51,17 +50,17 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf,
  *
  * \return The size of the key on success, negative on errors.
  */
-int get_public_key(const char *key_file, struct asymmetric_key **result);
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result);
 
 /**
  * Deallocate a public key.
  *
  * \param key Pointer to the key structure to free.
  *
- * This should be called for keys obtained by get_public_key() if the key is no
+ * This should be called for keys obtained by \ref apc_get_pubkey() if the key is no
  * longer needed.
  */
-void free_public_key(struct asymmetric_key *key);
+void apc_free_pubkey(struct asymmetric_key *key);
 
 
 /**
@@ -78,17 +77,20 @@ void free_public_key(struct asymmetric_key *key);
 void get_random_bytes_or_die(unsigned char *buf, int num);
 
 /**
- * Seed pseudo random number generators.
+ * Initialize the crypto backend.
  *
- * This function seeds the PRNG used by random() with a random seed obtained
- * from the crypto implementation. On errors, an error message is logged and
- * the function calls exit().
+ * This function initializes the crypto library and seeds the pseudo random
+ * number generator used by random() with a random seed obtained from the
+ * crypto implementation. On errors, an error message is logged and the
+ * function calls exit().
  *
  * \sa \ref get_random_bytes_or_die(), srandom(3), random(3), \ref
  * para_random().
  */
-void init_random_seed_or_die(void);
+void crypt_init(void);
 
+/** Allocate all resources of the crypto backend. */
+void crypt_shutdown(void);
 
 /** Opaque structure for stream ciphers. */
 struct stream_cipher;
index ff956ce..175a688 100644 (file)
@@ -7,6 +7,6 @@
 /** AES block size in bytes. */
 #define AES_CRT128_BLOCK_SIZE 16
 
-size_t is_ssh_rsa_key(char *data, size_t size);
-int check_ssh_key_header(const unsigned char *blob, int blen);
+int decode_ssh_key(const char *filename, unsigned char **blob,
+               size_t *decoded_size);
 int check_private_key_file(const char *file);
index 08361b2..235b8b8 100644 (file)
 #include "crypt.h"
 #include "crypt_backend.h"
 #include "portable_io.h"
+#include "fd.h"
+#include "base64.h"
 
 /** If the key begins with this text, we treat it as an ssh key. */
 #define KEY_TYPE_TXT "ssh-rsa"
 
-/**
- * Check if given buffer starts with a ssh rsa key signature.
- *
- * \param data The buffer.
- * \param size Number of data bytes.
+/*
+ * Check if the given buffer starts with an ssh rsa key signature.
  *
- * \return Number of header bytes to be skipped on success, zero if
- * ssh rsa signature was not found.
+ * Returns number of header bytes to be skipped on success, zero if no ssh rsa
+ * signature was found.
  */
-size_t is_ssh_rsa_key(char *data, size_t size)
+static size_t is_ssh_rsa_key(char *data, size_t size)
 {
        char *cp;
 
@@ -42,20 +41,13 @@ size_t is_ssh_rsa_key(char *data, size_t size)
        return cp - data;
 }
 
-/**
- * Sanity checks for the header of an ssh key.
- *
- * \param blob The buffer.
- * \param blen The number of bytes of \a blob.
+/*
+ * Perform some sanity checks on the decoded ssh key.
  *
- * This performs some checks to make sure we really have an ssh key. It also
- * computes the offset in bytes of the start of the key values (modulus,
- * exponent..).
- *
- * \return The number of bytes to skip until the start of the first encoded
- * number (usually 11).
+ * This function returns the size of the header. Usually, the header is 11
+ * bytes long: four bytes for the length field, and the string "ssh-rsa".
  */
-int check_ssh_key_header(const unsigned char *blob, int blen)
+static int check_ssh_key_header(const unsigned char *blob, int blen)
 {
        const unsigned char *p = blob, *end = blob + blen;
        uint32_t rlen;
@@ -76,6 +68,51 @@ int check_ssh_key_header(const unsigned char *blob, int blen)
        return 4 + rlen;
 }
 
+/**
+ * Perform sanity checks and base64-decode an ssh-rsa key.
+ *
+ * \param filename The public key file (usually id_rsa.pub).
+ * \param blob Pointer to base64-decoded blob is returned here.
+ * \param decoded_size The size of the decoded blob.
+ *
+ * The memory pointed at by the returned blob pointer has to be freed by the
+ * caller.
+ *
+ * \return On success, the offset in bytes of the start of the key values
+ * (modulus, exponent..). This is the number of bytes to skip from the blob
+ * until the start of the first encoded number. On failure, a negative error
+ * code is returned.
+ *
+ * \sa \ref uudecode().
+ */
+int decode_ssh_key(const char *filename, unsigned char **blob,
+               size_t *decoded_size)
+{
+       int ret, ret2;
+       void *map;
+       size_t map_size;
+
+       ret = mmap_full_file(filename, O_RDONLY, &map, &map_size, NULL);
+       if (ret < 0)
+               return ret;
+       ret = is_ssh_rsa_key(map, map_size);
+       if (ret == 0) {
+               ret = -E_SSH_PARSE;
+               goto unmap;
+       }
+       ret = uudecode(map + ret, map_size - ret, (char **)blob, decoded_size);
+       if (ret < 0)
+               goto unmap;
+       ret = check_ssh_key_header(*blob, *decoded_size);
+       if (ret < 0)
+               goto unmap;
+unmap:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       return ret;
+}
+
 /**
  * Check existence and permissions of a private key file.
  *
index ddfe680..bfa8148 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -31,6 +31,12 @@ struct daemon {
        /** Used for colored log messages. */
        char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN];
        char *old_cwd;
+       /*
+        * If these pointers are non-NULL, the functions are called from
+        * daemon_log() before and after writing each log message.
+        */
+       void (*pre_log_hook)(void);
+       void (*post_log_hook)(void);
 };
 
 static struct daemon the_daemon, *me = &the_daemon;
@@ -140,6 +146,12 @@ void daemon_set_loglevel(const char *loglevel)
        me->loglevel = ret;
 }
 
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void))
+{
+       me->pre_log_hook = pre_log_hook;
+       me->post_log_hook = post_log_hook;
+}
+
 /**
  * Set one of the daemon config flags.
  *
@@ -409,6 +421,8 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
                return;
 
        fp = me->logfile? me->logfile : stderr;
+       if (me->pre_log_hook)
+               me->pre_log_hook();
        color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
        if (color)
                fprintf(fp, "%s", color);
@@ -441,4 +455,6 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
        va_end(argp);
        if (color)
                fprintf(fp, "%s", COLOR_RESET);
+       if (me->post_log_hook)
+               me->post_log_hook();
 }
index 989678d..b530b0d 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -11,6 +11,7 @@ void daemon_set_start_time(void);
 time_t daemon_get_uptime(const struct timeval *current_time);
 __malloc char *daemon_get_uptime_str(const struct timeval *current_time);
 void daemon_set_logfile(const char *logfile_name);
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void));
 void daemon_set_flag(unsigned flag);
 void daemon_set_loglevel(const char *loglevel);
 bool daemon_init_colors_or_die(int color_arg, int color_arg_auto,
index 69ba65f..496895a 100644 (file)
@@ -28,9 +28,6 @@
 #include "sched.h"
 #include "vss.h"
 #include "fd.h"
-#include "close_on_fork.h"
-#include "chunk_queue.h"
-#include "acl.h"
 
 static struct sender_status dccp_sender_status, *dss = &dccp_sender_status;
 
@@ -42,8 +39,11 @@ struct dccp_fec_client {
 static void dccp_pre_select(int *max_fileno, fd_set *rfds,
                __a_unused fd_set *wfds)
 {
-       if (dss->listen_fd >= 0)
-               para_fd_set(dss->listen_fd, rfds, max_fileno);
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, dss)
+               if (dss->listen_fds[n] >= 0)
+                       para_fd_set(dss->listen_fds[n], rfds, max_fileno);
 }
 
 /**
@@ -83,6 +83,12 @@ static void dccp_shutdown_clients(void)
                dccp_shutdown_client(sc);
 }
 
+static void dccp_shutdown(void)
+{
+       dccp_shutdown_clients();
+       generic_acl_deplete(&dss->acl);
+}
+
 /** * Obtain current MPS according to RFC 4340, sec. 14. */
 static int dccp_init_fec(struct sender_client *sc)
 {
@@ -215,32 +221,40 @@ static char *dccp_status(void)
        return result;
 }
 
-/**
- * The init function of the dccp sender.
- *
- * \param s pointer to the dccp sender struct.
- *
- * It initializes all function pointers of \a s and starts
- * listening on the given port.
+/*
+ * Initialize the client list and the access control list and listen on the
+ * dccp port.
  */
-void dccp_send_init(struct sender *s)
+static void dccp_send_init(void)
 {
-       s->status = dccp_status;
-       s->send = NULL;
-       s->pre_select = dccp_pre_select;
-       s->post_select = dccp_post_select;
-       s->shutdown_clients = dccp_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = dccp_com_on;
-       s->client_cmds[SENDER_off] = dccp_com_off;
-       s->client_cmds[SENDER_deny] = dccp_com_deny;
-       s->client_cmds[SENDER_allow] = dccp_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
        init_sender_status(dss, OPT_RESULT(DCCP_ACCESS),
+               OPT_RESULT(DCCP_LISTEN_ADDRESS),
                OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
                OPT_GIVEN(DCCP_DEFAULT_DENY));
        generic_com_on(dss, IPPROTO_DCCP);
 }
+
+/**
+ * The DCCP sender.
+ *
+ * This sender offers congestion control not available in plain TCP. Most
+ * methods of the sender structure are implemented as simple wrappers for the
+ * generic functions defined in \ref send_common.c. Like UDP streams, DCCP
+ * streams are sent FEC-encoded.
+ */
+const struct sender dccp_sender = {
+       .name = "dccp",
+       .init = dccp_send_init,
+       .shutdown = dccp_shutdown,
+       .pre_select = dccp_pre_select,
+       .post_select = dccp_post_select,
+       .shutdown_clients = dccp_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = dccp_com_on,
+               [SENDER_off] = dccp_com_off,
+               [SENDER_deny] = dccp_com_deny,
+               [SENDER_allow] = dccp_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = dccp_status,
+};
diff --git a/error.h b/error.h
index 1904a6d..02f4224 100644 (file)
--- a/error.h
+++ b/error.h
@@ -70,6 +70,7 @@
        PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \
        PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \
        PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \
+       PARA_ERROR(CHILD_CONTEXT, "now running in child context"), \
        PARA_ERROR(CHMOD, "failed to set socket mode"), \
        PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \
        PARA_ERROR(CLIENT_WRITE, "client write error"), \
        PARA_ERROR(TARGET_EXISTS, "requested target is already present"),\
        PARA_ERROR(TARGET_NOT_FOUND, "requested target not found"), \
        PARA_ERROR(TASK_STARTED, "task started"), \
+       PARA_ERROR(DEADLY_SIGNAL, "termination request by signal"), \
        PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \
        PARA_ERROR(UCRED_PERM, "permission denied"), \
        PARA_ERROR(UDP_OVERRUN, "output buffer overrun"), \
diff --git a/fd.c b/fd.c
index 0347fd8..33891d2 100644 (file)
--- a/fd.c
+++ b/fd.c
@@ -419,34 +419,6 @@ void para_fd_set(int fd, fd_set *fds, int *max_fileno)
        *max_fileno = PARA_MAX(*max_fileno, fd);
 }
 
-/**
- * Paraslash's wrapper for fgets(3).
- *
- * \param line Pointer to the buffer to store the line.
- * \param size The size of the buffer given by \a line.
- * \param f The stream to read from.
- *
- * \return Unlike the standard fgets() function, an integer value
- * is returned. On success, this function returns 1. On errors, -E_FGETS
- * is returned. A zero return value indicates an end of file condition.
- */
-__must_check int para_fgets(char *line, int size, FILE *f)
-{
-again:
-       if (fgets(line, size, f))
-               return 1;
-       if (feof(f))
-               return 0;
-       if (!ferror(f))
-               return -E_FGETS;
-       if (errno != EINTR) {
-               PARA_ERROR_LOG("%s\n", strerror(errno));
-               return -E_FGETS;
-       }
-       clearerr(f);
-       goto again;
-}
-
 /**
  * Paraslash's wrapper for mmap.
  *
diff --git a/fd.h b/fd.h
index 25eea8a..c9e7942 100644 (file)
--- a/fd.h
+++ b/fd.h
@@ -11,7 +11,6 @@ int para_select(int n, fd_set *readfds, fd_set *writefds,
 __must_check int mark_fd_nonblocking(int fd);
 __must_check int mark_fd_blocking(int fd);
 void para_fd_set(int fd, fd_set *fds, int *max_fileno);
-__must_check int para_fgets(char *line, int size, FILE *f);
 int para_mmap(size_t length, int prot, int flags, int fd, void *map);
 int para_open(const char *path, int flags, mode_t mode);
 int para_mkdir(const char *path, mode_t mode);
index dfdadab..d4a2423 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -9,6 +9,7 @@
 #include "filter_cmd.lsg.h"
 #include "para.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
 #include "buffer_tree.h"
 #include "filter.h"
@@ -71,50 +72,15 @@ static void handle_help_flag(void)
 
 static int parse_config(void)
 {
-       char *home, *cf; /* config file */
-       struct stat statbuf;
        int ret;
 
        version_handle_flag("filter", OPT_GIVEN(VERSION));
        handle_help_flag();
-       home = para_homedir();
-       cf = make_message("%s/.paraslash/filter.conf", home);
-       free(home);
-       if (!stat(cf, &statbuf)) {
-               void *map;
-               size_t sz;
-               int cf_argc;
-               char **cf_argv, *errctx;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-
-               ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-               if (ret != -E_EMPTY) {
-                       if (ret < 0)
-                               return ret;
-                       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv,
-                               &errctx));
-                       para_munmap(map, sz);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("syntax error in %s\n", cf);
-                               return ret;
-                       }
-                       cf_argc = ret;
-                       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr,
-                               &errctx));
-                       lls_free_argv(cf_argv);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("parse error in %s\n", cf);
-                               return ret;
-                       }
-                       ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr, &errctx));
-                       lls_free_parse_result(cf_lpr, CMD_PTR);
-                       if (ret < 0)
-                               return ret;
-                       lls_free_parse_result(lpr, CMD_PTR);
-                       lpr = merged_lpr;
-                       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-               }
-       }
+       ret = lsu_merge_config_file_options(NULL, "filter.conf",
+               &lpr, CMD_PTR, filter_suite, 0 /* default flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        if (!OPT_GIVEN(FILTER)) {
                print_filter_list();
                exit(EXIT_SUCCESS);
@@ -147,7 +113,6 @@ int main(int argc, char *argv[])
        ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto out;
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        ret = parse_config();
        if (ret < 0)
                goto free_lpr;
index 052546d..ff4dab3 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -56,7 +56,7 @@ void get_random_bytes_or_die(unsigned char *buf, int num)
  * call to gcry_check_version() initializes the gcrypt library and checks that
  * we have at least the minimal required version.
  */
-void init_random_seed_or_die(void)
+void crypt_init(void)
 {
        const char *req_ver = "1.5.0";
        int seed;
@@ -66,10 +66,29 @@ void init_random_seed_or_die(void)
                        req_ver, gcry_check_version(NULL));
                exit(EXIT_FAILURE);
        }
+
+       /*
+        * Allocate a pool of secure memory. This also drops privileges where
+        * needed.
+        */
+       gcry_control(GCRYCTL_INIT_SECMEM, 65536, 0);
+
+       /* Tell Libgcrypt that initialization has completed. */
+       gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+
        get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
        srandom(seed);
 }
 
+void crypt_shutdown(void)
+{
+       /*
+        * WK does not see a way to apply a patch for the sake of Valgrind, so
+        * as of 2018 libgrypt has no deinitialization routine to free the
+        * resources on exit.
+        */
+}
+
 /** S-expression for the public part of an RSA key. */
 #define RSA_PUBKEY_SEXP "(public-key (rsa (n %m) (e %m)))"
 /** S-expression for a private RSA key. */
@@ -366,27 +385,21 @@ free_blob:
        return ret;
 }
 
-static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result)
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
 {
+       unsigned char *blob, *p, *end;
        int ret;
        gcry_error_t gret;
-       unsigned char *blob = NULL, *p, *end;
        size_t nr_scanned, erroff, decoded_size;
-       gcry_mpi_t e = NULL, n = NULL;
+       gcry_mpi_t e, n;
+       gcry_sexp_t sexp;
+       struct asymmetric_key *key;
 
-       PARA_DEBUG_LOG("decoding %d byte public rsa-ssh key\n", size);
-       ret = uudecode((char *)data, size, (char **)&blob, &decoded_size);
+       ret = decode_ssh_key(key_file, &blob, &decoded_size);
        if (ret < 0)
-               goto free_blob;
-       end = blob + decoded_size;
-       dump_buffer("decoded key", blob, decoded_size);
-       ret = check_ssh_key_header(blob, decoded_size);
-       if (ret < 0)
-               goto free_blob;
+               return ret;
        p = blob + ret;
-       ret = -E_SSH_PARSE;
-       if (p >= end)
-               goto free_blob;
+       end = blob + decoded_size;
        PARA_DEBUG_LOG("scanning modulus and public exponent\n");
        gret = gcry_mpi_scan(&e, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
        if (gret) {
@@ -395,8 +408,6 @@ static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result
                goto free_blob;
        }
        PARA_DEBUG_LOG("scanned e (%zu bytes)\n", nr_scanned);
-//     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_e);
-//     PARA_CRIT_LOG("e: %s\n", buf);
        p += nr_scanned;
        if (p >= end)
                goto release_e;
@@ -407,17 +418,19 @@ static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result
                goto release_e;
        }
        PARA_DEBUG_LOG("scanned n (%zu bytes)\n", nr_scanned);
-//     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_n);
-//     PARA_CRIT_LOG("n: %s\n", buf);
-       gret = gcry_sexp_build(result, &erroff, RSA_PUBKEY_SEXP, n, e);
+       gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e);
        if (gret) {
                PARA_ERROR_LOG("offset %zu: %s\n", erroff,
                        gcry_strerror(gcry_err_code(gret)));
                ret = -E_SEXP_BUILD;
                goto release_n;
        }
-       ret = nr_scanned / 32 * 32;
+       ret = ROUND_DOWN(nr_scanned, 32);
        PARA_INFO_LOG("successfully read %d bit ssh public key\n", ret * 8);
+       key = para_malloc(sizeof(*key));
+       key->num_bytes = ret;
+       key->sexp = sexp;
+       *result = key;
 release_n:
        gcry_mpi_release(n);
 release_e:
@@ -427,43 +440,7 @@ free_blob:
        return ret;
 }
 
-int get_public_key(const char *key_file, struct asymmetric_key **result)
-{
-       int ret, ret2;
-       void *map;
-       size_t map_size;
-       unsigned char *start, *end;
-       gcry_sexp_t sexp;
-       struct asymmetric_key *key;
-
-       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
-       if (ret < 0)
-               return ret;
-       ret = is_ssh_rsa_key(map, map_size);
-       if (!ret) {
-               para_munmap(map, map_size);
-               return -E_SSH_PARSE;
-       }
-       start = map + ret;
-       end = map + map_size;
-       ret = -E_SSH_PARSE;
-       if (start >= end)
-               goto unmap;
-       ret = get_ssh_public_key(start, end - start, &sexp);
-       if (ret < 0)
-               goto unmap;
-       key = para_malloc(sizeof(*key));
-       key->num_bytes = ret;
-       key->sexp = sexp;
-       *result = key;
-unmap:
-       ret2 = para_munmap(map, map_size);
-       if (ret >= 0 && ret2 < 0)
-               ret = ret2;
-       return ret;
-}
-
-void free_public_key(struct asymmetric_key *key)
+void apc_free_pubkey(struct asymmetric_key *key)
 {
        if (!key)
                return;
@@ -481,7 +458,7 @@ static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes)
        return 1;
 }
 
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen)
 {
        gcry_error_t gret;
@@ -548,7 +525,7 @@ free_key:
        return ret;
 }
 
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf)
 {
        gcry_error_t gret;
diff --git a/gui.c b/gui.c
index 69a9243..a4f1717 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -13,6 +13,7 @@
 #include "gui.lsg.h"
 #include "para.h"
 #include "gui.h"
+#include "lsu.h"
 #include "string.h"
 #include "ringbuffer.h"
 #include "fd.h"
@@ -857,64 +858,23 @@ static void check_key_map_args_or_die(void)
 static void parse_config_file_or_die(bool reload)
 {
        int ret;
-       char *cf = NULL, *errctx = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/gui.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-               ret = 0;
-               lpr = cmdline_lpr;
-               goto success;
-       }
-       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-       para_munmap(map, sz);
-       if (ret < 0)
-               goto free_cf;
-       cf_argc = ret;
-       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
+       unsigned flags = MCF_DONT_FREE;
+
        if (lpr != cmdline_lpr)
                lls_free_parse_result(lpr, CMD_PTR);
-       lpr = merged_lpr;
-success:
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-       check_key_map_args_or_die();
-       theme_init(OPT_STRING_VAL(THEME), &theme);
-free_cf:
-       free(cf);
+       lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "gui.conf", &lpr, CMD_PTR, gui_suite, flags);
        if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       check_key_map_args_or_die();
+       theme_init(OPT_STRING_VAL(THEME), &theme);
 }
 
 /* Reread configuration, terminate on errors. */
index 210f85a..330b45a 100644 (file)
@@ -26,7 +26,6 @@
 #include "close_on_fork.h"
 #include "fd.h"
 #include "chunk_queue.h"
-#include "acl.h"
 
 /** Message sent to clients that do not send a valid get request. */
 #define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
@@ -77,6 +76,12 @@ static void http_shutdown_clients(void)
        shutdown_clients(hss);
 }
 
+static void http_shutdown(void)
+{
+       http_shutdown_clients();
+       generic_acl_deplete(&hss->acl);
+}
+
 static int queue_chunk_or_shutdown(struct sender_client *sc,
                struct sender_status *ss, const char *buf, size_t num_bytes)
 {
@@ -193,10 +198,13 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
 static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
 {
        struct sender_client *sc, *tmp;
+       unsigned n;
 
-       if (hss->listen_fd < 0)
-               return;
-       para_fd_set(hss->listen_fd, rfds, max_fileno);
+       FOR_EACH_LISTEN_FD(n, hss) {
+               if (hss->listen_fds[n] < 0)
+                       continue;
+               para_fd_set(hss->listen_fds[n], rfds, max_fileno);
+       }
        list_for_each_entry_safe(sc, tmp, &hss->client_list, node) {
                struct private_http_sender_data *phsd = sc->private_data;
                if (phsd->status == HTTP_CONNECTED) /* need to recv get request */
@@ -236,34 +244,45 @@ static char *http_status(void)
        return generic_sender_status(hss, "http");
 }
 
-/**
- * The init function of the http sender.
- *
- * \param s Pointer to the http sender struct.
- *
- * It initializes all function pointers of \a s, the client list and the access
- * control list. If the autostart option was given, the tcp port is opened.
+/*
+ * Initialize the client list and the access control list, and optionally
+ * listen on the tcp port.
  */
-void http_send_init(struct sender *s)
+static void http_send_init(void)
 {
-       s->status = http_status;
-       s->send = http_send;
-       s->pre_select = http_pre_select;
-       s->post_select = http_post_select;
-       s->shutdown_clients = http_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = http_com_on;
-       s->client_cmds[SENDER_off] = http_com_off;
-       s->client_cmds[SENDER_deny] = http_com_deny;
-       s->client_cmds[SENDER_allow] = http_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
        init_sender_status(hss, OPT_RESULT(HTTP_ACCESS),
+               OPT_RESULT(HTTP_LISTEN_ADDRESS),
                OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS),
                OPT_GIVEN(HTTP_DEFAULT_DENY));
        if (OPT_GIVEN(HTTP_NO_AUTOSTART))
                return;
        generic_com_on(hss, IPPROTO_TCP);
 }
+
+/**
+ * The HTTP sender.
+ *
+ * This is the only sender which does not FEC-encode the stream. This is not
+ * necessary because HTTP sits on top of TCP, a reliable transport which
+ * retransmits lost packets automatically. The sender employs per-client queues
+ * which queue chunks of audio data if they can not be sent immediately because
+ * the write operation would block. Most methods of the sender are implemented
+ * as wrappers for the generic functions defined in \ref send_common.c.
+ */
+const struct sender http_sender = {
+       .name = "http",
+       .init = http_send_init,
+       .shutdown = http_shutdown,
+       .pre_select = http_pre_select,
+       .post_select = http_post_select,
+       .send = http_send,
+       .shutdown_clients = http_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = http_com_on,
+               [SENDER_off] = http_com_off,
+               [SENDER_deny] = http_com_deny,
+               [SENDER_allow] = http_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = http_status,
+};
diff --git a/lsu.c b/lsu.c
new file mode 100644 (file)
index 0000000..8ccf80d
--- /dev/null
+++ b/lsu.c
@@ -0,0 +1,235 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.c Utilities related to the lopsub library. */
+
+#include <lopsub.h>
+#include <regex.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "lsu.h"
+#include "fd.h"
+
+static int lsu_lopsub_error(int ret, char **errctx, char **result, unsigned *num_chars)
+{
+       const char *se = para_strerror(-ret);
+       unsigned n;
+
+       if (*errctx)
+               n = xasprintf(result, "%s: %s\n", *errctx, se);
+       else
+               n = xasprintf(result, "lopsub error: %s\n", se);
+       free(*errctx);
+       *errctx = NULL;
+       if (num_chars)
+               *num_chars = n;
+       return ret;
+}
+
+static void lsu_get_subcommand_summary(bool long_summary,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int i;
+       const struct lls_command *cmd;
+       const char *name, *aux_info = NULL;
+       struct para_buffer pb = {.max_size = 0 /* unlimited */};
+
+       para_printf(&pb, "Available subcommands:\n");
+       if (long_summary) {
+               int maxname = 0, max_aux_info = 0;
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       maxname = PARA_MAX(maxname,
+                               (int)strlen(lls_command_name(cmd)));
+                       if (aux_info_cb) {
+                               aux_info = aux_info_cb(i, false);
+                               if (!aux_info)
+                                       continue;
+                               max_aux_info = PARA_MAX(max_aux_info,
+                                       (int)strlen(aux_info));
+                       }
+               }
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       if (aux_info_cb)
+                               aux_info = aux_info_cb(i, false);
+                       para_printf(&pb, "%-*s %-*s %s\n", maxname,
+                               lls_command_name(cmd), max_aux_info, aux_info?
+                               aux_info : "", lls_purpose(cmd));
+               }
+       } else {
+               unsigned n = 8;
+               para_printf(&pb, "\t");
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       name = lls_command_name(cmd);
+                       if (i > 1)
+                               n += para_printf(&pb, ", ");
+                       if (n > 70) {
+                               para_printf(&pb, "\n\t");
+                               n = 8;
+                       }
+                       n += para_printf(&pb, "%s", name);
+               }
+               para_printf(&pb, "\n");
+       }
+       *result = pb.buf;
+       if (num_chars)
+               *num_chars = pb.offset;
+}
+
+/**
+ * A generic implementation of the help subcommand.
+ *
+ * This function returns the help text for the given subcommand, or the list of
+ * all subcommands if no non-option argument is given. The function is generic
+ * in that it works for arbitrary lopsub suites.
+ *
+ * \param long_help Applies to both command list and command help.
+ * \param suite The supercommand, if any, is omitted.
+ * \param lpr Used to determine whether a non-option argument is given.
+ * \param aux_info_cb Optional callback, may return NULL, static memory.
+ * \param result Must be freed by the caller.
+ * \param num_chars Initialized to the length of the returned string, optional.
+ *
+ * If the optional aux_info_cb function pointer is not NULL, the callback
+ * function must return the string representation of the aux_info structure of
+ * the given command, or NULL to indicate that this command has no aux info
+ * structure.
+ *
+ * The function fails if lpr has more than one non-option argument, or if there
+ * is exactly one non-option argument, but this argument is not the name of a
+ * subcommand in the given lopsub suite.
+ *
+ * \return Standard. In the failure case a suitable error message is returned
+ * via the result pointer and num_chars is set accordingly.
+ */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int ret;
+       unsigned n;
+       char *errctx, *tmp;
+       const char *arg, *aux_info = NULL;
+       const struct lls_command *cmd;
+
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       if (lls_num_inputs(lpr) == 0) {
+               lsu_get_subcommand_summary(long_help, suite,
+                       aux_info_cb, result, num_chars);
+               return 0;
+       }
+       arg = lls_input(0, lpr);
+       ret = lls(lls_lookup_subcmd(arg, suite, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       cmd = lls_cmd(ret, suite);
+       tmp = long_help? lls_long_help(cmd) : lls_short_help(cmd);
+       if (aux_info_cb)
+               aux_info = aux_info_cb(ret, true);
+       n = xasprintf(result, "%s%s%s", tmp, aux_info? aux_info : "",
+               aux_info? "\n" : "");
+       free(tmp);
+       if (num_chars)
+               *num_chars = n;
+       return 1;
+}
+
+/**
+ * Merge command line options and config file options.
+ *
+ * This function parses the options stored in the configuration file and merges
+ * them with the currently effective options. If the application supports
+ * config files, it is supposed to call this after the command line options
+ * have been parsed. If the application also supports config file reloading,
+ * the function will be called for that purpose.
+ *
+ * \param path Config file path, usually the argument to --config-file.
+ * \param dflt Relative to ~/.paraslash, ignored if path is not NULL.
+ * \param lpr Value-result pointer.
+ * \param cmd Passed to lls_parse() and lls_merge().
+ * \param suite Needed to tell whether cmd is the supercommand.
+ * \param flags See enum \ref lsu_merge_cf_flags.
+ *
+ * The function does nothing if path is NULL and the default config file does
+ * not exist, or if path is an empty file. Otherwise, the options of the config
+ * file are parsed, the parse result is merged with lpr, and the merged parse
+ * result is returned via lpr.
+ *
+ * By default, lpr is freed if the merge was done, but this can be changed by
+ * including MCF_DONT_FREE flags.
+ *
+ * \return Zero if there was nothing to do, one if the config file options were
+ * merged successfully, negative error code on failure. It is considered an error
+ * if path is given, but the file does not exist.
+ */
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags)
+{
+       int ret;
+       void *map;
+       size_t sz;
+       int cf_argc;
+       char *cf, **cf_argv, *errctx = NULL;
+       struct lls_parse_result *old_lpr = *lpr, *cf_lpr, *merged_lpr;
+       const char *subcmd_name;
+
+       if (path)
+               cf = para_strdup(path);
+       else {
+               char *home = para_homedir();
+               cf = make_message("%s/.paraslash/%s", home, dflt);
+               free(home);
+       }
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret == -E_EMPTY)
+                       ret = 0;
+               else if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && !path)
+                       ret = 0;
+               else
+                       PARA_ERROR_LOG("failed to mmap config file %s\n", cf);
+               goto free_cf;
+       }
+       subcmd_name = (lls_cmd(0, suite) == cmd)? NULL : lls_command_name(cmd);
+       ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
+       para_munmap(map, sz);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to convert config file %s\n", cf);
+               goto lopsub_error;
+       }
+       cf_argc = ret;
+       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+       lls_free_argv(cf_argv);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to parse config file %s\n", cf);
+               goto lopsub_error;
+       }
+       if (flags & MCF_OVERRIDE)
+               ret = lls(lls_merge(cf_lpr, old_lpr, cmd, &merged_lpr, &errctx));
+       else
+               ret = lls(lls_merge(old_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
+       lls_free_parse_result(cf_lpr, cmd);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not merge options in %s\n", cf);
+               goto lopsub_error;
+       }
+       if (!(flags & MCF_DONT_FREE))
+               lls_free_parse_result(old_lpr, cmd);
+       *lpr = merged_lpr;
+       ret = 1;
+       goto free_cf;
+lopsub_error:
+       assert(ret < 0);
+       if (errctx)
+               PARA_ERROR_LOG("lopsub error: %s\n", errctx);
+       free(errctx);
+free_cf:
+       free(cf);
+       return ret;
+}
diff --git a/lsu.h b/lsu.h
new file mode 100644 (file)
index 0000000..6141dfc
--- /dev/null
+++ b/lsu.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.h Lopsub Utilities, enums and public functions. */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars);
+
+/** Flags for \ref lsu_merge_config_file_options(). */
+enum lsu_merge_cf_flags {
+       /**
+        * Whether the options specified in the configuration file should
+        * override the currently effective options. At application startup
+        * this is usually unset so that command line options take precedence
+        * over config file options. However, if the application supports
+        * re-reading the configuration, it can make sense to enable this flag.
+        */
+       MCF_OVERRIDE = 1,
+       /**
+        * After the two lopsub parse results have been merged, the merged
+        * parse result usually becomes the effective configuration and the
+        * parse result which corresponds to the former effective options is no
+        * longer needed. Therefore \ref lsu_merge_config_file_options() frees
+        * this former parse result by default. This flag instructs the
+        * function to keep it. This is mostly useful if the application
+        * supports re-reading the config file so that the parse result which
+        * corresponds to the command line options is kept for future calls to
+        * \ref lsu_merge_config_file_options().
+        */
+       MCF_DONT_FREE = 2,
+};
+
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags);
index 1c037bc..6ad8c71 100644 (file)
@@ -6,9 +6,9 @@ version-string = GIT_VERSION()
        purpose = communicate with para_audiod through a local socket
        non-opts-name = [command [options]]
        [description]
-               The client program to control para_audiod at runtime. It allows to
-               enable/disable streaming, to receive status info, or to grab the
-               audio stream at any point of the decoding process.
+               The client program to control para_audiod at runtime. It can
+               enable/disable streaming, receive status info, or grab the audio
+               stream at any point of the decoding process.
 
                If no command is given, para_audioc enters interactive mode.
        [/description]
index 11de987..80ae2e4 100644 (file)
@@ -7,14 +7,14 @@ caption = list of audiod commands
        [/description]
 
 [subcommand help]
-       purpose = display command list or help for given command
+       purpose = list available commands or print command-specific help
        non-opts-name = [subcommand]
        [description]
-               When I was younger, so much younger than today, I never needed
-               anybody's help in any way. But now these days are gone, I'm not so self
-               assured. Now I find I've changed my mind and opened up the doors. --
-               Beatles: Help
+               When executed without any arguments, the available audiod commands
+               are listed. Otherwise, if the first argument is the name of an audiod
+               command, the description of this command is shown.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand grab]
        purpose = grab the audio stream
index 0cd69eb..376e875 100644 (file)
@@ -4,9 +4,9 @@ version-string = GIT_VERSION()
 [supercommand para_filter]
        purpose = decode or process audio data from STDIN to STDOUT
        [description]
-               This program allows to specify a chain of filters which transform the
-               audio stream read from STDIN. A common mode of operation is to decode
-               an mp3 file with the mp3dec filter, but many other filters exist which
+               This program transforms the audio stream read from STDIN by chaining
+               one or more filters. A common mode of operation is to decode an
+               mp3 file with the mp3dec filter, but many other filters exist which
                transform the audio stream in different ways.
        [/description]
        m4_include(common-option-section.m4)
@@ -35,3 +35,15 @@ version-string = GIT_VERSION()
                        through all given filters (in a single thread without copying the
                        data). The same filter may appear more than once, and order matters.
                [/help]
+[section Examples]
+       .IP \(bu 4
+       Decode a wma file to wav format:
+       .EX
+       \       para_filter -f wmadec -f wav < file.wma > file.wav
+       .EE
+       .IP \(bu 4
+       Amplify a raw audio file by a factor of 1.5:
+       .EX
+       \       para_filter -f amp --amp 32 < foo.raw > bar.raw
+       .EE
+[/section]
index a7364e1..ba19be9 100644 (file)
@@ -3,7 +3,7 @@
        summary = show milliseconds in log messages
        [help]
                Selecting this option causes milliseconds to be included in
-               the log message output. This allows to measure the interval
+               the log message output. This allows measuring of the interval
                between log messages in milliseconds which is useful for
                identifying timing problems.
        [/help]
diff --git a/m4/lls/include/long-help.m4 b/m4/lls/include/long-help.m4
new file mode 100644 (file)
index 0000000..408f8e1
--- /dev/null
@@ -0,0 +1,15 @@
+[option long]
+       short_opt = l
+       summary = show the long help text
+[help]
+       If no non-option argument is supplied to the help subcommand and --long
+       is not given, only the names of all subcommands are shown. With --long
+       the purpose of each command is printed as well.
+
+       If the name of a subcommand is supplied and --long is given, the help
+       text for the given subcommand contains the synopsis, the purpose and
+       the description of the specified command, followed by the option list
+       including summary and help text of each option. Without --long the
+       description of the command and the option help are omitted.
+[/help]
+
index 0fbba0c..4af2a05 100644 (file)
@@ -7,8 +7,8 @@ version-string = GIT_VERSION()
        purpose = command line audio player
        non-opts-name = <audio_file>...
        [description]
-               para_play operates either in command mode or in insert mode. In
-               insert mode it presents a prompt and allows to enter commands like
+               para_play operates either in command mode or in insert mode. In insert
+               mode it presents a prompt and allows the user to enter commands like
                stop, play, pause etc. In command mode the current audio file and the
                playback position are shown and the program reads single key strokes
                from stdin. Keys may be mapped to commands so that the configured
index 195b47b..36d475d 100644 (file)
@@ -13,6 +13,7 @@ caption = list of commands
                list of available commands. When called with the name of a command
                as first argument, it prints the description of this command.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand fg]
        purpose = enter command mode
index 5bba85d..be8f02f 100644 (file)
@@ -34,18 +34,45 @@ version-string = GIT_VERSION()
        m4_include(log-timing.m4)
        m4_include(color.m4)
        m4_include(per-command-options-section.m4)
+       [option listen-address]
+               summary = local listening addresses for the control service
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       para_server listens on a TCP socket for incoming connections from
+                       para_client or para_audiod. This option controls on which addresses
+                       the server should listen. If the option is not given, the server
+                       listens on all local addresses (INADDR_ANY for IPv4 addresses,
+                       IN6ADDR_ANY_INIT for IPv6 addresses).
+
+                       The argument specifies an IPv4 or an IPv6 address, either a numerical
+                       network address (for IPv4, numbers-and-dots notation as supported
+                       by inet_aton(3); for IPv6, hexadecimal string format as supported
+                       by inet_pton(3)), or a network hostname, whose network addresses is
+                       looked up and resolved. The address can optionally include a port
+                       number. For addresses for which no port number is given, the argument
+                       of the --port option (see below) is implied.
+
+                       This option may be given multiple times. The server will then listen
+                       on each of the specified addresses.
+
+                       Examples: 10.10.1.1, 10.10.1.2:2991, localhost, localhost:2991,
+                       [::1]:2991, [badc0de::1].
+               [/help]
        [option port]
                short_opt = p
-               summary = listening port of the paraslash control service
+               summary = listening port of the control service
                arg_info = required_arg
                arg_type = uint32
                typestr = portnumber
                default_val = 2990
                [help]
-                       para_server listens on this TCP port for incoming connections
-                       from clients such as para_client. If the default port is changed,
-                       the corresponding option of para_client must be used to connect
-                       to para_server.
+                       This option applies only to addresses given to --listen-address
+                       (see above) which do no include a port number. If the default port
+                       is changed, the corresponding option of para_client must be used to
+                       connect to para_server.
                [/help]
        [option user-list]
                summary = file which contains user names and credentials
@@ -144,6 +171,18 @@ version-string = GIT_VERSION()
        [option http]
                summary = Options for the http sender
                flag ignored
+       [option http-listen-address]
+               summary = listening addresses of the http sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       The http sender of para_server listens on this port for incoming data
+                       connections. This option controls on which addresses the http sender
+                       should listen. See the documentation of the --listen-address above
+                       for the format of the address argument and the defaults.
+               [/help]
        [option http-port]
                summary = TCP port for http streaming
                arg_info = required_arg
@@ -151,18 +190,18 @@ version-string = GIT_VERSION()
                typestr = portnumber
                default_val = 8000
                [help]
-                       The http sender of para_server listens on this port for incoming
-                       connections. Clients are expected to send the usual http request
-                       message such as 'GET / HTTP/'.
+                       This option has the same meaning as --port, but applies to http
+                       data connections and applies to the addresses specified as arguments
+                       to --http-listen-address.
                [/help]
        [option http-default-deny]
                summary = make the http access control list a whitelist
                [help]
                        The default is to use blacklists, i.e. connections to the http sender
-                       are allowed unless the connecting host matches a pattern given by a
-                       http-access option. This allows to use access control the other way
-                       round: Connections are denied from hosts which are not explicitly
-                       allowed by one or more http-access options.
+                       are allowed unless the connecting host matches a pattern given by
+                       a http-access option. This option allows using access control lists
+                       the other way round: Connections are denied from hosts which are not
+                       explicitly allowed by one or more http-access options.
                [/help]
        [option http-access]
                summary = add an entry to the http access control list
@@ -202,6 +241,15 @@ version-string = GIT_VERSION()
        [option dccp]
                summary = Options for the dccp sender
                flag ignored
+       [option dccp-listen-address]
+               summary = listening addresses of the dccp sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       Like --http-listen-address, but for the dccp sender.
+               [/help]
        [option dccp-port]
                summary = port for dccp streaming
                arg_info = required_arg
@@ -247,9 +295,9 @@ version-string = GIT_VERSION()
                        (path MTU) of an incoming connection, i.e. on the largest packet size
                        that can be transmitted without causing fragmentation.
 
-                       This option allows to use a value less than the MPS in order to
-                       fine-tune application performance. Values greater than the MPS of an
-                       incoming connection can not be set.
+                       This option allows values less than the MPS in order to fine-tune
+                       application performance. Values greater than the MPS of an incoming
+                       connection can not be set.
                [/help]
        [option dccp-data-slices-per-group]
                summary = the number of non-redundant slices per FEC group
index 9378b7c..079589d 100644 (file)
@@ -144,10 +144,11 @@ aux_info_prefix = Permissions:
        non-opts-name = [command]
        aux_info = NO_PERMISSION_REQUIRED
        [description]
-               Without any arguments, help prints a list of available commands. When
-               called with a command name as first argument, it prints the description
-               of this command.
+               When executed without any arguments, the available server commands
+               are listed. Otherwise, if the first argument is the name of a server
+               command, the description of this command is shown.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand hup]
        purpose = reload config file, log file and user list
@@ -407,20 +408,45 @@ aux_info_prefix = Permissions:
 
 [subcommand sender]
        purpose = control paraslash senders
-       synopsis = [sender cmd [arguments]]
+       synopsis = [sender subcmd [arguments]]
        aux_info = VSS_READ | VSS_WRITE
        [description]
-               Send a command to a specific sender. The following commands are
-               available, but not all senders support every command.
+               This command executes a subcommand for the given sender, which is
+               one of "http", "dccp" or "udp". Various subcommands exist to print
+               information about the sender, to activate and deactivate the sender,
+               and to change the access permissions and targets. The following
+               subcommands are available:
 
-                      help, on, off, add, delete, allow, deny, status.
+                      help, status, on, off, allow, deny, add, delete.
 
-               The help command prints the help text of the given sender. If no
-               command is given the list of available senders is shown.
+               All senders support the first four commands. The "allow" and "deny"
+               commands are supported by the http and the dccp senders while "add"
+               and "delete" are only supported by the udp sender. If no sender is
+               given, the list of available senders is shown.
 
-               Example:
+               Examples:
+
+               Get help for the udp sender (contains further examples):
+
+                       sender udp help
+
+               Show the access control list and the number of connected clients of
+               the http sender:
+
+                       sender http status
+
+               Senders may be activated and deactivated independently of each
+               other. The following command switches off the dccp sender:
+
+                       sender dccp off
+
+               Add an UDP unicast for a client to the target list of the UDP sender:
+
+                       sender udp add client.foo.org
+
+               Start UDP multicast, using the default multicast address:
 
-                       para_client sender http help
+                       sender udp add 224.0.1.38
 
        [/description]
 
diff --git a/mixer.c b/mixer.c
index 52af25f..ad674bf 100644 (file)
--- a/mixer.c
+++ b/mixer.c
@@ -8,6 +8,7 @@
 
 #include "mixer.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "fd.h"
 #include "string.h"
 #include "mix.h"
@@ -453,60 +454,16 @@ static void handle_help_flags(void)
 static int parse_and_merge_config_file(const struct lls_command *cmd)
 {
        int ret;
-       int cf_argc;
-       char **cf_argv;
-       char *cf, *errctx = NULL;
-       struct lls_parse_result **lprp, *cf_lpr, *merged_lpr;
-       void *map;
-       size_t sz;
-       const char *subcmd_name;
-
-       if (cmd == lls_cmd(0, mixer_suite)) {
-               lprp = &lpr;
-               subcmd_name = NULL;
-       } else {
-               lprp = &sub_lpr;
-               subcmd_name = lls_command_name(cmd);
-       }
-       if (OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(PARA_MIXER, CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/mixer.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
-                               OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
-                       goto free_cf;
-       } else {
-               ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
-               para_munmap(map, sz);
-               if (ret < 0)
-                       goto free_cf;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto free_cf;
-               ret = lls(lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx));
-               lls_free_parse_result(cf_lpr, cmd);
-               if (ret < 0)
-                       goto free_cf;
-               lls_free_parse_result(*lprp, cmd);
-               *lprp = merged_lpr;
-               loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
-       }
-       ret = 1;
-free_cf:
-       free(cf);
-       if (errctx)
-               PARA_ERROR_LOG("%s\n", errctx);
-       free(errctx);
-       return ret;
+       struct lls_parse_result **lprp = (cmd == lls_cmd(0, mixer_suite))?
+               &lpr : &sub_lpr;
+
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(PARA_MIXER,
+               CONFIG_FILE), "mixer.conf", lprp, cmd, mixer_suite,
+               0 /* flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
+       return 1;
 }
 
 /**
diff --git a/mp.c b/mp.c
index c5702c6..bade05b 100644 (file)
--- a/mp.c
+++ b/mp.c
@@ -6,7 +6,7 @@
  * This file contains the public and the private API of the flex/bison based
  * mood parser.
  *
- * The public API (at the bottom of the file) allows to parse the same mood
+ * The public API (at the bottom of the file) allows parsing the same mood
  * definition many times in an efficient manner.
  *
  * The first function to call is \ref mp_init(), which analyzes the given mood
@@ -61,8 +61,8 @@ struct mp_context {
  *
  * This function turns a generalized C99 string literal like "xyz\n" into a C
  * string (containing the three characters 'x', 'y' and 'z', followed by a
- * newline character and the terminating zero byte). The function allows to
- * specify different quote characters so that, for example, regular expression
+ * newline character and the terminating zero byte). The function receives
+ * quote characters as an argument so that, for example, regular expression
  * patterns enclosed in '/' can be parsed as well. To parse a proper string
  * literal, one has to pass two double quotes as the second argument.
  *
diff --git a/net.c b/net.c
index 1fece04..ba19408 100644 (file)
--- a/net.c
+++ b/net.c
@@ -180,6 +180,36 @@ failed:
        return NULL;
 }
 
+/**
+ * Pretty-print a host/port pair.
+ *
+ * \param url NULL, or any string accepted by \ref parse_url().
+ * \param default_port Applies if url has no port.
+ *
+ * If the url argument is NULL, the function returns the string
+ * 0.0.0.0:default_port. Otherwise it calls \ref parse_url() to check the
+ * syntax of the input string given by url. On errors the string "?" is
+ * returned. Otherwise, if url contains a port, a copy of url is returned. If
+ * no port was supplied, a colon and the default port are appended to url.
+ *
+ * \return In all cases the returned string is a allocated with malloc(3) and
+ * has to be freed by the caller.
+ */
+char *format_url(const char *url, int default_port)
+{
+       char host[MAX_HOSTLEN];
+       int url_port;
+
+       if (!url)
+               return make_message("0.0.0.0:%d", default_port);
+       if (!parse_url(url, host, sizeof(host), &url_port))
+               return make_message("?");
+       if (url_port < 0)
+               return make_message("%s:%d", url, default_port);
+       else
+               return para_strdup(url);
+}
+
 /**
  * Stringify port number, resolve into service name where defined.
  *
@@ -502,17 +532,28 @@ int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_numb
  * Create a passive / listening socket.
  *
  * \param l4type The transport-layer type (\p IPPROTO_xxx).
- * \param port The decimal port number to listen on.
+ * \param addr Passed to \ref parse_url() if not NULL.
+ * \param port Ignored if addr contains a port number.
  *
  * \return Positive integer (socket descriptor) on success, negative value
  * otherwise.
  *
  * \sa \ref makesock(), ip(7), ipv6(7), bind(2), listen(2).
  */
-int para_listen_simple(unsigned l4type, uint16_t port)
+int para_listen(unsigned l4type, const char *addr, uint16_t port)
 {
-       int ret, fd = makesock(l4type, 1, NULL, port, NULL);
-
+       char host[MAX_HOSTLEN];
+       int ret, fd, addr_port;
+
+       if (addr) {
+               if (!parse_url(addr, host, sizeof(host), &addr_port))
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               if (addr_port > 0)
+                       port = addr_port;
+               addr = host;
+       }
+       fd = makesock(l4type, true /* passive */, addr, port,
+               NULL /* no flowopts */);
        if (fd > 0) {
                ret = listen(fd, BACKLOG);
                if (ret < 0) {
@@ -526,6 +567,22 @@ int para_listen_simple(unsigned l4type, uint16_t port)
        return fd;
 }
 
+/**
+ * Create a socket which listens on all network addresses.
+ *
+ * \param l4type See \ref para_listen().
+ * \param port See \ref para_listen().
+ *
+ * This is a simple wrapper for \ref para_listen() which passes a NULL pointer
+ * as the address information.
+ *
+ * \return See \ref para_listen().
+ */
+int para_listen_simple(unsigned l4type, uint16_t port)
+{
+       return para_listen(l4type, NULL, port);
+}
+
 /**
  * Determine IPv4/v6 socket address length.
  * \param sa Container of IPv4 or IPv6 address.
diff --git a/net.h b/net.h
index 143fb81..2256f37 100644 (file)
--- a/net.h
+++ b/net.h
@@ -71,6 +71,7 @@ extern char *parse_cidr(const char *cidr,
                        char *addr, ssize_t addrlen, int32_t *netmask);
 extern char *parse_url(const char *url,
                       char *host, ssize_t hostlen, int32_t *port);
+char *format_url(const char *url, int default_port);
 extern const char *stringify_port(int port, const char *transport);
 /**
  * Ensure that string conforms to the IPv4 address format.
@@ -128,6 +129,7 @@ bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2);
 /** How many pending connections queue of a listening server will hold. */
 #define BACKLOG        10
 
+int para_listen(unsigned l4type, const char *addr, uint16_t port);
 int para_listen_simple(unsigned l4type, uint16_t port);
 
 /** Pretty-printing of IPv4/6 socket addresses */
diff --git a/openssl.c b/openssl.c
new file mode 100644 (file)
index 0000000..7d5bb25
--- /dev/null
+++ b/openssl.c
@@ -0,0 +1,280 @@
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file openssl.c Openssl-based encryption/decryption routines. */
+
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/sha.h>
+#include <openssl/bn.h>
+#include <openssl/aes.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "crypt.h"
+#include "crypt_backend.h"
+#include "portable_io.h"
+
+struct asymmetric_key {
+       RSA *rsa;
+};
+
+void get_random_bytes_or_die(unsigned char *buf, int num)
+{
+       unsigned long err;
+
+       /* RAND_bytes() returns 1 on success, 0 otherwise. */
+       if (RAND_bytes(buf, num) == 1)
+               return;
+       err = ERR_get_error();
+       PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err));
+       exit(EXIT_FAILURE);
+}
+
+/*
+ * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG
+ * used by random(3) with a random seed obtained from SSL. If /dev/urandom is
+ * not readable, the function calls exit().
+ *
+ * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3),
+ * random(3), \ref para_random().
+ */
+void crypt_init(void)
+{
+       int seed, ret = RAND_load_file("/dev/urandom", 64);
+
+       if (ret != 64) {
+               PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret);
+               exit(EXIT_FAILURE);
+       }
+       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
+       srandom(seed);
+}
+
+void crypt_shutdown(void)
+{
+       CRYPTO_cleanup_all_ex_data();
+}
+
+static int get_private_key(const char *path, RSA **rsa)
+{
+       EVP_PKEY *pkey;
+       BIO *bio = BIO_new(BIO_s_file());
+
+       *rsa = NULL;
+       if (!bio)
+               return -E_PRIVATE_KEY;
+       if (BIO_read_filename(bio, path) <= 0)
+               goto bio_free;
+       pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+       if (!pkey)
+               goto bio_free;
+       *rsa = EVP_PKEY_get1_RSA(pkey);
+       EVP_PKEY_free(pkey);
+bio_free:
+       BIO_free(bio);
+       return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+}
+
+/*
+ * The public key loading functions below were inspired by corresponding code
+ * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo,
+ * Finland. However, not much of the original code remains.
+ */
+
+static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
+{
+       const unsigned char *p = buf, *end = buf + len;
+       uint32_t bnsize;
+       BIGNUM *bn;
+
+       if (p + 4 < p)
+               return -E_BIGNUM;
+       if (p + 4 > end)
+               return -E_BIGNUM;
+       bnsize = read_u32_be(p);
+       PARA_DEBUG_LOG("bnsize: %u\n", bnsize);
+       p += 4;
+       if (p + bnsize < p)
+               return -E_BIGNUM;
+       if (p + bnsize > end)
+               return -E_BIGNUM;
+       if (bnsize > 8192)
+               return -E_BIGNUM;
+       bn = BN_bin2bn(p, bnsize, NULL);
+       if (!bn)
+               return -E_BIGNUM;
+       *result = bn;
+       return bnsize + 4;
+}
+
+static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
+{
+       int ret;
+       RSA *rsa;
+       BIGNUM *n, *e;
+       const unsigned char *p = blob, *end = blob + blen;
+
+       rsa = RSA_new();
+       if (!rsa)
+               return -E_BIGNUM;
+       ret = read_bignum(p, end - p, &e);
+       if (ret < 0)
+               goto fail;
+       p += ret;
+       ret = read_bignum(p, end - p, &n);
+       if (ret < 0)
+               goto fail;
+#ifdef HAVE_RSA_SET0_KEY
+       RSA_set0_key(rsa, n, e, NULL);
+#else
+       rsa->n = n;
+       rsa->e = e;
+#endif
+       *result = rsa;
+       return 1;
+fail:
+       RSA_free(rsa);
+       return ret;
+}
+
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
+{
+       unsigned char *blob;
+       size_t decoded_size;
+       int ret;
+       struct asymmetric_key *key = para_malloc(sizeof(*key));
+
+       ret = decode_ssh_key(key_file, &blob, &decoded_size);
+       if (ret < 0)
+               goto out;
+       ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
+       if (ret < 0)
+               goto free_blob;
+       ret = RSA_size(key->rsa);
+       assert(ret > 0);
+       *result = key;
+free_blob:
+       free(blob);
+out:
+       if (ret < 0) {
+               free(key);
+               *result = NULL;
+               PARA_ERROR_LOG("can not load key %s\n", key_file);
+       }
+       return ret;
+}
+
+void apc_free_pubkey(struct asymmetric_key *key)
+{
+       if (!key)
+               return;
+       RSA_free(key->rsa);
+       free(key);
+}
+
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
+               unsigned char *inbuf, int inlen)
+{
+       struct asymmetric_key *priv;
+       int ret;
+
+       ret = check_private_key_file(key_file);
+       if (ret < 0)
+               return ret;
+       if (inlen < 0)
+               return -E_RSA;
+       priv = para_malloc(sizeof(*priv));
+       ret = get_private_key(key_file, &priv->rsa);
+       if (ret < 0) {
+               free(priv);
+               return ret;
+       }
+       /*
+        * RSA is vulnerable to timing attacks. Generate a random blinding
+        * factor to protect against this kind of attack.
+        */
+       ret = -E_BLINDING;
+       if (RSA_blinding_on(priv->rsa, NULL) == 0)
+               goto out;
+       ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa,
+               RSA_PKCS1_OAEP_PADDING);
+       RSA_blinding_off(priv->rsa);
+       if (ret <= 0)
+               ret = -E_DECRYPT;
+out:
+       RSA_free(priv->rsa);
+       free(priv);
+       return ret;
+}
+
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+               unsigned len, unsigned char *outbuf)
+{
+       int ret, flen = len; /* RSA_public_encrypt expects a signed int */
+
+       if (flen < 0)
+               return -E_ENCRYPT;
+       ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
+               RSA_PKCS1_OAEP_PADDING);
+       return ret < 0? -E_ENCRYPT : ret;
+}
+
+struct stream_cipher {
+       EVP_CIPHER_CTX *aes;
+};
+
+struct stream_cipher *sc_new(const unsigned char *data, int len)
+{
+       struct stream_cipher *sc = para_malloc(sizeof(*sc));
+
+       assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
+       sc->aes = EVP_CIPHER_CTX_new();
+       EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data,
+               data + AES_CRT128_BLOCK_SIZE);
+       return sc;
+}
+
+void sc_free(struct stream_cipher *sc)
+{
+       if (!sc)
+               return;
+       EVP_CIPHER_CTX_free(sc->aes);
+       free(sc);
+}
+
+static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
+               struct iovec *dst)
+{
+       int ret, inlen = src->iov_len, outlen, tmplen;
+
+       *dst = (typeof(*dst)) {
+               /* Add one for the terminating zero byte. */
+               .iov_base = para_malloc(inlen + 1),
+               .iov_len = inlen
+       };
+       ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
+       assert(ret != 0);
+       ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen);
+       assert(ret != 0);
+       outlen += tmplen;
+       ((char *)dst->iov_base)[outlen] = '\0';
+       dst->iov_len = outlen;
+}
+
+void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
+{
+       return aes_ctr128_crypt(sc->aes, src, dst);
+}
+
+void hash_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       SHA_CTX c;
+       SHA1_Init(&c);
+       SHA1_Update(&c, data, len);
+       SHA1_Final(hash, &c);
+}
diff --git a/play.c b/play.c
index 78d53fa..647367a 100644 (file)
--- a/play.c
+++ b/play.c
@@ -11,6 +11,7 @@
 #include "write_cmd.lsg.h"
 #include "play.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "list.h"
 #include "error.h"
 #include "buffer_tree.h"
@@ -150,73 +151,38 @@ static void handle_help_flags(void)
 
 static void parse_config_or_die(int argc, char *argv[])
 {
-       const struct lls_command *cmd = CMD_PTR;
-       int i, ret, cf_argc;
-       char *cf, *errctx, **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
+       int i, ret;
        unsigned num_kmas;
-       void *map;
-       size_t sz;
+       char *errctx;
 
-       ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx));
-       if (ret < 0)
-               goto fail;
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &play_lpr, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_EMERG_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("failed to parse command line options: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
        loglevel = OPT_UINT32_VAL(LOGLEVEL);
        version_handle_flag("play", OPT_GIVEN(VERSION));
        handle_help_flags(); /* also handles the zero-arg case */
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/play.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "play.conf", &play_lpr, CMD_PTR, play_suite, 0 /* flags */);
        if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-               ret = 0;
-               goto setup_keymap;
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-       para_munmap(map, sz);
-       if (ret < 0)
-               goto free_cf;
-       cf_argc = ret;
-       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
-       lls_free_parse_result(cf_lpr, cmd);
-       if (ret < 0)
-               goto free_cf;
-       lls_free_parse_result(play_lpr, cmd);
-       play_lpr = merged_lpr;
        loglevel = OPT_UINT32_VAL(LOGLEVEL);
-setup_keymap:
        num_kmas = OPT_GIVEN(KEY_MAP);
        for (i = 0; i < num_kmas; i++) {
                const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP));
                if (*kma && strchr(kma + 1, ':'))
                        continue;
                PARA_EMERG_LOG("invalid key map arg: %s\n", kma);
-               goto free_cf;
+               exit(EXIT_FAILURE);
        }
-       ret = 1;
-free_cf:
-       free(cf);
-       if (ret >= 0)
-               return;
-       lls_free_parse_result(play_lpr, cmd);
-fail:
-       if (errctx)
-               PARA_EMERG_LOG("%s\n", errctx);
-       free(errctx);
-       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-       exit(EXIT_FAILURE);
 }
 
 static char get_playback_state(void)
@@ -706,9 +672,15 @@ I9E_DUMMY_COMPLETER(quit);
 I9E_DUMMY_COMPLETER(ff);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, pp_completers);
+       char *opts[] = {LSG_PLAY_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, pp_completers);
 }
 
 static struct i9e_completer pp_completers[] = {
@@ -742,27 +714,15 @@ EXPORT_PLAY_CMD_HANDLER(quit);
 
 static int com_help(struct lls_parse_result *lpr)
 {
-       int i, ret;
-       char *buf, *errctx;
+       int i;
+       char *buf;
        size_t sz;
-       const struct lls_command *cmd;
+       unsigned n;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_PLAY_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0) {
-               if (pt->background) {
-                       for (i = 1; (cmd = lls_cmd(i, play_cmd_suite)); i++) {
-                               sz = xasprintf(&buf, "%s\t%s\n",
-                                       lls_command_name(cmd), lls_purpose(cmd));
-                               btr_add_output(buf, sz, pt->btrn);
-                       }
-                       return 0;
-               }
+       if (!pt->background) {
                FOR_EACH_MAPPED_KEY(i) {
                        bool internal = is_internal_key(i);
                        int idx = get_key_map_idx(i);
@@ -777,18 +737,8 @@ static int com_help(struct lls_parse_result *lpr)
                }
                return 0;
        }
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), play_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               return ret;
-       }
-       cmd = lls_cmd(ret, play_cmd_suite);
-       buf = lls_long_help(cmd);
-       assert(buf);
-       btr_add_output(buf, strlen(buf), pt->btrn);
+       lsu_com_help(long_help, lpr, play_cmd_suite, NULL, &buf, &n);
+       btr_add_output(buf, n, pt->btrn);
        return 0;
 }
 EXPORT_PLAY_CMD_HANDLER(help);
diff --git a/sched.c b/sched.c
index 4fc65b4..a290394 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -168,7 +168,7 @@ again:
  * \param tptr Identifies the task to reap.
  *
  * This function is similar to wait(2) in that it returns information about a
- * terminated task and allows to release the resources associated with the
+ * terminated task which allows releasing the resources associated with the
  * task. Until this function is called, the terminated task remains in a zombie
  * state.
  *
diff --git a/send.h b/send.h
index 84f35f9..67b47e4 100644 (file)
--- a/send.h
+++ b/send.h
@@ -27,14 +27,11 @@ struct sender {
        /** The name of the sender. */
        const char *name;
        /**
-        * The init function of this sender.
-        *
-        * It must fill in all function pointers of \a s as well as the \a
-        * client_cmds array, see below. It should also do all necessary
-        * preparations to init this sending facility, for example it could
-        * open a tcp port.
+        * Parse the command line options and initialize this sender (e.g.,
+        * initialize target or access control lists, listen on a network
+        * socket, etc.).
         */
-       void (*init)(struct sender *s);
+       void (*init)(void);
        /**
         * Return the help text of this sender.
         *
@@ -88,6 +85,8 @@ struct sender {
         * the clients aware of the end-of-file condition.
         */
        void (*shutdown_clients)(void);
+       /** Dellocate all resources. Only called on exit. */
+       void (*shutdown)(void);
        /**
         * Array of function pointers for the sender subcommands.
         *
@@ -107,6 +106,11 @@ struct sender {
        int (*resolve_target)(const char *, struct sender_command_data *);
 };
 
+/** NULL-terminated list, defined in \ref vss.c. */
+extern const struct sender * const senders[];
+/** Iterate over all senders. */
+#define FOR_EACH_SENDER(_i) for ((_i) = 0; senders[(_i)]; (_i)++)
+
 /** Describes one client, connected to a paraslash sender. */
 struct sender_client {
        /** The file descriptor of the client. */
@@ -160,10 +164,14 @@ struct fec_client_parms {
 
 /** Describes the current status of one paraslash sender. */
 struct sender_status {
-       /** The file descriptor of the socket this sender is listening on. */
-       int listen_fd;
-       /** The TCP/DCCP port used by this sender. */
-       int port;
+       /** Number of sockets to listen on, size of the two arrays below. */
+       unsigned num_listen_fds;
+       /** Derived from --http-listen-address and --dccp-listen-address. */
+       char **listen_addresses;
+       /** Default TCP/DCCP port number for addresses w/o port. */
+       int default_port;
+       /** The socket fd(s) this sender is listening on. */
+       int *listen_fds;
        /** The current number of simultaneous connections. */
        int num_clients;
        /** The maximal number of simultaneous connections. */
@@ -176,17 +184,22 @@ struct sender_status {
        struct list_head client_list;
 };
 
+/** Iterate over all listening addresses of the http/dccp sender. */
+#define FOR_EACH_LISTEN_FD(_n, _ss) for (_n = 0; _n < (_ss)->num_listen_fds; _n++)
+
 void shutdown_client(struct sender_client *sc, struct sender_status *ss);
 void shutdown_clients(struct sender_status *ss);
 void init_sender_status(struct sender_status *ss,
-               const struct lls_opt_result *acl_opt_result, int port,
-               int max_clients, int default_deny);
+               const struct lls_opt_result *acl_opt_result,
+               const struct lls_opt_result *listen_address_opt_result,
+               int default_port, int max_clients, int default_deny);
 char *generic_sender_status(struct sender_status *ss, const char *name);
 void generic_com_allow(struct sender_command_data *scd,
                struct sender_status *ss);
 void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss);
 void generic_com_on(struct sender_status *ss, unsigned protocol);
+void generic_acl_deplete(struct list_head *acl);
 void generic_com_off(struct sender_status *ss);
 char *generic_sender_help(void);
 struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds);
index 16820a5..24b14ab 100644 (file)
@@ -46,8 +46,10 @@ void shutdown_client(struct sender_client *sc, struct sender_status *ss)
 {
        PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
        free(sc->name);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        cq_destroy(sc->cq);
        list_del(&sc->node);
        free(sc->private_data);
@@ -103,20 +105,38 @@ int send_queued_chunks(int fd, struct chunk_queue *cq)
  *
  * \param ss The struct to initialize.
  * \param acl_opt_result Contains array of --{http|dccp}-access arguments.
- * \param port The tcp or dccp port to listen on.
+ * \param listen_address_opt_result Where to listen on.
+ * \param default_port Used for addresses with no specified port.
  * \param max_clients The maximal number of simultaneous connections.
  * \param default_deny Whether a blacklist should be used for access control.
  */
 void init_sender_status(struct sender_status *ss,
-               const struct lls_opt_result *acl_opt_result, int port,
-               int max_clients, int default_deny)
+               const struct lls_opt_result *acl_opt_result,
+               const struct lls_opt_result *listen_address_opt_result,
+               int default_port, int max_clients, int default_deny)
 {
        int i;
+       unsigned n = lls_opt_given(listen_address_opt_result);
 
-       ss->listen_fd = -1;
-       INIT_LIST_HEAD(&ss->client_list);
-       ss->port = port;
+       if (n == 0) {
+               ss->num_listen_fds = 1;
+               ss->listen_addresses = para_malloc(sizeof(char *));
+               ss->listen_addresses[0] = NULL;
+               ss->listen_fds = para_malloc(sizeof(int));
+               ss->listen_fds[0] = -1;
+       } else {
+               ss->num_listen_fds = n;
+               ss->listen_addresses = para_malloc(n * sizeof(char *));
+               ss->listen_fds = para_malloc(n * sizeof(int));
+               FOR_EACH_LISTEN_FD(i, ss) {
+                       ss->listen_addresses[i] = para_strdup(lls_string_val(i,
+                               listen_address_opt_result));
+                       ss->listen_fds[i] = -1;
+               }
+       }
+       ss->default_port = default_port;
 
+       INIT_LIST_HEAD(&ss->client_list);
        /* Initialize an access control list */
        INIT_LIST_HEAD(&ss->acl);
        for (i = 0; i < lls_opt_given(acl_opt_result); i++) {
@@ -144,24 +164,35 @@ void init_sender_status(struct sender_status *ss,
  */
 char *generic_sender_status(struct sender_status *ss, const char *name)
 {
-       char *clnts = NULL, *ret;
+       char *clnts = NULL, *ret, *addr = NULL;
        struct sender_client *sc, *tmp_sc;
-
+       unsigned n;
        char *acl_contents = acl_get_contents(&ss->acl);
+
        list_for_each_entry_safe(sc, tmp_sc, &ss->client_list, node) {
                char *tmp = make_message("%s%s ", clnts? clnts : "", sc->name);
                free(clnts);
                clnts = tmp;
        }
+       FOR_EACH_LISTEN_FD(n, ss) {
+               char *url = format_url(ss->listen_addresses[n], ss->default_port);
+               char *tmp = make_message("%s%s%s (fd %d)", addr?
+                       addr : "", addr? ", " : "", url,
+                       ss->listen_fds[n]);
+               free(url);
+               free(addr);
+               addr = tmp;
+       }
        ret = make_message(
-               "status: %s\n"
-               "port: %s\n"
+               "listening address(es): %s\n"
+               "default port: %s\n"
                "number of connected clients: %d\n"
                "maximal number of clients: %d%s\n"
                "connected clients: %s\n"
                "access %s list: %s\n",
-               (ss->listen_fd >= 0)? "on" : "off",
-               stringify_port(ss->port, strcmp(name, "http") ? "dccp" : "tcp"),
+               addr,
+               stringify_port(ss->default_port,
+                       strcmp(name, "http")? "dccp" : "tcp"),
                ss->num_clients,
                ss->max_clients,
                ss->max_clients > 0? "" : " (unlimited)",
@@ -188,6 +219,22 @@ void generic_com_allow(struct sender_command_data *scd,
        acl_allow(scd->host, scd->netmask, &ss->acl, ss->default_deny);
 }
 
+/**
+ * Empty the access control list of a sender.
+ *
+ * \param acl The access control list of the sender.
+ *
+ * This is called from the ->shutdown methods of the http and the dccp sender.
+ */
+void generic_acl_deplete(struct list_head *acl)
+{
+       /*
+        * Since default_deny is false, the ACL is considered a blacklist. A
+        * netmask of zero matches any IP address, so this call empties the ACL.
+        */
+       acl_allow("0.0.0.0", 0 /* netmask */, acl, 0 /* default_deny */);
+}
+
 /**
  * Deny connections from the given range of IP addresses.
  *
@@ -215,29 +262,39 @@ void generic_com_deny(struct sender_command_data *scd,
  */
 void generic_com_on(struct sender_status *ss, unsigned protocol)
 {
-       int fd, ret;
+       int ret;
+       unsigned n;
 
-       if (ss->listen_fd >= 0)
-               return;
-       ret = para_listen_simple(protocol, ss->port);
-       if (ret < 0) {
-               PARA_ERROR_LOG("could not listen on port %d: %s\n", ss->port,
-                       para_strerror(-ret));
-               return;
-       }
-       fd = ret;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0) {
-               PARA_ERROR_LOG("could not set %s socket fd for port %d to "
-                       "nonblocking mode: %s\n",
-                       protocol == IPPROTO_TCP? "TCP" : "DCCP", ss->port,
-                       para_strerror(-ret));
-               close(fd);
-               return;
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] >= 0)
+                       continue;
+               ret = para_listen(protocol, ss->listen_addresses[n],
+                       ss->default_port);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not listen on %s %s: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP",
+                               url, para_strerror(-ret));
+                       free(url);
+                       continue;
+               }
+               ss->listen_fds[n] = ret;
+               ret = mark_fd_nonblocking(ss->listen_fds[n]);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not set %s socket fd for %s to "
+                               "nonblocking mode: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP", url,
+                               para_strerror(-ret));
+                       free(url);
+                       close(ss->listen_fds[n]);
+                       ss->listen_fds[n] = -1;
+                       continue;
+               }
+               add_close_on_fork_list(ss->listen_fds[n]);
        }
-       add_close_on_fork_list(fd);
-       ss->listen_fd = fd;
-       return;
 }
 
 /**
@@ -251,23 +308,26 @@ void generic_com_on(struct sender_status *ss, unsigned protocol)
  */
 void generic_com_off(struct sender_status *ss)
 {
-       if (ss->listen_fd < 0)
-               return;
-       PARA_NOTICE_LOG("closing port %d\n", ss->port);
-       close(ss->listen_fd);
-       del_close_on_fork_list(ss->listen_fd);
-       shutdown_clients(ss);
-       ss->listen_fd = -1;
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       return;
+               close(ss->listen_fds[n]);
+               del_close_on_fork_list(ss->listen_fds[n]);
+               shutdown_clients(ss);
+               ss->listen_fds[n] = -1;
+       }
 }
 
 /**
- * Accept a connection on the socket this server is listening on.
+ * Accept a connection on the socket(s) this server is listening on.
  *
  * \param ss The sender whose listening fd is ready for reading.
  * \param rfds Passed to para_accept(),
  *
- * This calls para_accept() and performs the following actions on the resulting
- * file descriptor fd:
+ * This accepts incoming connections on any of the listening sockets of the
+ * server. If there is a connection pending, the function
  *
  *     - Checks whether the maximal number of connections are exceeded.
  *     - Sets \a fd to nonblocking mode.
@@ -292,36 +352,40 @@ struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfd
 {
        struct sender_client *sc;
        int fd, ret;
+       unsigned n;
 
-       if (ss->listen_fd < 0)
-               return NULL;
-       ret = para_accept(ss->listen_fd, rfds, NULL, 0, &fd);
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-       if (ret <= 0)
-               return NULL;
-       ret = -E_MAX_CLIENTS;
-       if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
-               goto err_out;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0)
-               goto err_out;
-       ret = acl_check_access(fd, &ss->acl, ss->default_deny);
-       if (ret < 0)
-               goto err_out;
-       ss->num_clients++;
-       sc = para_calloc(sizeof(*sc));
-       sc->fd = fd;
-       sc->name = para_strdup(remote_name(fd));
-       sc->cq = cq_new(MAX_CQ_BYTES);
-       para_list_add(&sc->node, &ss->client_list);
-       add_close_on_fork_list(fd);
-       PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
-               sc->name, fd);
-       return sc;
-err_out:
-       PARA_WARNING_LOG("%s\n", para_strerror(-ret));
-       close(fd);
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       continue;
+               ret = para_accept(ss->listen_fds[n], rfds, NULL, 0, &fd);
+               if (ret < 0)
+                       goto warn;
+               if (ret == 0)
+                       continue;
+               ret = -E_MAX_CLIENTS;
+               if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
+                       goto close_fd_and_warn;
+               ret = mark_fd_nonblocking(fd);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ret = acl_check_access(fd, &ss->acl, ss->default_deny);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ss->num_clients++;
+               sc = para_calloc(sizeof(*sc));
+               sc->fd = fd;
+               sc->name = para_strdup(remote_name(fd));
+               sc->cq = cq_new(MAX_CQ_BYTES);
+               para_list_add(&sc->node, &ss->client_list);
+               add_close_on_fork_list(fd);
+               PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
+                       sc->name, fd);
+               return sc;
+close_fd_and_warn:
+               close(fd);
+warn:
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       }
        return NULL;
 }
 
index 66c93ab..eee0a41 100644 (file)
--- a/server.c
+++ b/server.c
@@ -41,6 +41,7 @@
 #include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "afh.h"
 #include "string.h"
@@ -100,16 +101,40 @@ uint32_t afs_socket_cookie;
 /** The mutex protecting the shared memory area containing the mmd struct. */
 int mmd_mutex;
 
+/* Serializes log output. */
+static int log_mutex;
+
 static struct sched sched;
 static struct signal_task *signal_task;
 
 /** The process id of the audio file selector process. */
 pid_t afs_pid = 0;
 
+/* The the main server process (parent of afs and the command handlers). */
+static pid_t server_pid;
+
+/**
+ * Tell whether the executing process is a command handler.
+ *
+ * Cleanup on exit must be performed differently for command handlers.
+ *
+ * \return True if the pid of the executing process is neither the server pid
+ * nor the afs pid.
+ */
+bool process_is_command_handler(void)
+{
+       pid_t pid = getpid();
+
+       return pid != afs_pid && pid != server_pid;
+}
+
 /** The task responsible for server command handling. */
 struct server_command_task {
-       /** TCP port on which para_server listens for connections. */
-       int listen_fd;
+       unsigned num_listen_fds; /* only one by default */
+       /** TCP socket(s) on which para_server listens for connections. */
+       int *listen_fds;
+       /* File descriptor for the accepted socket. */
+       int child_fd;
        /** Copied from para_server's main function. */
        int argc;
        /** Argument vector passed to para_server's main function. */
@@ -131,9 +156,17 @@ char *server_get_tasks(void)
        return get_task_list(&sched);
 }
 
-/*
- * setup shared memory area and get mutex for locking
- */
+static void pre_log_hook(void)
+{
+       mutex_lock(log_mutex);
+}
+
+static void post_log_hook(void)
+{
+       mutex_unlock(log_mutex);
+}
+
+/* Setup shared memory area and init mutexes */
 static void init_ipc_or_die(void)
 {
        void *shm;
@@ -152,6 +185,10 @@ static void init_ipc_or_die(void)
        if (ret < 0)
                goto err_out;
        mmd_mutex = ret;
+       ret = mutex_new();
+       if (ret < 0)
+               goto destroy_mmd_mutex;
+       log_mutex = ret;
 
        mmd->num_played = 0;
        mmd->num_commands = 0;
@@ -161,6 +198,8 @@ static void init_ipc_or_die(void)
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
        return;
+destroy_mmd_mutex:
+       mutex_destroy(mmd_mutex);
 err_out:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -177,56 +216,20 @@ err_out:
 void parse_config_or_die(bool reload)
 {
        int ret;
-       char *cf = NULL, *errctx = NULL, *user_list_file = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-       char *home = para_homedir();
-
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else
-               cf = make_message("%s/.paraslash/server.conf", home);
-       if (!mmd || getpid() != afs_pid) {
-               if (OPT_GIVEN(USER_LIST))
-                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
-               else
-                       user_list_file = make_message("%s/.paraslash/server.users", home);
-       }
-       free(home);
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-               server_lpr = cmdline_lpr;
-               goto success;
-       }
-       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-       para_munmap(map, sz);
-       if (ret < 0)
-               goto free_cf;
-       cf_argc = ret;
-       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
+       unsigned flags = MCF_DONT_FREE;
+
        if (server_lpr != cmdline_lpr)
                lls_free_parse_result(server_lpr, CMD_PTR);
-       server_lpr = merged_lpr;
-success:
+       server_lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "server.conf", &server_lpr, CMD_PTR, server_suite, flags);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
        daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
        if (OPT_GIVEN(LOGFILE)) {
                daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
@@ -245,19 +248,19 @@ success:
        if (OPT_GIVEN(LOG_TIMING))
                daemon_set_flag(DF_LOG_TIMING);
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
-       if (user_list_file)
-               init_user_list(user_list_file);
-       ret = 1;
-free_cf:
-       free(cf);
-       free(user_list_file);
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+       if (!reload || getpid() != afs_pid) {
+               char *user_list_file;
+               if (OPT_GIVEN(USER_LIST))
+                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
+               else {
+                       char *home = para_homedir();
+                       user_list_file = make_message("%s/.paraslash/server.users", home);
+                       free(home);
+               }
+               user_list_init(user_list_file);
+               free(user_list_file);
        }
+       return;
 }
 
 /*
@@ -274,8 +277,12 @@ static void handle_sighup(void)
 
 static int signal_post_select(struct sched *s, __a_unused void *context)
 {
-       int signum = para_next_signal(&s->rfds);
+       int ret, signum;
 
+       ret = task_get_notification(signal_task->task);
+       if (ret < 0)
+               return ret;
+       signum = para_next_signal(&s->rfds);
        switch (signum) {
        case 0:
                return 0;
@@ -285,7 +292,7 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
        case SIGCHLD:
                for (;;) {
                        pid_t pid;
-                       int ret = para_reap_child(&pid);
+                       ret = para_reap_child(&pid);
                        if (ret <= 0)
                                break;
                        if (pid != afs_pid)
@@ -301,27 +308,22 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
                PARA_EMERG_LOG("terminating on signal %d\n", signum);
                kill(0, SIGTERM);
                /*
-                * We must wait for afs because afs catches SIGINT/SIGTERM.
-                * Before reacting to the signal, afs might want to use the
+                * We must wait for all of our children to die. For the afs
+                * process or a command handler might want to use the
                 * shared memory area and the mmd mutex.  If we destroy this
                 * mutex too early and afs tries to lock the shared memory
                 * area, the call to mutex_lock() will fail and terminate the
                 * afs process. This leads to dirty osl tables.
-                *
-                * There's no such problem with the other children of the
-                * server process (the command handlers) as these reset their
-                * SIGINT/SIGTERM handlers to the default action, i.e.  these
-                * processes get killed immediately by the above kill().
                 */
-               PARA_INFO_LOG("waiting for afs (pid %d) to die\n",
-                       (int)afs_pid);
-               waitpid(afs_pid, NULL, 0);
+               PARA_INFO_LOG("waiting for child processes to die\n");
+               mutex_unlock(mmd_mutex);
+               while (wait(NULL) != -1 || errno != ECHILD)
+                       ; /* still at least one child alive */
+               mutex_lock(mmd_mutex);
 cleanup:
                free(mmd->afd.afhi.chunk_table);
-               close_listed_fds();
-               mutex_destroy(mmd_mutex);
-               shm_detach(mmd);
-               exit(EXIT_FAILURE);
+               task_notify_all(s, E_DEADLY_SIGNAL);
+               return -E_DEADLY_SIGNAL;
        }
        return 0;
 }
@@ -346,20 +348,22 @@ static void init_signal_task(void)
 
 static void command_pre_select(struct sched *s, void *context)
 {
+       unsigned n;
        struct server_command_task *sct = context;
-       para_fd_set(sct->listen_fd, &s->rfds, &s->max_fileno);
+
+       for (n = 0; n < sct->num_listen_fds; n++)
+               para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
 }
 
-static int command_post_select(struct sched *s, void *context)
+static int command_task_accept(unsigned listen_idx, struct sched *s,
+               struct server_command_task *sct)
 {
-       struct server_command_task *sct = context;
-
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
 
-       ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
+       ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
@@ -391,9 +395,7 @@ static int command_post_select(struct sched *s, void *context)
        PARA_INFO_LOG("accepted connection from %s\n", peer_name);
        /* mmd might already have changed at this point */
        free(chunk_table);
-       alarm(ALARM_TIMEOUT);
-       close_listed_fds();
-       signal_shutdown(signal_task);
+       sct->child_fd = new_fd;
        /*
         * put info on who we are serving into argv[0] to make
         * client ip visible in top/ps
@@ -402,31 +404,84 @@ static int command_post_select(struct sched *s, void *context)
                memset(sct->argv[i], 0, strlen(sct->argv[i]));
        i = sct->argc - 1 - lls_num_inputs(cmdline_lpr);
        sprintf(sct->argv[i], "para_server (serving %s)", peer_name);
-       handle_connect(new_fd);
-       /* never reached*/
+       /* ask other tasks to terminate */
+       task_notify_all(s, E_CHILD_CONTEXT);
+       /*
+        * After we return, the scheduler calls server_select() with a minimal
+        * timeout value, because the remaining tasks have a notification
+        * pending. Next it calls the ->post_select method of these tasks,
+        * which will return negative in view of the notification. This causes
+        * schedule() to return as there are no more runnable tasks.
+        *
+        * Note that semaphores are not inherited across a fork(), so we don't
+        * hold the lock at this point. Since server_select() drops the lock
+        * prior to calling para_select(), we need to acquire it here.
+        */
+       mutex_lock(mmd_mutex);
+       return -E_CHILD_CONTEXT;
 out:
        if (ret < 0)
                PARA_CRIT_LOG("%s\n", para_strerror(-ret));
        return 0;
 }
 
-static void init_server_command_task(int argc, char **argv)
+static int command_post_select(struct sched *s, void *context)
 {
+       struct server_command_task *sct = context;
+       unsigned n;
        int ret;
-       static struct server_command_task server_command_task_struct,
-               *sct = &server_command_task_struct;
+
+       ret = task_get_notification(sct->task);
+       if (ret < 0)
+               return ret;
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = command_task_accept(n, s, sct);
+               if (ret < 0) {
+                       free(sct->listen_fds);
+                       return ret;
+               }
+       }
+       return 0;
+}
+
+static void init_server_command_task(struct server_command_task *sct,
+               int argc, char **argv)
+{
+       int ret;
+       unsigned n;
+       uint32_t port = OPT_UINT32_VAL(PORT);
 
        PARA_NOTICE_LOG("initializing tcp command socket\n");
+       sct->child_fd = -1;
        sct->argc = argc;
        sct->argv = argv;
-       ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT));
-       if (ret < 0)
-               goto err;
-       sct->listen_fd = ret;
-       ret = mark_fd_nonblocking(sct->listen_fd);
-       if (ret < 0)
-               goto err;
-       add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
+       if (!OPT_GIVEN(LISTEN_ADDRESS)) {
+               sct->num_listen_fds = 1;
+               sct->listen_fds = para_malloc(sizeof(int));
+               ret = para_listen_simple(IPPROTO_TCP, port);
+               if (ret < 0)
+                       goto err;
+               sct->listen_fds[0] = ret;
+       } else {
+               sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
+               sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
+               for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
+                       const char *arg;
+                       arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
+                       ret = para_listen(IPPROTO_TCP, arg, port);
+                       if (ret < 0)
+                               goto err;
+                       sct->listen_fds[n] = ret;
+               }
+       }
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = mark_fd_nonblocking(sct->listen_fds[n]);
+               if (ret < 0)
+                       goto err;
+               /* child doesn't need the listener */
+               add_close_on_fork_list(sct->listen_fds[n]);
+       }
+
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
                .pre_select = command_pre_select,
@@ -456,6 +511,8 @@ static int init_afs(int argc, char **argv)
                int i;
 
                afs_pid = getpid();
+               crypt_shutdown();
+               user_list_deplete();
                for (i = argc - 1; i >= 0; i--)
                        memset(argv[i], 0, strlen(argv[i]));
                i = argc - lls_num_inputs(cmdline_lpr) - 1;
@@ -493,7 +550,7 @@ static void handle_help_flags(void)
        exit(EXIT_SUCCESS);
 }
 
-static void server_init(int argc, char **argv)
+static void server_init(int argc, char **argv, struct server_command_task *sct)
 {
        int ret, afs_socket, daemon_pipe = -1;
        char *errctx;
@@ -513,10 +570,12 @@ static void server_init(int argc, char **argv)
        /* become daemon */
        if (OPT_GIVEN(DAEMON))
                daemon_pipe = daemonize(true /* parent waits for SIGTERM */);
-       init_random_seed_or_die();
+       server_pid = getpid();
+       crypt_init();
        daemon_log_welcome("server");
-       init_ipc_or_die(); /* init mmd struct and mmd->lock */
+       init_ipc_or_die(); /* init mmd struct, mmd and log mutex */
        daemon_set_start_time();
+       daemon_set_hooks(pre_log_hook, post_log_hook);
        PARA_NOTICE_LOG("initializing audio format handlers\n");
        afh_init();
 
@@ -539,7 +598,7 @@ static void server_init(int argc, char **argv)
        para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
        vss_init(afs_socket, &sched);
-       init_server_command_task(argc, argv);
+       init_server_command_task(sct, argc, argv);
        if (daemon_pipe >= 0) {
                if (write(daemon_pipe, "\0", 1) < 0) {
                        PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno));
@@ -591,6 +650,21 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
        return ret;
 }
 
+/**
+ * Deallocate all lopsub parse results.
+ *
+ * The server allocates a parse result for command line options and optionally
+ * a second parse result for the effective configuration, defined by merging
+ * the command line options with the options stored in the configuration file.
+ * This function frees both structures.
+ */
+void free_lpr(void)
+{
+       lls_free_parse_result(server_lpr, CMD_PTR);
+       if (server_lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR);
+}
+
 /**
  * The main function of para_server.
  *
@@ -602,18 +676,38 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 int main(int argc, char *argv[])
 {
        int ret;
+       struct server_command_task server_command_task_struct,
+               *sct = &server_command_task_struct;
 
        sched.default_timeout.tv_sec = 1;
        sched.select_function = server_select;
 
-       server_init(argc, argv);
+       server_init(argc, argv, sct);
        mutex_lock(mmd_mutex);
        ret = schedule(&sched);
+       /*
+        * We hold the mmd lock: it was re-acquired in server_select()
+        * after the select call.
+        */
+       mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
-       lls_free_parse_result(server_lpr, CMD_PTR);
-       if (server_lpr != cmdline_lpr)
-               lls_free_parse_result(cmdline_lpr, CMD_PTR);
-       if (ret < 0)
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       crypt_shutdown();
+       signal_shutdown(signal_task);
+       if (!process_is_command_handler()) { /* parent (server) */
+               mutex_destroy(mmd_mutex);
+               daemon_set_hooks(NULL, NULL); /* only one process remaining */
+               mutex_destroy(log_mutex);
+               deplete_close_on_fork_list();
+               if (ret < 0)
+                       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       } else {
+               alarm(ALARM_TIMEOUT);
+               close_listed_fds();
+               ret = handle_connect(sct->child_fd);
+       }
+       vss_shutdown();
+       shm_detach(mmd);
+       user_list_deplete();
+       free_lpr();
        exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
 }
index 988a98d..da75d86 100644 (file)
--- a/server.h
+++ b/server.h
@@ -109,6 +109,8 @@ extern struct lls_parse_result *server_lpr;
 #define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
        lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR)))
 
-__noreturn void handle_connect(int fd);
+int handle_connect(int fd);
 void parse_config_or_die(bool reload);
 char *server_get_tasks(void);
+bool process_is_command_handler(void);
+void free_lpr(void);
index 3023c7b..6973b84 100644 (file)
@@ -97,7 +97,7 @@ struct sb_context;
 /**
  * The type of a sideband transformation.
  *
- * The sideband API allows to filter all data through an arbitrary
+ * The sideband API allows the filtering of data through an arbitrary
  * transformation, which is useful for crypto purposes. The transformation may
  * either transform the data in place, or return a pointer to a new buffer
  * which contains the transformed source buffer. The internal sideband
index 6a8e5bf..7e8ea82 100755 (executable)
@@ -27,8 +27,8 @@ declare -a oggs_base=(${oggs[@]##*/})
 declare -a commands=() cmdline=() required_objects=() good=() bad=()
 i=0
 commands[$i]="help"
-cmdline[$i]="help"
-good[$i]='help ----'
+cmdline[$i]="help -l"
+good[$i]='help \{1,\}----'
 
 let i++
 commands[$i]="init"
index 38d49e3..04e2982 100644 (file)
@@ -72,8 +72,11 @@ static void udp_delete_target(struct sender_client *sc, const char *msg)
 
        PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
        udp_close_target(sc);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       /* command handlers already called close_listed_fds() */
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        vss_del_fec_client(ut->fc);
        list_del(&sc->node);
        free(sc->name);
@@ -164,6 +167,13 @@ static void udp_shutdown_targets(void)
                udp_close_target(sc);
 }
 
+static void udp_shutdown(void)
+{
+       struct sender_client *sc, *tmp;
+       list_for_each_entry_safe(sc, tmp, &targets, node)
+               udp_delete_target(sc, "shutdown");
+}
+
 static int udp_resolve_target(const char *url, struct sender_command_data *scd)
 {
        const char *result;
@@ -412,32 +422,38 @@ static char *udp_help(void)
        );
 }
 
-/**
- * The init function of para_server's udp sender.
- *
- * \param s Pointer to the udp sender struct.
- *
- * It initializes all function pointers of \a s and the list of udp targets.
- */
-void udp_send_init(struct sender *s)
+/* Initialize the list of udp targets. */
+static void udp_send_init(void)
 {
        INIT_LIST_HEAD(&targets);
-       s->status = udp_status;
-       s->help = udp_help;
-       s->send = NULL;
-       s->pre_select = NULL;
-       s->post_select = NULL;
-       s->shutdown_clients = udp_shutdown_targets;
-       s->resolve_target = udp_resolve_target;
-       s->client_cmds[SENDER_on] = udp_com_on;
-       s->client_cmds[SENDER_off] = udp_com_off;
-       s->client_cmds[SENDER_deny] = NULL;
-       s->client_cmds[SENDER_allow] = NULL;
-       s->client_cmds[SENDER_add] = udp_com_add;
-       s->client_cmds[SENDER_delete] = udp_com_delete;
        sender_status = SENDER_off;
        udp_init_target_list();
        if (!OPT_GIVEN(UDP_NO_AUTOSTART))
                sender_status = SENDER_on;
        PARA_DEBUG_LOG("udp sender init complete\n");
 }
+
+/**
+ * The UDP sender.
+ *
+ * In contrast to the other senders the UDP sender is active in the sense that
+ * it initiates the network connection according to its list of targets rather
+ * than passively waiting for clients to connect. Like DCCP streams, UDP
+ * streams are always sent FEC-encoded. The UDP sender is the only sender which
+ * supports IP multicasting.
+ */
+const struct sender udp_sender = {
+       .name = "udp",
+       .init = udp_send_init,
+       .shutdown = udp_shutdown,
+       .shutdown_clients = udp_shutdown_targets,
+       .resolve_target = udp_resolve_target,
+       .client_cmds = {
+               [SENDER_on] = udp_com_on,
+               [SENDER_off] = udp_com_off,
+               [SENDER_add] = udp_com_add,
+               [SENDER_delete] = udp_com_delete,
+       },
+       .help = udp_help,
+       .status = udp_status,
+};
index e486602..32a4309 100644 (file)
 #include "list.h"
 #include "user_list.h"
 
-static struct list_head user_list;
+static INITIALIZED_LIST_HEAD(user_list);
 
 /*
- * Fill the list of users known to para_server.
+ * Wrapper for fgets(3).
  *
- * Populates a linked list of all users in \a user_list_file.  Returns on
- * success, calls exit() on errors.
+ * Unlike fgets(3), an integer value is returned. On success, this function
+ * returns 1. On errors, -E_FGETS is returned. A zero return value indicates an
+ * end of file condition.
  */
-static void populate_user_list(char *user_list_file)
+static int xfgets(char *line, int size, FILE *f)
+{
+again:
+       if (fgets(line, size, f))
+               return 1;
+       if (feof(f))
+               return 0;
+       if (!ferror(f))
+               return -E_FGETS;
+       if (errno != EINTR) {
+               PARA_ERROR_LOG("%s\n", strerror(errno));
+               return -E_FGETS;
+       }
+       clearerr(f);
+       goto again;
+}
+
+/**
+ * Remove all entries from the user list.
+ *
+ * This is called on shutdown and when the user list is reloaded because the
+ * server received SIGHUP.
+ */
+void user_list_deplete(void)
+{
+       struct user *u, *tmpu;
+
+       list_for_each_entry_safe(u, tmpu, &user_list, node) {
+               list_del(&u->node);
+               free(u->name);
+               apc_free_pubkey(u->pubkey);
+               free(u);
+       }
+}
+
+/**
+ * Initialize the list of users allowed to connect to para_server.
+ *
+ * \param user_list_file The file containing access information.
+ *
+ * If this function is called for the second time, the contents of the
+ * previous call are discarded, i.e. the user list is reloaded.
+ *
+ * This function either succeeds or calls exit(3).
+ */
+void user_list_init(const char *user_list_file)
 {
        int ret = -E_USERLIST;
        FILE *file_ptr = fopen(user_list_file, "r");
+       struct user *u;
 
        if (!file_ptr)
                goto err;
+
+       user_list_deplete();
        for (;;) {
                int num;
                char line[255];
                /* keyword, name, key, perms */
                char w[255], n[255], k[255], p[255], tmp[4][255];
-               struct user *u;
                struct asymmetric_key *pubkey;
 
-               ret = para_fgets(line, sizeof(line), file_ptr);
+               ret = xfgets(line, sizeof(line), file_ptr);
                if (ret <= 0)
                        break;
                if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
@@ -44,22 +92,22 @@ static void populate_user_list(char *user_list_file)
                if (strcmp(w, "user"))
                        continue;
                PARA_DEBUG_LOG("found entry for user %s\n", n);
-               ret = get_public_key(k, &pubkey);
+               ret = apc_get_pubkey(k, &pubkey);
                if (ret < 0) {
                        PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n,
                                para_strerror(-ret));
                        continue;
                }
                /*
-                * In order to encrypt len := CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
+                * In order to encrypt len := APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
                 * bytes using RSA_public_encrypt() with EME-OAEP padding mode,
                 * RSA_size(rsa) must be greater than len + 41. So ignore keys
                 * which are too short. For details see RSA_public_encrypt(3).
                 */
-               if (ret <= CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
+               if (ret <= APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
                        PARA_WARNING_LOG("public key %s too short (%d)\n",
                                k, ret);
-                       free_public_key(pubkey);
+                       apc_free_pubkey(pubkey);
                        continue;
                }
                u = para_malloc(sizeof(*u));
@@ -93,32 +141,6 @@ err:
        exit(EXIT_FAILURE);
 }
 
-/**
- * Initialize the list of users allowed to connect to para_server.
- *
- * \param user_list_file The file containing access information.
- *
- * If this function is called for the second time, the contents of the
- * previous call are discarded, i.e. the user list is reloaded.
- */
-void init_user_list(char *user_list_file)
-{
-       struct user *u, *tmp;
-       static int initialized;
-
-       if (initialized) {
-               list_for_each_entry_safe(u, tmp, &user_list, node) {
-                       list_del(&u->node);
-                       free(u->name);
-                       free_public_key(u->pubkey);
-                       free(u);
-               }
-       } else
-               INIT_LIST_HEAD(&user_list);
-       initialized = 1;
-       populate_user_list(user_list_file);
-}
-
 /**
  * Lookup a user in the user list.
  *
@@ -127,9 +149,9 @@ void init_user_list(char *user_list_file)
  * \return A pointer to the corresponding user struct if the user was found, \p
  * NULL otherwise.
  */
-struct user *lookup_user(const char *name)
+const struct user *user_list_lookup(const char *name)
 {
-       struct user *u;
+       const struct user *u;
        list_for_each_entry(u, &user_list, node) {
                if (strcmp(u->name, name))
                        continue;
index 41fad16..1cb9476 100644 (file)
@@ -32,5 +32,6 @@ struct user {
        unsigned int perms;
 };
 
-void init_user_list(char *user_list_file);
-struct user *lookup_user(const char *name);
+void user_list_init(const char *user_list_file);
+void user_list_deplete(void);
+const struct user *user_list_lookup(const char *name);
diff --git a/vss.c b/vss.c
index 13a8e25..2cd0a16 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -40,24 +40,9 @@ extern void dccp_send_init(struct sender *);
 extern void http_send_init(struct sender *);
 extern void udp_send_init(struct sender *);
 
-/** The list of supported senders. */
-struct sender senders[] = {
-       {
-               .name = "http",
-               .init = http_send_init,
-       },
-       {
-               .name = "dccp",
-               .init = dccp_send_init,
-       },
-       {
-               .name = "udp",
-               .init = udp_send_init,
-       },
-       {
-               .name = NULL,
-       }
-};
+extern const struct sender udp_sender, dccp_sender, http_sender;
+const struct sender * const senders[] = {
+       &http_sender, &dccp_sender, &udp_sender, NULL};
 
 /** The possible states of the afs socket. */
 enum afs_socket_status {
@@ -298,7 +283,7 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (fcp->init_fec) {
                /*
                 * Set the maximum slice size to the Maximum Packet Size if the
-                * transport protocol allows to determine this value. The user
+                * transport protocol allows determination of this value. The user
                 * can specify a slice size up to this value.
                 */
                ret = fcp->init_fec(fc->sc);
@@ -918,10 +903,10 @@ static void vss_pre_select(struct sched *s, void *context)
                vsst->afsss = AFS_SOCKET_CHECK_FOR_WRITE;
        } else
                para_fd_set(vsst->afs_socket, &s->rfds, &s->max_fileno);
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].pre_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->pre_select)
                        continue;
-               senders[i].pre_select(&s->max_fileno, &s->rfds, &s->wfds);
+               senders[i]->pre_select(&s->max_fileno, &s->rfds, &s->wfds);
        }
        vss_compute_timeout(s, vsst);
 }
@@ -1082,10 +1067,10 @@ static void vss_send(struct vss_task *vsst)
                 * We call ->send() even if len is zero because senders might
                 * have data queued which can be sent now.
                 */
-               for (i = 0; senders[i].name; i++) {
-                       if (!senders[i].send)
+               FOR_EACH_SENDER(i) {
+                       if (!senders[i]->send)
                                continue;
-                       senders[i].send(mmd->current_chunk, mmd->chunks_sent,
+                       senders[i]->send(mmd->current_chunk, mmd->chunks_sent,
                                buf, len, vsst->header_buf, vsst->header_len);
                }
        }
@@ -1098,12 +1083,17 @@ static int vss_post_select(struct sched *s, void *context)
        int ret, i;
        struct vss_task *vsst = context;
 
+       ret = task_get_notification(vsst->task);
+       if (ret < 0) {
+               afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
+               return ret;
+       }
        if (!vsst->map || vss_next() || vss_paused() || vss_repos()) {
                /* shut down senders and fec clients */
                struct fec_client *fc, *tmp;
-               for (i = 0; senders[i].name; i++)
-                       if (senders[i].shutdown_clients)
-                               senders[i].shutdown_clients();
+               FOR_EACH_SENDER(i)
+                       if (senders[i]->shutdown_clients)
+                               senders[i]->shutdown_clients();
                list_for_each_entry_safe(fc, tmp, &fec_client_list, node)
                        fc->state = FEC_STATE_NONE;
                mmd->stream_start.tv_sec = 0;
@@ -1129,8 +1119,8 @@ static int vss_post_select(struct sched *s, void *context)
                int num = mmd->sender_cmd_data.cmd_num,
                        sender_num = mmd->sender_cmd_data.sender_num;
 
-               if (senders[sender_num].client_cmds[num]) {
-                       ret = senders[sender_num].client_cmds[num]
+               if (senders[sender_num]->client_cmds[num]) {
+                       ret = senders[sender_num]->client_cmds[num]
                                (&mmd->sender_cmd_data);
                        if (ret < 0)
                                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
@@ -1147,10 +1137,10 @@ static int vss_post_select(struct sched *s, void *context)
                else
                        vsst->afsss = AFS_SOCKET_AFD_PENDING;
        }
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].post_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->post_select)
                        continue;
-               senders[i].post_select(&s->rfds, &s->wfds);
+               senders[i]->post_select(&s->rfds, &s->wfds);
        }
        if ((vss_playing() && !(mmd->vss_status_flags & VSS_PLAYING)) ||
                        (vss_next() && vss_playing()))
@@ -1179,9 +1169,9 @@ void vss_init(int afs_socket, struct sched *s)
        ms2tv(announce_time, &vsst->announce_tv);
        PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv));
        INIT_LIST_HEAD(&fec_client_list);
-       for (i = 0; senders[i].name; i++) {
-               PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name);
-               senders[i].init(&senders[i]);
+       FOR_EACH_SENDER(i) {
+               PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name);
+               senders[i]->init();
        }
        mmd->sender_cmd_data.cmd_num = -1;
        if (OPT_GIVEN(AUTOPLAY)) {
@@ -1200,3 +1190,20 @@ void vss_init(int afs_socket, struct sched *s)
                .context = vsst,
        }, s);
 }
+
+/**
+ * Turn off the virtual streaming system.
+ *
+ * This is only executed on exit. It calls the ->shutdowwn method of all senders.
+ */
+void vss_shutdown(void)
+{
+       int i;
+
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->shutdown)
+                       continue;
+               PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name);
+               senders[i]->shutdown();
+       }
+}
diff --git a/vss.h b/vss.h
index 5ebcc4e..46bb0e7 100644 (file)
--- a/vss.h
+++ b/vss.h
@@ -9,6 +9,7 @@ unsigned int vss_repos(void);
 unsigned int vss_paused(void);
 unsigned int vss_stopped(void);
 struct timeval *vss_chunk_time(void);
+void vss_shutdown(void);
 
 /** Stop playing after current audio file. */
 #define VSS_NOMORE 1
index 1ca8a4d..9ef92b7 100644 (file)
@@ -15,8 +15,8 @@ provided at this point. There are several ways to download the source:
 
                <p> The repository contains the full history of the
                project since 2006, all work in progress and the source
-               code for the web pages. Choosing this option allows to
-               check out any of the four integration branches maint,
+               code for the web pages. Choosing this option allows the
+               checkout of any of the four integration branches maint,
                master, next, pu (see the
 
                <a href="manual.html#Git.branches">Git branches</a>
@@ -68,7 +68,7 @@ provided at this point. There are several ways to download the source:
                        <a href="http://git.tuebingen.mpg.de/paraslash.git">gitweb</a>
 
                page contains a snapshot link for each revision. This
-               allows to get a specific revision without downloading
+               allows getting a specific revision without downloading
                the full history.
 
        </li>
index fb3a05f..04c716a 100644 (file)
@@ -230,9 +230,9 @@ compatible with arbitrary HTTP streaming sources (e.g. icecast).
 In addition to the three network streaming modes, para_recv can also
 operate in local (afh) mode. In this mode it writes the content of
 an audio file on the local file system in complete chunks to stdout,
-optionally 'just in time'. This allows to cut an audio file without
-first decoding it, and it enables third-party software which is unaware
-of the particular audio format to send complete frames in real time.
+optionally 'just in time'. This allows cutting audio files without
+decoding, and it enables third-party software which is unaware of
+the particular audio format to send complete frames in real time.
 
 <h3> para_filter </h3>
 
@@ -1541,27 +1541,6 @@ currently running server process.
 
        para_client si
 
-The sender command of para_server prints information about senders,
-like the various access control lists, and it allows to (de-)activate
-senders and to change the access permissions at runtime.
-
--> List all senders
-
-       para_client sender
-
--> Obtain general help for the sender command:
-
-       para_client help sender
-
--> Get help for a specific sender (contains further examples):
-
-       s=http # or dccp or udp
-       para_client sender $s help
-
--> Show status of the http sender
-
-       para_client sender http status
-
 By default para_server activates both the HTTP and th DCCP sender on
 startup. This can be changed via command line options or para_server's
 config file.
@@ -1570,13 +1549,6 @@ config file.
 
        para_server -h
 
-All senders share the "on" and "off" commands, so senders may be
-activated and deactivated independently of each other.
-
--> Switch off the http sender:
-
-       para_client sender http off
-
 -> Receive a DCCP stream using CCID2 and write the output into a file:
 
        host=foo.org; ccid=2; filename=bar
@@ -1587,20 +1559,11 @@ receiver has its own set of command line options and its own command
 line parser, so arguments for the dccp receiver must be protected
 from being interpreted by para_recv.
 
--> Start UDP multicast, using the default multicast address:
-
-       para_client sender udp add 224.0.1.38
-
 -> Receive FEC-encoded multicast stream and write the output into a file:
 
        filename=foo
        para_recv -r udp > $filename
 
--> Add an UDP unicast for a client to the target list of the UDP sender:
-
-       t=client.foo.org
-       para_client sender udp add $t
-
 -> Receive this (FEC-encoded) unicast stream:
 
        filename=foo
@@ -1778,7 +1741,7 @@ These filters are rather simple and do not modify the audio stream at
 all. The wav filter is only useful with para_filter and in connection
 with a decoder. It asks the decoder for the number of channels and the
 sample rate of the stream and adds a Microsoft wave header containing
-this information at the beginning. This allows to write wav files
+this information at the beginning. This allows writing wav files
 rather than raw PCM files (which do not contain any information about
 the number of channels and the sample rate).
 
@@ -1792,17 +1755,6 @@ Both filters require almost no additional computing time, even when
 operating on uncompressed audio streams, since data buffers are simply
 "pushed down" rather than copied.
 
-Examples
---------
-
--> Decode an mp3 file to wav format:
-
-       para_filter -f mp3dec -f wav < file.mp3 > file.wav
-
--> Amplify a raw audio file by a factor of 1.5:
-
-       para_filter -f amp --amp 32 < foo.raw > bar.raw
-
 ======
 Output
 ======
@@ -1852,8 +1804,8 @@ emulation for backwards compatibility. This API is rather simple but
 also limited. For example only one application can open the device
 at any time. The OSS writer is activated by default on BSD Systems.
 
-- *FILE*. The file writer allows to capture the audio stream and
-write the PCM data to a file on the file system rather than playing
+- *FILE*. The file writer allows capturing the audio stream and
+writing the PCM data to a file on the file system rather than playing
 it through a sound device. It is supported on all platforms and is
 always compiled in.