X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/5a7df0480aa9e958e612b2da9addd589d404fd95..5c1ae3bc9c3a3cbed192fed2d68fc9b7bbe5c96f:/server/actions.c diff --git a/server/actions.c b/server/actions.c index 2bdb8c4..961fdec 100644 --- a/server/actions.c +++ b/server/actions.c @@ -17,58 +17,24 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file server/actions.c + * @brief DisOrder web actions + * + * Actions are anything that the web interface does beyond passive template + * expansion and inspection of state recieved from the server. This means + * playing tracks, editing prefs etc but also setting extra headers e.g. to + * auto-refresh the playing list. + */ -#include -#include "types.h" - -#include "actions.h" -#include "lookups.h" - -/** @brief Login cookie */ -char *login_cookie; - -/** @brief Return a Cookie: header */ -static char *cookie(void) { - struct dynstr d[1]; - struct url u; - char *s; - - memset(&u, 0, sizeof u); - dynstr_init(d); - parse_url(config->url, &u); - if(login_cookie) { - dynstr_append_string(d, "disorder="); - dynstr_append_string(d, login_cookie); - } else { - /* Force browser to discard cookie */ - dynstr_append_string(d, "disorder=none;Max-Age=0"); - } - if(u.path) { - /* The default domain matches the request host, so we need not override - * that. But the default path only goes up to the rightmost /, which would - * cause the browser to expose the cookie to other CGI programs on the same - * web server. */ - dynstr_append_string(d, ";Version=1;Path="); - /* Formally we are supposed to quote the path, since it invariably has a - * slash in it. However Safari does not parse quoted paths correctly, so - * this won't work. Fortunately nothing else seems to care about proper - * quoting of paths, so in practice we get with it. (See also - * parse_cookie() where we are liberal about cookie paths on the way back - * in.) */ - dynstr_append_string(d, u.path); - } - dynstr_terminate(d); - byte_xasprintf(&s, "Set-Cookie: %s", d->vec); - return s; -} +#include "disorder-cgi.h" /** @brief Redirect to some other action or URL */ static void redirect(const char *url) { /* By default use the 'back' argument */ if(!url) - url = cgi_get("back") + url = cgi_get("back"); if(url) { - if(!strncmp(url, "http", 4)) + if(strncmp(url, "http", 4)) /* If the target is not a full URL assume it's the action */ url = cgi_makeurl(config->url, "action", url, (char *)0); } else { @@ -77,39 +43,53 @@ static void redirect(const char *url) { } if(printf("Location: %s\n" "%s\n" - "\n", url, cookie()) < 0) + "\n", url, dcgi_cookie_header()) < 0) fatal(errno, "error writing to stdout"); } -/* 'playing' and 'manage' just add a Refresh: header */ +/*! playing + * + * Expands \fIplaying.tmpl\fR as if there was no special 'playing' action, but + * adds a Refresh: field to the HTTP header. The maximum refresh interval is + * defined by \fBrefresh\fR (see \fBdisorder_config\fR(5)) but may be less if + * the end of the track is near. + */ +/*! manage + * + * Expands \fIplaying.tmpl\fR (NB not \fImanage.tmpl\fR) as if there was no + * special 'playing' action, and adds a Refresh: field to the HTTP header. The + * maximum refresh interval is defined by \Bfrefresh\fR (see + * \fBdisorder_config\fR(5)) but may be less if the end of the track is near. + */ static void act_playing(void) { long refresh = config->refresh; long length; time_t now, fin; char *url; + const char *action; - lookups(DC_PLAYING|DC_QUEUE|DC_ENABLED|DC_RANDOM_ENABLED); - if(playing - && playing->state == playing_started /* i.e. not paused */ - && !disorder_length(client, playing->track, &length) + dcgi_lookup(DCGI_PLAYING|DCGI_QUEUE|DCGI_ENABLED|DCGI_RANDOM_ENABLED); + if(dcgi_playing + && dcgi_playing->state == playing_started /* i.e. not paused */ + && !disorder_length(dcgi_client, dcgi_playing->track, &length) && length - && playing->sofar >= 0) { + && dcgi_playing->sofar >= 0) { /* Try to put the next refresh at the start of the next track. */ time(&now); - fin = now + length - playing->sofar + config->gap; + fin = now + length - dcgi_playing->sofar + config->gap; if(now + refresh > fin) refresh = fin - now; } - if(queue && queue->state == playing_isscratch) { + if(dcgi_queue && dcgi_queue->state == playing_isscratch) { /* next track is a scratch, don't leave more than the inter-track gap */ if(refresh > config->gap) refresh = config->gap; } - if(!playing - && ((queue - && queue->state != playing_random) - || random_enabled) - && enabled) { + if(!dcgi_playing + && ((dcgi_queue + && dcgi_queue->state != playing_random) + || dcgi_random_enabled) + && dcgi_enabled) { /* no track playing but playing is enabled and there is something coming * up, must be in a gap */ if(refresh > config->gap) @@ -119,13 +99,577 @@ static void act_playing(void) { url = cgi_makeurl(config->url, "action", action, (char *)0); else url = config->url; - if(printf("Content-Type: text/html\n" - "Refresh: %ld;url=%s\n" - "%s\n" - "\n", - refresh, url, cookie()) < 0) + if(printf("Refresh: %ld;url=%s\n", + refresh, url) < 0) fatal(errno, "error writing to stdout"); - disorder_cgi_expand(action ? action : "playing"); + dcgi_expand("playing", 1); +} + +/*! disable + * + * Disables play. + */ +static void act_disable(void) { + if(dcgi_client) + disorder_disable(dcgi_client); + redirect(0); +} + +/*! enable + * + * Enables play. + */ +static void act_enable(void) { + if(dcgi_client) + disorder_enable(dcgi_client); + redirect(0); +} + +/*! random-disable + * + * Disables random play. + */ +static void act_random_disable(void) { + if(dcgi_client) + disorder_random_disable(dcgi_client); + redirect(0); +} + +/*! random-enable + * + * Enables random play. + */ +static void act_random_enable(void) { + if(dcgi_client) + disorder_random_enable(dcgi_client); + redirect(0); +} + +/*! pause + * + * Pauses the current track (if there is one and it's not paused already). + */ +static void act_pause(void) { + if(dcgi_client) + disorder_pause(dcgi_client); + redirect(0); +} + +/*! resume + * + * Resumes the current track (if there is one and it's paused). + */ +static void act_resume(void) { + if(dcgi_client) + disorder_resume(dcgi_client); + redirect(0); +} + +/*! remove + * + * Removes the track given by the \fBid\fR argument. If this is the currently + * playing track then it is scratched. + */ +static void act_remove(void) { + const char *id; + struct queue_entry *q; + + if(dcgi_client) { + if(!(id = cgi_get("id"))) + error(0, "missing 'id' argument"); + else if(!(q = dcgi_findtrack(id))) + error(0, "unknown queue id %s", id); + else switch(q->state) { + case playing_isscratch: + case playing_failed: + case playing_no_player: + case playing_ok: + case playing_quitting: + case playing_scratched: + error(0, "does not make sense to scratch %s", id); + break; + case playing_paused: /* started but paused */ + case playing_started: /* started to play */ + disorder_scratch(dcgi_client, id); + break; + case playing_random: /* unplayed randomly chosen track */ + case playing_unplayed: /* haven't played this track yet */ + disorder_remove(dcgi_client, id); + break; + } + } + redirect(0); +} + +/*! move + * + * Moves the track given by the \fBid\fR argument the distance given by the + * \fBdelta\fR argument. If this is positive the track is moved earlier in the + * queue and if negative, later. + */ +static void act_move(void) { + const char *id, *delta; + struct queue_entry *q; + + if(dcgi_client) { + if(!(id = cgi_get("id"))) + error(0, "missing 'id' argument"); + else if(!(delta = cgi_get("delta"))) + error(0, "missing 'delta' argument"); + else if(!(q = dcgi_findtrack(id))) + error(0, "unknown queue id %s", id); + else switch(q->state) { + case playing_random: /* unplayed randomly chosen track */ + case playing_unplayed: /* haven't played this track yet */ + disorder_move(dcgi_client, id, atol(delta)); + break; + default: + error(0, "does not make sense to scratch %s", id); + break; + } + } + redirect(0); +} + +/*! play + * + * Play the track given by the \fBtrack\fR argument, or if that is not set all + * the tracks in the directory given by the \fBdir\fR argument. + */ +static void act_play(void) { + const char *track, *dir; + char **tracks; + int ntracks, n; + struct dcgi_entry *e; + + if(dcgi_client) { + if((track = cgi_get("track"))) { + disorder_play(dcgi_client, track); + } else if((dir = cgi_get("dir"))) { + if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks)) + ntracks = 0; + e = xmalloc(ntracks * sizeof (struct dcgi_entry)); + for(n = 0; n < ntracks; ++n) { + e[n].track = tracks[n]; + e[n].sort = trackname_transform("track", tracks[n], "sort"); + e[n].display = trackname_transform("track", tracks[n], "display"); + } + qsort(e, ntracks, sizeof (struct dcgi_entry), dcgi_compare_entry); + for(n = 0; n < ntracks; ++n) + disorder_play(dcgi_client, e[n].track); + } + } + redirect(0); +} + +static int clamp(int n, int min, int max) { + if(n < min) + return min; + if(n > max) + return max; + return n; +} + +/*! volume + * + * If the \fBdelta\fR argument is set: adjust both channels by that amount (up + * if positive, down if negative). + * + * Otherwise if \fBleft\fR and \fBright\fR are set, set the channels + * independently to those values. + */ +static void act_volume(void) { + const char *l, *r, *d; + int nd; + + if(dcgi_client) { + if((d = cgi_get("delta"))) { + dcgi_lookup(DCGI_VOLUME); + nd = clamp(atoi(d), -255, 255); + disorder_set_volume(dcgi_client, + clamp(dcgi_volume_left + nd, 0, 255), + clamp(dcgi_volume_right + nd, 0, 255)); + } else if((l = cgi_get("left")) && (r = cgi_get("right"))) + disorder_set_volume(dcgi_client, atoi(l), atoi(r)); + } + redirect(0); +} + +/** @brief Expand the login template with @b @@error set to @p error + * @param e Error keyword + */ +static void login_error(const char *e) { + dcgi_error_string = e; + dcgi_expand("login", 1); +} + +/** @brief Log in + * @param username Login name + * @param password Password + * @return 0 on success, non-0 on error + * + * On error, calls login_error() to expand the login template. + */ +static int login_as(const char *username, const char *password) { + disorder_client *c; + + if(dcgi_cookie && dcgi_client) + disorder_revoke(dcgi_client); + /* We'll need a new connection as we are going to stop being guest */ + c = disorder_new(0); + if(disorder_connect_user(c, username, password)) { + login_error("loginfailed"); + return -1; + } + /* Generate a cookie so we can log in again later */ + if(disorder_make_cookie(c, &dcgi_cookie)) { + login_error("cookiefailed"); + return -1; + } + /* Use the new connection henceforth */ + dcgi_client = c; + dcgi_lookup_reset(); + return 0; /* OK */ +} + +/*! login + * + * If \fBusername\fR and \fBpassword\fR are set (and the username isn't + * "guest") then attempt to log in using those credentials. On success, + * redirects to the \fBback\fR argument if that is set, or just expands + * \fIlogin.tmpl\fI otherwise, with \fB@status\fR set to \fBloginok\fR. + * + * If they aren't set then just expands \fIlogin.tmpl\fI. + */ +static void act_login(void) { + const char *username, *password; + + /* We try all this even if not connected since the subsequent connection may + * succeed. */ + + username = cgi_get("username"); + password = cgi_get("password"); + if(!username + || !password + || !strcmp(username, "guest")/*bodge to avoid guest cookies*/) { + /* We're just visiting the login page, not performing an action at all. */ + dcgi_expand("login", 1); + return; + } + if(!login_as(username, password)) { + /* Report the succesful login */ + dcgi_status_string = "loginok"; + /* Redirect back to where we came from, if necessary */ + if(cgi_get("back")) + redirect(0); + else + dcgi_expand("login", 1); + } +} + +/*! logout + * + * Logs out the current user and expands \fIlogin.tmpl\fR with \fBstatus\fR or + * \fB@error\fR set according to the result. + */ +static void act_logout(void) { + if(dcgi_client) { + /* Ask the server to revoke the cookie */ + if(!disorder_revoke(dcgi_client)) + dcgi_status_string = "logoutok"; + else + dcgi_error_string = "revokefailed"; + } else { + /* We can't guarantee a logout if we can't connect to the server to revoke + * the cookie, so we report an error. We'll still ask the browser to + * forget the cookie though. */ + dcgi_error_string = "connect"; + } + /* Attempt to reconnect without the cookie */ + dcgi_cookie = 0; + dcgi_login(); + /* Back to login page, hopefuly forcing the browser to forget the cookie. */ + dcgi_expand("login", 1); +} + +/*! register + * + * Register a new user using \fBusername\fR, \fBpassword1\fR, \fBpassword2\fR + * and \fBemail\fR and expands \fIlogin.tmpl\fR with \fBstatus\fR or + * \fB@error\fR set according to the result. + */ +static void act_register(void) { + const char *username, *password, *password2, *email; + char *confirm, *content_type; + const char *text, *encoding, *charset; + + /* If we're not connected then this is a hopeless exercise */ + if(!dcgi_client) { + login_error("connect"); + return; + } + + /* Collect arguments */ + username = cgi_get("username"); + password = cgi_get("password1"); + password2 = cgi_get("password2"); + email = cgi_get("email"); + + /* Verify arguments */ + if(!username || !*username) { + login_error("nousername"); + return; + } + if(!password || !*password) { + login_error("nopassword"); + return; + } + if(!password2 || !*password2 || strcmp(password, password2)) { + login_error("passwordmismatch"); + return; + } + if(!email || !*email) { + login_error("noemail"); + return; + } + /* We could well do better address validation but for now we'll just do the + * minimum */ + if(!strchr(email, '@')) { + login_error("bademail"); + return; + } + if(disorder_register(dcgi_client, username, password, email, &confirm)) { + login_error("cannotregister"); + return; + } + /* Send the user a mail */ + /* TODO templatize this */ + byte_xasprintf((char **)&text, + "Welcome to DisOrder. To active your login, please visit this URL:\n" + "\n" + "%s?c=%s\n", config->url, urlencodestring(confirm)); + if(!(text = mime_encode_text(text, &charset, &encoding))) + fatal(0, "cannot encode email"); + byte_xasprintf(&content_type, "text/plain;charset=%s", + quote822(charset, 0)); + sendmail("", config->mail_sender, email, "Welcome to DisOrder", + encoding, content_type, text); /* TODO error checking */ + /* We'll go back to the login page with a suitable message */ + dcgi_status_string = "registered"; + dcgi_expand("login", 1); +} + +/*! confirm + * + * Confirm a user registration using the nonce supplied in \fBc\fR and expands + * \fIlogin.tmpl\fR with \fBstatus\fR or \fB@error\fR set according to the + * result. + */ +static void act_confirm(void) { + const char *confirmation; + + /* If we're not connected then this is a hopeless exercise */ + if(!dcgi_client) { + login_error("connect"); + return; + } + + if(!(confirmation = cgi_get("c"))) { + login_error("noconfirm"); + return; + } + /* Confirm our registration */ + if(disorder_confirm(dcgi_client, confirmation)) { + login_error("badconfirm"); + return; + } + /* Get a cookie */ + if(disorder_make_cookie(dcgi_client, &dcgi_cookie)) { + login_error("cookiefailed"); + return; + } + /* Junk cached data */ + dcgi_lookup_reset(); + /* Report success */ + dcgi_status_string = "confirmed"; + dcgi_expand("login", 1); +} + +/*! edituser + * + * Edit user details using \fBusername\fR, \fBchangepassword1\fR, + * \fBchangepassword2\fR and \fBemail\fR and expands \fIlogin.tmpl\fR with + * \fBstatus\fR or \fB@error\fR set according to the result. + */ +static void act_edituser(void) { + const char *email = cgi_get("email"), *password = cgi_get("changepassword1"); + const char *password2 = cgi_get("changepassword2"); + int newpassword = 0; + + /* If we're not connected then this is a hopeless exercise */ + if(!dcgi_client) { + login_error("connect"); + return; + } + + /* Verify input */ + + /* If either password or password2 is set we insist they match. If they + * don't we report an error. */ + if((password && *password) || (password2 && *password2)) { + if(!password || !password2 || strcmp(password, password2)) { + login_error("passwordmismatch"); + return; + } + } else + password = password2 = 0; + if(email && !strchr(email, '@')) { + login_error("bademail"); + return; + } + + /* Commit changes */ + if(email) { + if(disorder_edituser(dcgi_client, disorder_user(dcgi_client), + "email", email)) { + login_error("badedit"); + return; + } + } + if(password) { + if(disorder_edituser(dcgi_client, disorder_user(dcgi_client), + "password", password)) { + login_error("badedit"); + return; + } + newpassword = 1; + } + + if(newpassword) { + /* If we changed the password, the cookie is now invalid, so we must log + * back in. */ + if(login_as(disorder_user(dcgi_client), password)) + return; + } + /* Report success */ + dcgi_status_string = "edited"; + dcgi_expand("login", 1); +} + +/*! reminder + * + * Issue an email password reminder to \fBusername\fR and expands + * \fIlogin.tmpl\fR with \fBstatus\fR or \fB@error\fR set according to the + * result. + */ +static void act_reminder(void) { + const char *const username = cgi_get("username"); + + /* If we're not connected then this is a hopeless exercise */ + if(!dcgi_client) { + login_error("connect"); + return; + } + + if(!username || !*username) { + login_error("nousername"); + return; + } + if(disorder_reminder(dcgi_client, username)) { + login_error("reminderfailed"); + return; + } + /* Report success */ + dcgi_status_string = "reminded"; + dcgi_expand("login", 1); +} + +/** @brief Get the numbered version of an argument + * @param argname Base argument name + * @param numfile File number + * @return cgi_get(NUMFILE_ARGNAME) + */ +static const char *numbered_arg(const char *argname, int numfile) { + char *fullname; + + byte_xasprintf(&fullname, "%d_%s", numfile, argname); + return cgi_get(fullname); +} + +/** @brief Set preferences for file @p numfile + * @return 0 on success, -1 if there is no such track number + * + * The old @b nfiles parameter has been abolished, we just keep look for more + * files until we run out. + */ +static int process_prefs(int numfile) { + const char *file, *name, *value, *part, *parts, *context; + char **partslist; + + if(!(file = numbered_arg("track", numfile))) + return -1; + if(!(parts = cgi_get("parts"))) + parts = "artist album title"; + if(!(context = cgi_get("context"))) + context = "display"; + partslist = split(parts, 0, 0, 0, 0); + while((part = *partslist++)) { + if(!(value = numbered_arg(part, numfile))) + continue; + byte_xasprintf((char **)&name, "trackname_%s_%s", context, part); + disorder_set(dcgi_client, file, name, value); + } + if((value = numbered_arg("random", numfile))) + disorder_unset(dcgi_client, file, "pick_at_random"); + else + disorder_set(dcgi_client, file, "pick_at_random", "0"); + if((value = numbered_arg("tags", numfile))) { + if(!*value) + disorder_unset(dcgi_client, file, "tags"); + else + disorder_set(dcgi_client, file, "tags", value); + } + if((value = numbered_arg("weight", numfile))) { + if(!*value) + disorder_unset(dcgi_client, file, "weight"); + else + disorder_set(dcgi_client, file, "weight", value); + } + return 0; +} + +/*! prefs + * + * Set preferences on a number of tracks. + * + * The tracks to modify are specified in arguments \fB0_track\fR, \fB1_track\fR + * etc. The number sequence must be contiguous and start from 0. + * + * For each track \fIINDEX\fB_track\fR: + * - \fIINDEX\fB_\fIPART\fR is used to set the trackname preference for + * that part. (See \fBparts\fR below.) + * - \fIINDEX\fB_\fIrandom\fR if present enables random play for this track + * or disables it if absent. + * - \fIINDEX\fB_\fItags\fR sets the list of tags for this track. + * - \fIINDEX\fB_\fIweight\fR sets the weight for this track. + * + * \fBparts\fR can be set to the track name parts to modify. The default is + * "artist album title". + * + * \fBcontext\fR can be set to the context to modify. The default is + * "display". + * + * If the server detects a preference being set to its default, it removes the + * preference, thus keeping the database tidy. + */ +static void act_set(void) { + int numfile; + + if(dcgi_client) { + for(numfile = 0; !process_prefs(numfile); ++numfile) + ; + } + redirect(0); } /** @brief Table of actions */ @@ -134,42 +678,74 @@ static const struct action { const char *name; /** @brief Action handler */ void (*handler)(void); + /** @brief Union of suitable rights */ + rights_type rights; } actions[] = { - { "confirm", act_confirm }, - { "disable", act_disable }, - { "edituser", act_edituser }, - { "enable", act_enable }, - { "login", act_login }, - { "logout", act_logout }, - { "manage", act_playing }, - { "move", act_move }, - { "pause", act_pause }, - { "play", act_play }, - { "playing", act_playing }, - { "prefs", act_prefs }, - { "random-disable", act_random_disable }, - { "random-enable", act_random_enable }, - { "register", act_register }, - { "reminder", act_reminder }, - { "remove", act_remove }, - { "resume", act_resume }, - { "scratch", act_scratch }, - { "volume", act_volume }, + { "confirm", act_confirm, 0 }, + { "disable", act_disable, RIGHT_GLOBAL_PREFS }, + { "edituser", act_edituser, 0 }, + { "enable", act_enable, RIGHT_GLOBAL_PREFS }, + { "login", act_login, 0 }, + { "logout", act_logout, 0 }, + { "manage", act_playing, 0 }, + { "move", act_move, RIGHT_MOVE__MASK }, + { "pause", act_pause, RIGHT_PAUSE }, + { "play", act_play, RIGHT_PLAY }, + { "playing", act_playing, 0 }, + { "randomdisable", act_random_disable, RIGHT_GLOBAL_PREFS }, + { "randomenable", act_random_enable, RIGHT_GLOBAL_PREFS }, + { "register", act_register, 0 }, + { "reminder", act_reminder, 0 }, + { "remove", act_remove, RIGHT_MOVE__MASK|RIGHT_SCRATCH__MASK }, + { "resume", act_resume, RIGHT_PAUSE }, + { "set", act_set, RIGHT_PREFS }, + { "volume", act_volume, RIGHT_VOLUME }, }; +/** @brief Check that an action name is valid + * @param name Action + * @return 1 if valid, 0 if not + */ +static int dcgi_valid_action(const char *name) { + int c; + + /* First character must be letter or digit (this also requires there to _be_ + * a first character) */ + if(!isalnum((unsigned char)*name)) + return 0; + /* Only letters, digits, '.' and '-' allowed */ + while((c = (unsigned char)*name++)) { + if(!(isalnum(c) + || c == '.' + || c == '_')) + return 0; + } + return 1; +} + /** @brief Expand a template * @param name Base name of template, or NULL to consult CGI args + * @param header True to write header */ -void disorder_cgi_expand(const char *name) { - const char *p; - +void dcgi_expand(const char *name, int header) { + const char *p, *found; + + /* Parse macros first */ + if((found = mx_find("macros.tmpl"))) + mx_expand_file(found, sink_discard(), 0); /* For unknown actions check that they aren't evil */ - for(p = name; *p && isalnum((unsigned char)*p); ++p) - ; - if(*p) - fatal(0, "invalid action name '%s'", action); - byte_xasprintf((char **)&p, "%s.tmpl", action); - if(mx_expand_file(p, sink_stdio(stdout), 0) == -1 + if(!dcgi_valid_action(name)) + fatal(0, "invalid action name '%s'", name); + byte_xasprintf((char **)&p, "%s.tmpl", name); + if(!(found = mx_find(p))) + fatal(errno, "cannot find %s", p); + if(header) { + if(printf("Content-Type: text/html\n" + "%s\n" + "\n", dcgi_cookie_header()) < 0) + fatal(errno, "error writing to stdout"); + } + if(mx_expand_file(found, sink_stdio("stdout", stdout), 0) == -1 || fflush(stdout) < 0) fatal(errno, "error writing to stdout"); } @@ -179,9 +755,8 @@ void disorder_cgi_expand(const char *name) { * * If no recognized action is specified then 'playing' is assumed. */ -void disorder_cgi_action(const char *action) { +void dcgi_action(const char *action) { int n; - char *s; /* Consult CGI args if caller had no view */ if(!action) @@ -198,41 +773,33 @@ void disorder_cgi_action(const char *action) { /* Make sure 'action' is always set */ cgi_set("action", action); } - if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) - /* Its a known action */ + if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) { + if(actions[n].rights) { + /* Some right or other is required */ + dcgi_lookup(DCGI_RIGHTS); + if(!(actions[n].rights & dcgi_rights)) { + const char *back = cgi_thisurl(config->url); + /* Failed operations jump you to the login screen with an error + * message. On success, the user comes back to the page they were + * after. */ + cgi_clear(); + cgi_set("back", back); + login_error("noright"); + return; + } + } + /* It's a known action */ actions[n].handler(); - else { + } else { /* Just expand the template */ - if(printf("Content-Type: text/html\n" - "%s\n" - "\n", cookie()) < 0) - fatal(errno, "error writing to stdout"); - disorder_cgi_expand(action); + dcgi_expand(action, 1/*header*/); } } /** @brief Generate an error page */ -void disorder_cgi_error(const char *msg, ...) { - va_list ap; - - va_start(ap, msg); - byte_xvasprintf(&error_string, msg, ap); - va_end(ap); - disorder_cgi_expand("error"); -} - -/** @brief Log in as the current user or guest if none */ -void disorder_cgi_login(dcgi_state *ds, struct sink *output) { - /* Junk old data */ - disorder_macros_reset(); - /* Reconnect */ - if(disorder_connect_cookie(client, login_cookie)) { - disorder_cgi_error("Cannot connect to server"); - exit(0); - } - /* If there was a cookie but it went bad, we forget it */ - if(login_cookie && !strcmp(disorder_user(>client), "guest")) - login_cookie = 0; +void dcgi_error(const char *key) { + dcgi_error_string = xstrdup(key); + dcgi_expand("error", 1); } /*