details about this field.
.PP
All commands require the connection to have been already authenticated unless
-stated otherwise.
+stated otherwise. If not stated otherwise, the \fBread\fR right is sufficient
+to execute the command.
.PP
Neither commands nor responses have a body unless stated otherwise.
.TP
+.B adduser \fIUSERNAME PASSWORD
+Creates a new user with the given username and password. Requires the
+\fBadmin\fR right, and only works on local connections.
+.TP
.B allfiles \fIDIRECTORY\fR [\fIREGEXP\fR]
Lists all the files and directories in \fIDIRECTORY\fR in a response body.
If \fIREGEXP\fR is present only matching files and directories are returned.
.TP
-.B become \fIUSER\fR
-Instructs the server to treat the connection as if \fIUSER\fR had
-authenticated it. Only trusted users may issue this command.
+.B cookie \fICOOKIE
+Log a user back in using a cookie created with \fBmake-cookie\fR.
+.TP
+.B deluser \fIUSERNAME
+Deletes the named user. Requires the \fBadmin\fR right, and only works on
+local connections.
.TP
.B dirs \fIDIRECTORY\fR [\fIREGEXP\fR]
Lists all the directories in \fIDIRECTORY\fR in a response body.
.TP
.B disable \fR[\fBnow\fR]
Disables further playing. If the optional \fBnow\fR argument is present then
-the current track is stopped.
+the current track is stopped. Requires the \fBglobal prefs\fR right.
+.TP
+.B edituser \fIUSERNAME PROPERTY VALUE
+Sets a user property. With the \fBadmin\fR right any username and property may
+be specified. Otherwise the \fBuserinfo\fR right is required and only the
+\fBemail\fR and \fBpassword\fR properties may be set.
.TP
.B enable
-Re-enables further playing, and is the opposite of \fBdisable\fR.
+Re-enables further playing, and is the opposite of \fBdisable\fR. Requires the
+\fBglobal prefs\fR right.
.TP
.B enabled
Reports whether playing is enabled. The second field of the response line will
.IP
See \fBEVENT LOG\fR below for more details.
.TP
+.B make-cookie
+Returns an opaque string that can be used by the \fBcookie\fR command to log
+this user back in on another connection (until the cookie expires).
+.TP
.B move \fITRACK\fR \fIDELTA\fR
Move a track in the queue. The track may be identified by ID (preferred) or
name (which might cause confusion if it's there twice). \fIDELTA\fR should be
an negative or positive integer and indicates how many steps towards the head
of the queue the track should be moved.
+.IP
+Requires one of the \fBmove mine\fR, \fBmove random\fR or \fBmove any\fR rights
+depending on how the track came to be added to the queue.
.TP
.B moveafter \fITARGET\fR \fIID\fR ...
Move all the tracks in the \fIID\fR list after ID \fITARGET\fR. If
the queue. If \fITARGET\fR is listed in the ID list then the tracks are moved
to just after the first non-listed track before it, or to the head if there is
no such track.
+.IP
+Requires one of the \fBmove mine\fR, \fBmove random\fR or \fBmove any\fR rights
+depending on how the tracks came to be added to the queue.
.TP
.B new \fR[\fIMAX\fR]
Sends the most recently added \fIMAX\fR tracks in a response body. If the
.B nop
Do nothing. Used by
.BR disobedience (1)
-as a keepalive measure.
+as a keepalive measure. This command does not require authentication.
.TP
.B part \fITRACK\fR \fICONTEXT\fI \fIPART\fR
Get a track name part. Returns an empty string if a name part cannot be
.BR title .
.TP
.B pause
-Pause the current track.
+Pause the current track. Requires the \fBpause\R right.
.TP
.B play \fITRACK\fR
Add a track to the queue. The response contains the queue ID of the track.
+Requires the \fBplay\fR right.
.TP
.B playing
Reports what track is playing.
track information syntax.
.TP
.B random-disable
-Disable random play (but don't stop the current track).
+Disable random play (but don't stop the current track). Requires the \fBglobal
+prefs\fR right.
.TP
.B random-enable
-Enable random play.
+Enable random play. Requires the \fBglobal prefs\fR right.
.TP
.B random-enabled
Reports whether random play is enabled. The second field of the response line
information syntax.
.TP
.B reconfigure
-Request that DisOrder reconfigure itself. Only trusted users may issue this
+Request that DisOrder reconfigure itself. Requires the \fBadmin\fR right.
command.
.TP
.B remove \fIID\fR
-Remove the track identified by \fIID\fR. If \fBrestrict remove\fR is enabled
-in the server's configuration then only the user that submitted the track may
-remove it.
+Remove the track identified by \fIID\fR. Requires one of the \fBremove
+mine\fR, \fBremove random\fR or \fBremove any\fR rights depending on how the
+track came to be added to the queue.
.TP
.B rescan
-Rescan all roots for new or obsolete tracks.
+Rescan all roots for new or obsolete tracks. Requires the \fBrescan\fR right.
.TP
.B resolve \fITRACK\fR
Resolve a track name, i.e. if this is an alias then return the real track name.
.TP
.B resume
-Resume the current track after a \fBpause\fR command.
+Resume the current track after a \fBpause\fR command. Requires the \fBpause\fR
+right.
+.TP
+.B revoke \fBcookie\fR
+Revokes a cookie previously created with \fBmake-cookie\fR. It will not be
+possible to use this cookie in the future.
.TP
.B rtp-address
Reports the RTP broadcast (or multicast) address, in the form \fIADDRESS
-PORT\fR.
+PORT\fR. This command does not require authentication.
.TP
.B scratch \fR[\fIID\fR]
Remove the track identified by \fIID\fR, or the currently playing track if no
-\fIID\fR is specified. If \fBrestrict scratch\fR is enabled in the server's
-configuration then only the user that submitted the track may scratch it.
+\fIID\fR is specified. Requires one of the \fBscratch mine\fR, \fBscratch
+random\fR or \fBscratch any\fR rights depending on how the track came to be
+added to the queue.
.TP
.B search \fITERMS\fR
Search for tracks matching the search terms. The results are put in a response
allow searching for phrases.
.TP
.B \fBset\fR \fITRACK\fR \fIPREF\fR \fIVALUE\fR
-Set a preference.
+Set a preference. Requires the \fBprefs\fR right.
.TP
.B set-global \fIKEY\fR \fIVALUE\fR
-Set a global preference.
+Set a global preference. Requires the \fBglobal prefs\fR right.
.TP
.B stats
Send server statistics in plain text in a response body.
Send the list of currently known tags in a response body.
.TP
.B \fBunset\fR \fITRACK\fR \fIPREF\fR
-Unset a preference.
+Unset a preference. Requires the \fBprefs\fR right.
.TP
.B \fBunset-global\fR \fIKEY\fR
-Unset a global preference.
+Unset a global preference. Requires the \fBglobal prefs\fR right.
.TP
.B user \fIUSER\fR \fIRESPONSE\fR
Authenticate as \fIUSER\fR. See
.B AUTHENTICATION
below.
.TP
+.B users
+Sends the list of currently known users in a response body.
+.TP
.B version
Send back a response with the server version as the second field.
.TP
as the 2nd and 3rd fields of the response.
.IP
With one parameter sets both sides to the same value. With two parameters sets
-each side independently.
+each side independently. Setting the volume requires the \fBvolume\fR right.
.SH RESPONSES
Responses are three-digit codes. The first digit distinguishes errors from
succesful responses:
const struct listener *l;
/** @brief Login cookie or NULL */
char *cookie;
+ /** @brief Connection rights */
+ rights_type rights;
};
static int reader_callback(ev_source *ev,
return 0;
}
-/** @brief Return true if we are talking to a trusted user */
-static int trusted(struct conn *c) {
- int n;
-
- for(n = 0; (n < config->trust.n
- && strcmp(config->trust.s[n], c->who)); ++n)
- ;
- return n < config->trust.n;
-}
-
static int c_disable(struct conn *c, char **vec, int nvec) {
if(nvec == 0)
disable_playing(c->who);
static int c_remove(struct conn *c, char **vec,
int attribute((unused)) nvec) {
struct queue_entry *q;
+ rights_type r;
if(!(q = queue_find(vec[0]))) {
sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
return 1;
}
- if(config->restrictions & RESTRICT_REMOVE) {
- /* can only remove tracks that you submitted */
- if(!q->submitter || strcmp(q->submitter, c->who)) {
- sink_writes(ev_writer_sink(c->w), "550 you didn't submit that track!\n");
- return 1;
- }
+ if(q->submitter)
+ if(!strcmp(q->submitter, c->who))
+ r = RIGHT_REMOVE_MINE;
+ else
+ r = RIGHT_REMOVE_ANY;
+ else
+ r = RIGHT_REMOVE_RANDOM;
+ if(!(c->rights & r)) {
+ sink_writes(ev_writer_sink(c->w),
+ "550 Not authorized to remove that track\n");
+ return 1;
}
queue_remove(q, c->who);
/* De-prepare the track. */
abandon(c->ev, q);
- /* If we removed the random track then add another one. */
+ /* If we removed a random track then add another one. */
if(q->state == playing_random)
add_random_track();
/* Prepare whatever the next head track is. */
static int c_scratch(struct conn *c,
char **vec,
int nvec) {
+ rights_type r;
+
if(!playing) {
sink_writes(ev_writer_sink(c->w), "250 nothing is playing\n");
return 1; /* completed */
}
- if(config->restrictions & RESTRICT_SCRATCH) {
- /* can only scratch tracks you submitted and randomly selected ones */
- if(playing->submitter && strcmp(playing->submitter, c->who)) {
- sink_writes(ev_writer_sink(c->w), "550 you didn't submit that track!\n");
- return 1;
- }
+ /* TODO there is a bug here: if we specify an ID but it's not the currently
+ * playing track then you will get 550 if you weren't authorized to scratch
+ * the currently playing track. */
+ if(playing->submitter)
+ if(!strcmp(playing->submitter, c->who))
+ r = RIGHT_SCRATCH_MINE;
+ else
+ r = RIGHT_SCRATCH_ANY;
+ else
+ r = RIGHT_SCRATCH_RANDOM;
+ if(!(c->rights & r)) {
+ sink_writes(ev_writer_sink(c->w),
+ "550 Not authorized to scratch that track\n");
+ return 1;
}
scratch(c->who, nvec == 1 ? vec[0] : 0);
/* If you scratch an unpaused track then it is automatically unpaused */
return 1; /* completed */
}
-static int c_become(struct conn *c,
- char **vec,
- int attribute((unused)) nvec) {
- c->who = vec[0];
- sink_writes(ev_writer_sink(c->w), "230 OK\n");
- return 1;
-}
-
static const char *connection_host(struct conn *c) {
union {
struct sockaddr sa;
static int c_user(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
+ struct kvp *k;
const char *res, *host, *password;
+ rights_type rights;
if(c->who) {
sink_writes(ev_writer_sink(c->w), "530 already authenticated\n");
return 1;
}
/* find the user */
- password = trackdb_get_password(vec[0]);
+ k = trackdb_getuserinfo(vec[0]);
/* reject nonexistent users */
- if(!password) {
- info("S%x unknown user '%s' from %s", c->tag, vec[0], host);
+ if(!k) {
+ error(0, "S%x unknown user '%s' from %s", c->tag, vec[0], host);
+ sink_writes(ev_writer_sink(c->w), "530 authentication failed\n");
+ return 1;
+ }
+ /* reject unconfirmed users */
+ if(kvp_get(k, "confirmation")) {
+ error(0, "S%x unconfirmed user '%s' from %s", c->tag, vec[0], host);
+ sink_writes(ev_writer_sink(c->w), "530 authentication failed\n");
+ return 1;
+ }
+ password = kvp_get(k, "password");
+ if(!password) password = "";
+ if(parse_rights(kvp_get(k, "rights"), &rights)) {
+ error(0, "error parsing rights for %s", vec[0]);
sink_writes(ev_writer_sink(c->w), "530 authentication failed\n");
return 1;
}
config->authorization_algorithm);
if(wideopen || (res && !strcmp(res, vec[1]))) {
c->who = vec[0];
+ c->rights = rights;
/* currently we only bother logging remote connections */
- if(strcmp(host, "local"))
+ if(strcmp(host, "local")) {
info("S%x %s connected from %s", c->tag, vec[0], host);
+ c->rights |= RIGHT__LOCAL;
+ }
sink_writes(ev_writer_sink(c->w), "230 OK\n");
return 1;
}
int nvec) {
int l, r, set;
char lb[32], rb[32];
+ rights_type rights;
switch(nvec) {
case 0:
default:
abort();
}
+ rights = set ? RIGHT_VOLUME : RIGHT_READ;
+ if(!(c->rights & rights)) {
+ sink_writes(ev_writer_sink(c->w), "530 Prohibited\n");
+ return 1;
+ }
if(mixer_control(&l, &r, set))
sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
else {
return 0;
}
+/** @brief Test whether a move is allowed
+ * @param c Connection
+ * @param qs List of IDs on queue
+ * @param nqs Number of IDs
+ * @return 0 if move is prohibited, non-0 if it is allowed
+ */
+static int has_move_rights(struct conn *c, struct queue_entry **qs, int nqs) {
+ rights_type r = 0;
+
+ for(; nqs > 0; ++qs, --nqs) {
+ struct queue_entry *const q = *qs;
+
+ if(q->submitter)
+ if(!strcmp(q->submitter, c->who))
+ r |= RIGHT_MOVE_MINE;
+ else
+ r |= RIGHT_MOVE_ANY;
+ else
+ r |= RIGHT_MOVE_RANDOM;
+ }
+ return (c->rights & r) == r;
+}
+
static int c_move(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
struct queue_entry *q;
int n;
- if(config->restrictions & RESTRICT_MOVE) {
- if(!trusted(c)) {
- sink_writes(ev_writer_sink(c->w),
- "550 only trusted users can move tracks\n");
- return 1;
- }
- }
if(!(q = queue_find(vec[0]))) {
sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
return 1;
}
+ if(!has_move_rights(c, &q, 1)) {
+ sink_writes(ev_writer_sink(c->w),
+ "550 Not authorized to move that track\n");
+ return 1;
+ }
n = queue_move(q, atoi(vec[1]), c->who);
sink_printf(ev_writer_sink(c->w), "252 %d\n", n);
/* If we've moved to the head of the queue then prepare the track. */
struct queue_entry *q, **qs;
int n;
- if(config->restrictions & RESTRICT_MOVE) {
- if(!trusted(c)) {
- sink_writes(ev_writer_sink(c->w),
- "550 only trusted users can move tracks\n");
- return 1;
- }
- }
if(vec[0][0]) {
if(!(q = queue_find(vec[0]))) {
sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
return 1;
}
+ if(!has_move_rights(c, qs, nvec)) {
+ sink_writes(ev_writer_sink(c->w),
+ "550 Not authorized to move those tracks\n");
+ return 1;
+ }
queue_moveafter(q, nvec, qs, c->who);
sink_printf(ev_writer_sink(c->w), "250 Moved tracks\n");
/* If we've moved to the head of the queue then prepare the track. */
int attribute((unused)) nvec) {
const char *host;
char *user;
+ rights_type rights;
/* Can't log in twice on the same connection */
if(c->who) {
return 1;
}
/* Check the cookie */
- user = verify_cookie(vec[0]);
+ user = verify_cookie(vec[0], &rights);
if(!user) {
sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
return 1;
}
/* Log in */
- c->who = user;
+ c->who = vec[0];
c->cookie = vec[0];
- if(strcmp(host, "local"))
+ c->rights = rights;
+ if(strcmp(host, "local")) {
info("S%x %s connected with cookie from %s", c->tag, user, host);
+ c->rights |= RIGHT__LOCAL;
+ }
sink_writes(ev_writer_sink(c->w), "230 OK\n");
return 1;
}
const char *cookie = make_cookie(c->who);
if(cookie)
- sink_printf(ev_writer_sink(c->w), "252 %s\n", cookie);
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(cookie));
else
sink_writes(ev_writer_sink(c->w), "550 Cannot create cookie\n");
return 1;
static int c_adduser(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- /* TODO local only */
if(trackdb_adduser(vec[0], vec[1], default_rights(), 0))
sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
else
static int c_deluser(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- /* TODO local only */
if(trackdb_deluser(vec[0]))
sink_writes(ev_writer_sink(c->w), "550 Cannot deleted user\n");
else
static int c_edituser(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- /* TODO local only */
- if(trusted(c)
+ /* RIGHT_ADMIN can do anything; otherwise you can only set your own email
+ * address and password. */
+ if((c->rights & RIGHT_ADMIN)
|| (!strcmp(c->who, vec[0])
&& (!strcmp(vec[1], "email")
|| !strcmp(vec[1], "password")))) {
int attribute((unused)) nvec) {
struct kvp *k;
const char *value;
-
- /* TODO local only */
- if(trusted(c)
+
+ /* RIGHT_ADMIN allows anything; otherwise you can only get your own email
+ * address and righst list. */
+ if((c->rights & RIGHT_ADMIN)
|| (!strcmp(c->who, vec[0])
&& (!strcmp(vec[1], "email")
|| !strcmp(vec[1], "rights")))) {
return 1; /* completed */
}
-#define C_AUTH 0001 /* must be authenticated */
-#define C_TRUSTED 0002 /* must be trusted user */
-
static const struct command {
+ /** @brief Command name */
const char *name;
- int minargs, maxargs;
+
+ /** @brief Minimum number of arguments */
+ int minargs;
+
+ /** @brief Maximum number of arguments */
+ int maxargs;
+
+ /** @brief Function to process command */
int (*fn)(struct conn *, char **, int);
- unsigned flags;
+
+ /** @brief Rights required to execute command
+ *
+ * 0 means that the command can be issued without logging in. If multiple
+ * bits are listed here any of those rights will do.
+ */
+ rights_type rights;
} commands[] = {
- { "adduser", 2, 2, c_adduser, C_AUTH|C_TRUSTED },
- { "allfiles", 0, 2, c_allfiles, C_AUTH },
- { "become", 1, 1, c_become, C_AUTH|C_TRUSTED },
+ { "adduser", 2, 2, c_adduser, RIGHT_ADMIN|RIGHT__LOCAL },
+ { "allfiles", 0, 2, c_allfiles, RIGHT_READ },
{ "cookie", 1, 1, c_cookie, 0 },
- { "deluser", 1, 1, c_deluser, C_AUTH|C_TRUSTED },
- { "dirs", 0, 2, c_dirs, C_AUTH },
- { "disable", 0, 1, c_disable, C_AUTH },
- { "edituser", 3, 3, c_edituser, C_AUTH },
- { "enable", 0, 0, c_enable, C_AUTH },
- { "enabled", 0, 0, c_enabled, C_AUTH },
- { "exists", 1, 1, c_exists, C_AUTH },
- { "files", 0, 2, c_files, C_AUTH },
- { "get", 2, 2, c_get, C_AUTH },
- { "get-global", 1, 1, c_get_global, C_AUTH },
- { "length", 1, 1, c_length, C_AUTH },
- { "log", 0, 0, c_log, C_AUTH },
- { "make-cookie", 0, 0, c_make_cookie, C_AUTH },
- { "move", 2, 2, c_move, C_AUTH },
- { "moveafter", 1, INT_MAX, c_moveafter, C_AUTH },
- { "new", 0, 1, c_new, C_AUTH },
- { "nop", 0, 0, c_nop, C_AUTH },
- { "part", 3, 3, c_part, C_AUTH },
- { "pause", 0, 0, c_pause, C_AUTH },
- { "play", 1, 1, c_play, C_AUTH },
- { "playing", 0, 0, c_playing, C_AUTH },
- { "prefs", 1, 1, c_prefs, C_AUTH },
- { "queue", 0, 0, c_queue, C_AUTH },
- { "random-disable", 0, 0, c_random_disable, C_AUTH },
- { "random-enable", 0, 0, c_random_enable, C_AUTH },
- { "random-enabled", 0, 0, c_random_enabled, C_AUTH },
- { "recent", 0, 0, c_recent, C_AUTH },
- { "reconfigure", 0, 0, c_reconfigure, C_AUTH|C_TRUSTED },
- { "remove", 1, 1, c_remove, C_AUTH },
- { "rescan", 0, 0, c_rescan, C_AUTH|C_TRUSTED },
- { "resolve", 1, 1, c_resolve, C_AUTH },
- { "resume", 0, 0, c_resume, C_AUTH },
- { "revoke", 0, 0, c_revoke, C_AUTH },
- { "rtp-address", 0, 0, c_rtp_address, C_AUTH },
- { "scratch", 0, 1, c_scratch, C_AUTH },
- { "search", 1, 1, c_search, C_AUTH },
- { "set", 3, 3, c_set, C_AUTH, },
- { "set-global", 2, 2, c_set_global, C_AUTH },
- { "shutdown", 0, 0, c_shutdown, C_AUTH|C_TRUSTED },
- { "stats", 0, 0, c_stats, C_AUTH },
- { "tags", 0, 0, c_tags, C_AUTH },
- { "unset", 2, 2, c_set, C_AUTH },
- { "unset-global", 1, 1, c_set_global, C_AUTH },
+ { "deluser", 1, 1, c_deluser, RIGHT_ADMIN|RIGHT__LOCAL },
+ { "dirs", 0, 2, c_dirs, RIGHT_READ },
+ { "disable", 0, 1, c_disable, RIGHT_GLOBAL_PREFS },
+ { "edituser", 3, 3, c_edituser, RIGHT_ADMIN|RIGHT_USERINFO },
+ { "enable", 0, 0, c_enable, RIGHT_GLOBAL_PREFS },
+ { "enabled", 0, 0, c_enabled, RIGHT_READ },
+ { "exists", 1, 1, c_exists, RIGHT_READ },
+ { "files", 0, 2, c_files, RIGHT_READ },
+ { "get", 2, 2, c_get, RIGHT_READ },
+ { "get-global", 1, 1, c_get_global, RIGHT_READ },
+ { "length", 1, 1, c_length, RIGHT_READ },
+ { "log", 0, 0, c_log, RIGHT_READ },
+ { "make-cookie", 0, 0, c_make_cookie, RIGHT_READ },
+ { "move", 2, 2, c_move, RIGHT_MOVE__MASK },
+ { "moveafter", 1, INT_MAX, c_moveafter, RIGHT_MOVE__MASK },
+ { "new", 0, 1, c_new, RIGHT_READ },
+ { "nop", 0, 0, c_nop, 0 },
+ { "part", 3, 3, c_part, RIGHT_READ },
+ { "pause", 0, 0, c_pause, RIGHT_PAUSE },
+ { "play", 1, 1, c_play, RIGHT_PLAY },
+ { "playing", 0, 0, c_playing, RIGHT_READ },
+ { "prefs", 1, 1, c_prefs, RIGHT_READ },
+ { "queue", 0, 0, c_queue, RIGHT_READ },
+ { "random-disable", 0, 0, c_random_disable, RIGHT_GLOBAL_PREFS },
+ { "random-enable", 0, 0, c_random_enable, RIGHT_GLOBAL_PREFS },
+ { "random-enabled", 0, 0, c_random_enabled, RIGHT_READ },
+ { "recent", 0, 0, c_recent, RIGHT_READ },
+ { "reconfigure", 0, 0, c_reconfigure, RIGHT_ADMIN },
+ { "remove", 1, 1, c_remove, RIGHT_REMOVE__MASK },
+ { "rescan", 0, 0, c_rescan, RIGHT_RESCAN },
+ { "resolve", 1, 1, c_resolve, RIGHT_READ },
+ { "resume", 0, 0, c_resume, RIGHT_PAUSE },
+ { "revoke", 0, 0, c_revoke, RIGHT_READ },
+ { "rtp-address", 0, 0, c_rtp_address, 0 },
+ { "scratch", 0, 1, c_scratch, RIGHT_SCRATCH__MASK },
+ { "search", 1, 1, c_search, RIGHT_READ },
+ { "set", 3, 3, c_set, RIGHT_PREFS, },
+ { "set-global", 2, 2, c_set_global, RIGHT_GLOBAL_PREFS },
+ { "shutdown", 0, 0, c_shutdown, RIGHT_ADMIN },
+ { "stats", 0, 0, c_stats, RIGHT_READ },
+ { "tags", 0, 0, c_tags, RIGHT_READ },
+ { "unset", 2, 2, c_set, RIGHT_PREFS },
+ { "unset-global", 1, 1, c_set_global, RIGHT_GLOBAL_PREFS },
{ "user", 2, 2, c_user, 0 },
- { "userinfo", 2, 2, c_userinfo, C_AUTH },
- { "users", 0, 0, c_users, C_AUTH },
- { "version", 0, 0, c_version, C_AUTH },
- { "volume", 0, 2, c_volume, C_AUTH }
+ { "userinfo", 2, 2, c_userinfo, RIGHT_READ },
+ { "users", 0, 0, c_users, RIGHT_READ },
+ { "version", 0, 0, c_version, RIGHT_READ },
+ { "volume", 0, 2, c_volume, RIGHT_READ|RIGHT_VOLUME }
};
static void command_error(const char *msg, void *u) {
if((n = TABLE_FIND(commands, struct command, name, vec[0])) < 0)
sink_writes(ev_writer_sink(c->w), "500 unknown command\n");
else {
- if((commands[n].flags & C_AUTH) && !c->who) {
- sink_writes(ev_writer_sink(c->w), "530 not authenticated\n");
- return 1;
- }
- if((commands[n].flags & C_TRUSTED) && !trusted(c)) {
- sink_writes(ev_writer_sink(c->w), "530 insufficient privilege\n");
+ if(commands[n].rights
+ && !(c->rights & commands[n].rights)) {
+ sink_writes(ev_writer_sink(c->w), "530 Prohibited\n");
return 1;
}
++vec;
c->fd = fd;
c->reader = reader_callback;
c->l = l;
+ c->rights = 0;
gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM);
if(!strcmp(config->authorization_algorithm, "sha1")
|| !strcmp(config->authorization_algorithm, "SHA1")) {