X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/d7167e899c303d29fd2ae9216cdd524477589ae9..fa7d59804e98cc73f0ab3ed96166889f1767d4db:/lib/eclient.c diff --git a/lib/eclient.c b/lib/eclient.c index 3faf259..48ae7e2 100644 --- a/lib/eclient.c +++ b/lib/eclient.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2006 Richard Kettlewell + * Copyright (C) 2006-2008 Richard Kettlewell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,22 +21,18 @@ * @brief Client code for event-driven programs */ -#include -#include "types.h" +#include "common.h" #include #include #include #include -#include -#include #include #include #include -#include -#include #include #include +#include #include "log.h" #include "mem.h" @@ -58,6 +54,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 */ @@ -110,7 +109,7 @@ struct operation { /** @brief Client structure */ struct disorder_eclient { - const char *ident; + char *ident; int fd; /**< @brief connection to server */ enum client_state state; /**< @brief current state */ int authenticated; /**< @brief true when authenicated */ @@ -125,22 +124,32 @@ 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. + */ + + /** @brief Protocol version */ + int protocol; }; /* Forward declarations ******************************************************/ -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident); +static int start_connect(disorder_eclient *c); static void process_line(disorder_eclient *c, char *line); -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident); static void maybe_connected(disorder_eclient *c); static void authbanner_opcallback(disorder_eclient *c, struct operation *op); @@ -170,6 +179,12 @@ 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); +static void logentry_user_add(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_confirm(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_delete(disorder_eclient *c, int nvec, char **vec); +static void logentry_user_edit(disorder_eclient *c, int nvec, char **vec); +static void logentry_rights_changed(disorder_eclient *c, int nvec, char **vec); /* Tables ********************************************************************/ @@ -194,8 +209,14 @@ 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(rights_changed, 1, 1), LE(scratched, 2, 2), LE(state, 1, 1), + LE(user_add, 1, 1), + LE(user_confirm, 1, 1), + LE(user_delete, 1, 1), + LE(user_edit, 2, 2), LE(volume, 2, 2) }; @@ -216,10 +237,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 +323,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,7 +337,12 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { if(c->state == state_disconnected) { D(("state_disconnected")); - with_sockaddr(c, start_connect); + /* If there is no password yet then we cannot connect */ + if(!config->password) { + comms_error(c, "no password is configured"); + return; + } + start_connect(c); /* might now be state_disconnected (on error), state_connecting (slow * connect) or state_connected (fast connect). If state_disconnected then * we just rely on a periodic callback from the event loop sometime. */ @@ -374,6 +397,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) { @@ -437,14 +468,13 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { } /** @brief Called to start connecting */ -static int start_connect(void *cc, - const struct sockaddr *sa, - socklen_t len, - const char *ident) { - disorder_eclient *c = cc; +static int start_connect(disorder_eclient *c) { + struct sockaddr *sa; + socklen_t len; D(("start_connect")); - c->ident = xstrdup(ident); + if((len = find_server(&sa, &c->ident)) == (socklen_t)-1) + return comms_error(c, "cannot look up server"); /* TODO better error */ if(c->fd != -1) { xclose(c->fd); c->fd = -1; @@ -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: @@ -463,7 +494,7 @@ static int start_connect(void *cc, return 0; default: /* Signal the error to the caller. */ - return comms_error(c, "connecting to %s: %s", ident, strerror(errno)); + return comms_error(c, "connecting to %s: %s", c->ident, strerror(errno)); } } else c->state = state_connected; @@ -498,6 +529,7 @@ static void maybe_connected(disorder_eclient *c) { /* Authentication ************************************************************/ +/** @brief Called with the greeting from the server */ static void authbanner_opcallback(disorder_eclient *c, struct operation *op) { size_t nonce_len; @@ -505,7 +537,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 +548,38 @@ static void authbanner_opcallback(disorder_eclient *c, disorder_eclient_close(c); return; } - if(nrvec > 1) { - algo = *rvec++; - --nrvec; + switch(nrvec) { + case 1: + protocol = "1"; + algorithm = "sha1"; + challenge = *rvec++; + break; + case 2: + protocol = "1"; + algorithm = *rvec++; + challenge = *rvec++; + break; + case 3: + protocol = *rvec++; + algorithm = *rvec++; + challenge = *rvec++; + break; + default: + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); + return; + } + c->protocol = atoi(protocol); + if(c->protocol < 1 || c->protocol > 2) { + protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + disorder_eclient_close(c); + return; } - 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; } @@ -533,6 +588,7 @@ static void authbanner_opcallback(disorder_eclient *c, (char *)0); } +/** @brief Called with the response to the @c user command */ static void authuser_opcallback(disorder_eclient *c, struct operation *op) { char *r; @@ -649,7 +705,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,38 +844,71 @@ static void stash_command(disorder_eclient *c, /* Command support ***********************************************************/ -/* for commands with a simple string response */ +static const char *errorstring(disorder_eclient *c) { + char *s; + + byte_xasprintf(&s, "%s: %s: %d", c->ident, c->line, c->rc); + return s; +} + +/* for commands with a quoted string response */ static void string_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_string_response *completed + = (disorder_eclient_string_response *)op->completed; + D(("string_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4); + if(c->rc / 100 == 2 || c->rc == 555) { + if(op->completed) { + if(c->rc == 555) + completed(op->v, NULL, NULL); + else if(c->protocol >= 2) { + char **rr = split(c->line + 4, 0, SPLIT_QUOTES, 0, 0); + + if(rr && *rr) + completed(op->v, NULL, *rr); + else + /* TODO error message a is bit lame but generally indicates a server + * bug rather than anything the user can address */ + completed(op->v, "error parsing response", NULL); + } else + completed(op->v, NULL, c->line + 4); + } } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), NULL); } /* for commands with a simple integer response */ static void integer_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_integer_response *completed + = (disorder_eclient_integer_response *)op->completed; + D(("string_response_callback")); if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_integer_response *)op->completed) - (op->v, strtol(c->line + 4, 0, 10)); + long n; + int e; + + e = xstrtol(&n, c->line + 4, 0, 10); + if(e) + completed(op->v, strerror(e), 0); + else + completed(op->v, 0, n); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for commands with no response */ static void no_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_no_response *completed + = (disorder_eclient_no_response *)op->completed; + D(("no_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_no_response *)op->completed)(op->v); - } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + if(c->rc / 100 == 2) + completed(op->v, NULL); + else + completed(op->v, errorstring(c)); } /* error callback for queue_unmarshall */ @@ -827,13 +916,17 @@ static void eclient_queue_error(const char *msg, void *u) { struct operation *op = u; + /* TODO don't use protocol_error here */ protocol_error(op->client, op, -1, "error parsing queue entry: %s", msg); } /* for commands that expect a queue dump */ static void queue_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_queue_response *const completed + = (disorder_eclient_queue_response *)op->completed; int n; + int parse_failed = 0; struct queue_entry *q, *qh = 0, **qtail = &qh, *qlast = 0; D(("queue_response_callback")); @@ -842,22 +935,29 @@ static void queue_response_opcallback(disorder_eclient *c, for(n = 0; n < c->vec.nvec; ++n) { q = xmalloc(sizeof *q); D(("queue_unmarshall %s", c->vec.vec[n])); - if(!queue_unmarshall(q, c->vec.vec[n], eclient_queue_error, op)) { + if(!queue_unmarshall(q, c->vec.vec[n], NULL, op)) { q->prev = qlast; *qtail = q; qtail = &q->next; qlast = q; - } + } else + parse_failed = 1; } - if(op->completed) - ((disorder_eclient_queue_response *)op->completed)(op->v, qh); + /* Currently we pass the partial queue to the callback along with the + * error. This might not be very useful in practice... */ + if(parse_failed) + completed(op->v, "cannot parse result", qh); + else + completed(op->v, 0, qh); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for 'playing' */ static void playing_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_queue_response *const completed + = (disorder_eclient_queue_response *)op->completed; struct queue_entry *q; D(("playing_response_callback")); @@ -865,51 +965,52 @@ static void playing_response_opcallback(disorder_eclient *c, switch(c->rc % 10) { case 2: if(queue_unmarshall(q = xmalloc(sizeof *q), c->line + 4, - eclient_queue_error, c)) - return; + NULL, c)) + completed(op->v, "cannot parse result", 0); + else + completed(op->v, 0, q); break; case 9: - q = 0; + completed(op->v, 0, 0); break; default: - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); - return; + completed(op->v, errorstring(c), 0); + break; } - if(op->completed) - ((disorder_eclient_queue_response *)op->completed)(op->v, q); } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0); } /* for commands that expect a list of some sort */ static void list_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_list_response *const completed = + (disorder_eclient_list_response *)op->completed; + D(("list_response_callback")); - if(c->rc / 100 == 2) { - if(op->completed) - ((disorder_eclient_list_response *)op->completed)(op->v, - c->vec.nvec, - c->vec.vec); - } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + if(c->rc / 100 == 2) + completed(op->v, NULL, c->vec.nvec, c->vec.vec); + else + completed(op->v, errorstring(c), 0, 0); } /* for volume */ static void volume_response_opcallback(disorder_eclient *c, struct operation *op) { + disorder_eclient_volume_response *completed + = (disorder_eclient_volume_response *)op->completed; int l, r; D(("volume_response_callback")); if(c->rc / 100 == 2) { if(op->completed) { if(sscanf(c->line + 4, "%d %d", &l, &r) != 2 || l < 0 || r < 0) - protocol_error(c, op, -1, "%s: invalid volume response: %s", - c->ident, c->line); + completed(op->v, "cannot parse volume response", 0, 0); else - ((disorder_eclient_volume_response *)op->completed)(op->v, l, r); + completed(op->v, 0, l, r); } } else - protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line); + completed(op->v, errorstring(c), 0, 0); } static int simple(disorder_eclient *c, @@ -1156,6 +1257,137 @@ 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) { + disorder_eclient_list_response *const completed = + (disorder_eclient_list_response *)op->completed; + D(("rtp_response_opcallback")); + if(c->rc / 100 == 2) { + int nvec; + char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0); + + if(vec) + completed(op->v, NULL, nvec, vec); + else + completed(op->v, "error parsing response", 0, 0); + } else + completed(op->v, errorstring(c), 0, 0); +} + +/** @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); +} + +/** @brief Get the list of users + * @param c Client + * @param completed Called with list of users + * @param v Passed to @p completed + * + * The user list is not sorted in any particular order. + */ +int disorder_eclient_users(disorder_eclient *c, + disorder_eclient_list_response *completed, + void *v) { + return simple(c, list_response_opcallback, (void (*)())completed, v, + "users", (char *)0); +} + +/** @brief Delete a user + * @param c Client + * @param completed Called on completion + * @param user User to delete + * @param v Passed to @p completed + */ +int disorder_eclient_deluser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "deluser", user, (char *)0); +} + +/** @brief Get a user property + * @param c Client + * @param completed Called on completion + * @param user User to look up + * @param property Property to look up + * @param v Passed to @p completed + */ +int disorder_eclient_userinfo(disorder_eclient *c, + disorder_eclient_string_response *completed, + const char *user, + const char *property, + void *v) { + return simple(c, string_response_opcallback, (void (*)())completed, v, + "userinfo", user, property, (char *)0); +} + +/** @brief Modify a user property + * @param c Client + * @param completed Called on completion + * @param user User to modify + * @param property Property to modify + * @param value New property value + * @param v Passed to @p completed + */ +int disorder_eclient_edituser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + const char *property, + const char *value, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "edituser", user, property, value, (char *)0); +} + +/** @brief Create a new user + * @param c Client + * @param completed Called on completion + * @param user User to create + * @param password Initial password + * @param rights Initial rights or NULL + * @param v Passed to @p completed + */ +int disorder_eclient_adduser(disorder_eclient *c, + disorder_eclient_no_response *completed, + const char *user, + const char *password, + const char *rights, + void *v) { + return simple(c, no_response_opcallback, (void (*)())completed, v, + "adduser", user, password, rights, (char *)0); +} + /* Log clients ***************************************************************/ /** @brief Monitor the server log @@ -1181,6 +1413,7 @@ int disorder_eclient_log(disorder_eclient *c, c->log_callbacks->state(c->log_v, c->statebits); stash_command(c, 0/*queuejump*/, log_opcallback, 0/*completed*/, v, "log", (char *)0); + disorder_eclient_polled(c, 0); return 0; } @@ -1195,6 +1428,7 @@ static void log_opcallback(disorder_eclient *c, /* error callback for log line parsing */ static void logline_error(const char *msg, void *u) { disorder_eclient *c = u; + /* TODO don't use protocol_error here */ protocol_error(c, c->ops, -1, "error parsing log line: %s", msg); } @@ -1210,48 +1444,55 @@ static void logline(disorder_eclient *c, const char *line) { * reported */ if(sscanf(vec[0], "%"SCNxMAX, &when) != 1) { /* probably the wrong side of a format change */ + /* TODO don't use protocol_error here */ protocol_error(c, c->ops, -1, "invalid log timestamp '%s'", vec[0]); return; } /* TODO: do something with the time */ - n = TABLE_FIND(logentry_handlers, struct logentry_handler, name, vec[1]); - if(n < 0) return; /* probably a future command */ + //fprintf(stderr, "log key: %s\n", vec[1]); + n = TABLE_FIND(logentry_handlers, name, vec[1]); + if(n < 0) { + //fprintf(stderr, "...not found\n"); + return; /* probably a future command */ + } vec += 2; nvec -= 2; - if(nvec < logentry_handlers[n].min || nvec > logentry_handlers[n].max) + if(nvec < logentry_handlers[n].min || nvec > logentry_handlers[n].max) { + //fprintf(stderr, "...wrong # args\n"); return; + } logentry_handlers[n].handler(c, nvec, vec); } static void logentry_completed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->completed) return; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->completed(c->log_v, vec[0]); + if(c->log_callbacks->completed) + c->log_callbacks->completed(c->log_v, vec[0]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_failed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->failed)return; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->failed(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->failed) + c->log_callbacks->failed(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_moved(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->moved) return; - c->log_callbacks->moved(c->log_v, vec[0]); + if(c->log_callbacks->moved) + c->log_callbacks->moved(c->log_v, vec[0]); } static void logentry_playing(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->playing) return; c->statebits |= DISORDER_PLAYING; - c->log_callbacks->playing(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->playing) + c->log_callbacks->playing(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } @@ -1260,7 +1501,7 @@ static void logentry_queue(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { struct queue_entry *q; - if(!c->log_callbacks->completed) return; + if(!c->log_callbacks->queue) return; q = xmalloc(sizeof *q); if(queue_unmarshall_vec(q, nvec, vec, eclient_queue_error, c)) return; /* bogus */ @@ -1280,25 +1521,65 @@ static void logentry_recent_added(disorder_eclient *c, static void logentry_recent_removed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->recent_removed) return; - c->log_callbacks->recent_removed(c->log_v, vec[0]); + if(c->log_callbacks->recent_removed) + c->log_callbacks->recent_removed(c->log_v, vec[0]); } static void logentry_removed(disorder_eclient *c, int attribute((unused)) nvec, char **vec) { - if(!c->log_callbacks->removed) return; - c->log_callbacks->removed(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->removed) + 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) + 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; c->statebits &= ~DISORDER_PLAYING; - c->log_callbacks->scratched(c->log_v, vec[0], vec[1]); + if(c->log_callbacks->scratched) + c->log_callbacks->scratched(c->log_v, vec[0], vec[1]); if(c->log_callbacks->state) c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } +static void logentry_user_add(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_add) + c->log_callbacks->user_add(c->log_v, vec[0]); +} + +static void logentry_user_confirm(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_confirm) + c->log_callbacks->user_confirm(c->log_v, vec[0]); +} + +static void logentry_user_delete(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_delete) + c->log_callbacks->user_delete(c->log_v, vec[0]); +} + +static void logentry_user_edit(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->user_edit) + c->log_callbacks->user_edit(c->log_v, vec[0], vec[1]); +} + +static void logentry_rights_changed(disorder_eclient *c, + int attribute((unused)) nvec, char **vec) { + if(c->log_callbacks->rights_changed) { + rights_type r; + if(!parse_rights(vec[0], &r, 0/*report*/)) + c->log_callbacks->rights_changed(c->log_v, r); + } +} + static const struct { unsigned long bit; const char *enable; @@ -1325,8 +1606,8 @@ static void logentry_state(disorder_eclient *c, c->statebits &= ~statestrings[n].bit; break; } - if(!c->log_callbacks->state) return; - c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); + if(c->log_callbacks->state) + c->log_callbacks->state(c->log_v, c->statebits | DISORDER_CONNECTED); } static void logentry_volume(disorder_eclient *c,