.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
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"
}
stop() {
- if ${CLIENT} >/dev/null 2>&1; then
+ if ${CLIENT} version >/dev/null 2>&1; then
printf "Stopping DisOrder server: disorderd"
${CLIENT} shutdown
echo .
* @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));
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
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)
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);
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
/*
* 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
#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();
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);
#include "trackname.h"
#include "charset.h"
+char *login_cookie;
+
static void expand(cgi_sink *output,
const char *template,
dcgi_state *ds);
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;
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 },
{ "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 },
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 },
{ "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 },
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) {
void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
const char *msg);
+extern char *login_cookie;
+
#endif /* DCGI_H */
/*
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
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<!--
+This file is part of DisOrder.
+Copyright (C) 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
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+-->
+<html>
+ <head>
+@include:stdhead@
+ <title>@label:login.title@</title>
+ </head>
+ <body>
+@include{@label{menu}@}@
+ <h1 class=title>@label:login.title@</h1>
+
+ @if{@ne{@label:error@}{error}@}{
+ @#{error reporting from some earlier operation}@
+ <!-- TODO make error string visually intrusive, also error.html -->
+ <p>@label{error.@label:error@}@</p>
+ }@
+
+ @if{@ne{@label:registered@}{registered}@}{
+ @#{registration succeeded}@
+ <p>@label:login.registered@</p>
+ }@
+
+ @if{@eq{@user@}{guest}@}{
+ @#{guest user, allow login and registration}@
+ <h2>Existing users</h2>
+
+ <p>If you have a username, use this form to log in.</p>
+
+ <form class=login action="@url@" method=POST
+ enctype="multipart/form-data" accept-charset=utf-8>
+ <table class=login>
+ <tr>
+ <td>@label:login.username@</td>
+ <td>
+ <input class=username name=username type=text value="@arg:username@" size=32>
+ </td>
+ </tr>
+ <tr>
+ <td>@label:login.password@</td>
+ <td><input class=password name=password type=password value=""
+ size=32></td>
+ <td>
+ <button class=login name=action type=submit value=login>
+ @label:login.login@
+ </button>
+ </td>
+ </tr>
+ </table>
+ <input name=nonce type=hidden value="@nonce@">
+ <input name=back type=hidden value="@arg:back@">
+ </form>
+
+ <!-- TODO disable registration button if guest doesn't have
+ register right -->
+
+ <h2>New Users</h2>
+
+ <p>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.<p>
+
+ <form class=register action="@url@" method=POST
+ enctype="multipart/form-data" accept-charset=utf-8>
+ <table class=register>
+ <tr>
+ <td>@label:login.username@</td>
+ <td>
+ <input class=username name=username type=text value="" size=32>
+ </td>
+ </tr>
+ <tr>
+ <td>@label:login.email@</td>
+ <td>
+ <input class=email name=email type=text value="" size=32>
+ </td>
+ </tr>
+ <tr>
+ <td>@label:login.password@</td>
+ <td><input class=password name=password type=password value=""
+ size=32></td>
+ <td>
+ <button class=register name=action type=submit value=register>
+ @label:login.login@
+ </button>
+ </td>
+ </tr>
+ </table>
+ <input name=nonce type=hidden value="@nonce@">
+ </form>
+ }{
+ @#{not the guest user, allow change of details and logout}@
+
+ <h2>Logged in as @user@</h2>
+
+ <p>TODO none of this stuff works yet</p>
+
+ <p>Use this form to change your email address and/or password.</p>
+
+ <form class=register action="@url@" method=POST
+ enctype="multipart/form-data" accept-charset=utf-8>
+ <table class=edituser>
+ <tr>
+ <td>@label:login.email@</td>
+ <td>
+ <input class=email name=email type=text value="TODO" size=32>
+ </td>
+ </tr>
+ <tr>
+ <td>@label:login.password@</td>
+ <td><input class=password name=password type=password value=""
+ size=32></td>
+ <td>
+ <button class=edituser name=action type=submit value=edituser>
+ @label:login.edituser@
+ </button>
+ </td>
+ </tr>
+ </table>
+ <input name=nonce type=hidden value="@nonce@">
+ </form>
+
+ <p>Use this button to log out @user@.</p>
+
+ <form class=register action="@url@" method=POST
+ enctype="multipart/form-data" accept-charset=utf-8>
+ <div class=logout>
+ <button class=logout name=action type=submit value=logout>
+ @label:login.logout@
+ </button>
+ </div>
+ <input name=nonce type=hidden value="@nonce@">
+ </form>
+
+ }@
+
+@include{@label{menu}@end}@
+ </body>
+</html>
+@@
+<!--
+Local variables:
+mode:sgml
+sgml-always-quote-attributes:nil
+sgml-indent-step:1
+sgml-indent-data:t
+End:
+-->
# <TITLE> 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"
label sidebar.new New
label sidebar.about About
label sidebar.volume Volume
+label sidebar.login Login
label sidebar.help Help
label sidebar.manage Manage
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"
<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>
<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>