From e7ce7665fd98a41e5b2c76643a58cdbc053ed41a Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 17 May 2008 21:13:25 +0100 Subject: [PATCH] Fix login/logout/etc and misc other bits and pieces Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/cgi.c | 12 +- lib/macros.c | 6 +- server/actions.c | 281 +++++++++++++++++++++++++++++++++++++-- server/dcgi.c | 213 ----------------------------- server/disorder-cgi.h | 6 +- server/macros-disorder.c | 5 +- templates/choose.tmpl | 20 +-- templates/disorder.css | 8 +- templates/macros.tmpl | 14 +- templates/new.tmpl | 4 +- templates/options.labels | 3 +- 11 files changed, 310 insertions(+), 262 deletions(-) diff --git a/lib/cgi.c b/lib/cgi.c index 9e455bf..1d1ddd7 100644 --- a/lib/cgi.c +++ b/lib/cgi.c @@ -350,13 +350,11 @@ char *cgi_thisurl(const char *url) { dynstr_init(d); dynstr_append_string(d, url); - if(*keys) { - dynstr_append(d, '?'); - for(n = 0; keys[n]; ++n) { - dynstr_append_string(d, urlencodestring(keys[n])); - dynstr_append(d, '='); - dynstr_append_string(d, cgi_get(keys[n])); - } + for(n = 0; keys[n]; ++n) { + dynstr_append(d, n ? '&' : '?'); + dynstr_append_string(d, urlencodestring(keys[n])); + dynstr_append(d, '='); + dynstr_append_string(d, cgi_get(keys[n])); } dynstr_terminate(d); return d->vec; diff --git a/lib/macros.c b/lib/macros.c index f177ae0..4d3b9a0 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -350,9 +350,7 @@ static int mx__register(unsigned flags, e->args = args; e->callback = callback; e->definition = definition; - return hash_add(expansions, name, &e, - ((flags & EXP_TYPE_MASK) == EXP_MACRO) - ? HASH_INSERT : HASH_INSERT_OR_REPLACE); + return hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE); } /** @brief Register a simple expansion rule @@ -394,11 +392,13 @@ int mx_register_macro(const char *name, const struct mx_node *definition) { if(mx__register(EXP_MACRO, name, nargs, nargs, args, 0/*callback*/, definition)) { +#if 0 /* This locates the error to the definition, which may be a line or two * beyond the @define command itself. The backtrace generated by * mx_expand() may help more. */ error(0, "%s:%d: duplicate definition of '%s'", definition->filename, definition->line, name); +#endif return -2; } return 0; diff --git a/server/actions.c b/server/actions.c index 59d6bcc..4754a8c 100644 --- a/server/actions.c +++ b/server/actions.c @@ -34,7 +34,7 @@ static void redirect(const char *url) { if(!url) 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 { @@ -86,13 +86,10 @@ 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, dcgi_cookie_header()) < 0) + if(printf("Refresh: %ld;url=%s\n", + refresh, url) < 0) fatal(errno, "error writing to stdout"); - dcgi_expand("playing"); + dcgi_expand("playing", 1); } static void act_disable(void) { @@ -237,6 +234,253 @@ static void act_volume(void) { redirect(0); } +/** @brief Expand the login template with @b @@error set to @p error + * @param error Error keyword + */ +static void login_error(const char *error) { + dcgi_error_string = error; + 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 */ +} + +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"; + dcgi_expand("login", 1); + } +} + +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); +} + +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); +} + +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); +} + +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); +} + +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 Table of actions */ static const struct action { /** @brief Action name */ @@ -244,8 +488,12 @@ static const struct action { /** @brief Action handler */ void (*handler)(void); } 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 }, @@ -253,6 +501,8 @@ static const struct action { { "playing", act_playing }, { "randomdisable", act_random_disable }, { "randomenable", act_random_enable }, + { "register", act_register }, + { "reminder", act_reminder }, { "remove", act_remove }, { "resume", act_resume }, { "volume", act_volume }, @@ -281,8 +531,9 @@ static int dcgi_valid_action(const char *name) { /** @brief Expand a template * @param name Base name of template, or NULL to consult CGI args + * @param header True to write header */ -void dcgi_expand(const char *name) { +void dcgi_expand(const char *name, int header) { const char *p, *found; /* Parse macros first */ @@ -294,6 +545,12 @@ void dcgi_expand(const char *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"); @@ -327,18 +584,14 @@ void dcgi_action(const char *action) { actions[n].handler(); else { /* Just expand the template */ - if(printf("Content-Type: text/html\n" - "%s\n" - "\n", dcgi_cookie_header()) < 0) - fatal(errno, "error writing to stdout"); - dcgi_expand(action); + dcgi_expand(action, 1/*header*/); } } /** @brief Generate an error page */ void dcgi_error(const char *key) { dcgi_error_string = xstrdup(key); - dcgi_expand("error"); + dcgi_expand("error", 1); } /* diff --git a/server/dcgi.c b/server/dcgi.c index c38ee6c..9a510c8 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -159,219 +159,6 @@ static void act_prefs(cgi_sink *output, dcgi_state *ds) { cgi_body(output->sink); expand(output, "prefs", ds); } - -static void act_login(cgi_sink *output, - dcgi_state *ds) { - const char *username, *password, *back; - disorder_client *c; - - 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 */ - expand_template(ds, output, "login"); - return; - } - /* 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)) { - cgi_set_option("error", "loginfailed"); - expand_template(ds, output, "login"); - return; - } - if(disorder_make_cookie(c, &login_cookie)) { - cgi_set_option("error", "cookiefailed"); - expand_template(ds, output, "login"); - return; - } - /* Use the new connection henceforth */ - ds->g->client = c; - ds->g->flags = 0; - /* We have a new cookie */ - header_cookie(output->sink); - cgi_set_option("status", "loginok"); - if((back = cgi_get("back")) && *back) - /* Redirect back to somewhere or other */ - redirect(output->sink); - else - /* Stick to the login page */ - expand_template(ds, output, "login"); -} - -static void act_logout(cgi_sink *output, - dcgi_state *ds) { - disorder_revoke(ds->g->client); - login_cookie = 0; - /* Reconnect as guest */ - disorder_cgi_login(ds, output); - /* Back to the login page */ - cgi_set_option("status", "logoutok"); - expand_template(ds, output, "login"); -} - -static void act_register(cgi_sink *output, - dcgi_state *ds) { - const char *username, *password, *password2, *email; - char *confirm, *content_type; - const char *text, *encoding, *charset; - - username = cgi_get("username"); - password = cgi_get("password1"); - password2 = cgi_get("password2"); - email = cgi_get("email"); - - if(!username || !*username) { - cgi_set_option("error", "nousername"); - expand_template(ds, output, "login"); - return; - } - if(!password || !*password) { - cgi_set_option("error", "nopassword"); - expand_template(ds, output, "login"); - return; - } - if(!password2 || !*password2 || strcmp(password, password2)) { - cgi_set_option("error", "passwordmismatch"); - expand_template(ds, output, "login"); - return; - } - if(!email || !*email) { - cgi_set_option("error", "noemail"); - expand_template(ds, output, "login"); - return; - } - /* We could well do better address validation but for now we'll just do the - * minimum */ - if(!strchr(email, '@')) { - cgi_set_option("error", "bademail"); - expand_template(ds, output, "login"); - return; - } - if(disorder_register(ds->g->client, username, password, email, &confirm)) { - cgi_set_option("error", "cannotregister"); - expand_template(ds, output, "login"); - 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 */ - cgi_set_option("status", "registered"); - expand_template(ds, output, "login"); -} - -static void act_confirm(cgi_sink *output, - dcgi_state *ds) { - const char *confirmation; - - if(!(confirmation = cgi_get("c"))) { - cgi_set_option("error", "noconfirm"); - expand_template(ds, output, "login"); - } - /* Confirm our registration */ - if(disorder_confirm(ds->g->client, confirmation)) { - cgi_set_option("error", "badconfirm"); - expand_template(ds, output, "login"); - } - /* Get a cookie */ - if(disorder_make_cookie(ds->g->client, &login_cookie)) { - cgi_set_option("error", "cookiefailed"); - expand_template(ds, output, "login"); - return; - } - /* Discard any cached data JIC */ - ds->g->flags = 0; - /* We have a new cookie */ - header_cookie(output->sink); - cgi_set_option("status", "confirmed"); - expand_template(ds, output, "login"); -} - -static void act_edituser(cgi_sink *output, - dcgi_state *ds) { - const char *email = cgi_get("email"), *password = cgi_get("changepassword1"); - const char *password2 = cgi_get("changepassword2"); - int newpassword = 0; - disorder_client *c; - - if((password && *password) || (password && *password2)) { - if(!password || !password2 || strcmp(password, password2)) { - cgi_set_option("error", "passwordmismatch"); - expand_template(ds, output, "login"); - return; - } - } else - password = password2 = 0; - - if(email) { - if(disorder_edituser(ds->g->client, disorder_user(ds->g->client), - "email", email)) { - cgi_set_option("error", "badedit"); - expand_template(ds, output, "login"); - return; - } - } - if(password) { - if(disorder_edituser(ds->g->client, disorder_user(ds->g->client), - "password", password)) { - cgi_set_option("error", "badedit"); - expand_template(ds, output, "login"); - return; - } - newpassword = 1; - } - if(newpassword) { - login_cookie = 0; /* it'll be invalid now */ - /* This is a bit duplicative of act_login() */ - c = disorder_new(0); - if(disorder_connect_user(c, disorder_user(ds->g->client), password)) { - cgi_set_option("error", "loginfailed"); - expand_template(ds, output, "login"); - return; - } - if(disorder_make_cookie(c, &login_cookie)) { - cgi_set_option("error", "cookiefailed"); - expand_template(ds, output, "login"); - return; - } - /* Use the new connection henceforth */ - ds->g->client = c; - ds->g->flags = 0; - /* We have a new cookie */ - header_cookie(output->sink); - } - cgi_set_option("status", "edited"); - expand_template(ds, output, "login"); -} - -static void act_reminder(cgi_sink *output, - dcgi_state *ds) { - const char *const username = cgi_get("username"); - - if(!username || !*username) { - cgi_set_option("error", "nousername"); - expand_template(ds, output, "login"); - return; - } - if(disorder_reminder(ds->g->client, username)) { - cgi_set_option("error", "reminderfailed"); - expand_template(ds, output, "login"); - return; - } - cgi_set_option("status", "reminded"); - expand_template(ds, output, "login"); -} - /* expansions *****************************************************************/ static void exp_label(int attribute((unused)) nargs, diff --git a/server/disorder-cgi.h b/server/disorder-cgi.h index 93e4e2f..9da8975 100644 --- a/server/disorder-cgi.h +++ b/server/disorder-cgi.h @@ -55,10 +55,12 @@ #include "inputline.h" #include "split.h" #include "mime.h" +#include "sendmail.h" extern disorder_client *dcgi_client; extern char *dcgi_cookie; -extern char *dcgi_error_string; +extern const char *dcgi_error_string; +extern const char *dcgi_status_string; /** @brief Entry in a list of tracks or directories */ struct dcgi_entry { @@ -73,7 +75,7 @@ struct dcgi_entry { /** @brief Compare two @ref entry objects */ int dcgi_compare_entry(const void *a, const void *b); -void dcgi_expand(const char *name); +void dcgi_expand(const char *name, int header); void dcgi_action(const char *action); void dcgi_error(const char *key); void dcgi_login(void); diff --git a/server/macros-disorder.c b/server/macros-disorder.c index a08f0ed..807f398 100644 --- a/server/macros-disorder.c +++ b/server/macros-disorder.c @@ -1,3 +1,4 @@ + /* * This file is part of DisOrder. * Copyright (C) 2004-2008 Richard Kettlewell @@ -24,10 +25,10 @@ #include "disorder-cgi.h" /** @brief For error template */ -char *dcgi_error_string; +const char *dcgi_error_string; /** @brief For login template */ -char *dcgi_status_string; +const char *dcgi_status_string; /** @brief Return @p i as a string */ static const char *make_index(int i) { diff --git a/templates/choose.tmpl b/templates/choose.tmpl index 7e60d55..a0d5549 100644 --- a/templates/choose.tmpl +++ b/templates/choose.tmpl @@ -24,7 +24,7 @@ USA @stdmenu{choose} -

@label:choose.title@

+

@label{choose.title}

@if{@eq{@label{menu.choosewhich}}{choosealpha}} { @@ -97,7 +97,7 @@ USA - +

@@ -117,8 +117,8 @@ USA @martist{search}{@track} @malbum{search}{@track} - @mtitle{search}{@track} - @length{@id} + @mtitleplay{search}{@track} + @length{@track} @right{prefs}{
- @dirs{@arg{dir}}{@arg{re}}{ + @tracks{@arg{dir}}{@arg{re}}{

@define{sometracks}{template}{@template}@# - @rights{prefs}{ + @right{prefs}{ @label:choose.prefs@ + title="@label{choose.prefsverbose}" + alt="@label{choose.prefs}"> }@# @label:choose.allprefs@ + title="@label{choose.allprefsverbose}" + alt="@label{choose.allprefs}"> } diff --git a/templates/disorder.css b/templates/disorder.css index a1697c1..5782975 100644 --- a/templates/disorder.css +++ b/templates/disorder.css @@ -60,19 +60,19 @@ table { /* playing, recent and new classes correspond to the tables in playing.html, * recent.html and new.html */ -table.playing, table.recent, table.new { +table.playing, table.recent, table.new, table.search { width: 100% /* use full screen width */ } -table.playing th, table.recent th, table.new th { +table.playing th, table.recent th, table.new th, table.search th { text-align: left /* titles should be left-aligned */ } -table.playing td, table.recent td, table.new td { +table.playing td, table.recent td, table.new td, table.search td { vertical-align: middle /* centre cell contents vertically */ } -table.playing a, table.recent a, table.new a { +table.playing a, table.recent a, table.new a, table.search a { color: black } diff --git a/templates/macros.tmpl b/templates/macros.tmpl index c3b7ed8..495c29a 100644 --- a/templates/macros.tmpl +++ b/templates/macros.tmpl @@ -110,8 +110,8 @@ and then redefines macros as desired. @define {martist} {what track} {@right{play} {@part{@track}{artist}{short}} + href="@url?action=choose&dir=@urlquote{@dirname{@dirname{@track}}}" + title="@label{playing.artistverbose}">@part{@track}{artist}{short}} {@part{@track}{artist}{short}}} @@ -121,8 +121,8 @@ and then redefines macros as desired. @define {malbum} {what track} {@right{play} {@part{@track}{album}{short}} + href="@url?action=choose&dir=@urlquote{@dirname{@track}}" + title="@label{playing.albumverbose}">@part{@track}{album}{short}} {@part{@track}{album}{short}}} @@ -132,6 +132,12 @@ and then redefines macros as desired. @define {mtitle} {what track} {@part{@track}{title}{short}} +@# As @mtitle but make a link to play the track +@# @what is the section +@# @track is the track name +@define {mtitleplay} {what track} + {@part{@track}{title}{short}} + @# Expand to the remove/scratch entry for @id @# @what is the section @# @id is the track ID diff --git a/templates/new.tmpl b/templates/new.tmpl index e017a74..6611f08 100644 --- a/templates/new.tmpl +++ b/templates/new.tmpl @@ -41,8 +41,8 @@ USA @martist{new}{@track} @malbum{new}{@track} - @mtitle{new}{@track} - @length{@id} + @mtitleplay{new}{@track} + @length{@track} @right{prefs}{