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);