Base64 output (and related stuff) for the SVDRP GRAB command

Message ID 4DB42CB13B%linux@youmustbejoking.demon.co.uk
State New
Headers

Commit Message

Darren Salt Oct. 3, 2005, 6:31 p.m. UTC
  The attached patch does three things to the GRAB command:

 * If the filename is "-", it outputs the file, encoded using base64, to the
   socket. Result code 216 is used.

 * Files are restricted to being in "/tmp" and "$VIDEODIR/snaps.dir").
   If a full pathname is not given, the default is "$VIDEODIR/snaps.dir".
   (This is my previously-existing fix for CAN-2005-0071. It's rolled up into
   this patch because it gets reindented; it occupies the section between the
   two "we're using a ..." comments.)

 * Writing to files is permitted only for connections from localhost since it
   is _likely_ that only users on the local machine have access to them.
   (This is done entirely in the first patch hunk for svdrp.c.)

It works with 1.3.33 and is cleanly applicable to 1.3.34.

Ideally, cDevice::GrabImage (and methods which override this) would require a
file handle instead of a filename. This change would require that some
plugins (vdr-xine, for one) also be patched, so I've not done this - yet.
  

Patch

diff -urNad vdr-1.3.33~/dvbdevice.c vdr-1.3.33/dvbdevice.c
--- vdr-1.3.33~/dvbdevice.c	2005-10-03 17:46:41.000000000 +0100
+++ vdr-1.3.33/dvbdevice.c	2005-10-03 17:46:41.760315434 +0100
@@ -564,8 +564,9 @@ 
               Quality = 100;
 
            isyslog("grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height);
-           FILE *f = fopen(FileName, "wb");
-           if (f) {
+           int fd = open (FileName, O_CREAT | O_NOFOLLOW | O_TRUNC | O_RDWR, 0644);
+           FILE *f;
+           if (fd != -1 && (f = fdopen(fd, "wb"))) {
               if (Jpeg) {
                  // write JPEG file:
                  struct jpeg_compress_struct cinfo;
@@ -602,6 +603,8 @@ 
               }
            else {
               LOG_ERROR_STR(FileName);
+              if (fd != -1 && close (fd))
+                 LOG_ERROR_STR(FileName);
               result |= 1;
               }
            munmap(mem, msize);
diff -urNad vdr-1.3.33~/svdrp.c vdr-1.3.33/svdrp.c
--- vdr-1.3.33~/svdrp.c	2005-10-03 17:46:41.000000000 +0100
+++ vdr-1.3.33/svdrp.c	2005-10-03 17:47:33.688695436 +0100
@@ -119,6 +119,8 @@ 
            close(newsock);
            newsock = -1;
            }
+        else // FIXME - IPv6
+           localhost = ((ntohl (clientname.sin_addr.s_addr) & 0xFF000000) == 0x7F000000);
         isyslog("connect from %s, port %hd - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
         }
      else if (errno != EINTR && errno != EAGAIN)
@@ -309,6 +311,7 @@ 
 
  214 Help message
  215 EPG or recording data record
+ 216 Image grab data (base 64)
  220 VDR service ready
  221 VDR service closing transmission channel
  250 Requested VDR action okay, completed
@@ -646,17 +649,72 @@ 
      Reply(501, "Missing recording number");
 }
 
+void cSVDRP::Base64 (const char *file, const char *Option)
+{
+  static const char b64[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  FILE *fd = fopen (file, "rb");
+  if (!fd) {
+     Reply (554, "Grab image failed");
+     return;
+  }
+
+  unsigned char in[3], out[65];
+  size_t count, ptr = 0;
+  while ((count = fread (in, 1, 3, fd)) == 3)
+  {
+    out[ptr++] = b64[in[0] >> 2];
+    out[ptr++] = b64[(in[0] << 4 | in[1] >> 4) & 63];
+    out[ptr++] = b64[(in[1] << 2 | in[2] >> 6) & 63];
+    out[ptr++] = b64[in[2] & 63];
+    if (ptr < 64)
+      continue;
+    out[ptr] = 0;
+    Reply (-216, (const char *) out);
+    ptr = 0;
+  }
+  fclose (fd); // file handle is no longer needed
+  // output is <= 60 bytes long (can't be 64 else it would have been sent)
+  if (count > 0) // count == 1 or count == 2 (can't be 3 due to above loop)
+  {
+    in[count] = 0; // padding in case count == 1
+    out[ptr++] = b64[in[0] >> 2];
+    out[ptr++] = b64[(in[0] << 4 | in[1] >> 4) & 63];
+    out[ptr++] = (count == 1) ? '=' : b64[(in[1] << 2) & 63];
+    out[ptr++] = '=';
+    out[ptr] = 0;
+  }
+  else
+    strcpy ((char *)(out + ptr), "====");
+  Reply (-216, (const char *) out);
+  Reply (216, "Grabbed image %s", Option);
+}
+
 void cSVDRP::CmdGRAB(const char *Option)
 {
-  char *FileName = NULL;
   bool Jpeg = true;
+  bool tempfile = false;
   int Quality = -1, SizeX = -1, SizeY = -1;
   if (*Option) {
      char buf[strlen(Option) + 1];
      char *p = strcpy(buf, Option);
      const char *delim = " \t";
      char *strtok_next;
-     FileName = strtok_r(p, delim, &strtok_next);
+     cString FileName = strtok_r(p, delim, &strtok_next);
+     if (!strcmp (*FileName, "-")) {
+        char *temp = tempnam (NULL, "vdr");
+        if (!temp) {
+           Reply(451, "Grab image failed");
+           return;
+           }
+        FileName = temp;
+        free (temp); // that was malloc()ed...
+        tempfile = true;
+        }
+     else if (!socket.IsFromLocalHost ()) {
+        Reply (550, "Write to file only permitted locally");
+        return;
+        }
      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
         if (strcasecmp(p, "JPEG") == 0)
            Jpeg = true;
@@ -699,10 +757,70 @@ 
         Reply(501, "Unexpected parameter \"%s\"", p);
         return;
         }
-     if (cDevice::PrimaryDevice()->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY))
-        Reply(250, "Grabbed image %s", Option);
-     else
-        Reply(451, "Grab image failed");
+     if (!tempfile) {
+        // we're using a permanent file
+        char *dir, *fpath = NULL;
+        asprintf (&dir, "%s/snaps.dir", VideoDirectory);
+        if (mkdir (dir, 0755) && errno != EEXIST) {
+	   LOG_ERROR_STR(dir);
+	   Reply(451, "Grab image failed");
+	   free (dir);
+	   return;
+           }
+        if (**FileName != '/')
+	   asprintf (&fpath, "%s/%s", dir, *FileName);
+        // fpath = full pathname (not canonicalised) or NULL
+
+        char *tmp = strrchr (fpath ? fpath : *FileName, '/'); // there is one
+        *tmp = 0;
+        char path[PATH_MAX];
+        if (!realpath (fpath ? fpath : *FileName, path)) { // canonicalise
+	   Reply (501, errno == EIO ? "Internal error" : "Invalid filename");
+	   free (fpath);
+	   free (dir);
+	   return;
+           }
+        //
+        asprintf (&tmp, "%s/%s", path, tmp + 1);
+        free (fpath);
+        fpath = tmp; // full pathname (canonicalised)
+
+        if (!realpath (dir, path)) { // dir name (canonicalised)
+	   Reply (501, errno == EIO ? "Internal error" : "Invalid filename");
+	   free (fpath);
+	   free (dir);
+	   return;
+           }
+        if (!strncmp (fpath, path, strlen (path)) && fpath[strlen (path)] == '/') {
+           /* nothing */
+           }
+        else if (strncmp (fpath, "/tmp/", 5)) {
+	   Reply(501, "Invalid filename");
+	   free (fpath);
+	   free (dir);
+	   return;
+           }
+        free (dir);
+
+        if (cDevice::PrimaryDevice()->GrabImage(fpath, Jpeg, Quality, SizeX, SizeY))
+           Reply(250, "Grabbed image %s", Option);
+        else
+           Reply(451, "Grab image failed");
+        free (fpath);
+        }
+     else {
+        // we're using a temporary file
+        if (cDevice::PrimaryDevice()->GrabImage(*FileName, Jpeg, Quality, SizeX, SizeY)) {
+           if (tempfile)
+              Base64 (FileName, Option);
+           else
+              Reply(250, "Grabbed image %s", Option);
+           }
+        else
+           Reply(451, "Grab image failed");
+        // file is no longer needed
+        unlink (*FileName);
+        }
      }
   else
      Reply(501, "Missing filename");
diff -urNad vdr-1.3.33~/svdrp.h vdr-1.3.33/svdrp.h
--- vdr-1.3.33~/svdrp.h	2005-10-03 17:46:41.000000000 +0100
+++ vdr-1.3.33/svdrp.h	2005-10-03 17:46:41.760315434 +0100
@@ -18,12 +18,14 @@ 
   int port;
   int sock;
   int queue;
+  bool localhost;
   void Close(void);
 public:
   cSocket(int Port, int Queue = 1);
   ~cSocket();
   bool Open(void);
   int Accept(void);
+  bool IsFromLocalHost() { return localhost; }
   };
 
 class cPUTEhandler {
@@ -53,6 +55,7 @@ 
   bool Send(const char *s, int length = -1);
   void Reply(int Code, const char *fmt, ...);
   void PrintHelpTopics(const char **hp);
+  void Base64(const char *file, const char *Option);
   void CmdCHAN(const char *Option);
   void CmdCLRE(const char *Option);
   void CmdDELC(const char *Option);