added a protocol generation field, to allow future such changes to be
handled more gracefuly, but 2.1 will therefore require all clients to
be upgraded too.
.B 9
The text part is just commentary (but would normally be a response for this
command) e.g. \fBplaying\fR.
.B 9
The text part is just commentary (but would normally be a response for this
command) e.g. \fBplaying\fR.
+.PP
+Result strings (not bodies) intended for machine parsing (i.e. xx1 and xx2
+responses) are quoted.
.SH AUTHENTICATION
When a connection is made the server sends a \fB231\fR response before any
.SH AUTHENTICATION
When a connection is made the server sends a \fB231\fR response before any
-command is received. This contains an algorithm name and a challenge encoded
-in hex.
+command is received. This contains a protocol generation, an algorithm name
+and a challenge encoded in hex, all separated by whitespace.
+.PP
+The current protocol generation is \fB2\fR.
.PP
The possible algorithms are (currently) \fBsha1\fR, \fBsha256\fR, \fBsha384\fR
and \fBsha512\fR. \fBSHA1\fR etc work as synonyms.
.PP
The possible algorithms are (currently) \fBsha1\fR, \fBsha256\fR, \fBsha384\fR
and \fBsha512\fR. \fBSHA1\fR etc work as synonyms.
size_t nl;
const char *res;
char *r, **rvec;
size_t nl;
const char *res;
char *r, **rvec;
- const char *algo = "SHA1";
+ const char *protocol, *algorithm, *challenge;
if(!password) {
error(0, "no password found");
if(!password) {
error(0, "no password found");
return -1;
if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
return -1;
return -1;
if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
return -1;
- if(nrvec > 1) {
- algo = *rvec++;
- --nrvec;
+ if(nrvec != 3) {
+ error(0, "cannot parse server greeting %s", r);
+ return -1;
+ protocol = *rvec++;
+ if(strcmp(protocol, "2")) {
+ error(0, "unknown protocol version: %s", protocol);
- if(!(nonce = unhex(*rvec, &nl)))
+ }
+ algorithm = *rvec++;
+ challenge = *rvec++;
+ if(!(nonce = unhex(challenge, &nl)))
- if(!(res = authhash(nonce, nl, password, algo))) goto error;
+ if(!(res = authhash(nonce, nl, password, algorithm))) goto error;
if(disorder_simple(c, 0, "user", username, res, (char *)0))
return -1;
c->user = xstrdup(username);
if(disorder_simple(c, 0, "user", username, res, (char *)0))
return -1;
c->user = xstrdup(username);
return disorder_simple(c, 0, "rescan", (char *)0);
}
return disorder_simple(c, 0, "rescan", (char *)0);
}
+static int dequote(int rc, char **rp) {
+ char **rr;
+ if(!rc){
+ if((rr = split(*rp, 0, SPLIT_QUOTES, 0, 0)) && *rr) {
+ *rp = *rr;
+ return 0;
+ }
+ error(0, "invalid reply: %s", *rp);
+ }
+ return -1;
+}
+
int disorder_version(disorder_client *c, char **rp) {
int disorder_version(disorder_client *c, char **rp) {
- return disorder_simple(c, rp, "version", (char *)0);
+ return dequote(disorder_simple(c, rp, "version", (char *)0), rp);
}
static void client_error(const char *msg,
}
static void client_error(const char *msg,
int disorder_get(disorder_client *c,
const char *track, const char *key, char **valuep) {
int disorder_get(disorder_client *c,
const char *track, const char *key, char **valuep) {
- return disorder_simple(c, valuep, "get", track, key, (char *)0);
+ return dequote(disorder_simple(c, valuep, "get", track, key, (char *)0),
+ valuep);
}
static void pref_error_handler(const char *msg,
}
static void pref_error_handler(const char *msg,
int disorder_part(disorder_client *c, char **partp,
const char *track, const char *context, const char *part) {
int disorder_part(disorder_client *c, char **partp,
const char *track, const char *context, const char *part) {
- return disorder_simple(c, partp, "part", track, context, part, (char *)0);
+ return dequote(disorder_simple(c, partp, "part",
+ track, context, part, (char *)0), partp);
}
int disorder_resolve(disorder_client *c, char **trackp, const char *track) {
}
int disorder_resolve(disorder_client *c, char **trackp, const char *track) {
- return disorder_simple(c, trackp, "resolve", track, (char *)0);
+ return dequote(disorder_simple(c, trackp, "resolve", track, (char *)0),
+ trackp);
}
int disorder_pause(disorder_client *c) {
}
int disorder_pause(disorder_client *c) {
}
int disorder_get_global(disorder_client *c, const char *key, char **valuep) {
}
int disorder_get_global(disorder_client *c, const char *key, char **valuep) {
- return disorder_simple(c, valuep, "get-global", key, (char *)0);
+ return dequote(disorder_simple(c, valuep, "get-global", key, (char *)0),
+ valuep);
}
int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
}
int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
int disorder_userinfo(disorder_client *c, const char *user, const char *key,
char **valuep) {
int disorder_userinfo(disorder_client *c, const char *user, const char *key,
char **valuep) {
- return disorder_simple(c, valuep, "userinfo", user, key, (char *)0);
+ return dequote(disorder_simple(c, valuep, "userinfo", user, key, (char *)0),
+ valuep);
}
int disorder_edituser(disorder_client *c, const char *user,
}
int disorder_edituser(disorder_client *c, const char *user,
/* Command support ***********************************************************/
/* Command support ***********************************************************/
-/* for commands with a simple string response */
+/* for commands with a quoted string response */
static void string_response_opcallback(disorder_eclient *c,
struct operation *op) {
D(("string_response_callback"));
if(c->rc / 100 == 2) {
static void string_response_opcallback(disorder_eclient *c,
struct operation *op) {
D(("string_response_callback"));
if(c->rc / 100 == 2) {
- if(op->completed)
- ((disorder_eclient_string_response *)op->completed)(op->v, c->line + 4);
+ if(op->completed) {
+ char **rr = split(c->line + 4, 0, SPLIT_QUOTES, 0, 0);
+
+ if(rr && *rr)
+ ((disorder_eclient_string_response *)op->completed)(op->v, *rr);
+ else
+ protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+ }
} else
protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
}
} else
protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
}
s.connect(self.who)
self.w = s.makefile("wb")
self.r = s.makefile("rb")
s.connect(self.who)
self.w = s.makefile("wb")
self.r = s.makefile("rb")
- (res, challenge_and_algo) = self._simple()
- (algo, challenge) = _split(challenge_and_algo)
+ (res, details) = self._simple()
+ (protocol, algo, challenge) = _split(details)
+ if protocol != '2':
+ raise communicationError(self.who,
+ "unknown protocol version %s" % protocol)
if cookie is None:
if self.user is None:
user = self.config['username']
if cookie is None:
if self.user is None:
user = self.config['username']
def version(self):
"""Return the server's version number."""
def version(self):
"""Return the server's version number."""
- return self._simple("version")[1]
+ return _split(self._simple("version")[1])[0]
def playing(self):
"""Return the currently playing track.
def playing(self):
"""Return the currently playing track.
if ret == 555:
return None
else:
if ret == 555:
return None
else:
+ return _split(details)[0]
def prefs(self, track):
"""Get all the preferences for a track.
def prefs(self, track):
"""Get all the preferences for a track.
The return value is the preference
"""
ret, details = self._simple("part", track, context, part)
The return value is the preference
"""
ret, details = self._simple("part", track, context, part)
+ return _split(details)[0]
def setglobal(self, key, value):
"""Set a global preference value.
def setglobal(self, key, value):
"""Set a global preference value.
if ret == 555:
return None
else:
if ret == 555:
return None
else:
+ return _split(details)[0]
def make_cookie(self):
"""Create a login cookie"""
def make_cookie(self):
"""Create a login cookie"""
const char *v;
if(vec[1][0] != '_' && (v = trackdb_get(vec[0], vec[1])))
const char *v;
if(vec[1][0] != '_' && (v = trackdb_get(vec[0], vec[1])))
- sink_printf(ev_writer_sink(c->w), "252 %s\n", v);
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(v));
else
sink_writes(ev_writer_sink(c->w), "555 not found\n");
return 1;
else
sink_writes(ev_writer_sink(c->w), "555 not found\n");
return 1;
return 1;
}
if((v = trackdb_get(track, "_length")))
return 1;
}
if((v = trackdb_get(track, "_length")))
- sink_printf(ev_writer_sink(c->w), "252 %s\n", v);
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(v));
else
sink_writes(ev_writer_sink(c->w), "550 not found\n");
return 1;
else
sink_writes(ev_writer_sink(c->w), "550 not found\n");
return 1;
char **vec,
int attribute((unused)) nvec) {
sink_printf(ev_writer_sink(c->w), "252 %s\n",
char **vec,
int attribute((unused)) nvec) {
sink_printf(ev_writer_sink(c->w), "252 %s\n",
- trackdb_getpart(vec[0], vec[1], vec[2]));
+ quoteutf8(trackdb_getpart(vec[0], vec[1], vec[2])));
sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
return 1;
}
sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
return 1;
}
- sink_printf(ev_writer_sink(c->w), "252 %s\n", track);
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(track));
const char *s = trackdb_get_global(vec[0]);
if(s)
const char *s = trackdb_get_global(vec[0]);
if(s)
- sink_printf(ev_writer_sink(c->w), "252 %s\n", s);
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(s));
else
sink_writes(ev_writer_sink(c->w), "555 not found\n");
return 1;
else
sink_writes(ev_writer_sink(c->w), "555 not found\n");
return 1;
c->l = l;
c->rights = 0;
gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM);
c->l = l;
c->rights = 0;
gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM);
- sink_printf(ev_writer_sink(c->w), "231 %s %s\n",
+ sink_printf(ev_writer_sink(c->w), "231 %d %s %s\n",
+ 2,
config->authorization_algorithm,
hex(c->nonce, sizeof c->nonce));
return 0;
config->authorization_algorithm,
hex(c->nonce, sizeof c->nonce));
return 0;
name = module.__name__
# Open the error log
global errs
name = module.__name__
# Open the error log
global errs
- errs = open("%s.log" % name, "w")
+ logfile = "%s.log" % name
+ try:
+ os.remove(logfile)
+ except:
+ pass
+ errs = open(logfile, "a")
# Ensure that disorder.py uses the test installation
disorder._configfile = "%s/config" % testroot
disorder._userconf = False
# Ensure that disorder.py uses the test installation
disorder._configfile = "%s/config" % testroot
disorder._userconf = False
print dtest.command(["disorder-dump", "--config", disorder._configfile,
"--dump", dump])
print " changing track pref"
print dtest.command(["disorder-dump", "--config", disorder._configfile,
"--dump", dump])
print " changing track pref"
- c.set(track, "foo", "after");
- assert c.get(track, "foo") == "after", "checking track foo=before"
+ c.set(track, "foo", "after dump");
+ print c.get(track, "foo")
+ assert c.get(track, "foo") == "after dump", "checking track foo=after dump"
print " changing global pref"
print " changing global pref"
- c.setglobal("foo", "after");
- assert c.getglobal("foo") == "after", "checking global foo=before"
+ c.setglobal("foo", "after dump");
+ assert c.getglobal("foo") == "after dump", "checking global foo=after dump"
print " adding fresh track pref"
print " adding fresh track pref"
- c.set(track, "bar", "after")
+ c.set(track, "bar", "after dump")
print " adding fresh global pref"
print " adding fresh global pref"
- c.setglobal("bar", "after")
+ c.setglobal("bar", "after dump")
dtest.stop_daemon();
print "restoring database"
print dtest.command(["disorder-dump", "--config", disorder._configfile,
dtest.stop_daemon();
print "restoring database"
print dtest.command(["disorder-dump", "--config", disorder._configfile,