Implement client-server feature negotiation.
authorAndre Noll <maan@systemlinux.org>
Sat, 3 Dec 2011 13:42:12 +0000 (14:42 +0100)
committerAndre Noll <maan@systemlinux.org>
Sat, 5 May 2012 10:54:53 +0000 (12:54 +0200)
The server announces a list of supported features and the client may
request any subset of features.

The only supported feature is "sideband". It has no effect at the
moment but this will change in subsequent patches.

TODO: Documentation, remove "foo" feature.

client.h
client_common.c
command.c
command.h
error.h
string.c
string.h

index 8490b72f8f598aa15798e0a4746fd9176370bebd..a423406238204c3ba9b86e066fc82fead3dc16d9 100644 (file)
--- a/client.h
+++ b/client.h
@@ -32,6 +32,10 @@ struct client_task {
        int status;
        /** The file descriptor and the session keys. */
        struct stream_cipher_context scc;
+       /** True if this connections uses the sideband API. */
+       bool use_sideband;
+       /** The sideband context, ignored if \a use_sideband is false. */
+       struct sb_context *sbc;
        /** The configuration (including the command). */
        struct client_args_info conf;
        /** The config file for client options. */
@@ -44,6 +48,8 @@ struct client_task {
        struct task task;
        /** The buffer tree node of the client task. */
        struct btr_node *btrn;
+       /** List of features supported by the server. */
+       char **features;
 };
 
 void client_disconnect(struct client_task *ct);
index 5bd2241b8d933fb69309560d5b2056ed6438cdd9..e34ed641401c7b91f612e8e5922c0fbd19baa0ac 100644 (file)
@@ -42,6 +42,7 @@ void client_disconnect(struct client_task *ct)
                return;
        if (ct->scc.fd >= 0)
                close(ct->scc.fd);
+       free_argv(ct->features);
        sc_free(ct->scc.recv);
        ct->scc.recv = NULL;
        sc_free(ct->scc.send);
@@ -152,6 +153,31 @@ static int client_recv_buffer(struct client_task *ct, fd_set *rfds,
        return 0;
 }
 
+static char **parse_features(char *buf)
+{
+       int i;
+       const char id[] = "\nFeatures: ";
+       char *p, *q, **features;
+
+       p = strstr(buf, id);
+       if (!p)
+               return NULL;
+       p += strlen(id);
+       q = strchr(p, '\n');
+       if (!q)
+               return NULL;
+       *q = '\0';
+       create_argv(p, ",", &features);
+       for (i = 0; features[i]; i++)
+               PARA_INFO_LOG("server feature: %s\n", features[i]);
+       return features;
+}
+
+static bool has_feature(const char *feature, struct client_task *ct)
+{
+       return find_arg(feature, ct->features) >= 0? true : false;
+}
+
 /**
  * The post select hook for client commands.
  *
@@ -181,13 +207,18 @@ static void client_post_select(struct sched *s, struct task *t)
                ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n);
                if (ret < 0 || n == 0)
                        goto out;
+               ct->features = parse_features(buf);
                ct->status = CL_RECEIVED_WELCOME;
                return;
        case CL_RECEIVED_WELCOME: /* send auth command */
-               sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user);
-               PARA_INFO_LOG("--> %s\n", buf);
                if (!FD_ISSET(ct->scc.fd, &s->wfds))
                        return;
+               if (has_feature("sideband", ct)) {
+                       ct->use_sideband = true;
+                       sprintf(buf, AUTH_REQUEST_MSG "%s sideband", ct->user);
+               } else
+                       sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user);
+               PARA_INFO_LOG("--> %s\n", buf);
                ret = write_buffer(ct->scc.fd, buf);
                if (ret < 0)
                        goto out;
index f520e3ea0ac7ff3d210d0d9c30b0a510302dba46..cdc42fa1a220662ac7413b52fc40aba0f56e14d8 100644 (file)
--- a/command.c
+++ b/command.c
@@ -701,6 +701,46 @@ static void reset_signals(void)
        para_sigaction(SIGHUP, SIG_DFL);
 }
 
