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