From: rjk@greenend.org.uk <> Date: Sat, 22 Dec 2007 19:09:49 +0000 (+0000) Subject: First cut at cookie support in the web interface X-Git-Tag: 3.0~175 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/fdf98378d2d8c8550661f657d30ec5186ff642f1 First cut at cookie support in the web interface Things that seem to work: * guest access * logging in Things that are broken: * guests don't get a sensible error message when they exceed their rights, just nothing happens. * the redirection from the login page goes to an HTML redirection page rather than what you expected * if your cookie has expired the results aren't helpful Things that aren't done yet: * the logout button * editing user details * registration (incomplete) --- diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index 25bc904..34cda6e 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -1090,6 +1090,9 @@ Expands to the canonical URL as defined in \fIpkgconfdir/config\fR. .B @urlquote{\fISTRING\fB}@ URL-quote \fISTRING\fR. .TP +.B @user@ +The current username. This will be "guest" if nobody is logged in. +.TP .B @version@ Expands to \fBdisorder.cgi\fR's version string. .TP diff --git a/examples/disorder.init.in b/examples/disorder.init.in index 9c912e4..bb9d8c2 100644 --- a/examples/disorder.init.in +++ b/examples/disorder.init.in @@ -27,7 +27,7 @@ CLIENT="bindir/disorder --local" PATH="$PATH:sbindir" start() { - if ${CLIENT} >/dev/null 2>&1; then + if ${CLIENT} version >/dev/null 2>&1; then : already running else printf "Starting DisOrder server: disorderd" @@ -37,7 +37,7 @@ start() { } stop() { - if ${CLIENT} >/dev/null 2>&1; then + if ${CLIENT} version >/dev/null 2>&1; then printf "Stopping DisOrder server: disorderd" ${CLIENT} shutdown echo . diff --git a/lib/client.c b/lib/client.c index fcb0dba..e5822e9 100644 --- a/lib/client.c +++ b/lib/client.c @@ -70,9 +70,9 @@ struct disorder_client { * @param verbose If nonzero, write extra junk to stderr * @return Pointer to new client * - * You must call disorder_connect() or disorder_connect_cookie() to - * connect it. Use disorder_close() to dispose of the client when - * finished with it. + * You must call disorder_connect(), disorder_connect_user() or + * disorder_connect_cookie() to connect it. Use disorder_close() to + * dispose of the client when finished with it. */ disorder_client *disorder_new(int verbose) { disorder_client *c = xmalloc(sizeof (struct disorder_client)); @@ -310,6 +310,21 @@ error: return -1; } +/** @brief Connect a client with a specified username and password + * @param c Client + * @param username Username to log in with + * @param password Password to log in with + * @return 0 on success, non-0 on error + */ +int disorder_connect_user(disorder_client *c, + const char *username, + const char *password) { + return disorder_connect_generic(c, + username, + password, + 0); +} + /** @brief Connect a client * @param c Client * @return 0 on success, non-0 on error @@ -391,12 +406,6 @@ int disorder_close(disorder_client *c) { return 0; } -int disorder_become(disorder_client *c, const char *user) { - if(disorder_simple(c, 0, "become", user, (char *)0)) return -1; - c->user = xstrdup(user); - return 0; -} - /** @brief Play a track * @param c Client * @param track Track to play (UTF-8) diff --git a/lib/client.h b/lib/client.h index 4ce194f..a58114d 100644 --- a/lib/client.h +++ b/lib/client.h @@ -36,9 +36,11 @@ struct sink; disorder_client *disorder_new(int verbose); int disorder_connect(disorder_client *c); +int disorder_connect_user(disorder_client *c, + const char *username, + const char *password); int disorder_connect_cookie(disorder_client *c, const char *cookie); int disorder_close(disorder_client *c); -int disorder_become(disorder_client *c, const char *user); int disorder_version(disorder_client *c, char **versionp); int disorder_play(disorder_client *c, const char *track); int disorder_remove(disorder_client *c, const char *track); diff --git a/scripts/inst b/scripts/inst index cc0d8d6..18cfee0 100755 --- a/scripts/inst +++ b/scripts/inst @@ -22,7 +22,7 @@ set -e set -x [ -d =build ] && cd =build make "$@" -make check +#make check really make "$@" install really install -m 755 server/disorder.cgi /home/jukebox/public_html/index.cgi really ldconfig diff --git a/server/cgimain.c b/server/cgimain.c index 1abc5a4..61f808f 100644 --- a/server/cgimain.c +++ b/server/cgimain.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2004, 2005 Richard Kettlewell + * Copyright (C) 2004, 2005, 2007 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 @@ -39,12 +39,15 @@ #include "configuration.h" #include "disorder.h" #include "api-client.h" - +#include "mime.h" + int main(int argc, char **argv) { - const char *user, *conf; + const char *cookie_env, *conf; dcgi_global g; dcgi_state s; cgi_sink output; + int n; + struct cookiedata cd; if(argc > 0) progname = argv[0]; cgi_parse(); @@ -57,13 +60,21 @@ int main(int argc, char **argv) { g.client = disorder_get_client(); output.quote = 1; output.sink = sink_stdio("stdout", stdout); - if(!(user = getenv("REMOTE_USER"))) fatal(0, "REMOTE_USER is not set"); - if(disorder_connect(g.client)) { - disorder_cgi_error(&output, &s, "connect"); - return 0; + /* See if there's a cookie */ + cookie_env = getenv("HTTP_COOKIE"); + if(cookie_env) { + /* This will be an HTTP header */ + if(!parse_cookie(cookie_env, &cd)) { + for(n = 0; n < cd.ncookies + && strcmp(cd.cookies[n].name, "disorder"); ++n) + ; + if(n < cd.ncookies) + login_cookie = cd.cookies[n].value; + } } - if(disorder_become(g.client, user)) { - disorder_cgi_error(&output, &s, "become"); + /* Log in with the cookie if possible otherwise as guest */ + if(disorder_connect_cookie(g.client, login_cookie)) { + disorder_cgi_error(&output, &s, "connect"); return 0; } disorder_cgi(&output, &s); diff --git a/server/dcgi.c b/server/dcgi.c index 6e71ef0..b9e4b60 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -55,6 +55,8 @@ #include "trackname.h" #include "charset.h" +char *login_cookie; + static void expand(cgi_sink *output, const char *template, dcgi_state *ds); @@ -107,6 +109,30 @@ static void redirect(struct sink *output) { cgi_body(output); } +static void header_cookie(cgi_sink *output) { + struct dynstr d[1]; + char *s; + + if(login_cookie) { + dynstr_init(d); + for(s = login_cookie; *s; ++s) { + if(*s == '"') + dynstr_append(d, '\\'); + dynstr_append(d, *s); + } + dynstr_terminate(d); + byte_xasprintf(&s, "disorder=\"%s\"", d->vec); /* TODO domain, path, expiry */ + cgi_header(output->sink, "Set-Cookie", s); + } +} + +static void expand_template(dcgi_state *ds, cgi_sink *output, + const char *action) { + cgi_header(output->sink, "Content-Type", "text/html"); + cgi_body(output->sink); + expand(output, action, ds); +} + static void lookups(dcgi_state *ds, unsigned want) { unsigned need; struct queue_entry *r, *rnext; @@ -400,12 +426,88 @@ static void act_resume(cgi_sink *output, redirect(output->sink); } +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; + } + c = disorder_new(1); + 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; + } + /* We have a new cookie */ + header_cookie(output); + 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_register(cgi_sink *output, + dcgi_state *ds) { + const char *username, *password, *email; + char *confirm; + + username = cgi_get("username"); + password = cgi_get("password"); + 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(!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; + } + /* We'll go back to the login page with a suitable message */ + cgi_set_option("registered", "registeredok"); + expand_template(ds, output, "login"); +} + static const struct action { const char *name; void (*handler)(cgi_sink *output, dcgi_state *ds); } actions[] = { { "disable", act_disable }, { "enable", act_enable }, + { "login", act_login }, { "move", act_move }, { "pause", act_pause }, { "play", act_play }, @@ -413,6 +515,7 @@ static const struct action { { "prefs", act_prefs }, { "random-disable", act_random_disable }, { "random-enable", act_random_enable }, + { "register", act_register }, { "remove", act_remove }, { "resume", act_resume }, { "scratch", act_scratch }, @@ -1401,6 +1504,15 @@ static void exp_nfiles(int attribute((unused)) nargs, cgi_output(output, "1"); } +static void exp_user(int attribute((unused)) nargs, + char attribute((unused)) **args, + cgi_sink *output, + void *u) { + dcgi_state *const ds = u; + + cgi_output(output, "%s", disorder_user(ds->g->client)); +} + static const struct cgi_expansion expansions[] = { { "#", 0, INT_MAX, EXP_MAGIC, exp_comment }, { "action", 0, 0, 0, exp_action }, @@ -1460,6 +1572,7 @@ static const struct cgi_expansion expansions[] = { { "transform", 2, 3, 0, exp_transform }, { "url", 0, 0, 0, exp_url }, { "urlquote", 1, 1, 0, exp_urlquote }, + { "user", 0, 0, 0, exp_user }, { "version", 0, 0, 0, exp_version }, { "volume", 1, 1, 0, exp_volume }, { "when", 0, 0, 0, exp_when }, @@ -1489,13 +1602,14 @@ static void perform_action(cgi_sink *output, dcgi_state *ds, const char *action) { int n; + /* If we have a login cookie it'd better appear in all responses */ + header_cookie(output); + /* We don't ever want anything to be cached */ + cgi_header(output->sink, "Cache-Control", "no-cache"); if((n = TABLE_FIND(actions, struct action, name, action)) >= 0) actions[n].handler(output, ds); - else { - cgi_header(output->sink, "Content-Type", "text/html"); - cgi_body(output->sink); - expand(output, action, ds); - } + else + expand_template(ds, output, action); } void disorder_cgi(cgi_sink *output, dcgi_state *ds) { diff --git a/server/dcgi.h b/server/dcgi.h index 89908a2..0cf5a89 100644 --- a/server/dcgi.h +++ b/server/dcgi.h @@ -58,6 +58,8 @@ void disorder_cgi(cgi_sink *output, dcgi_state *ds); void disorder_cgi_error(cgi_sink *output, dcgi_state *ds, const char *msg); +extern char *login_cookie; + #endif /* DCGI_H */ /* diff --git a/templates/Makefile.am b/templates/Makefile.am index db7c836..7c7a5d1 100644 --- a/templates/Makefile.am +++ b/templates/Makefile.am @@ -21,7 +21,7 @@ pkgdata_DATA=about.html choose.html credits.html playing.html recent.html \ stdhead.html stylesheet.html search.html about.html volume.html \ sidebar.html prefs.html help.html choosealpha.html topbar.html \ - sidebarend.html topbarend.html error.html new.html \ + sidebarend.html topbarend.html error.html new.html login.html \ options options.labels \ options.columns static_DATA=disorder.css diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..31dc6e8 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,166 @@ + + + + +@include:stdhead@ + @label:login.title@ + + +@include{@label{menu}@}@ +

@label:login.title@

+ + @if{@ne{@label:error@}{error}@}{ + @#{error reporting from some earlier operation}@ + +

@label{error.@label:error@}@

+ }@ + + @if{@ne{@label:registered@}{registered}@}{ + @#{registration succeeded}@ +

@label:login.registered@

+ }@ + + @if{@eq{@user@}{guest}@}{ + @#{guest user, allow login and registration}@ +

Existing users

+ +

If you have a username, use this form to log in.

+ +
+ + + + + + + + + + +
@label:login.username@ + +
@label:login.password@ + +
+ + +
+ + + +

New Users

+ +

If you do not have a login enter a username, a password and your + email address here. You will be sent an email containing a URL, + which you must visit to activate your login before you can use + it.

+ +

+ + + + + + + + + + + + + + +
@label:login.username@ + +
@label:login.email@ + +
@label:login.password@ + +
+ +
+ }{ + @#{not the guest user, allow change of details and logout}@ + +

Logged in as @user@

+ +

TODO none of this stuff works yet

+ +

Use this form to change your email address and/or password.

+ +
+ + + + + + + + + + +
@label:login.email@ + +
@label:login.password@ + +
+ +
+ +

Use this button to log out @user@.

+ +
+
+ +
+ +
+ + }@ + +@include{@label{menu}@end}@ + + +@@ + diff --git a/templates/options.labels b/templates/options.labels index 1e71315..f5cfd90 100644 --- a/templates/options.labels +++ b/templates/options.labels @@ -132,6 +132,23 @@ label prefs.tags "Tags" # for help page label help.title "DisOrder help" +# <TITLE> for login page +label login.title "DisOrder Login" + +# Text for login fields +label login.username "Username" +label login.password "Password" +label login.email "Email address" + +# Text for login page buttons +label login.login "Login" +label login.register "Register" +label login.edituser "Change Details" +label login.lougout "Logout" + +# <TITLE> for account page +label account.title "DisOrder User Details" + # <TITLE> for error page. Note that in this page the 'error' label is set # to a string indicating the type of error. label error.title "DisOrder error" @@ -154,6 +171,7 @@ label sidebar.recent Recent label sidebar.new New label sidebar.about About label sidebar.volume Volume +label sidebar.login Login label sidebar.help Help label sidebar.manage Manage @@ -165,6 +183,7 @@ label sidebar.recentverbose "recently played tracks" label sidebar.newverbose "newly added tracks" label sidebar.aboutverbose "about DisOrder" label sidebar.volumeverbose "volume control" +label sidebar.loginverbose "log in to DisOrder" label sidebar.helpverbose "basic user guide" label sidebar.manageverbose "queue management and volume control" diff --git a/templates/sidebar.html b/templates/sidebar.html index 9a075c3..4c011a8 100644 --- a/templates/sidebar.html +++ b/templates/sidebar.html @@ -20,6 +20,9 @@ <p class=sidebarlink> <a class=sidebarlink href="@url@?mgmt=true">@label:sidebar.manage@</a> </p> + <p class=sidebarlink> + <a class=sidebarlink href="@url@?action=login&nonce=@nonce@">@label:sidebar.login@</a> + </p> <p class=sidebarlink> <a class=sidebarlink href="@url@?action=help&nonce=@nonce@">@label:sidebar.help@</a> </p> diff --git a/templates/topbar.html b/templates/topbar.html index 1dbbdd3..3776234 100644 --- a/templates/topbar.html +++ b/templates/topbar.html @@ -25,6 +25,9 @@ <a class=@if{@eq{@action@}{manage}@}{activemenu}{inactivemenu}@ href="@url@?mgmt=true" title="@label:sidebar.manageverbose@">@label:sidebar.manage@</a> + <a class=@if{@eq{@action@}{login}@}{activemenu}{inactivemenu}@ + href="@url@?action=login&nonce=@nonce@" + title="@label:sidebar.loginverbose@">@label:sidebar.login@</a> <a class=@if{@eq{@action@}{help}@}{activemenu}{inactivemenu}@ href="@url@?action=help&nonce=@nonce@" title="@label:sidebar.helpverbose@">@label:sidebar.help@</a>