aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README67
-rw-r--r--libsbus.c22
-rw-r--r--libsbus.h4
-rw-r--r--sbusd.c67
-rw-r--r--test.c1
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
@@ -60,6 +60,20 @@ libsbuf_prepare_message(const char *key, char *buf, size_t *remaining)
}
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)
{
ssize_t r;
@@ -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 <client> 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 <client>:");
+ 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 <client>:");
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 */