1 /*
2  * This file is part of MPlayer.
3  *
4  * MPlayer is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * MPlayer is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with MPlayer; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include "config.h"
20 
21 #include <stdlib.h>
22 #include <stdio.h>
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #if !HAVE_WINSOCK2_H
30 #include <sys/socket.h>
31 #else
32 #include <winsock2.h>
33 #endif
34 
35 #include "libavutil/avutil.h"
36 #include "libavutil/common.h"
37 #include "mp_msg.h"
38 #include "network.h"
39 #include "stream.h"
40 #include "help_mp.h"
41 #include "m_option.h"
42 #include "m_struct.h"
43 #include "tcp.h"
44 
45 static const struct stream_priv_s {
46   char* user;
47   char* pass;
48   char* host;
49   int port;
50   char* filename;
51 
52   char *cput,*cget;
53   int handle;
54   int cavail,cleft;
55   char *buf;
56   char *cmd_buf;
57 } stream_priv_dflts = {
58   .user = "anonymous",
59   .pass = "no@spam",
60   .port = 21,
61   .handle = -1,
62 };
63 
64 #define CMD_BUFSIZE 8192
65 
66 #define BUFSIZE 2048
67 
68 #define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
69 /// URL definition
70 static const m_option_t stream_opts_fields[] = {
71   {"username", ST_OFF(user), CONF_TYPE_STRING, 0, 0 ,0, NULL},
72   {"password", ST_OFF(pass), CONF_TYPE_STRING, 0, 0 ,0, NULL},
73   {"hostname", ST_OFF(host), CONF_TYPE_STRING, 0, 0 ,0, NULL},
74   {"port", ST_OFF(port), CONF_TYPE_INT, 0, 0 ,65635, NULL},
75   {"filename", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL},
76   { NULL, NULL, 0, 0, 0, 0,  NULL }
77 };
78 static const struct m_struct_st stream_opts = {
79   "ftp",
80   sizeof(struct stream_priv_s),
81   &stream_priv_dflts,
82   stream_opts_fields
83 };
84 
85 #define TELNET_IAC      255             /* interpret as command: */
86 #define TELNET_IP       244             /* interrupt process--permanently */
87 #define TELNET_SYNCH    242             /* for telfunc calls */
88 
89 // Check if there is something to read on a fd. This avoid hanging
90 // forever if the network stop responding.
fd_can_read(int fd,int timeout)91 static int fd_can_read(int fd,int timeout) {
92   fd_set fds;
93   struct timeval tv;
94 
95   FD_ZERO(&fds);
96   FD_SET(fd,&fds);
97   tv.tv_sec = timeout;
98   tv.tv_usec = 0;
99 
100   return select(fd+1, &fds, NULL, NULL, &tv) > 0;
101 }
102 
103 /*
104  * read a line of text
105  *
106  * If the line is too long to fit in the buffer, provided via parameters
107  * buf and max, the remaining characters are skipped. So the next call to
108  * this function is synchronized to the start of the following response
109  * line.
110  *
111  * The parameter buf will always be initialized as long as max is bigger
112  * then 1. If nothing is read it will contain an empty string.
113  *
114  * return -1 on error or bytecount
115  */
readline(char * buf,int max,struct stream_priv_s * ctl)116 static int readline(char *buf,int max,struct stream_priv_s *ctl)
117 {
118     int x,retval = 0;
119     char *end,*bp=buf;
120     int eof = 0;
121 
122     if (max <= 0) {
123       return -1;
124     }
125     *bp = '\0';
126 
127     do {
128       if (ctl->cavail > 0) {
129 	x = FFMIN(ctl->cavail, max-1);
130 	end = memccpy(bp,ctl->cget,'\n',x);
131 	if (end != NULL)
132 	  x = end - bp;
133 	retval += x;
134 	bp += x;
135 	*bp = '\0';
136 	max -= x;
137 	ctl->cget += x;
138 	ctl->cavail -= x;
139 	if (end != NULL) {
140 	  bp -= 2;
141 	  if (strcmp(bp,"\r\n") == 0) {
142 	    *bp++ = '\n';
143 	    *bp++ = '\0';
144 	    --retval;
145 	  }
146 	  break;
147 	}
148       }
149       if (max == 1) {
150         char *q = memchr(ctl->cget, '\n', ctl->cavail);
151 
152         if (q) { // found EOL: update state and return
153           ++q;
154           ctl->cavail -= q - ctl->cget;
155           ctl->cget = q;
156 
157           break;
158         }
159 
160         // receive more data to find end of current line
161         ctl->cget = ctl->cput;
162       }
163       if (ctl->cput == ctl->cget) {
164 	ctl->cput = ctl->cget = ctl->buf;
165 	ctl->cavail = 0;
166 	ctl->cleft = BUFSIZE;
167       }
168       if(eof) {
169 	if (retval == 0)
170 	  retval = -1;
171 	break;
172       }
173 
174       if(!fd_can_read(ctl->handle, 15)) {
175         mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n");
176         retval = -1;
177         break;
178       }
179 
180       if ((x = recv(ctl->handle,ctl->cput,ctl->cleft,0)) == -1) {
181 	mp_msg(MSGT_STREAM,MSGL_ERR, "[ftp] read error: %s\n",strerror(errno));
182 	retval = -1;
183 	break;
184       }
185       if (x == 0)
186 	eof = 1;
187       ctl->cleft -= x;
188       ctl->cavail += x;
189       ctl->cput += x;
190     } while (1);
191 
192     return retval;
193 }
194 
195 /*
196  * read a response from the server
197  *
198  * return 0 if first char doesn't match
199  * return 1 if first char matches
200  */
readresp(struct stream_priv_s * ctl,char * rsp)201 static int readresp(struct stream_priv_s* ctl,char* rsp)
202 {
203     static char response[256];
204     char match[5];
205     int r, len;
206 
207     len = readline(response,256,ctl);
208     if (rsp) strcpy(rsp,response);
209     if (len == -1)
210       return 0;
211 
212     r = atoi(response)/100;
213 
214     mp_msg(MSGT_STREAM,MSGL_V, "[ftp] < %s",response);
215 
216     if (response[3] == '-') {
217       strncpy(match,response,3);
218       match[3] = ' ';
219       match[4] = '\0';
220       do {
221 	if (readline(response,256,ctl) == -1) {
222 	  mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Control socket read failed\n");
223 	  return 0;
224 	}
225 	mp_msg(MSGT_OPEN,MSGL_V, "[ftp] < %s",response);
226       }	while (strncmp(response,match,4));
227     }
228     return r;
229 }
230 
231 
FtpSendCmd(const char * cmd,struct stream_priv_s * nControl,char * rsp)232 static int FtpSendCmd(const char *cmd, struct stream_priv_s *nControl,char* rsp)
233 {
234   int l = strlen(cmd);
235   int hascrlf = cmd[l - 2] == '\r' && cmd[l - 1] == '\n';
236 
237   if(hascrlf && l == 2) mp_msg(MSGT_STREAM,MSGL_V, "\n");
238   else mp_msg(MSGT_STREAM,MSGL_V, "[ftp] > %s",cmd);
239   while(l > 0) {
240     int s = send(nControl->handle,cmd,l,DEFAULT_SEND_FLAGS);
241 
242     if(s <= 0) {
243       mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] write error: %s\n",strerror(errno));
244       return 0;
245     }
246 
247     cmd += s;
248     l -= s;
249   }
250 
251   if (hascrlf)
252     return readresp(nControl,rsp);
253   else
254     return FtpSendCmd("\r\n", nControl, rsp);
255 }
256 
FtpOpenPort(struct stream_priv_s * p)257 static int FtpOpenPort(struct stream_priv_s* p) {
258   int resp,fd;
259   char rsp_txt[256];
260   char* par,str[128];
261   int num[6];
262 
263   resp = FtpSendCmd("PASV",p,rsp_txt);
264   if(resp != 2) {
265     mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'PASV' failed: %s\n",rsp_txt);
266     return 0;
267   }
268 
269   par = strchr(rsp_txt,'(');
270 
271   if(!par || !par[0] || !par[1]) {
272     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] invalid server response: %s ??\n",rsp_txt);
273     return 0;
274   }
275 
276   sscanf(par+1,"%u,%u,%u,%u,%u,%u",&num[0],&num[1],&num[2],
277 	 &num[3],&num[4],&num[5]);
278   snprintf(str,sizeof(str),"%d.%d.%d.%d",num[0],num[1],num[2],num[3]);
279   fd = connect2Server(str,(num[4]<<8)+num[5],0);
280 
281   if(fd < 0)
282     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] failed to create data connection\n");
283 
284   return fd;
285 }
286 
FtpOpenData(stream_t * s,int64_t newpos)287 static int FtpOpenData(stream_t* s, int64_t newpos) {
288   struct stream_priv_s* p = s->priv;
289   int resp;
290   char rsp_txt[256];
291 
292   // Open a new connection
293   s->fd = FtpOpenPort(p);
294 
295   if(s->fd < 0) return 0;
296 
297   if(newpos > 0) {
298     snprintf(p->cmd_buf,CMD_BUFSIZE,"REST %"PRIu64, newpos);
299 
300     resp = FtpSendCmd(p->cmd_buf,p,rsp_txt);
301     if(resp != 3) {
302       mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",p->cmd_buf,rsp_txt);
303       newpos = 0;
304     }
305   }
306 
307   // Get the file
308   snprintf(p->cmd_buf,CMD_BUFSIZE,"RETR %s",p->filename);
309   resp = FtpSendCmd(p->cmd_buf,p,rsp_txt);
310 
311   if(resp != 1) {
312     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",p->cmd_buf,rsp_txt);
313     return 0;
314   }
315 
316   s->pos = newpos;
317   return 1;
318 }
319 
fill_buffer(stream_t * s,char * buffer,int max_len)320 static int fill_buffer(stream_t *s, char* buffer, int max_len){
321   int r;
322 
323   if(s->fd < 0 && !FtpOpenData(s,s->pos))
324     return -1;
325 
326   if(!fd_can_read(s->fd, 15)) {
327     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n");
328     return -1;
329   }
330 
331   r = recv(s->fd,buffer,max_len,0);
332   return (r <= 0) ? -1 : r;
333 }
334 
seek(stream_t * s,int64_t newpos)335 static int seek(stream_t *s, int64_t newpos) {
336   struct stream_priv_s* p = s->priv;
337   int resp;
338   char rsp_txt[256];
339 
340   if(s->pos > s->end_pos) {
341     s->eof=1;
342     return 0;
343   }
344 
345   // Check to see if the server did not already terminate the transfer
346   if(fd_can_read(p->handle, 0)) {
347     if(readresp(p,rsp_txt) != 2)
348       mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] Warning the server didn't finished the transfer correctly: %s\n",rsp_txt);
349     closesocket(s->fd);
350     s->fd = -1;
351   }
352 
353   // Close current download
354   if(s->fd >= 0) {
355     static const char pre_cmd[]={TELNET_IAC,TELNET_IP,TELNET_IAC,TELNET_SYNCH};
356     //int fl;
357 
358     // First close the fd
359     closesocket(s->fd);
360     s->fd = -1;
361 
362     // Send send the telnet sequence needed to make the server react
363 
364     // Dunno if this is really needed, lftp have it. I let
365     // it here in case it turn out to be needed on some other OS
366     //fl=fcntl(p->handle,F_GETFL);
367     //fcntl(p->handle,F_SETFL,fl&~O_NONBLOCK);
368 
369     // send only first byte as OOB due to OOB braindamage in many unices
370     send(p->handle,pre_cmd,1,MSG_OOB|DEFAULT_SEND_FLAGS);
371     send(p->handle,pre_cmd+1,sizeof(pre_cmd)-1,DEFAULT_SEND_FLAGS);
372 
373     //fcntl(p->handle,F_SETFL,fl);
374 
375     // Get the 426 Transfer aborted
376     // Or the 226 Transfer complete
377     resp = readresp(p,rsp_txt);
378     if(resp != 4 && resp != 2) {
379       mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Server didn't abort correctly: %s\n",rsp_txt);
380       s->eof = 1;
381       return 0;
382     }
383     // Send the ABOR command
384     // Ignore the return code as sometimes it fail with "nothing to abort"
385     FtpSendCmd("ABOR",p,rsp_txt);
386   }
387   return FtpOpenData(s,newpos);
388 }
389 
390 
close_f(stream_t * s)391 static void close_f(stream_t *s) {
392   struct stream_priv_s* p = s->priv;
393 
394   if(!p) return;
395 
396   if(s->fd >= 0) {
397     closesocket(s->fd);
398     s->fd = -1;
399   }
400 
401   if (p->handle >= 0) {
402     FtpSendCmd("QUIT", p, NULL);
403     closesocket(p->handle);
404   }
405 
406   free(p->buf);
407   free(p->cmd_buf);
408 
409   m_struct_free(&stream_opts,p);
410 }
411 
412 
413 
open_f(stream_t * stream,int mode,void * opts,av_unused int * file_format)414 static int open_f(stream_t *stream,int mode, void* opts, av_unused int* file_format) {
415   int resp;
416   int64_t len = 0;
417   struct stream_priv_s* p = opts;
418   char rsp_txt[256];
419 
420   if(mode != STREAM_READ) {
421     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Unknown open mode %d\n",mode);
422     m_struct_free(&stream_opts,opts);
423     return STREAM_UNSUPPORTED;
424   }
425 
426   if(!p->filename || !p->host) {
427     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Bad url\n");
428     m_struct_free(&stream_opts,opts);
429     return STREAM_ERROR;
430   }
431 
432   // Allocate buffers
433   p->buf = malloc(BUFSIZE);
434   p->cmd_buf = malloc(CMD_BUFSIZE);
435 
436   if (!p->buf || !p->cmd_buf) {
437     close_f(stream);
438     m_struct_free(&stream_opts,opts);
439     return STREAM_ERROR;
440   }
441 
442   // Open the control connection
443   p->handle = connect2Server(p->host,p->port,1);
444 
445   if(p->handle < 0) {
446     m_struct_free(&stream_opts,opts);
447     return STREAM_ERROR;
448   }
449 
450   // We got a connection, let's start serious things
451   stream->fd = -1;
452   stream->priv = p;
453 
454   if (readresp(p, NULL) == 0) {
455     close_f(stream);
456     m_struct_free(&stream_opts,opts);
457     return STREAM_ERROR;
458   }
459 
460   // Login
461   snprintf(p->cmd_buf,CMD_BUFSIZE,"USER %s",p->user);
462   resp = FtpSendCmd(p->cmd_buf,p,rsp_txt);
463 
464   // password needed
465   if(resp == 3) {
466     snprintf(p->cmd_buf,CMD_BUFSIZE,"PASS %s",p->pass);
467     resp = FtpSendCmd(p->cmd_buf,p,rsp_txt);
468     if(resp != 2) {
469       mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",p->cmd_buf,rsp_txt);
470       close_f(stream);
471       return STREAM_ERROR;
472     }
473   } else if(resp != 2) {
474     mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",p->cmd_buf,rsp_txt);
475     close_f(stream);
476     return STREAM_ERROR;
477   }
478 
479   // Set the transfer type
480   resp = FtpSendCmd("TYPE I",p,rsp_txt);
481   if(resp != 2) {
482     mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'TYPE I' failed: %s\n",rsp_txt);
483     close_f(stream);
484     return STREAM_ERROR;
485   }
486 
487   // Get the filesize
488   snprintf(p->cmd_buf,CMD_BUFSIZE,"SIZE %s",p->filename);
489   resp = FtpSendCmd(p->cmd_buf,p,rsp_txt);
490   if(resp != 2) {
491     mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",p->cmd_buf,rsp_txt);
492   } else {
493     int dummy;
494     sscanf(rsp_txt,"%d %"SCNd64,&dummy,&len);
495   }
496 
497   if(len > 0) {
498     stream->seek = seek;
499     stream->end_pos = len;
500   }
501 
502   // The data connection is really opened only at the first
503   // read/seek. This must be done when the cache is used
504   // because the connection would stay open in the main process,
505   // preventing correct abort with many servers.
506   stream->fd = -1;
507   stream->priv = p;
508   stream->fill_buffer = fill_buffer;
509   stream->close = close_f;
510   stream->type = STREAMTYPE_STREAM;
511 
512   return STREAM_OK;
513 }
514 
515 const stream_info_t stream_info_ftp = {
516   "File Transfer Protocol",
517   "ftp",
518   "Albeu",
519   "reuse a bit of code from ftplib written by Thomas Pfau",
520   open_f,
521   { "ftp", NULL },
522   &stream_opts,
523   1 // Urls are an option string
524 };
525