From ddc2d09588f366218d48730f5eaa359ec492b44c Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 22 Oct 2017 02:06:03 +0200 Subject: Add support for secret messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- README | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- libsbus.c | 22 +++++++++++++++++++++ libsbus.h | 4 +++- sbusd.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- test.c | 1 + 5 files changed, 151 insertions(+), 10 deletions(-) diff --git a/README b/README index d5f624c..826973a 100644 --- a/README +++ b/README @@ -8,6 +8,7 @@ Features: No send-time timestamps Increasing message size limit may cause problems Support for routing keys with wildcards. + Support for secret communication. Non-features: No IP or cluster support, should be implemented as a separate service. @@ -18,25 +19,29 @@ Non-features: No support for shared queues, should be implemented as a separate service. No file descriptor passing support, not network-compatible, should be implemented as a separate service or at application level. - No support for server-verified credentials, not network-compatible + No support for server-verified credentials, can be implemented at + application level by using '!.cred.' routing keys. Routing keys: Routing keys are used to filter received messages. A routing key - may contain any byte, whoever there are two bytes with special - meaning: '*' and '.'. '*' should not be used in routin keys, but - only in routing key patterns, it matches until the next '.' or + may contain any byte, whoever there are three bytes with special + meaning: '*', '.', and '!'. '*' should not be used in routing keys, + but only in routing key patterns, it matches until the next '.' or end if there are not more '.'s. Additionally if a routing key pattern ends with '.' that '.' will match to a '.' and any subsequent byte. For example 'a.*.c.' will match 'a.b.c.' and - 'a.b.c.d.e' but not 'a.b.c' or 'a.c.d'. And empty routiung key - pattern shall match everything. Routing keys starting with '!' - are reserved. + 'a.b.c.d.e' but not 'a.b.c' or 'a.c.d'. And empty routing key + pattern shall match everything. The token '!' is reserved, a client + should never use '!' for any other purpose than specified in the + protocol, unless it has an other byte than a '.' next to it. The + server may choose to disconnect a client using '!' in an invalid + way or simply ignore such messages. Protocol: Communication is done over unix domain, sequenced-packet sockets. Each packet is interpreted as a complete message. A packet cannot contain multiple message, and a message cannot be split over - multiple packets. There are 3 types of messages: + multiple packets. There are 4 types of messages: Subscribe: Send a routing key pattern to the server for the client. @@ -77,3 +82,49 @@ Protocol: where \1 is the routing key for the message and \2 is the message payload. + + Control message: + Send a control message to the server. The server may + also send control message to clients. The server will + never forward control message it receives, and no + subscriptions are required to receive the messages. + + Messages of this type shall match the regular expression + + ^CMSG \([^\x00]*\)\(\x00\(.*\)\)$ + + where \1 is the routing key for the message and \3 + is the message payload. \2 may be om be omitted if + sent by the client. + +Secret messages: + Routing keys starting with '!.cred.' can only be subscribed to + by clients with matching credentials. These routing keys look + match this regular expression + + ^!\.cred\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\(.*\)$ + + where \1 is the group ID, \2 is the user ID, \3 is the process + ID, and \4 the rest of routing key which may also use this + pattern a process it communicates. Client can only subscribe + to the routing key \1, \2, and \3 match it's credentials. + If \1, \2, or \3 are empty, they will match the clients it's + credentials when subscribing. However, the server will not + accept '*' for \1, \2, or \3, or truncated routing key.s + + However, due to network support, these routing keys may need + to be prefixed with the credentials for the servers the message + goes through. This prefix can be retrieved by simply sending an + empty control message (CMSG) with the routing key '!.cred.prefix' + and the server will reply with a control message containing prefix + using this routing key. Note, prefix is probably the empty string, + as the master server do not need to add its credentials to be + prefixed. Note, the server will never send control messages, so + received control message are guaranteed to come from the server. + + Example of how two client can prove their identities to each oter: + + A: Send A's credentials to B. + B: Send B's credentials with routing key private to A. + A: Send a random message with routing key private to B. + B: Send back the message with routing key private to A. diff --git a/libsbus.c b/libsbus.c index 88e6a31..ab8d3fe 100644 --- a/libsbus.c +++ b/libsbus.c @@ -59,6 +59,20 @@ libsbuf_prepare_message(const char *key, char *buf, size_t *remaining) return (ssize_t)len; } +int +libsbus_send_cmsg(int fd, const char *key, const char *msg, size_t n, int flags, char *buf) +{ + size_t len = strlen(key) + 1; + if (len + n > LIBSBUS_BUFFER_SIZE - 5) { + errno = EMSGSIZE; + return -1; + } + buf[0] = 'C', buf[1] = 'M', buf[2] = 'S', buf[3] = 'G', buf[4] = ' '; + memcpy(&buf[5], key, len); + memcpy(&buf[5 + len], msg, n); + return -(send(fd, buf, len + n + 5, flags) < 0); +} + int libsbus_receive(int fd, int flags, char *buf, union libsbus_packet *packet) { @@ -80,6 +94,14 @@ libsbus_receive(int fd, int flags, char *buf, union libsbus_packet *packet) packet->message.key = &buf[4]; packet->message.msg = p; packet->message.n = (size_t)(r - (p - buf)); + } else if (r >= 5 && !strncmp(buf, "CMSG ", 5)) { + p = memchr(buf, '\0', r); + if (!p++) + goto unknown; + packet->type = LIBSBUS_CONTROL_MESSAGE; + packet->message.key = &buf[4]; + packet->message.msg = p; + packet->message.n = (size_t)(r - (p - buf)); } else { unknown: packet->type = LIBSBUS_UNKNOWN; diff --git a/libsbus.h b/libsbus.h index 006b0e2..6ac4eed 100644 --- a/libsbus.h +++ b/libsbus.h @@ -8,7 +8,8 @@ enum libsbus_packet_type { LIBSBUS_UNKNOWN, - LIBSBUS_MESSAGE + LIBSBUS_MESSAGE, + LIBSBUS_CONTROL_MESSAGE }; struct libsbus_unknown { @@ -32,6 +33,7 @@ union libsbus_packet { int libsbus_subscribe(int fd, const char *pattern, int flags, char *buf); int libsbus_unsubscribe(int fd, const char *pattern, int flags, char *buf); int libsbus_publish(int fd, const char *key, const char *msg, size_t n, int flags, char *buf); +int libsbus_send_cmsg(int fd, const char *key, const char *msg, size_t n, int flags, char *buf); ssize_t libsbuf_prepare_message(const char *key, char *buf, size_t *remaining); int libsbus_receive(int fd, int flags, char *buf, union libsbus_packet *packet); diff --git a/sbusd.c b/sbusd.c index 8e4a060..17e6761 100644 --- a/sbusd.c +++ b/sbusd.c @@ -175,11 +175,53 @@ is_subscribed(const struct client *cl, const char *key) return 0; } +static int +is_subscription_acceptable(struct client *cl, const char *key) +{ + struct ucred cred; + long long int tmp; + const char *p; + if (!strncmp(key, "!.cred.", sizeof("!.cred.") - 1)) { + if (getsockopt(cl->fd, SOL_SOCKET, SO_PEERCRED, &cred, &(socklen_t){sizeof(cred)}) < 0) { + weprintf("getsockopt SOL_SOCKET SO_PEERCRED:"); + return -1; + } + errno = 0; + p = &key[sizeof("!.cred.") - 1]; +#define TEST_CRED(ID)\ + if (!*p) {\ + return 0;\ + } else if (*p++ != '.') {\ + if (!isdigit(*p))\ + return 0;\ + tmp = strtoll(p, (void *)&p, 10);\ + if (errno || (*p && *p != '.') || (ID##_t)tmp != cred.ID)\ + return 0;\ + } + TEST_CRED(gid); + TEST_CRED(uid); + TEST_CRED(pid); +#undef TEST_CRED + } + return 1; +} + static void add_subscription(struct client *cl, const char *key) { size_t n; char **new, *k; + switch (is_subscription_acceptable(cl, key)) { + case -1: + remove_client(cl); + return; + case 0: + weprintf("client subscribed unacceptable routing key\n"); + remove_client(cl); + return; + default: + break; + } if (cl->subs_siz == cl->nsubs) { n = cl->subs_siz ? (cl->subs_siz << 1) : 1; new = realloc(cl->subs, n * sizeof(char *)); @@ -221,6 +263,27 @@ remove_subscription(struct client *cl, const char *key) } } +static int +send_packet(struct client *cl, const char *buf, size_t n) +{ + /* TODO queue instead of block */ + return -(send(cl->fd, buf, n, 0) < 0); +} + +static void +handle_cmsg(struct client *cl, const char *msg, size_t n) +{ + if (!strcmp(msg, "CMSG !.cred.prefix")) { + n = sizeof("CMSG !.cred.prefix"); + } else { + return; + } + if (send_packet(cl, msg, n)) { + weprintf("send :"); + remove_client(cl); + } +} + static void broadcast(const char *msg, size_t n) { @@ -228,7 +291,7 @@ broadcast(const char *msg, size_t n) for (; cl->next; cl = cl->next) { if (!is_subscribed(cl, &msg[4])) continue; - if (send(cl->fd, msg, n, 0) < 0) { /* TODO queue instead of block */ + if (send_packet(cl, msg, n)) { cl = (tmp = cl)->prev; weprintf("send :"); remove_client(tmp); @@ -257,6 +320,8 @@ handle_message(struct client *cl) remove_subscription(cl, &buf[6]); } else if (!strncmp(buf, "SUB ", 4)) { add_subscription(cl, &buf[4]); + } else if (!strncmp(buf, "CMSG ", 5)) { + handle_cmsg(cl, buf, r); } else { weprintf("received bad message\n"); remove_client(cl); diff --git a/test.c b/test.c index 62113d9..43864b8 100644 --- a/test.c +++ b/test.c @@ -354,3 +354,4 @@ main(void) } /* TODO untested sbusd flags: -p[/dev/null] (-f) -u */ +/* TODO test credentials */ -- cgit v1.2.3-70-g09d2