chiark / gitweb /
Web interface starts to reflect user rights properly:
authorrjk@greenend.org.uk <>
Sun, 23 Dec 2007 18:16:43 +0000 (18:16 +0000)
committerrjk@greenend.org.uk <>
Sun, 23 Dec 2007 18:16:43 +0000 (18:16 +0000)
- scratch/move/remove rights check is moved to lib/queue-rights.c
  which is shared with the web interface
- new @rights@ and @movable@ expansions (not used yet)
- @removable@ and @scratchable@ rewritten for the new world

doc/disorder_config.5.in
lib/Makefile.am
lib/queue-rights.c [new file with mode: 0644]
lib/rights.h
server/cgimain.c
server/dcgi.c
server/dcgi.h
server/server.c
templates/playing.html

index 269abe8..eeef352 100644 (file)
@@ -911,6 +911,10 @@ file for full documentation of the labels used by the standard templates.
 .B @length@
 Expands to the length of the current track.
 .TP
 .B @length@
 Expands to the length of the current track.
 .TP
+.B @movable@
+Expands to \fBtrue\fR if the current track is movable, otherwise to
+\fBfalse\fR.
+.TP
 .B @navigate{\fIDIRECTORY\fB}{\fITEMPLATE\fB}
 Expands \fITEMPLATE\fR for each component of \fIDIRECTORY\fR in turn.
 Use \fB@dirname\fR and \fB@basename@\fR to get the components of the path to
 .B @navigate{\fIDIRECTORY\fB}{\fITEMPLATE\fB}
 Expands \fITEMPLATE\fR for each component of \fIDIRECTORY\fR in turn.
 Use \fB@dirname\fR and \fB@basename@\fR to get the components of the path to
@@ -992,9 +996,25 @@ Expands to \fBtrue\fR if random play is currently enabled, otherwise to
 Expands \fITEMPLATE\fR repeatedly using the each recently played track in turn
 as the current track.  The most recently played track comes first.
 .TP
 Expands \fITEMPLATE\fR repeatedly using the each recently played track in turn
 as the current track.  The most recently played track comes first.
 .TP
+.B @removable@
+Expands to \fBtrue\fR if the current track is removable, otherwise to
+\fBfalse\fR.
+.TP
 .B @resolve{\fITRACK\fB}@
 Resolve aliases for \fITRACK\fR and expands to the result.
 .TP
 .B @resolve{\fITRACK\fB}@
 Resolve aliases for \fITRACK\fR and expands to the result.
 .TP
+.B @right{\fIRIGHT\fB}@
+Exapnds to \fBtrue\fR if the user has right \fIRIGHT\fR, otherwise to
+\fBfalse\fR.
+.TP
+.B @right{\fIRIGHT\fB}{\fITRUEPART\fB}{\fIFALSEPART\fB}@
+Expands to \fITRUEPART\fR if the user right \fIRIGHT\fR, otherwise to
+\fIFALSEPART\fR (which may be omitted).
+.TP
+.B @scratchable@
+Expands to \fBtrue\fR if the currently playing track is scratchable, otherwise
+to \fBfalse\fR.
+.TP
 .B @search{\fIPART\fB}\fR[\fB{\fICONTEXT\fB}\fR]\fB{\fITEMPLATE\fB}@
 Expands \fITEMPLATE\fR once for each group of search results that have
 a common value of track part \fIPART\fR.
 .B @search{\fIPART\fB}\fR[\fB{\fICONTEXT\fB}\fR]\fB{\fITEMPLATE\fB}@
 Expands \fITEMPLATE\fR once for each group of search results that have
 a common value of track part \fIPART\fR.
index a7ddf48..7b4ce0a 100644 (file)
@@ -53,7 +53,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        asprintf.c fprintf.c snprintf.c                 \
        queue.c queue.h                                 \
        regsub.c regsub.h                               \
        asprintf.c fprintf.c snprintf.c                 \
        queue.c queue.h                                 \
        regsub.c regsub.h                               \
-       rights.c rights.h                               \
+       rights.c queue-rights.c rights.h                \
        rtp.h                                           \
        selection.c selection.h                         \
        signame.c signame.h                             \
        rtp.h                                           \
        selection.c selection.h                         \
        signame.c signame.h                             \
