/* * svdrp.c: Simple Video Disk Recorder Protocol * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII * text based. Therefore you can simply 'telnet' to your VDR port * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * * $Id: svdrp.c 4.1 2015/04/29 13:10:01 kls Exp $ */ #include "svdrp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "channels.h" #include "config.h" #include "device.h" #include "eitscan.h" #include "keys.h" #include "menu.h" #include "plugin.h" #include "recording.h" #include "remote.h" #include "skins.h" #include "thread.h" #include "timers.h" #include "tools.h" #include "videodir.h" // --- cSocket --------------------------------------------------------------- class cSocket { private: int port; int sock; int queue; cString lastAcceptedConnection; public: cSocket(int Port, int Queue = 1); ~cSocket(); bool Open(void); void Close(void); int Socket(void) const { return sock; } int Accept(void); const char *LastAcceptedConnection(void) const { return lastAcceptedConnection; } }; cSocket::cSocket(int Port, int Queue) { port = Port; sock = -1; queue = Queue; } cSocket::~cSocket() { Close(); } void cSocket::Close(void) { if (sock >= 0) { close(sock); sock = -1; } } bool cSocket::Open(void) { if (sock < 0) { // create socket: sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { LOG_ERROR; port = 0; return false; } // allow it to always reuse the same port: int ReUseAddr = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr)); // struct sockaddr_in name; name.sin_family = AF_INET; name.sin_port = htons(port); name.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) { LOG_ERROR; Close(); return false; } // make it non-blocking: int oldflags = fcntl(sock, F_GETFL, 0); if (oldflags < 0) { LOG_ERROR; return false; } oldflags |= O_NONBLOCK; if (fcntl(sock, F_SETFL, oldflags) < 0) { LOG_ERROR; return false; } // listen to the socket: if (listen(sock, queue) < 0) { LOG_ERROR; return false; } isyslog("SVDRP listening on port %d/tcp", port); } return true; } int cSocket::Accept(void) { if (Open()) { struct sockaddr_in clientname; uint size = sizeof(clientname); int newsock = accept(sock, (struct sockaddr *)&clientname, &size); if (newsock >= 0) { bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr); if (!accepted) { const char *s = "Access denied!\n"; if (write(newsock, s, strlen(s)) < 0) LOG_ERROR; close(newsock); newsock = -1; } lastAcceptedConnection = cString::sprintf("%s:%hu", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port)); isyslog("SVDRP %s connection %s", *lastAcceptedConnection, accepted ? "accepted" : "DENIED"); } else if (errno != EINTR && errno != EAGAIN) LOG_ERROR; return newsock; } return -1; } // --- cPUTEhandler ---------------------------------------------------------- class cPUTEhandler { private: FILE *f; int status; const char *message; public: cPUTEhandler(void); ~cPUTEhandler(); bool Process(const char *s); int Status(void) { return status; } const char *Message(void) { return message; } }; cPUTEhandler::cPUTEhandler(void) { if ((f = tmpfile()) != NULL) { status = 354; message = "Enter EPG data, end with \".\" on a line by itself"; } else { LOG_ERROR; status = 554; message = "Error while opening temporary file"; } } cPUTEhandler::~cPUTEhandler() { if (f) fclose(f); } bool cPUTEhandler::Process(const char *s) { if (f) { if (strcmp(s, ".") != 0) { fputs(s, f); fputc('\n', f); return true; } else { rewind(f); if (cSchedules::Read(f)) { cSchedules::Cleanup(true); status = 250; message = "EPG data processed"; } else { status = 451; message = "Error while processing EPG data"; } fclose(f); f = NULL; } } return false; } // --- cSVDRP ---------------------------------------------------------------- #define MAXHELPTOPIC 10 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command // adjust the help for CLRE accordingly if changing this! const char *HelpPages[] = { "CHAN [ + | - | | | ]\n" " Switch channel up, down or to the given channel number, name or id.\n" " Without option (or after successfully switching to the channel)\n" " it returns the current channel number and name.", "CLRE [ | | ]\n" " Clear the EPG list of the given channel number, name or id.\n" " Without option it clears the entire EPG list.\n" " After a CLRE command, no further EPG processing is done for 10\n" " seconds, so that data sent with subsequent PUTE commands doesn't\n" " interfere with data from the broadcasters.", "DELC \n" " Delete channel.", "DELR \n" " Delete the recording with the given number. Before a recording can be\n" " deleted, an LSTR command must have been executed in order to retrieve\n" " the recording numbers. The numbers don't change during subsequent DELR\n" " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n" " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!", "DELT \n" " Delete timer.", "EDIT \n" " Edit the recording with the given number. Before a recording can be\n" " edited, an LSTR command must have been executed in order to retrieve\n" " the recording numbers.", "GRAB [ [ ] ]\n" " Grab the current frame and save it to the given file. Images can\n" " be stored as JPEG or PNM, depending on the given file name extension.\n" " The quality of the grabbed image can be in the range 0..100, where 100\n" " (the default) means \"best\" (only applies to JPEG). The size parameters\n" " define the size of the resulting image (default is full screen).\n" " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n" " data will be sent to the SVDRP connection encoded in base64. The same\n" " happens if '-' (a minus sign) is given as file name, in which case the\n" " image format defaults to JPEG.", "HELP [ ]\n" " The HELP command gives help info.", "HITK [ ... ]\n" " Hit the given remote control key. Without option a list of all\n" " valid key names is given. If more than one key is given, they are\n" " entered into the remote control queue in the given sequence. There\n" " can be up to 31 keys.", "LSTC [ :groups | | | ]\n" " List channels. Without option, all channels are listed. Otherwise\n" " only the given channel is listed. If a name is given, all channels\n" " containing the given string as part of their name are listed.\n" " If ':groups' is given, all channels are listed including group\n" " separators. The channel number of a group separator is always 0.", "LSTE [ ] [ now | next | at