+static int parse_auth_request(char *buf, int len, struct user **u,
+               bool *use_sideband)
+{
+       int ret;
+       char *p, *username, **features = NULL;
+       size_t auth_rq_len = strlen(AUTH_REQUEST_MSG);
+
+       *u = NULL;
+       *use_sideband = false;
+       if (len < auth_rq_len + 2)
+               return -E_AUTH_REQUEST;
+       if (strncmp(buf, AUTH_REQUEST_MSG, auth_rq_len) != 0)
+               return -E_AUTH_REQUEST;
+       username = buf + auth_rq_len;
+       p = strchr(username, ' ');
+       if (p) {
+               int i;
+               if (p == username)
+                       return -E_AUTH_REQUEST;
+               *p = '\0';
+               p++;
+               create_argv(p, ",", &features);
+               for (i = 0; features[i]; i++) {
+                       if (strcmp(features[i], "sideband") == 0)
+                               *use_sideband = true;
+                       else {
+                               ret = -E_BAD_FEATURE;
+                               goto out;
+                       }
+               }
+       }
+       PARA_DEBUG_LOG("received auth request for user %s (sideband = %s)\n",
+               username, *use_sideband? "true" : "false");
+       *u = lookup_user(username);
+       ret = 1;
+out:
+       free_argv(features);
+       return ret;
+}
+
 /**
  * Perform user authentication and execute a command.
  *
@@ -746,19 +786,17 @@ __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,foo\n"
+       );
        if (ret < 0)
                goto net_err;
        /* recv auth request line */
        ret = recv_buffer(fd, buf, sizeof(buf));
        if (ret < 0)
                goto net_err;
-       if (ret < 10) {
-               ret = -E_AUTH_REQUEST;
-               goto net_err;
-       }
-       ret = -E_AUTH_REQUEST;
-       if (strncmp(buf, AUTH_REQUEST_MSG, strlen(AUTH_REQUEST_MSG)))
+       ret = parse_auth_request(buf, ret, &cc->u, &cc->use_sideband);
+       if (ret < 0)
                goto net_err;
        p = buf + strlen(AUTH_REQUEST_MSG);
        PARA_DEBUG_LOG("received auth request for user %s\n", p);
index 05510cc08aa33d50a104f71a05d353ad3027e7d2..851c00ba2f4ab4450493d29f878ea65125462588 100644 (file)
--- a/command.h
+++ b/command.h
@@ -14,6 +14,8 @@ struct command_context {
        struct server_command *cmd;
        /** File descriptor and crypto keys. */
        struct stream_cipher_context scc;
+       /** Whether to use the sideband API for this command. */
+       bool use_sideband;
 };
 
 /**
diff --git a/error.h b/error.h
index cfd2b1bdccb29ad4dede7f4f4988ba60f0000411..a79cb37b58fa773102e2ec8afd7660d745c661fe 100644 (file)
--- a/error.h
+++ b/error.h
@@ -352,7 +352,8 @@ extern const char **para_errlist[];
        PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \
        PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \
        PARA_ERROR(SIZE_PREFIX, "bad size prefix"), \
-       PARA_ERROR(REGEX, "regular expression error") \
+       PARA_ERROR(REGEX, "regular expression error"), \
+       PARA_ERROR(ARG_NOT_FOUND, "argument not found in arg vector"), \
 
 
 #define EXEC_ERRORS \
@@ -431,6 +432,7 @@ extern const char **para_errlist[];
        PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \
        PARA_ERROR(SERVER_CRASH, "para_server crashed -- can not live without it"), \
        PARA_ERROR(BAD_USER, "auth request for invalid user"), \
+       PARA_ERROR(BAD_FEATURE, "request for unknown or invalid feature"), \
        PARA_ERROR(BAD_AUTH, "authentication failure"), \
 
 
index aa3bcbddabe425cc823e3ad5e567f707f70dbccb..7123ba1ae5e00dfc13e1417bfbafd102c3946175 100644 (file)
--- a/string.c
+++ b/string.c
@@ -855,6 +855,27 @@ err:
        return ret;
 }
 
+/**
+ * Find out if the given string is contained in the arg vector.
+ *
+ * \param arg The string to look for.
+ * \param argv The array to search.
+ *
+ * \return The first index whose value equals \a arg, or \p -E_ARG_NOT_FOUND if
+ * arg was not found in \a argv.
+ */
+int find_arg(const char *arg, char **argv)
+{
+       int i;
+
+       if (!argv)
+               return -E_ARG_NOT_FOUND;
+       for (i = 0; argv[i]; i++)
+               if (strcmp(arg, argv[i]) == 0)
+                       return i;
+       return -E_ARG_NOT_FOUND;
+}
+
 /**
  * Compile a regular expression.
  *
index cdc55d2da64cef1da942543608602b0a6592af2e..aebb6516afb6e0de78811d1329b012d5edd5b348 100644 (file)
--- a/string.h
+++ b/string.h
@@ -83,6 +83,7 @@ int para_atoi32(const char *str, int32_t *value);
 int get_loglevel_by_name(const char *txt);
 int read_size_header(const char *buf);
 int create_argv(const char *buf, const char *delim, char ***result);
+int find_arg(const char *arg, char **argv);
 void free_argv(char **argv);
 int para_regcomp(regex_t *preg, const char *regex, int cflags);
 void freep(void *arg);