diff --git a/lib/queue-rights.c b/lib/queue-rights.c
new file mode 100644 (file)
index 0000000..523a4ad
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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
+ */
+/** @file lib/queue-rights.c
+ * @brief Various rights-checking operations
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <string.h>
+
+#include "queue.h"
+#include "rights.h"
+
+/** @brief Test for scratchability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if scratchable, else 0
+ */
+int right_scratchable(rights_type rights, const char *who,
+                     const struct queue_entry *q) {
+  rights_type r;
+  
+  if(!q)
+    return 0;
+  if(q->submitter)
+    if(!strcmp(q->submitter, who))
+      r = RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_ANY;
+    else
+      r = RIGHT_SCRATCH_ANY;
+  else
+    r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY;
+  return !!(rights & r);
+}
+
+/** @brief Test for movability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if movable, else 0
+ */
+int right_movable(rights_type rights, const char *who,
+                 const struct queue_entry *q) {
+  rights_type r;
+
+  if(!q)
+    return 0;
+  if(q->submitter)
+    if(!strcmp(q->submitter, who))
+      r = RIGHT_MOVE_MINE|RIGHT_MOVE_ANY;
+    else
+      r = RIGHT_MOVE_ANY;
+  else
+    r = RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY;
+  return !!(rights & r);
+}
+
+/** @brief Test for removability
+ * @param rights User rights
+ * @param who Username
+ * @param q Queue entry or NULL
+ * @return non-0 if removable, else 0
+ */
+int right_removable(rights_type rights, const char *who,
+                   const struct queue_entry *q) {
+  rights_type r;
+  
+  if(!q)
+    return 0;
+  if(q->submitter)
+    if(!strcmp(q->submitter, who))
+      r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY;
+    else
+      r = RIGHT_REMOVE_ANY;
+  else
+    r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY;
+  return !!(rights & r);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 794213c..c51ca5c 100644 (file)
@@ -24,6 +24,8 @@
 #ifndef RIGHTS_H
 #define RIGHTS_H
 
 #ifndef RIGHTS_H
 #define RIGHTS_H
 
+struct queue_entry;
+
 /** @brief User can perform read-only operations */
 #define RIGHT_READ            0x00000001
 
 /** @brief User can perform read-only operations */
 #define RIGHT_READ            0x00000001
 
@@ -102,6 +104,12 @@ typedef uint32_t rights_type;
 
 char *rights_string(rights_type r);
 int parse_rights(const char *s, rights_type *rp, int report);
 
 char *rights_string(rights_type r);
 int parse_rights(const char *s, rights_type *rp, int report);
+int right_scratchable(rights_type rights, const char *who,
+                     const struct queue_entry *q);
+int right_movable(rights_type rights, const char *who,
+                 const struct queue_entry *q);
+int right_removable(rights_type rights, const char *who,
+                   const struct queue_entry *q);
 
 #endif /* RIGHTS_H */
 
 
 #endif /* RIGHTS_H */
 
index ec63bf8..53f7a9b 100644 (file)
@@ -33,7 +33,6 @@
 #include "client.h"
 #include "sink.h"
 #include "cgi.h"
 #include "client.h"
 #include "sink.h"
 #include "cgi.h"
-#include "dcgi.h"
 #include "mem.h"
 #include "log.h"
 #include "configuration.h"
 #include "mem.h"
 #include "log.h"
 #include "configuration.h"
@@ -41,6 +40,7 @@
 #include "api-client.h"
 #include "mime.h"
 #include "printf.h"
 #include "api-client.h"
 #include "mime.h"
 #include "printf.h"
+#include "dcgi.h"
 
 /** @brief Infer the base URL for the web interface if it's not set
  *
 
 /** @brief Infer the base URL for the web interface if it's not set
  *
@@ -111,14 +111,7 @@ int main(int argc, char **argv) {
        login_cookie = cd.cookies[n].value;
     }
   }
        login_cookie = cd.cookies[n].value;
     }
   }
-  /* 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;
-  }
-  /* If there was a cookie but it went bad, we forget it */
-  if(login_cookie && !strcmp(disorder_user(g.client), "guest"))
-    login_cookie = 0;
+  disorder_cgi_login(&s, &output);
   /* TODO RFC 3875 s8.2 recommendations e.g. concerning PATH_INFO */
   disorder_cgi(&output, &s);
   if(fclose(stdout) < 0) fatal(errno, "error closing stdout");
   /* TODO RFC 3875 s8.2 recommendations e.g. concerning PATH_INFO */
   disorder_cgi(&output, &s);
   if(fclose(stdout) < 0) fatal(errno, "error closing stdout");
index e81aac6..08203f8 100644 (file)
@@ -39,7 +39,6 @@
 #include "vector.h"
 #include "sink.h"
 #include "cgi.h"
 #include "vector.h"
 #include "sink.h"
 #include "cgi.h"
-#include "dcgi.h"
 #include "log.h"
 #include "configuration.h"
 #include "table.h"
 #include "log.h"
 #include "configuration.h"
 #include "table.h"
@@ -54,6 +53,7 @@
 #include "defs.h"
 #include "trackname.h"
 #include "charset.h"
 #include "defs.h"
 #include "trackname.h"
 #include "charset.h"
+#include "dcgi.h"
 
 char *login_cookie;
 
 
 char *login_cookie;
 
@@ -141,6 +141,7 @@ static void lookups(dcgi_state *ds, unsigned want) {
   unsigned need;
   struct queue_entry *r, *rnext;
   const char *dir, *re;
   unsigned need;
   struct queue_entry *r, *rnext;
   const char *dir, *re;
+  char *rights;
 
   if(ds->g->client && (need = want ^ (ds->g->flags & want)) != 0) {
     if(need & DC_QUEUE)
 
   if(ds->g->client && (need = want ^ (ds->g->flags & want)) != 0) {
     if(need & DC_QUEUE)
@@ -175,6 +176,12 @@ static void lookups(dcgi_state *ds, unsigned want) {
                          &ds->g->files, &ds->g->nfiles))
          ds->g->nfiles = 0;
     }
                          &ds->g->files, &ds->g->nfiles))
          ds->g->nfiles = 0;
     }
