X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/d6dde5a3f49c7036a73f835fac1ece3144397fb7..b12be54a68a7738d948d866eb7b9231f8e55a12e:/server/server.c diff --git a/server/server.c b/server/server.c index a112f78..cd97435 100644 --- a/server/server.c +++ b/server/server.c @@ -63,6 +63,8 @@ #include "eventlog.h" #include "defs.h" #include "cache.h" +#include "unicode.h" +#include "cookies.h" #ifndef NONCE_SIZE # define NONCE_SIZE 16 @@ -81,22 +83,37 @@ struct listener { int pf; }; +/** @brief One client connection */ struct conn { + /** @brief Read commands from here */ ev_reader *r; + /** @brief Send responses to here */ ev_writer *w; + /** @brief Underlying file descriptor */ int fd; + /** @brief Unique identifier for connection used in log messages */ unsigned tag; + /** @brief Login name or NULL */ char *who; + /** @brief Event loop */ ev_source *ev; + /** @brief Nonce chosen for this connection */ unsigned char nonce[NONCE_SIZE]; + /** @brief Current reader callback + * + * We change this depending on whether we're servicing the @b log command + */ ev_reader_callback *reader; + /** @brief Event log output sending to this connection */ struct eventlog_output *lo; + /** @brief Parent listener */ const struct listener *l; + /** @brief Login cookie or NULL */ + char *cookie; }; static int reader_callback(ev_source *ev, ev_reader *reader, - int fd, void *ptr, size_t bytes, int eof, @@ -104,49 +121,54 @@ static int reader_callback(ev_source *ev, static const char *noyes[] = { "no", "yes" }; +/** @brief Called when a connection's writer fails or is shut down + * + * If the connection still has a raeder that is cancelled. + */ static int writer_error(ev_source attribute((unused)) *ev, - int fd, int errno_value, void *u) { struct conn *c = u; - D(("server writer_error %d %d", fd, errno_value)); + D(("server writer_error S%x %d", c->tag, errno_value)); if(errno_value == 0) { /* writer is done */ - c->w = 0; - if(c->r == 0) { - D(("server writer_error closes %d", fd)); - xclose(fd); /* reader is done too, close */ - } else { - D(("server writer_error shutdown %d SHUT_WR", fd)); - xshutdown(fd, SHUT_WR); /* reader is not done yet */ - } + D(("S%x writer completed", c->tag)); } else { if(errno_value != EPIPE) error(errno_value, "S%x write error on socket", c->tag); - if(c->r) + if(c->r) { + D(("cancel reader")); ev_reader_cancel(c->r); - xclose(fd); + c->r = 0; + } + D(("done cancel reader")); } + c->w = 0; + ev_report(ev); return 0; } +/** @brief Called when a conncetion's reader fails or is shut down + * + * If connection still has a writer then it is closed. + */ static int reader_error(ev_source attribute((unused)) *ev, - int fd, int errno_value, void *u) { struct conn *c = u; - D(("server reader_error %d %d", fd, errno_value)); - error(errno, "S%x read error on socket", c->tag); - ev_writer_cancel(c->w); + D(("server reader_error S%x %d", c->tag, errno_value)); + error(errno_value, "S%x read error on socket", c->tag); + if(c->w) + ev_writer_close(c->w); + c->w = 0; + c->r = 0; ev_report(ev); - info("closing fd %d", fd); - xclose(fd); return 0; } -/* return true if we are talking to a trusted user */ +/** @brief Return true if we are talking to a trusted user */ static int trusted(struct conn *c) { int n; @@ -206,7 +228,7 @@ static int c_play(struct conn *c, char **vec, * anything. */ if(q == qhead.next && playing) prepare(c->ev, q); - sink_writes(ev_writer_sink(c->w), "250 queued\n"); + sink_printf(ev_writer_sink(c->w), "252 %s\n", q->id); /* If the queue was empty but we are for some reason paused then * unpause. */ if(!playing) resume_playing(0); @@ -327,7 +349,7 @@ static int c_version(struct conn *c, char attribute((unused)) **vec, int attribute((unused)) nvec) { /* VERSION had better only use the basic character set */ - sink_printf(ev_writer_sink(c->w), "251 %s\n", disorder_version_string); + sink_printf(ev_writer_sink(c->w), "251 %s\n", disorder_short_version_string); return 1; /* completed */ } @@ -351,39 +373,48 @@ static int c_become(struct conn *c, return 1; } -static int c_user(struct conn *c, - char **vec, - int attribute((unused)) nvec) { - int n; - const char *res; +static const char *connection_host(struct conn *c) { union { struct sockaddr sa; struct sockaddr_in in; struct sockaddr_in6 in6; } u; socklen_t l; + int n; char host[1024]; - if(c->who) { - sink_writes(ev_writer_sink(c->w), "530 already authenticated\n"); - return 1; - } /* get connection data */ l = sizeof u; if(getpeername(c->fd, &u.sa, &l) < 0) { error(errno, "S%x error calling getpeername", c->tag); - sink_writes(ev_writer_sink(c->w), "530 authentication failure\n"); - return 1; + return 0; } if(c->l->pf != PF_UNIX) { if((n = getnameinfo(&u.sa, l, host, sizeof host, 0, 0, NI_NUMERICHOST))) { error(0, "S%x error calling getnameinfo: %s", c->tag, gai_strerror(n)); - sink_writes(ev_writer_sink(c->w), "530 authentication failure\n"); - return 1; + return 0; } + return xstrdup(host); } else - strcpy(host, "local"); + return "local"; +} + +static int c_user(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + int n; + const char *res, *host; + + if(c->who) { + sink_writes(ev_writer_sink(c->w), "530 already authenticated\n"); + return 1; + } + /* get connection data */ + if(!(host = connection_host(c))) { + sink_writes(ev_writer_sink(c->w), "530 authentication failure\n"); + return 1; + } /* find the user */ for(n = 0; n < config->allow.n && strcmp(config->allow.s[n].s[0], vec[0]); ++n) @@ -399,7 +430,7 @@ static int c_user(struct conn *c, if(wideopen || (res && !strcmp(res, vec[1]))) { c->who = vec[0]; /* currently we only bother logging remote connections */ - if(c->l->pf != PF_UNIX) + if(strcmp(host, "local")) info("S%x %s connected from %s", c->tag, vec[0], host); sink_writes(ev_writer_sink(c->w), "230 OK\n"); return 1; @@ -561,7 +592,7 @@ static int c_get(struct conn *c, if(vec[1][0] != '_' && (v = trackdb_get(vec[0], vec[1]))) sink_printf(ev_writer_sink(c->w), "252 %s\n", v); else - sink_writes(ev_writer_sink(c->w), "550 not found\n"); + sink_writes(ev_writer_sink(c->w), "555 not found\n"); return 1; } @@ -717,29 +748,41 @@ static int c_volume(struct conn *c, return 1; } -/* we are logging, and some data is available to read */ -static int logging_reader_callback(ev_source *ev, +/** @brief Called when data arrives on a log connection + * + * We just discard all such data. The client may occasionally send data as a + * keepalive. + */ +static int logging_reader_callback(ev_source attribute((unused)) *ev, ev_reader *reader, - int fd, - void *ptr, + void attribute((unused)) *ptr, size_t bytes, - int eof, - void *u) { + int attribute((unused)) eof, + void attribute((unused)) *u) { struct conn *c = u; - /* don't log to this conn any more */ - eventlog_remove(c->lo); - /* terminate the log output */ - sink_writes(ev_writer_sink(c->w), ".\n"); - /* restore the reader callback */ - c->reader = reader_callback; - /* ...and exit via it */ - return c->reader(ev, reader, fd, ptr, bytes, eof, u); + ev_reader_consume(reader, bytes); + if(eof) { + /* Oops, that's all for now */ + D(("logging reader eof")); + if(c->w) { + D(("close writer")); + ev_writer_close(c->w); + c->w = 0; + } + c->r = 0; + } + return 0; } static void logclient(const char *msg, void *user) { struct conn *c = user; + if(!c->w || !c->r) { + /* This connection has gone up in smoke for some reason */ + eventlog_remove(c->lo); + return; + } sink_printf(ev_writer_sink(c->w), "%"PRIxMAX" %s\n", (uintmax_t)time(0), msg); } @@ -775,19 +818,6 @@ static int c_log(struct conn *c, return 0; } -static void post_move_cleanup(void) { - struct queue_entry *q; - - /* If we have caused any random tracks to not be at the end then we make them - * no longer be random. */ - for(q = qhead.next; q != &qhead; q = q->next) - if(q->state == playing_random && q->next != &qhead) - q->state = playing_unplayed; - /* That might mean we need to add a new random track. */ - add_random_track(); - queue_write(); -} - static int c_move(struct conn *c, char **vec, int attribute((unused)) nvec) { @@ -806,7 +836,6 @@ static int c_move(struct conn *c, return 1; } n = queue_move(q, atoi(vec[1]), c->who); - post_move_cleanup(); sink_printf(ev_writer_sink(c->w), "252 %d\n", n); /* If we've moved to the head of the queue then prepare the track. */ if(q == qhead.next) @@ -843,7 +872,6 @@ static int c_moveafter(struct conn *c, return 1; } queue_moveafter(q, nvec, qs, c->who); - post_move_cleanup(); sink_printf(ev_writer_sink(c->w), "250 Moved tracks\n"); /* If we've moved to the head of the queue then prepare the track. */ if(q == qhead.next) @@ -891,6 +919,10 @@ static int c_tags(struct conn *c, static int c_set_global(struct conn *c, char **vec, int attribute((unused)) nvec) { + if(vec[0][0] == '_') { + sink_writes(ev_writer_sink(c->w), "550 cannot set internal global preferences\n"); + return 1; + } trackdb_set_global(vec[0], vec[1], c->who); sink_printf(ev_writer_sink(c->w), "250 OK\n"); return 1; @@ -904,7 +936,7 @@ static int c_get_global(struct conn *c, if(s) sink_printf(ev_writer_sink(c->w), "252 %s\n", s); else - sink_writes(ev_writer_sink(c->w), "550 not found\n"); + sink_writes(ev_writer_sink(c->w), "555 not found\n"); return 1; } @@ -942,7 +974,61 @@ static int c_rtp_address(struct conn *c, sink_writes(ev_writer_sink(c->w), "550 No RTP\n"); return 1; } - + +static int c_cookie(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + const char *host; + char *user; + + /* Can't log in twice on the same connection */ + if(c->who) { + sink_writes(ev_writer_sink(c->w), "530 already authenticated\n"); + return 1; + } + /* Get some kind of peer identifcation */ + if(!(host = connection_host(c))) { + sink_writes(ev_writer_sink(c->w), "530 authentication failure\n"); + return 1; + } + /* Check the cookie */ + user = verify_cookie(vec[0]); + if(!user) { + sink_writes(ev_writer_sink(c->w), "530 authentication failure\n"); + return 1; + } + /* Log in */ + c->who = user; + c->cookie = vec[0]; + if(strcmp(host, "local")) + info("S%x %s connected with cookie from %s", c->tag, user, host); + sink_writes(ev_writer_sink(c->w), "230 OK\n"); + return 1; +} + +static int c_make_cookie(struct conn *c, + char attribute((unused)) **vec, + int attribute((unused)) nvec) { + const char *cookie = make_cookie(c->who); + + if(cookie) + sink_printf(ev_writer_sink(c->w), "252 %s\n", cookie); + else + sink_writes(ev_writer_sink(c->w), "550 Cannot create cookie\n"); + return 1; +} + +static int c_revoke(struct conn *c, + char attribute((unused)) **vec, + int attribute((unused)) nvec) { + if(c->cookie) { + revoke_cookie(c->cookie); + sink_writes(ev_writer_sink(c->w), "250 OK\n"); + } else + sink_writes(ev_writer_sink(c->w), "550 Did not log in with cookie\n"); + return 1; +} + #define C_AUTH 0001 /* must be authenticated */ #define C_TRUSTED 0002 /* must be trusted user */ @@ -954,6 +1040,7 @@ static const struct command { } commands[] = { { "allfiles", 0, 2, c_allfiles, C_AUTH }, { "become", 1, 1, c_become, C_AUTH|C_TRUSTED }, + { "cookie", 1, 1, c_cookie, 0 }, { "dirs", 0, 2, c_dirs, C_AUTH }, { "disable", 0, 1, c_disable, C_AUTH }, { "enable", 0, 0, c_enable, C_AUTH }, @@ -964,6 +1051,7 @@ static const struct command { { "get-global", 1, 1, c_get_global, C_AUTH }, { "length", 1, 1, c_length, C_AUTH }, { "log", 0, 0, c_log, C_AUTH }, + { "make-cookie", 0, 0, c_make_cookie, C_AUTH }, { "move", 2, 2, c_move, C_AUTH }, { "moveafter", 1, INT_MAX, c_moveafter, C_AUTH }, { "new", 0, 1, c_new, C_AUTH }, @@ -983,6 +1071,7 @@ static const struct command { { "rescan", 0, 0, c_rescan, C_AUTH|C_TRUSTED }, { "resolve", 1, 1, c_resolve, C_AUTH }, { "resume", 0, 0, c_resume, C_AUTH }, + { "revoke", 0, 0, c_revoke, C_AUTH }, { "rtp-address", 0, 0, c_rtp_address, C_AUTH }, { "scratch", 0, 1, c_scratch, C_AUTH }, { "search", 1, 1, c_search, C_AUTH }, @@ -1010,6 +1099,11 @@ static int command(struct conn *c, char *line) { int nvec, n; D(("server command %s", line)); + /* We force everything into NFC as early as possible */ + if(!(line = utf8_compose_canon(line, strlen(line), 0))) { + sink_writes(ev_writer_sink(c->w), "500 cannot normalize command\n"); + return 1; + } if(!(vec = split(line, &nvec, SPLIT_QUOTES, command_error, c))) { sink_writes(ev_writer_sink(c->w), "500 cannot parse command\n"); return 1; @@ -1047,20 +1141,18 @@ static int command(struct conn *c, char *line) { /* redirect to the right reader callback for our current state */ static int redirect_reader_callback(ev_source *ev, ev_reader *reader, - int fd, void *ptr, size_t bytes, int eof, void *u) { struct conn *c = u; - return c->reader(ev, reader, fd, ptr, bytes, eof, u); + return c->reader(ev, reader, ptr, bytes, eof, u); } /* the main command reader */ static int reader_callback(ev_source attribute((unused)) *ev, ev_reader *reader, - int attribute((unused)) fd, void *ptr, size_t bytes, int eof, @@ -1090,8 +1182,13 @@ static int reader_callback(ev_source attribute((unused)) *ev, if(eof) { if(bytes) error(0, "S%x unterminated line", c->tag); + D(("normal reader close")); c->r = 0; - return ev_writer_close(c->w); + if(c->w) { + D(("close associated writer")); + ev_writer_close(c->w); + c->w = 0; + } } return 0; } @@ -1114,6 +1211,7 @@ static int listen_callback(ev_source *ev, "client writer"); c->r = ev_reader_new(ev, fd, redirect_reader_callback, reader_error, c, "client reader"); + ev_tie(c->r, c->w); c->fd = fd; c->reader = reader_callback; c->l = l;