server: Do not fail if client does not request sideband.
[paraslash.git] / command.c
index 80584510b88437a6272c5490c455c8721b9759ba..0eb4efc22fa564fd2c055aa0753392696ad49a2f 100644 (file)
--- a/command.c
+++ b/command.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1997-2014 Andre Noll <maan@tuebingen.mpg.de>
+ * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 #include "signal.h"
 #include "version.h"
 
+typedef int server_command_handler_t(struct command_context *);
+static server_command_handler_t SERVER_COMMAND_HANDLERS;
+server_command_handler_t AFS_COMMAND_HANDLERS;
+
+/* Defines one command of para_server. */
+struct server_command {
+       /* The name of the command. */
+       const char *name;
+       /* Pointer to the function that handles the command. */
+       server_command_handler_t *handler;
+       /* The privileges a user must have to execute this command. */
+       unsigned int perms;
+       /* One-line description of the command. */
+       const char *description;
+       /* Summary of the command line options. */
+       const char *usage;
+       /* The long help text. */
+       const char *help;
+};
+
 static struct server_command afs_cmds[] = {DEFINE_AFS_CMD_ARRAY};
 static struct server_command server_cmds[] = {DEFINE_SERVER_CMD_ARRAY};
 
@@ -55,20 +75,14 @@ static void dummy(__a_unused int s)
 {
 }
 