+    if(need & DC_RIGHTS) {
+      ds->g->rights = RIGHT_READ;      /* fail-safe */
+      if(!disorder_userinfo(ds->g->client, disorder_user(ds->g->client),
+                           "rights", &rights))
+       parse_rights(rights, &ds->g->rights, 1);
+    }
     ds->g->flags |= need;
   }
 }
     ds->g->flags |= need;
   }
 }
@@ -473,11 +480,7 @@ static void act_logout(cgi_sink *output,
   disorder_revoke(ds->g->client);
   login_cookie = 0;
   /* Reconnect as guest */
   disorder_revoke(ds->g->client);
   login_cookie = 0;
   /* Reconnect as guest */
-  ds->g->client = disorder_new(0);
-  if(disorder_connect_cookie(ds->g->client, 0)) {
-    disorder_cgi_error(output, ds, "connect");
-    exit(0);
-  }
+  disorder_cgi_login(ds, output);
   /* Back to the login page */
   expand_template(ds, output, "login");
 }
   /* Back to the login page */
   expand_template(ds, output, "login");
 }
@@ -1236,17 +1239,12 @@ static void exp_scratchable(int attribute((unused)) nargs,
                            cgi_sink *output,
                            void attribute((unused)) *u) {
   dcgi_state *ds = u;
                            cgi_sink *output,
                            void attribute((unused)) *u) {
   dcgi_state *ds = u;
-  int result;
-
-  if(config->restrictions & RESTRICT_SCRATCH) {
-    lookups(ds, DC_PLAYING);
-    result = (ds->g->playing
-             && (!ds->g->playing->submitter
-                 || !strcmp(ds->g->playing->submitter,
-                            disorder_user(ds->g->client))));
-  } else
-    result = 1;
-  sink_printf(output->sink, "%s", bool2str(result));
+
+  lookups(ds, DC_PLAYING|DC_RIGHTS);
+  sink_printf(output->sink, "%s",
+             bool2str(right_scratchable(ds->g->rights,
+                                        disorder_user(ds->g->client),
+                                        ds->g->playing)));
 }
 
 static void exp_removable(int attribute((unused)) nargs,
 }
 
 static void exp_removable(int attribute((unused)) nargs,
@@ -1254,16 +1252,25 @@ static void exp_removable(int attribute((unused)) nargs,
                          cgi_sink *output,
                          void attribute((unused)) *u) {
   dcgi_state *ds = u;
                          cgi_sink *output,
                          void attribute((unused)) *u) {
   dcgi_state *ds = u;
-  int result;
 
 
-  if(config->restrictions & RESTRICT_REMOVE)
-    result = (ds->track
-             && ds->track->submitter
-             && !strcmp(ds->track->submitter,
-                        disorder_user(ds->g->client)));
-  else
-    result = 1;
-  sink_printf(output->sink, "%s", bool2str(result));
+  lookups(ds, DC_RIGHTS);
+  sink_printf(output->sink, "%s",
+             bool2str(right_removable(ds->g->rights,
+                                      disorder_user(ds->g->client),
+                                      ds->track)));
+}
+
+static void exp_movable(int attribute((unused)) nargs,
+                       char attribute((unused)) **args,
+                       cgi_sink *output,
+                       void attribute((unused)) *u) {
+  dcgi_state *ds = u;
+
+  lookups(ds, DC_RIGHTS);
+  sink_printf(output->sink, "%s",
+             bool2str(right_movable(ds->g->rights,
+                                    disorder_user(ds->g->client),
+                                    ds->track)));
 }
 
 static void exp_navigate(int attribute((unused)) nargs,
 }
 
 static void exp_navigate(int attribute((unused)) nargs,
@@ -1536,6 +1543,25 @@ static void exp_user(int attribute((unused)) nargs,
   cgi_output(output, "%s", disorder_user(ds->g->client));
 }
 
   cgi_output(output, "%s", disorder_user(ds->g->client));
 }
 
+static void exp_right(int attribute((unused)) nargs,
+                     char **args,
+                     cgi_sink *output,
+                     void *u) {
+  dcgi_state *const ds = u;
+  const char *right = expandarg(args[0], ds);
+  rights_type r;
+
+  lookups(ds, DC_RIGHTS);
+  if(parse_rights(right, &r, 1/*report*/))
+    r = 0;
+  if(args[1] == 0)
+    cgi_output(output, "%s", bool2str(!!(r & ds->g->rights)));
+  else if(r & ds->g->rights)
+    expandstring(output, args[1], ds);
+  else if(args[2])
+    expandstring(output, args[2], ds);
+}
+
 static const struct cgi_expansion expansions[] = {
   { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
   { "action", 0, 0, 0, exp_action },
 static const struct cgi_expansion expansions[] = {
   { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
   { "action", 0, 0, 0, exp_action },
@@ -1563,6 +1589,7 @@ static const struct cgi_expansion expansions[] = {
   { "isrecent", 0, 0, 0, exp_isrecent },
   { "label", 1, 1, 0, exp_label },
   { "length", 0, 0, 0, exp_length },
   { "isrecent", 0, 0, 0, exp_isrecent },
   { "label", 1, 1, 0, exp_label },
   { "length", 0, 0, 0, exp_length },
+  { "movable", 0, 0, 0, exp_movable },
   { "navigate", 2, 2, EXP_MAGIC, exp_navigate },
   { "ne", 2, 2, 0, exp_ne },
   { "new", 1, 1, EXP_MAGIC, exp_new },
   { "navigate", 2, 2, EXP_MAGIC, exp_navigate },
   { "ne", 2, 2, 0, exp_ne },
   { "new", 1, 1, EXP_MAGIC, exp_new },
@@ -1583,6 +1610,7 @@ static const struct cgi_expansion expansions[] = {
   { "recent", 1, 1, EXP_MAGIC, exp_recent },
   { "removable", 0, 0, 0, exp_removable },
   { "resolve", 1, 1, 0, exp_resolve },
   { "recent", 1, 1, EXP_MAGIC, exp_recent },
   { "removable", 0, 0, 0, exp_removable },
   { "resolve", 1, 1, 0, exp_resolve },
+  { "right", 1, 3, EXP_MAGIC, exp_right },
   { "scratchable", 0, 0, 0, exp_scratchable },
   { "search", 2, 3, EXP_MAGIC, exp_search },
   { "server-version", 0, 0, 0, exp_server_version },
   { "scratchable", 0, 0, 0, exp_scratchable },
   { "search", 2, 3, EXP_MAGIC, exp_search },
   { "server-version", 0, 0, 0, exp_server_version },
@@ -1646,6 +1674,22 @@ void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
   perform_action(output, ds, "error");
 }
 
   perform_action(output, ds, "error");
 }
 
+/** @brief Log in as the current user or guest if none */
+void disorder_cgi_login(dcgi_state *ds, cgi_sink *output) {
+  /* Create a new connection */
+  ds->g->client = disorder_new(0);
+  /* Forget everything we knew */
+  ds->g->flags = 0;
+  /* Reconnect */
+  if(disorder_connect_cookie(ds->g->client, login_cookie)) {
+    disorder_cgi_error(output, ds, "connect");
+    exit(0);
+  }
+  /* If there was a cookie but it went bad, we forget it */
+  if(login_cookie && !strcmp(disorder_user(ds->g->client), "guest"))
+    login_cookie = 0;
+}
+
 /*
 Local Variables:
 c-basic-offset:2
 /*
 Local Variables:
 c-basic-offset:2
index 0cf5a89..ac110d8 100644 (file)
@@ -31,12 +31,14 @@ typedef struct dcgi_global {
 #define DC_DIRS 0x0010
 #define DC_FILES 0x0020
 #define DC_NEW 0x0040
 #define DC_DIRS 0x0010
 #define DC_FILES 0x0020
 #define DC_NEW 0x0040
+#define DC_RIGHTS 0x0080
   struct queue_entry *queue, *playing, *recent;
   int volume_left, volume_right;
   char **files, **dirs;
   int nfiles, ndirs;
   char **new;
   int nnew;
   struct queue_entry *queue, *playing, *recent;
   int volume_left, volume_right;
   char **files, **dirs;
   int nfiles, ndirs;
   char **new;
   int nnew;
+  rights_type rights;
 } dcgi_global;
 
 typedef struct dcgi_state {
 } dcgi_global;
 
 typedef struct dcgi_state {
@@ -57,6 +59,7 @@ typedef struct dcgi_state {
 void disorder_cgi(cgi_sink *output, dcgi_state *ds);
 void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
                        const char *msg);
 void disorder_cgi(cgi_sink *output, dcgi_state *ds);
 void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
                        const char *msg);
+void disorder_cgi_login(dcgi_state *ds, cgi_sink *output);
 
 extern char *login_cookie;
 
 
 extern char *login_cookie;
 
index e6ba7f8..b680f2d 100644 (file)
@@ -233,20 +233,12 @@ static int c_play(struct conn *c, char **vec,
 static int c_remove(struct conn *c, char **vec,
                    int attribute((unused)) nvec) {
   struct queue_entry *q;
 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(!(q = queue_find(vec[0]))) {
     sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
     return 1;
   }
-  if(q->submitter)
-    if(!strcmp(q->submitter, c->who))
-      r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY;
-    else
-      r = RIGHT_REMOVE_ANY;
-  else
-    r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY;
-  if(!(c->rights & r)) {
+  if(!right_removable(c->rights, c->who, q)) {
     error(0, "%s attempted remove but lacks required rights", c->who);
     sink_writes(ev_writer_sink(c->w),
                "510 Not authorized to remove that track\n");
     error(0, "%s attempted remove but lacks required rights", c->who);
     sink_writes(ev_writer_sink(c->w),
                "510 Not authorized to remove that track\n");
@@ -269,8 +261,6 @@ static int c_remove(struct conn *c, char **vec,
 static int c_scratch(struct conn *c,
                     char **vec,
                     int nvec) {
 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(!playing) {
     sink_writes(ev_writer_sink(c->w), "250 nothing is playing\n");
     return 1;                  /* completed */
@@ -278,14 +268,7 @@ static int c_scratch(struct conn *c,
   /* 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. */
   /* 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|RIGHT_SCRATCH_ANY;
-    else
-      r = RIGHT_SCRATCH_ANY;
-  else
-    r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY;
-  if(!(c->rights & r)) {
+  if(!right_scratchable(c->rights, c->who, playing)) {
     error(0, "%s attempted scratch but lacks required rights", c->who);
     sink_writes(ev_writer_sink(c->w),
                "510 Not authorized to scratch that track\n");
     error(0, "%s attempted scratch but lacks required rights", c->who);
     sink_writes(ev_writer_sink(c->w),
                "510 Not authorized to scratch that track\n");
@@ -852,20 +835,13 @@ static int c_log(struct conn *c,
  * @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) {
  * @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;
 
   for(; nqs > 0; ++qs, --nqs) {
     struct queue_entry *const q = *qs;
 
-    if(q->submitter)
-      if(!strcmp(q->submitter, c->who))
-       r |= RIGHT_MOVE_MINE|RIGHT_MOVE_ANY;
-      else
-      r |= RIGHT_MOVE_ANY;
-    else
-      r |= RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY;
+    if(!right_movable(c->rights, c->who, q))
+      return 0;
   }
   }
-  return !!(c->rights & r);
+  return 1;
 }
 
 static int c_move(struct conn *c,
 }
 
 static int c_move(struct conn *c,
index 2f1f42f..31eadfd 100644 (file)
@@ -186,7 +186,10 @@ USA
        href="@url@?action=remove&#38;nonce=@nonce@&#38;id=@id@&#38;mgmt=@arg:mgmt@"><img
        class=button src="@label:images.scratch@"
        title="@label:playing.removeverbose@" 
        href="@url@?action=remove&#38;nonce=@nonce@&#38;id=@id@&#38;mgmt=@arg:mgmt@"><img
        class=button src="@label:images.scratch@"
        title="@label:playing.removeverbose@" 
-       alt="@label:playing.remove@"></a>}{&nbsp;}@</td>
+       alt="@label:playing.remove@"></a>}{<img
+       class=button src="@label:images.noscratch@"
+       title="@label:playing.removeverbose@"
+       alt="@label:playing.remove@">}@</td>
       @if{@arg:mgmt@}{
       @if{@isfirst@}
     {<td class=imgbutton>
       @if{@arg:mgmt@}{
       @if{@isfirst@}
     {<td class=imgbutton>