X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/1020001ce9f8c0f2e9119d92b0b7f680a6be79b9..416609cff1292890e48a89a0d90bcf421e735a32:/lib/eclient.c diff --git a/lib/eclient.c b/lib/eclient.c index cefa5e3..012db98 100644 --- a/lib/eclient.c +++ b/lib/eclient.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "log.h" #include "mem.h" @@ -58,6 +59,9 @@ /* TODO: more commands */ +/** @brief How often to send data to the server when receiving logs */ +#define LOG_PROD_INTERVAL 10 + /* Types *********************************************************************/ /** @brief Client state */ @@ -125,9 +129,23 @@ struct disorder_eclient { int rc; /**< @brief response code */ char *line; /**< @brief complete line */ struct vector vec; /**< @brief body */ - const disorder_eclient_log_callbacks *log_callbacks; /**< @brief log callbacks */ + + const disorder_eclient_log_callbacks *log_callbacks; + /**< @brief log callbacks + * + * Once disorder_eclient_log() has been issued this is always set. When we + * re-connect it is checked to re-issue the log command. + */ void *log_v; /**< @brief user data */ unsigned long statebits; /**< @brief latest state */ + + time_t last_prod; + /**< @brief last time we sent a prod + * + * When we are receiving log data we send a "prod" byte to the server from + * time to time so that we detect broken connections reasonably quickly. The + * server just ignores these bytes. + */ }; /* Forward declarations ******************************************************/ @@ -170,6 +188,7 @@ static void logentry_removed(disorder_eclient *c, int nvec, char **vec); static void logentry_scratched(disorder_eclient *c, int nvec, char **vec); static void logentry_state(disorder_eclient *c, int nvec, char **vec); static void logentry_volume(disorder_eclient *c, int nvec, char **vec); +static void logentry_rescanned(disorder_eclient *c, int nvec, char **vec); /* Tables ********************************************************************/ @@ -194,6 +213,7 @@ static const struct logentry_handler logentry_handlers[] = { LE(recent_added, 2, INT_MAX), LE(recent_removed, 1, 1), LE(removed, 1, 2), + LE(rescanned, 0, 0), LE(scratched, 2, 2), LE(state, 1, 1), LE(volume, 2, 2) @@ -216,10 +236,6 @@ disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb, vector_init(&c->vec); dynstr_init(&c->input); dynstr_init(&c->output); - if(!config->password) { - error(0, "no password set"); - return 0; - } return c; } @@ -306,6 +322,7 @@ static int protocol_error(disorder_eclient *c, struct operation *op, */ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { struct operation *op; + time_t now; D(("disorder_eclient_polled fd=%d state=%s mode=[%s %s]", c->fd, states[c->state], @@ -319,6 +336,11 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { if(c->state == state_disconnected) { D(("state_disconnected")); + /* If there is no password yet then we cannot connect */ + if(!config->password) { + comms_error(c, "no password is configured"); + return; + } with_sockaddr(c, start_connect); /* might now be state_disconnected (on error), state_connecting (slow * connect) or state_connected (fast connect). If state_disconnected then @@ -374,6 +396,14 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { c->callbacks->report(c->u, 0); } + /* Queue up a byte to send */ + if(c->state == state_log + && c->output.nvec == 0 + && time(&now) - c->last_prod > LOG_PROD_INTERVAL) { + put(c, "x", 1); + c->last_prod = now; + } + if(c->state == state_cmdresponse || c->state == state_body || c->state == state_log) { @@ -453,6 +483,7 @@ static int start_connect(void *cc, return comms_error(c, "socket: %s", strerror(errno)); c->eof = 0; nonblock(c->fd); + cloexec(c->fd); if(connect(c->fd, sa, len) < 0) { switch(errno) { case EINTR: @@ -505,7 +536,7 @@ static void authbanner_opcallback(disorder_eclient *c, const char *res; char **rvec; int nrvec; - const char *algo = "SHA1"; + const char *protocol, *algorithm, *challenge; D(("authbanner_opcallback")); if(c->rc / 100 != 2 @@ -516,15 +547,22 @@ static void authbanner_opcallback(disorder_eclient *c, disorder_eclient_close(c); return; } - if(nrvec > 1) { - algo = *rvec++; - --nrvec; + if(nrvec != 3) { + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); + } + protocol = *rvec++; + algorithm = *rvec++; + challenge = *rvec++; + if(strcmp(protocol, "2")) { + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); } - nonce = unhex(rvec[0], &nonce_len); - res = authhash(nonce, nonce_len, config->password, algo); + nonce = unhex(challenge, &nonce_len); + res = authhash(nonce, nonce_len, config->password, algorithm); if(!res) { protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'", - c->ident, algo); + c->ident, algorithm); disorder_eclient_close(c); return; } @@ -649,7 +687,7 @@ static void process_line(disorder_eclient *c, char *line) { case 3: /* We need to collect the body. */ c->state = state_body; - c->vec.nvec = 0; + vector_init(&c->vec); break; case 4: assert(c->log_callbacks != 0); @@ -788,13 +826,19 @@ static void stash_command(disorder_eclient *c, /* Command support ***********************************************************/ -/* for commands with a simple string response */ +/* for commands with a quoted string response */ static void string_response_opcallback(disorder_eclient *c, struct operation *op) { D(("string_response_callback")); if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4); + if(op->completed) { + char **rr = split(c->line + 4, 0, SPLIT_QUOTES, 0, 0); + + if(rr && *rr) + ((disorder_eclient_string_response *)op->completed)(op->v, *rr); + else + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + } } else protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); } @@ -1156,6 +1200,54 @@ int disorder_eclient_nop(disorder_eclient *c, "nop", (char *)0); } +/** @brief Get the last @p max added tracks + * @param c Client + * @param completed Called with list + * @param max Number of tracks to get, 0 for all + * @param v Passed to @p completed + * + * The first track in the list is the most recently added. + */ +int disorder_eclient_new_tracks(disorder_eclient *c, + disorder_eclient_list_response *completed, + int max, + void *v) { + char limit[32]; + + sprintf(limit, "%d", max); + return simple(c, list_response_opcallback, (void (*)())completed, v, + "new", limit, (char *)0); +} + +static void rtp_response_opcallback(disorder_eclient *c, + struct operation *op) { + D(("rtp_response_opcallback")); + if(c->rc / 100 == 2) { + if(op->completed) { + int nvec; + char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0); + + ((disorder_eclient_list_response *)op->completed)(op->v, nvec, vec); + } + } else + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); +} + +/** @brief Determine the RTP target address + * @param c Client + * @param completed Called with address details + * @param v Passed to @p completed + * + * The address details will be two elements, the first being the hostname and + * the second the service (port). + */ +int disorder_eclient_rtp_address(disorder_eclient *c, + disorder_eclient_list_response *completed, + void *v) { + return simple(c, rtp_response_opcallback, (void (*)())completed, v, + "rtp-address", (char *)0); +} + /* Log clients ***************************************************************/ /** @brief Monitor the server log @@ -1290,6 +1382,13 @@ static void logentry_removed(disorder_eclient *c, c->log_callbacks->removed(c->log_v, vec[0], vec[1]); } +static void logentry_rescanned(disorder_eclient *c, + int attribute((unused)) nvec, + char attribute((unused)) **vec) { + if(!c->log_callbacks->rescanned) return; + c->log_callbacks->rescanned(c->log_v); +} + static void logentry_scratched(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { if(!c->log_callbacks->scratched) return;