17 #include <arpa/inet.h>
21 #include <netinet/in.h>
26 #include <sys/socket.h>
70 sock = socket(PF_INET, SOCK_STREAM, 0);
78 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
80 struct sockaddr_in name;
81 name.sin_family = AF_INET;
82 name.sin_port = htons(
port);
84 if (bind(
sock, (
struct sockaddr *)&name,
sizeof(name)) < 0) {
90 int oldflags = fcntl(
sock, F_GETFL, 0);
95 oldflags |= O_NONBLOCK;
96 if (fcntl(
sock, F_SETFL, oldflags) < 0) {
112 struct sockaddr_in clientname;
113 uint size =
sizeof(clientname);
114 int newsock = accept(
sock, (
struct sockaddr *)&clientname, &size);
118 const char *s =
"Access denied!\n";
119 if (write(newsock, s, strlen(s)) < 0)
124 isyslog(
"connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ?
"accepted" :
"DENIED");
126 else if (errno != EINTR && errno != EAGAIN)
137 if ((
f = tmpfile()) != NULL) {
139 message =
"Enter EPG data, end with \".\" on a line by itself";
144 message =
"Error while opening temporary file";
157 if (strcmp(s,
".") != 0) {
167 message =
"EPG data processed";
171 message =
"Error while processing EPG data";
182 #define MAXHELPTOPIC 10
183 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
187 "CHAN [ + | - | <number> | <name> | <id> ]\n"
188 " Switch channel up, down or to the given channel number, name or id.\n"
189 " Without option (or after successfully switching to the channel)\n"
190 " it returns the current channel number and name.",
191 "CLRE [ <number> | <name> | <id> ]\n"
192 " Clear the EPG list of the given channel number, name or id.\n"
193 " Without option it clears the entire EPG list.\n"
194 " After a CLRE command, no further EPG processing is done for 10\n"
195 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
196 " interfere with data from the broadcasters.",
197 "CPYR <number> <new name>\n"
198 " Copy the recording with the given number. Before a recording can be\n"
199 " copied, an LSTR command must have been executed in order to retrieve\n"
200 " the recording numbers. The numbers don't change during subsequent CPYR\n"
205 " Delete the recording with the given number. Before a recording can be\n"
206 " deleted, an LSTR command must have been executed in order to retrieve\n"
207 " the recording numbers. The numbers don't change during subsequent DELR\n"
208 " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
209 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
213 " Edit the recording with the given number. Before a recording can be\n"
214 " edited, an LSTR command must have been executed in order to retrieve\n"
215 " the recording numbers.",
216 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
217 " Grab the current frame and save it to the given file. Images can\n"
218 " be stored as JPEG or PNM, depending on the given file name extension.\n"
219 " The quality of the grabbed image can be in the range 0..100, where 100\n"
220 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
221 " define the size of the resulting image (default is full screen).\n"
222 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
223 " data will be sent to the SVDRP connection encoded in base64. The same\n"
224 " happens if '-' (a minus sign) is given as file name, in which case the\n"
225 " image format defaults to JPEG.",
227 " The HELP command gives help info.",
228 "HITK [ <key> ... ]\n"
229 " Hit the given remote control key. Without option a list of all\n"
230 " valid key names is given. If more than one key is given, they are\n"
231 " entered into the remote control queue in the given sequence. There\n"
232 " can be up to 31 keys.",
233 "LSTC [ :groups | <number> | <name> | <id> ]\n"
234 " List channels. Without option, all channels are listed. Otherwise\n"
235 " only the given channel is listed. If a name is given, all channels\n"
236 " containing the given string as part of their name are listed.\n"
237 " If ':groups' is given, all channels are listed including group\n"
238 " separators. The channel number of a group separator is always 0.",
239 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
240 " List EPG data. Without any parameters all data of all channels is\n"
241 " listed. If a channel is given (either by number or by channel ID),\n"
242 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
243 " restricts the returned data to present events, following events, or\n"
244 " events at the given time (which must be in time_t form).",
245 "LSTR [ <number> ]\n"
246 " List recordings. Without option, all recordings are listed. Otherwise\n"
247 " the information for the given recording is listed.",
248 "LSTT [ <number> ] [ id ]\n"
249 " List timers. Without option, all timers are listed. Otherwise\n"
250 " only the given timer is listed. If the keyword 'id' is given, the\n"
251 " channels will be listed with their unique channel ids instead of\n"
254 " Displays the given message on the OSD. The message will be queued\n"
255 " and displayed whenever this is suitable.\n",
256 "MODC <number> <settings>\n"
257 " Modify a channel. Settings must be in the same format as returned\n"
258 " by the LSTC command.",
259 "MODT <number> on | off | <settings>\n"
260 " Modify a timer. Settings must be in the same format as returned\n"
261 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
262 " used to easily activate or deactivate a timer.",
263 "MOVC <number> <to>\n"
264 " Move a channel to a new position.",
265 "MOVR <number> <new name>\n"
266 " Move the recording with the given number. Before a recording can be\n"
267 " moved, an LSTR command must have been executed in order to retrieve\n"
268 " the recording numbers. The numbers don't change during subsequent MOVR\n"
271 " Create a new channel. Settings must be in the same format as returned\n"
272 " by the LSTC command.",
274 " Create a new timer. Settings must be in the same format as returned\n"
275 " by the LSTT command. It is an error if a timer with the same channel,\n"
276 " day, start and stop time already exists.",
277 "NEXT [ abs | rel ]\n"
278 " Show the next timer event. If no option is given, the output will be\n"
279 " in human readable form. With option 'abs' the absolute time of the next\n"
280 " event will be given as the number of seconds since the epoch (time_t\n"
281 " format), while with option 'rel' the relative time will be given as the\n"
282 " number of seconds from now until the event. If the absolute time given\n"
283 " is smaller than the current time, or if the relative time is less than\n"
284 " zero, this means that the timer is currently recording and has started\n"
285 " at the given time. The first value in the resulting line is the number\n"
287 "PLAY <number> [ begin | <position> ]\n"
288 " Play the recording with the given number. Before a recording can be\n"
289 " played, an LSTR command must have been executed in order to retrieve\n"
290 " the recording numbers.\n"
291 " The keyword 'begin' plays the recording from its very beginning, while\n"
292 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
293 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
294 " at the position where any previous replay was stopped, or from the beginning\n"
295 " by default. To control or stop the replay session, use the usual remote\n"
296 " control keypresses via the HITK command.",
297 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
298 " Send a command to a plugin.\n"
299 " The PLUG command without any parameters lists all plugins.\n"
300 " If only a name is given, all commands known to that plugin are listed.\n"
301 " If a command is given (optionally followed by parameters), that command\n"
302 " is sent to the plugin, and the result will be displayed.\n"
303 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
304 " If 'help' is followed by a command, the detailed help for that command is\n"
305 " given. The keyword 'main' initiates a call to the main menu function of the\n"
308 " Put data into the EPG list. The data entered has to strictly follow the\n"
309 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
310 " by itself terminates the input and starts processing of the data (all\n"
311 " entered data is buffered until the terminating '.' is seen).\n"
312 " If a file name is given, epg data will be read from this file (which\n"
313 " must be accessible under the given name from the machine VDR is running\n"
314 " on). In case of file input, no terminating '.' shall be given.\n",
315 "REMO [ on | off ]\n"
316 " Turns the remote control on or off. Without a parameter, the current\n"
317 " status of the remote control is reported.",
319 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
320 " will be done on the primary device unless it is currently recording.",
322 " Return information about disk usage (total, free, percent).",
324 " Updates a timer. Settings must be in the same format as returned\n"
325 " by the LSTT command. If a timer with the same channel, day, start\n"
326 " and stop time does not yet exists, it will be created.",
328 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
329 " equivalent to 'touch .update'.",
330 "VOLU [ <number> | + | - | mute ]\n"
331 " Set the audio volume to the given number (which is limited to the range\n"
332 " 0...255). If the special options '+' or '-' are given, the volume will\n"
333 " be turned up or down, respectively. The option 'mute' will toggle the\n"
334 " audio muting. If no option is given, the current audio volume level will\n"
337 " Exit vdr (SVDRP).\n"
338 " You can also hit Ctrl-D to exit.",
366 const char *q = HelpPage;
369 uint n = q - HelpPage;
370 if (n >=
sizeof(topic))
371 n =
sizeof(topic) - 1;
372 strncpy(topic, HelpPage, n);
386 if (strcasecmp(Cmd, t) == 0)
404 isyslog(
"SVDRP listening on port %d", Port);
419 gethostname(buffer,
sizeof(buffer));
420 Reply(221,
"%s closing connection%s", buffer, Timeout ?
" (timeout)" :
"");
422 isyslog(
"closing SVDRP connection");
448 const char *s = buffer;
450 const char *n = strchr(s,
'\n');
452 if (Code < 0 || n && *(n + 1))
455 sprintf(number,
"%03d%c", abs(Code), cont);
456 if (!(
Send(number) &&
Send(s, n ? n - s : -1) &&
Send(
"\r\n")))
458 s = n ? n + 1 : NULL;
462 Reply(451,
"Zero return code - looks like a programming error!");
463 esyslog(
"SVDRP: zero return code!");
478 const int TopicsPerLine = 5;
480 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
483 q += sprintf(q,
" ");
484 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
485 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
490 Reply(-214,
"%s", buffer);
500 int o = strtol(Option, NULL, 10);
504 else if (strcmp(Option,
"-") == 0) {
511 else if (strcmp(Option,
"+") == 0) {
525 if (strcasecmp(channel->
Name(), Option) == 0) {
534 Reply(501,
"Undefined channel \"%s\"", Option);
541 Reply(554,
"Error switching to channel \"%d\"", channel->
Number());
546 Reply(550,
"Unable to find channel \"%s\"", Option);
565 int o = strtol(Option, NULL, 10);
573 if (!Channel->GroupSep()) {
574 if (strcasecmp(Channel->Name(), Option) == 0) {
575 ChannelID = Channel->GetChannelID();
589 if (p->ChannelID() == ChannelID) {
596 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
597 Timer->SetEvent(NULL);
601 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
604 Reply(550,
"No EPG data found for channel \"%s\"", Option);
609 Reply(451,
"Can't get EPG data");
612 Reply(501,
"Undefined channel \"%s\"", Option);
617 Reply(250,
"EPG data cleared");
621 Reply(451,
"Error while clearing EPG data");
629 int n = strtol(Option, &tail, 10);
631 if (recording && tail && tail != Option) {
632 char *oldName = strdup(recording->
Name());
636 Reply(250,
"Copying recording \"%s\" to \"%s\"", oldName, tail);
638 Reply(554,
"Can't start file transfer");
641 Reply(554,
"File transfer already active");
645 Reply(550,
"Recording \"%d\" not found%s", n,
Recordings.
Count() ?
"" :
" (use LSTR before copying)");
648 Reply(501,
"Invalid Option \"%s\"", Option);
659 if (timer->Channel() == channel) {
660 Reply(550,
"Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
666 if (CurrentChannel && channel == CurrentChannel) {
671 CurrentChannelNr = 0;
676 isyslog(
"channel %s deleted", Option);
677 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
683 Reply(250,
"Channel \"%s\" deleted", Option);
686 Reply(501,
"Channel \"%s\" not defined", Option);
689 Reply(550,
"Channels are being edited - try again later");
692 Reply(501,
"Error in channel number \"%s\"", Option);
695 Reply(501,
"Missing channel number");
707 if (recording->
Delete()) {
708 Reply(250,
"Recording \"%s\" deleted", Option);
712 Reply(554,
"Error while deleting recording!");
715 Reply(550,
"Recording \"%s\" is being edited", Option);
718 Reply(550,
"Recording \"%s\" is in use by timer %d", Option, rc->
Timer()->
Index() + 1);
721 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before deleting)");
724 Reply(501,
"Error in recording number \"%s\"", Option);
727 Reply(501,
"Missing recording number");
741 Reply(250,
"Timer \"%s\" deleted", Option);
744 Reply(550,
"Timer \"%s\" is recording", Option);
747 Reply(501,
"Timer \"%s\" not defined", Option);
750 Reply(550,
"Timers are being edited - try again later");
753 Reply(501,
"Error in timer number \"%s\"", Option);
756 Reply(501,
"Missing timer number");
769 Reply(250,
"Editing recording \"%s\" [%s]", Option, recording->
Title());
771 Reply(554,
"Can't start editing process");
774 Reply(554,
"Editing process already active");
777 Reply(554,
"No editing marks defined");
780 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before editing)");
783 Reply(501,
"Error in recording number \"%s\"", Option);
786 Reply(501,
"Missing recording number");
791 const char *FileName = NULL;
793 int Quality = -1, SizeX = -1, SizeY = -1;
795 char buf[strlen(Option) + 1];
796 char *p = strcpy(buf, Option);
797 const char *delim =
" \t";
799 FileName = strtok_r(p, delim, &strtok_next);
801 const char *Extension = strrchr(FileName,
'.');
803 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
805 else if (strcasecmp(Extension,
".pnm") == 0)
808 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
811 if (Extension == FileName)
814 else if (strcmp(FileName,
"-") == 0)
817 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
818 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
820 p = strtok_r(NULL, delim, &strtok_next);
826 Reply(501,
"Invalid quality \"%s\"", p);
832 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
836 Reply(501,
"Invalid sizex \"%s\"", p);
839 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
843 Reply(501,
"Invalid sizey \"%s\"", p);
848 Reply(501,
"Missing sizey");
852 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
853 Reply(501,
"Unexpected parameter \"%s\"", p);
857 char RealFileName[PATH_MAX];
862 const char *slash = strrchr(FileName,
'/');
867 slash = strrchr(FileName,
'/');
870 char *r = realpath(t, RealFileName);
873 Reply(501,
"Invalid file name \"%s\"", FileName);
876 strcat(RealFileName, slash);
877 FileName = RealFileName;
879 Reply(501,
"Invalid file name \"%s\"", FileName);
884 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
893 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
895 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
896 dsyslog(
"grabbed image to %s", FileName);
897 Reply(250,
"Grabbed image %s", Option);
901 Reply(451,
"Can't write to '%s'", FileName);
907 Reply(451,
"Can't open '%s'", FileName);
913 while ((s = Base64.
NextLine()) != NULL)
914 Reply(-216,
"%s", s);
915 Reply(216,
"Grabbed image %s", Option);
920 Reply(451,
"Grab image failed");
923 Reply(501,
"Missing filename");
931 Reply(-214,
"%s", hp);
933 Reply(504,
"HELP topic \"%s\" unknown", Option);
939 Reply(-214,
"Topics:");
948 Reply(-214,
"To report bugs in the implementation send email to");
949 Reply(-214,
" vdr-bugs@tvdr.de");
951 Reply(214,
"End of HELP info");
957 char buf[strlen(Option) + 1];
959 const char *delim =
" \t";
961 char *p = strtok_r(buf, delim, &strtok_next);
967 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
972 Reply(504,
"Unknown key: \"%s\"", p);
976 p = strtok_r(NULL, delim, &strtok_next);
978 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
981 Reply(-214,
"Valid <key> names for the HITK command:");
982 for (
int i = 0; i <
kNone; i++) {
985 Reply(214,
"End of key list");
991 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
992 if (*Option && !WithGroupSeps) {
998 Reply(501,
"Channel \"%s\" not defined", Option);
1004 if (!channel->GroupSep()) {
1005 if (strcasestr(channel->Name(), Option)) {
1016 Reply(501,
"Channel \"%s\" not defined", Option);
1022 Reply(channel->Next() ? -250: 250,
"%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
1023 else if (!channel->GroupSep())
1024 Reply(channel->Number() <
Channels.
MaxNumber() ? -250 : 250,
"%d %s", channel->Number(), *channel->ToText());
1028 Reply(550,
"No channels defined");
1040 char buf[strlen(Option) + 1];
1041 strcpy(buf, Option);
1042 const char *delim =
" \t";
1044 char *p = strtok_r(buf, delim, &strtok_next);
1045 while (p && DumpMode ==
dmAll) {
1046 if (strcasecmp(p,
"NOW") == 0)
1048 else if (strcasecmp(p,
"NEXT") == 0)
1050 else if (strcasecmp(p,
"AT") == 0) {
1052 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1054 AtTime = strtol(p, NULL, 10);
1056 Reply(501,
"Invalid time");
1061 Reply(501,
"Missing time");
1065 else if (!Schedule) {
1074 Reply(550,
"No schedule found");
1079 Reply(550,
"Channel \"%s\" not defined", p);
1084 Reply(501,
"Unknown option: \"%s\"", p);
1087 p = strtok_r(NULL, delim, &strtok_next);
1092 FILE *f = fdopen(fd,
"w");
1095 Schedule->
Dump(f,
"215-", DumpMode, AtTime);
1097 Schedules->
Dump(f,
"215-", DumpMode, AtTime);
1099 Reply(215,
"End of EPG data");
1103 Reply(451,
"Can't open file connection");
1108 Reply(451,
"Can't dup stream descriptor");
1111 Reply(451,
"Can't get EPG data");
1121 FILE *f = fdopen(
file,
"w");
1125 Reply(215,
"End of recording information");
1129 Reply(451,
"Can't open file connection");
1132 Reply(550,
"Recording \"%s\" not found", Option);
1135 Reply(501,
"Error in recording number \"%s\"", Option);
1145 Reply(550,
"No recordings available");
1153 char buf[strlen(Option) + 1];
1154 strcpy(buf, Option);
1155 const char *delim =
" \t";
1157 char *p = strtok_r(buf, delim, &strtok_next);
1160 Number = strtol(p, NULL, 10);
1161 else if (strcasecmp(p,
"ID") == 0)
1164 Reply(501,
"Unknown option: \"%s\"", p);
1167 p = strtok_r(NULL, delim, &strtok_next);
1175 Reply(501,
"Timer \"%s\" not defined", Option);
1183 Reply(501,
"Timer \"%d\" not found", i + 1);
1187 Reply(550,
"No timers defined");
1193 isyslog(
"SVDRP message: '%s'", Option);
1195 Reply(250,
"Message queued");
1198 Reply(501,
"Missing message");
1205 int n = strtol(Option, &tail, 10);
1206 if (tail && tail != Option) {
1212 if (ch.
Parse(tail)) {
1221 Reply(501,
"Channel settings are not unique");
1224 Reply(501,
"Error in channel settings");
1227 Reply(501,
"Channel \"%d\" not defined", n);
1230 Reply(550,
"Channels are being edited - try again later");
1233 Reply(501,
"Error in channel number");
1236 Reply(501,
"Missing channel settings");
1243 int n = strtol(Option, &tail, 10);
1244 if (tail && tail != Option) {
1250 if (strcasecmp(tail,
"ON") == 0)
1252 else if (strcasecmp(tail,
"OFF") == 0)
1254 else if (!t.
Parse(tail)) {
1255 Reply(501,
"Error in timer settings");
1264 Reply(501,
"Timer \"%d\" not defined", n);
1267 Reply(550,
"Timers are being edited - try again later");
1270 Reply(501,
"Error in timer number");
1273 Reply(501,
"Missing timer settings");
1281 int From = strtol(Option, &tail, 10);
1282 if (tail && tail != Option) {
1284 if (tail && tail != Option) {
1285 int To = strtol(tail, NULL, 10);
1292 int FromNumber = FromChannel->
Number();
1293 int ToNumber = ToChannel->
Number();
1294 if (FromNumber != ToNumber) {
1298 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1304 isyslog(
"channel %d moved to %d", FromNumber, ToNumber);
1305 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
1308 Reply(501,
"Can't move channel to same postion");
1311 Reply(501,
"Channel \"%d\" not defined", To);
1314 Reply(501,
"Channel \"%d\" not defined", From);
1317 Reply(501,
"Error in channel number");
1320 Reply(501,
"Error in channel number");
1323 Reply(550,
"Channels or timers are being edited - try again later");
1326 Reply(501,
"Missing channel number");
1333 int n = strtol(Option, &tail, 10);
1335 if (recording && tail && tail != Option) {
1336 char *oldName = strdup(recording->
Name());
1340 Reply(250,
"Moving recording \"%s\" to \"%s\"", oldName, tail);
1342 Reply(554,
"Can't start file transfer");
1345 Reply(554,
"File transfer already active");
1349 Reply(550,
"Recording \"%d\" not found%s", n,
Recordings.
Count() ?
"" :
" (use LSTR before moving)");
1352 Reply(501,
"Invalid Option \"%s\"", Option);
1359 if (ch.
Parse(Option)) {
1370 Reply(501,
"Channel settings are not unique");
1373 Reply(501,
"Error in channel settings");
1376 Reply(501,
"Missing channel settings");
1383 if (timer->
Parse(Option)) {
1396 Reply(501,
"Error in timer settings");
1400 Reply(501,
"Missing timer settings");
1408 int Number = t->
Index() + 1;
1411 else if (strcasecmp(Option,
"ABS") == 0)
1412 Reply(250,
"%d %ld", Number, Start);
1413 else if (strcasecmp(Option,
"REL") == 0)
1414 Reply(250,
"%d %ld", Number, Start - time(NULL));
1416 Reply(501,
"Unknown option: \"%s\"", Option);
1419 Reply(550,
"No active timers");
1425 char *opt = strdup(Option);
1428 while (*option && !isspace(*option))
1441 if (strcasecmp(option,
"BEGIN") != 0)
1452 Reply(250,
"Playing recording \"%s\" [%s]", num, recording->
Title());
1455 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before playing)");
1458 Reply(501,
"Error in recording number \"%s\"", num);
1462 Reply(501,
"Missing recording number");
1468 char *opt = strdup(Option);
1470 char *option = name;
1471 while (*option && !isspace(*option))
1480 while (*option && !isspace(*option))
1486 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
1487 if (*cmd && *option) {
1490 Reply(-214,
"%s", hp);
1491 Reply(214,
"End of HELP info");
1494 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
1500 Reply(-214,
"SVDRP commands:");
1502 Reply(214,
"End of HELP info");
1505 Reply(214,
"This plugin has no SVDRP commands");
1508 else if (strcasecmp(cmd,
"MAIN") == 0) {
1510 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
1512 Reply(550,
"A plugin call is already pending - please try again later");
1515 int ReplyCode = 900;
1518 Reply(abs(ReplyCode),
"%s", *s);
1520 Reply(500,
"Command unrecognized: \"%s\"", cmd);
1524 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1528 Reply(-214,
"Available plugins:");
1532 Reply(214,
"End of plugin list");
1539 FILE *f = fopen(Option,
"r");
1543 Reply(250,
"EPG data processed from \"%s\"", Option);
1546 Reply(451,
"Error while processing EPG from \"%s\"", Option);
1550 Reply(501,
"Cannot open file \"%s\"", Option);
1564 if (!strcasecmp(Option,
"ON")) {
1566 Reply(250,
"Remote control enabled");
1568 else if (!strcasecmp(Option,
"OFF")) {
1570 Reply(250,
"Remote control disabled");
1573 Reply(501,
"Invalid Option \"%s\"", Option);
1582 Reply(250,
"EPG scan triggered");
1588 if (strcasecmp(Option,
"DISK") == 0) {
1591 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1594 Reply(501,
"Invalid Option \"%s\"", Option);
1597 Reply(501,
"No option given");
1604 if (timer->
Parse(Option)) {
1622 Reply(550,
"Timers are being edited - try again later");
1625 Reply(501,
"Error in timer settings");
1629 Reply(501,
"Missing timer settings");
1635 Reply(250,
"Re-read of recordings directory triggered");
1643 else if (strcmp(Option,
"+") == 0)
1645 else if (strcmp(Option,
"-") == 0)
1647 else if (strcasecmp(Option,
"MUTE") == 0)
1650 Reply(501,
"Unknown option: \"%s\"", Option);
1655 Reply(250,
"Audio is mute");
1660 #define CMD(c) (strcasecmp(Cmd, c) == 0)
1677 while (*s && !isspace(*s))
1714 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
1720 bool SendGreeting = NewConnection;
1725 char buffer[BUFSIZ];
1726 gethostname(buffer,
sizeof(buffer));
1727 time_t now = time(NULL);
1736 if (c ==
'\n' || c == 0x00) {
1751 else if (c == 0x04 &&
numChars == 0) {
1755 else if (c == 0x08 || c == 0x7F) {
1760 else if (c <= 0x03 || c == 0x0D) {
1765 int NewLength =
length + BUFSIZ;
1766 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
1771 esyslog(
"ERROR: out of memory");
1782 isyslog(
"lost connection to SVDRP client");
1787 isyslog(
"timeout on SVDRP connection");
1798 grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;