diff --git a/aucat/amsg.h b/aucat/amsg.h index 057a912..fc08662 100644 --- a/aucat/amsg.h +++ b/aucat/amsg.h @@ -41,6 +41,7 @@ struct amsg { #define AMSG_SETVOL 9 /* set volume */ #define AMSG_HELLO 10 /* say hello, check versions and so ... */ #define AMSG_BYE 11 /* ask server to drop connection */ +#define AMSG_AUTH 12 /* send authentication cookie */ uint32_t cmd; uint32_t __pad; union { @@ -79,6 +80,10 @@ struct amsg { char opt[12]; /* profile name */ char who[12]; /* hint for leases */ } hello; + struct amsg_auth { +#define AMSG_COOKIELEN 16 + uint8_t cookie[AMSG_COOKIELEN]; + } auth; } u; }; diff --git a/aucat/aucat.1 b/aucat/aucat.1 index eefe711..a491ab2 100644 --- a/aucat/aucat.1 +++ b/aucat/aucat.1 @@ -533,12 +533,14 @@ Note that the sequencer must use .Va aucat:0 as the MTC source, i.e. the audio server, not the audio player. .Sh ENVIRONMENT -.Bl -tag -width "AUDIODEVICE" -compact +.Bl -tag -width "AUCAT_COOKIE" -compact .It Ev AUDIODEVICE .Xr sndio 7 audio device to use if the .Fl f option is not specified. +.It Ev AUCAT_COOKIE +file containing a user's session cookie. .El .Sh EXAMPLES The following will mix and play two stereo streams, diff --git a/aucat/sock.c b/aucat/sock.c index 1ca9fd2..6434949 100644 --- a/aucat/sock.c +++ b/aucat/sock.c @@ -96,15 +96,16 @@ struct ctl_ops ctl_sockops = { sock_quitreq }; -unsigned sock_sesrefs = 0; /* connections to the session */ -uid_t sock_sesuid; /* owner of the session */ +unsigned sock_sesrefs = 0; /* connections to the session */ +uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */ void sock_close(struct file *arg) { struct sock *f = (struct sock *)arg; - sock_sesrefs--; + if (f->pstate != SOCK_AUTH) + sock_sesrefs--; pipe_close(&f->pipe.file); if (f->dev) { dev_unref(f->dev); @@ -337,22 +338,13 @@ sock_new(struct fileops *ops, int fd) close(fd); return NULL; } - if (sock_sesrefs == 0) { - /* start a new session */ - sock_sesuid = uid; - } else if (uid != sock_sesuid) { - /* session owned by another user, drop connection */ - close(fd); - return NULL; - } - sock_sesrefs++; f = (struct sock *)pipe_new(ops, fd, "sock"); if (f == NULL) { close(fd); return NULL; } - f->pstate = SOCK_HELLO; + f->pstate = SOCK_AUTH; f->mode = 0; f->opt = NULL; f->dev = NULL; @@ -982,6 +974,23 @@ sock_midiattach(struct sock *f) dev_midiattach(f->dev, rbuf, wbuf); } +int +sock_auth(struct sock *f) +{ + struct amsg_auth *p = &f->rmsg.u.auth; + + if (sock_sesrefs == 0) { + /* start a new session */ + memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN); + } else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) { + /* another session is active, drop connection */ + return 0; + } + sock_sesrefs++; + f->pstate = SOCK_HELLO; + return 1; +} + int sock_hello(struct sock *f) { @@ -1323,6 +1332,30 @@ sock_execmsg(struct sock *f) f->rtodo = sizeof(struct amsg); f->rstate = SOCK_RMSG; break; + case AMSG_AUTH: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": AUTH message\n"); + } +#endif + if (f->pstate != SOCK_AUTH) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": AUTH, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (!sock_auth(f)) { + aproc_del(f->pipe.file.rproc); + return 0; + } + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + break; case AMSG_HELLO: #ifdef DEBUG if (debug_level >= 3) { diff --git a/aucat/sock.h b/aucat/sock.h index 1ca64ff..b1a94f8 100644 --- a/aucat/sock.h +++ b/aucat/sock.h @@ -42,13 +42,14 @@ struct sock { #define SOCK_WMSG 1 /* amsg being written */ #define SOCK_WDATA 2 /* data chunk being written */ unsigned wstate; /* state of the write-end FSM */ -#define SOCK_HELLO 0 /* waiting for HELLO message */ -#define SOCK_INIT 1 /* parameter negotiation */ -#define SOCK_START 2 /* filling play buffers */ -#define SOCK_READY 3 /* play buffers full */ -#define SOCK_RUN 4 /* attached to the mix / sub */ -#define SOCK_STOP 5 /* draining rec buffers */ -#define SOCK_MIDI 6 /* raw byte stream (midi) */ +#define SOCK_AUTH 0 /* waiting for AUTH message */ +#define SOCK_HELLO 1 /* waiting for HELLO message */ +#define SOCK_INIT 2 /* parameter negotiation */ +#define SOCK_START 3 /* filling play buffers */ +#define SOCK_READY 4 /* play buffers full */ +#define SOCK_RUN 5 /* attached to the mix / sub */ +#define SOCK_STOP 6 /* draining rec buffers */ +#define SOCK_MIDI 7 /* raw byte stream (midi) */ unsigned pstate; /* one of the above */ unsigned mode; /* bitmask of MODE_XXX */ struct aparams rpar; /* read (ie play) parameters */ diff --git a/libsndio/aucat.c b/libsndio/aucat.c index d4f9b7b..a4330d8 100644 --- a/libsndio/aucat.c +++ b/libsndio/aucat.c @@ -17,10 +17,12 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -194,6 +196,70 @@ aucat_wdata(struct aucat *hdl, const void *buf, size_t len, unsigned wbpf, int * return n; } +int +aucat_gencookie(unsigned char *cookie) +{ + arc4random_buf(cookie, AMSG_COOKIELEN); + return 1; +} + +void +aucat_savecookie(char *path, unsigned char *cookie) +{ + int fd; + + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (fd < 0) { + DPERROR(path); + return; + } + if (write(fd, cookie, AMSG_COOKIELEN) < 0) { + DPERROR(path); + return; + } + close(fd); +} + +int +aucat_loadcookie(unsigned char *cookie) +{ + char buf[PATH_MAX], *path; + int fd, len, res; + + path = issetugid() ? NULL : getenv("AUCAT_COOKIE"); + if (path == NULL) { + path = issetugid() ? NULL : getenv("HOME"); + if (path == NULL) + goto bad_gen; + snprintf(buf, PATH_MAX, "%s/.aucat_cookie", path); + path = buf; + } + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno != ENOENT) + DPERROR(path); + goto bad_gen; + } + len = read(fd, cookie, AMSG_COOKIELEN); + if (len < 0) { + DPERROR(path); + goto bad_close; + } + if (len != AMSG_COOKIELEN) { + DPRINTF("%s: short read\n", path); + goto bad_close; + } + close(fd); + return 1; +bad_close: + close(fd); +bad_gen: + res = aucat_gencookie(cookie); + if (path != NULL) + aucat_savecookie(path, cookie); + return res; +} + int aucat_open(struct aucat *hdl, const char *str, char *sock, unsigned mode, int nbio) { @@ -256,6 +322,13 @@ aucat_open(struct aucat *hdl, const char *str, char *sock, unsigned mode, int nb * say hello to server */ AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_AUTH; + if (!aucat_loadcookie(hdl->wmsg.u.auth.cookie)) + goto bad_connect; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl, &eof)) + goto bad_connect; + AMSG_INIT(&hdl->wmsg); hdl->wmsg.cmd = AMSG_HELLO; hdl->wmsg.u.hello.version = AMSG_VERSION; hdl->wmsg.u.hello.mode = mode; diff --git a/libsndio/sndio.7 b/libsndio/sndio.7 index fa2823a..5d81503 100644 --- a/libsndio/sndio.7 +++ b/libsndio/sndio.7 @@ -146,6 +146,25 @@ MIDI port controlling the first .Xr aucat 1 audio server. .El +.Sh AUTHENTICATION +If a shared +.Xr aucat 1 +or +.Xr midicat 1 +server is running, for privacy reasons only one user may have +connections to it at a given time +(though the same user could have multiple connections to it). +Users are identified by their +.Em session cookie , +which is automatically generated by audio or MIDI applications +upon the first connection to the server. +The cookie is stored in +.Pa "$HOME/.aucat_cookie" +and contains 128 bits of raw random data generated with +.Xr arc4random 3 . +.Pp +If a session needs to be shared between multiple users, they +can connect to the server using the same cookie. .Sh ENVIRONMENT .Bl -tag -width "AUDIODEVICEXXX" -compact .It Ev AUDIODEVICE @@ -154,6 +173,12 @@ no device chooser. .It Ev MIDIDEVICE MIDI port to use if the application provides no MIDI port chooser. +.It AUCAT_COOKIE +Path to file containing the session cookie to be used +when connecting to +.Xr aucat +or +.Xr midicat .El .Pp Environment variables are ignored by programs