/* * 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 1.109.1.3 2012/02/14 13:55:52 kls Exp $ */ #include "svdrp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "channels.h" #include "config.h" #include "cutter.h" #include "device.h" #include "eitscan.h" #include "keys.h" #include "menu.h" #include "plugin.h" #include "remote.h" #include "skins.h" #include "timers.h" #include "tools.h" #include "videodir.h" // --- cSocket --------------------------------------------------------------- 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 = 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; } } 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; } isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED"); } else if (errno != EINTR && errno != EAGAIN) LOG_ERROR; return newsock; } return -1; } // --- cPUTEhandler ---------------------------------------------------------- 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 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.", "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.", "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