-static void mmd_dup(struct misc_meta_data *new_mmd)
-{
-       mutex_lock(mmd_mutex);
-       *new_mmd = *mmd;
-       mutex_unlock(mmd_mutex);
-}
-
 /*
- * Compute human readable string containing vss status for given integer value.
+ * Compute human readable vss status text.
  *
- * We don't want to use vss_playing() and friends here because we take a
- * snapshot of the mmd struct and use the copy for computing the state of the
- * vss. If the real data were used, we would take the mmd lock for a rather
- * long time or risk to get an inconsistent view.
+ * We can't call vss_playing() and friends here because those functions read
+ * the flags from the primary mmd structure, so calling them from command
+ * handler context would require to take the mmd lock. At the time the function
+ * is called we already took a copy of the mmd structure and want to use the
+ * flags value of the copy for computing the vss status text.
  */
 static char *vss_status_tohuman(unsigned int flags)
 {
@@ -112,21 +126,15 @@ static char *vss_get_status_flags(unsigned int flags)
 static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
                char **result)
 {
-       char mtime[30] = "";
        char *status, *flags; /* vss status info */
        /* nobody updates our version of "now" */
        long offset = (nmmd->offset + 500) / 1000;
        struct timeval current_time;
-       struct tm mtime_tm;
        struct para_buffer b = {.flags = parser_friendly? PBF_SIZE_PREFIX : 0};
 
        /* report real status */
        status = vss_status_tohuman(nmmd->vss_status_flags);
        flags = vss_get_status_flags(nmmd->vss_status_flags);
-       if (nmmd->size) { /* parent currently has an audio file open */
-               localtime_r(&nmmd->mtime, &mtime_tm);
-               strftime(mtime, 29, "%b %d %Y", &mtime_tm);
-       }
        clock_get_realtime(&current_time);
        /*
         * The calls to WRITE_STATUS_ITEM() below never fail because
@@ -134,8 +142,6 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
         * is not smart enough to prove this and complains nevertheless.
         * Casting the return value to void silences clang.
         */
-       (void)WRITE_STATUS_ITEM(&b, SI_FILE_SIZE, "%zu\n", nmmd->size / 1024);
-       (void)WRITE_STATUS_ITEM(&b, SI_MTIME, "%s\n", mtime);
        (void)WRITE_STATUS_ITEM(&b, SI_STATUS, "%s\n", status);
        (void)WRITE_STATUS_ITEM(&b, SI_STATUS_FLAGS, "%s\n", flags);
        (void)WRITE_STATUS_ITEM(&b, SI_OFFSET, "%li\n", offset);
@@ -155,11 +161,10 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
 static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd)
 {
        int i;
-       /* this has to match sender.h */
-       const char *subcmds[] = {"add", "delete", "allow", "deny", "on", "off", NULL};
 
+       const char *subcmds[] = {SENDER_SUBCOMMANDS NULL};
        scd->sender_num = -1;
-       if (argc < 2)
+       if (argc < 3)
                return -E_COMMAND_SYNTAX;
        for (i = 0; senders[i].name; i++)
                if (!strcmp(senders[i].name, argv[1]))
@@ -177,19 +182,19 @@ static int check_sender_args(int argc, char * const * argv, struct sender_comman
        if (!senders[scd->sender_num].client_cmds[scd->cmd_num])
                return -E_SENDER_CMD;
        switch (scd->cmd_num) {
-       case SENDER_ON:
-       case SENDER_OFF:
+       case SENDER_on:
+       case SENDER_off:
                if (argc != 3)
                        return -E_COMMAND_SYNTAX;
                break;
-       case SENDER_DENY:
-       case SENDER_ALLOW:
+       case SENDER_deny:
+       case SENDER_allow:
                if (argc != 4 || parse_cidr(argv[3], scd->host,
                                sizeof(scd->host), &scd->netmask) == NULL)
                        return -E_COMMAND_SYNTAX;
                break;
-       case SENDER_ADD:
-       case SENDER_DELETE:
+       case SENDER_add:
+       case SENDER_delete:
                if (argc != 4)
                        return -E_COMMAND_SYNTAX;
                return parse_fec_url(argv[3], scd);
@@ -210,7 +215,7 @@ static int check_sender_args(int argc, char * const * argv, struct sender_comman
  *
  * The nonblock flag must be disabled for the file descriptor given by \a scc.
  *
- * Stream cipher encryption is automatically activated if neccessary via the
+ * Stream cipher encryption is automatically activated if necessary via the
  * sideband transformation, depending on the value of \a band.
  *
  * \return Standard.
@@ -352,8 +357,8 @@ static int com_sender(struct command_context *cc)
        }
 
        switch (scd.cmd_num) {
-       case SENDER_ADD:
-       case SENDER_DELETE:
+       case SENDER_add:
+       case SENDER_delete:
                assert(senders[scd.sender_num].resolve_target);
                ret = senders[scd.sender_num].resolve_target(cc->argv[3], &scd);
                if (ret < 0)
@@ -363,8 +368,10 @@ static int com_sender(struct command_context *cc)
        for (i = 0; i < 10; i++) {
                mutex_lock(mmd_mutex);
                if (mmd->sender_cmd_data.cmd_num >= 0) {
+                       /* another sender command is active, retry in 100ms */
+                       struct timespec ts = {.tv_nsec = 100 * 1000 * 1000};
                        mutex_unlock(mmd_mutex);
-                       usleep(100 * 1000);
+                       nanosleep(&ts, NULL);
                        continue;
                }
                mmd->sender_cmd_data = scd;
@@ -411,12 +418,14 @@ static int com_version(struct command_context *cc)
        char *msg;
        size_t len;
 
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
-       len = xasprintf(&msg, "%s", version_text("server"));
+       if (cc->argc > 1 && strcmp(cc->argv[1], "-v") == 0)
+               len = xasprintf(&msg, "%s", version_text("server"));
+       else
+               len = xasprintf(&msg, "%s\n", version_single_line("server"));
        return send_sb(&cc->scc, msg, len, SBD_OUTPUT, false);
 }
 
+/** These status items are cleared if no audio file is currently open. */
 #define EMPTY_STATUS_ITEMS \
        ITEM(PATH) \
        ITEM(DIRECTORY) \
@@ -443,7 +452,11 @@ static int com_version(struct command_context *cc)
        ITEM(YEAR) \
        ITEM(ALBUM) \
        ITEM(COMMENT) \
-       ITEM(AMPLIFICATION)
+       ITEM(MTIME) \
+       ITEM(FILE_SIZE) \
+       ITEM(CHUNK_TIME) \
+       ITEM(NUM_CHUNKS) \
+       ITEM(AMPLIFICATION) \
 
 /**
  * Write a list of audio-file related status items with empty values.
@@ -512,7 +525,13 @@ static int com_stat(struct command_context *cc)
        if (i != cc->argc)
                return -E_COMMAND_SYNTAX;
        for (;;) {
-               mmd_dup(nmmd);
+               /*
+                * Copy the mmd structure to minimize the time we hold the mmd
+                * lock.
+                */
+               mutex_lock(mmd_mutex);
+               *nmmd = *mmd;
+               mutex_unlock(mmd_mutex);
                ret = get_status(nmmd, parser_friendly, &s);
                ret = send_sb(&cc->scc, s, ret, SBD_OUTPUT, false);
                if (ret < 0)
@@ -716,7 +735,7 @@ static int com_ff(struct command_context *cc)
                promille += 1000 * i / mmd->afd.afhi.seconds_total;
        if (promille < 0)
                promille = 0;
-       if (promille >  1000) {
+       if (promille > 1000) {
                mmd->new_vss_status_flags |= VSS_NEXT;
                goto out;
        }
@@ -747,8 +766,8 @@ static int com_jmp(struct command_context *cc)
        if (i > 100)
                i = 100;
        PARA_INFO_LOG("jumping to %lu%%\n", i);
-       mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50)/ 100;
-       PARA_INFO_LOG("sent: %lu,  offset before jmp: %lu\n",
+       mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50) / 100;
+       PARA_INFO_LOG("sent: %lu, offset before jmp: %lu\n",
                mmd->chunks_sent, mmd->offset);
        mmd->new_vss_status_flags |= VSS_REPOS;
        mmd->new_vss_status_flags &= ~VSS_NEXT;
@@ -759,11 +778,21 @@ out:
        return ret;
 }
 
+static int com_tasks(struct command_context *cc)
+{
+       char *tl = server_get_tasks();
+       int ret = 1;
+
+       if (tl)
+               ret = send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false);
+       return ret;
+}
+
 /*
  * check if perms are sufficient to exec a command having perms cmd_perms.
  * Returns 0 if perms are sufficient, -E_PERM otherwise.
  */
-static int check_perms(unsigned int perms, struct server_command *cmd_ptr)
+static int check_perms(unsigned int perms, const struct server_command *cmd_ptr)
 {
        PARA_DEBUG_LOG("checking permissions\n");
        return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
@@ -778,7 +807,6 @@ static void reset_signals(void)
 }
 
 struct connection_features {
-       bool sideband_requested;
        bool aes_ctr128_requested;
 };
 
@@ -806,8 +834,8 @@ static int parse_auth_request(char *buf, int len, struct user **u,
                create_argv(p, ",", &features);
                for (i = 0; features[i]; i++) {
                        if (strcmp(features[i], "sideband") == 0)
-                               cf->sideband_requested = true;
-                       else if (strcmp(features[i], "aes_ctr128") == 0)
+                               continue;
+                       if (strcmp(features[i], "aes_ctr128") == 0)
                                cf->aes_ctr128_requested = true;
                        else {
                                ret = -E_BAD_FEATURE;
@@ -825,22 +853,23 @@ out:
 
 #define HANDSHAKE_BUFSIZE 4096
 
-static int parse_sb_command(struct command_context *cc, struct iovec *iov)
+static int run_command(struct command_context *cc, struct iovec *iov,
+               const char *peername)
 {
        int ret, i;
        char *p, *end;
+       struct server_command *cmd;
 
-       ret = -E_BAD_CMD;
        if (iov->iov_base == NULL || iov->iov_len == 0)
-               goto out;
+               return -E_BAD_CMD;
        p = iov->iov_base;
        p[iov->iov_len - 1] = '\0'; /* just to be sure */
-       cc->cmd = get_cmd_ptr(p, NULL);
-       if (!cc->cmd)
-               goto out;
-       ret = check_perms(cc->u->perms, cc->cmd);
+       cmd = get_cmd_ptr(p, NULL);
+       if (!cmd)
+               return -E_BAD_CMD;
+       ret = check_perms(cc->u->perms, cmd);
        if (ret < 0)
-               goto out;
+               return ret;
        end = iov->iov_base + iov->iov_len;
        for (i = 0; p < end; i++)
                p += strlen(p) + 1;
@@ -851,9 +880,15 @@ static int parse_sb_command(struct command_context *cc, struct iovec *iov)
                p += strlen(p) + 1;
        }
        cc->argv[cc->argc] = NULL;
-       ret = cc->argc;
-out:
-       free(iov->iov_base);
+       PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name,
+               cc->u->name, peername);
+       ret = cmd->handler(cc);
+       free_argv(cc->argv);
+       mutex_lock(mmd_mutex);
+       mmd->num_commands++;
+       if (ret >= 0 && (cmd->perms & AFS_WRITE))
+               mmd->events++;
+       mutex_unlock(mmd_mutex);
        return ret;
 }
 
@@ -863,24 +898,22 @@ out:
  * \param fd The file descriptor to send output to.
  * \param peername Identifies the connecting peer.
  *
- * Whenever para_server accepts an incoming tcp connection on
- * the port it listens on, it forks and the resulting child
- * calls this function.
+ * Whenever para_server accepts an incoming tcp connection on the port it
+ * listens on, it forks and the resulting child calls this function.
  *
- * An RSA-based challenge/response is used to authenticate
- * the peer. It that authentication succeeds, a random
- * session key is generated and sent back to the peer,
- * encrypted with its RSA public key.  From this point on,
- * all transfers are crypted with this session key.
+ * An RSA-based challenge/response is used to authenticate the peer. It that
+ * authentication succeeds, a random session key is generated and sent back to
+ * the peer, encrypted with its RSA public key. From this point on, all
+ * transfers are crypted with this session key.
  *
- * Next it is checked if the peer supplied  a valid server command or a command
- * for the audio file selector.  If yes, and if the user has sufficient
+ * Next it is checked if the peer supplied a valid server command or a command
+ * for the audio file selector. If yes, and if the user has sufficient
  * permissions to execute that command, the function calls the corresponding
  * command handler which does argument checking and further processing.
  *
- * In order to cope with a DOS attacks, a timeout is set up
- * which terminates the function if the connection was not
- * authenticated when the timeout expires.
+ * In order to cope with a DOS attacks, a timeout is set up which terminates
+ * the function if the connection was not authenticated when the timeout
+ * expires.
  *
  * \sa alarm(2), crypt.c, crypt.h
  */
@@ -903,7 +936,7 @@ __noreturn void handle_connect(int fd, const char *peername)
                goto net_err;
        /* send Welcome message */
        ret = write_va_buffer(fd, "This is para_server, version "
-               PACKAGE_VERSION  ".\n"
+               PACKAGE_VERSION ".\n"
                "Features: sideband,aes_ctr128\n"
        );
        if (ret < 0)
@@ -915,11 +948,6 @@ __noreturn void handle_connect(int fd, const char *peername)
        ret = parse_auth_request(buf, ret, &cc->u, &cf);
        if (ret < 0)
                goto net_err;
-       if (!cf.sideband_requested) { /* sideband is mandatory */
-               PARA_ERROR_LOG("client did not request sideband\n");
-               ret = -E_BAD_FEATURE;
-               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),
@@ -976,17 +1004,10 @@ __noreturn void handle_connect(int fd, const char *peername)
        ret = recv_sb(&cc->scc, SBD_COMMAND, MAX_COMMAND_LEN, &iov);
        if (ret < 0)
                goto net_err;
-       ret = parse_sb_command(cc, &iov);
+       ret = run_command(cc, &iov, peername);
+       free(iov.iov_base);
        if (ret < 0)
                goto err_out;
-       cc->argc = ret;
-       PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cc->cmd->name,
-               cc->u->name, peername);
-       ret = cc->cmd->handler(cc);
-       free_argv(cc->argv);
-       mutex_lock(mmd_mutex);
-       mmd->num_commands++;
-       mutex_unlock(mmd_mutex);
        if (ret >= 0)
                goto out;
 err_out:
@@ -998,8 +1019,6 @@ out:
        free(buf);
        free(command);
        mutex_lock(mmd_mutex);
-       if (cc->cmd && (cc->cmd->perms & AFS_WRITE) && ret >= 0)
-               mmd->events++;
        mmd->active_connections--;
        mutex_unlock(mmd_mutex);
        if (ret >= 0) {