comparison libmpdemux/stream_ftp.c @ 10625:620cc649f519

ftp support. The change on connect2Server is needed bcs we need 2 different level of verbosity for control and data connections.
author albeu
date Fri, 15 Aug 2003 19:13:23 +0000
parents
children f7df841c8b74
comparison
equal deleted inserted replaced
10624:cdfd4a43c406 10625:620cc649f519
1
2 #include "config.h"
3
4 #ifdef HAVE_FTP
5
6 #include <stdlib.h>
7 #include <stdio.h>
8
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <errno.h>
14
15 #include "mp_msg.h"
16 #include "stream.h"
17 #include "help_mp.h"
18 #include "../m_option.h"
19 #include "../m_struct.h"
20
21 static struct stream_priv_s {
22 char* user;
23 char* pass;
24 char* host;
25 int port;
26 char* filename;
27
28 char *cput,*cget;
29 int handle;
30 int cavail,cleft;
31 char *buf;
32 } stream_priv_dflts = {
33 "anonymous","no@spam",
34 NULL,
35 21,
36 NULL,
37 NULL,
38 NULL,
39
40 0,
41 0,0,
42 NULL
43 };
44
45 #define BUFSIZE 2048
46
47 #define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
48 /// URL definition
49 static m_option_t stream_opts_fields[] = {
50 {"username", ST_OFF(user), CONF_TYPE_STRING, 0, 0 ,0, NULL},
51 {"password", ST_OFF(pass), CONF_TYPE_STRING, 0, 0 ,0, NULL},
52 {"hostname", ST_OFF(host), CONF_TYPE_STRING, 0, 0 ,0, NULL},
53 {"port", ST_OFF(port), CONF_TYPE_INT, 0, 0 ,65635, NULL},
54 {"filename", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL},
55 { NULL, NULL, 0, 0, 0, 0, NULL }
56 };
57 static struct m_struct_st stream_opts = {
58 "ftp",
59 sizeof(struct stream_priv_s),
60 &stream_priv_dflts,
61 stream_opts_fields
62 };
63
64 #define TELNET_IAC 255 /* interpret as command: */
65 #define TELNET_IP 244 /* interrupt process--permanently */
66 #define TELNET_SYNCH 242 /* for telfunc calls */
67
68 /*
69 * read a line of text
70 *
71 * return -1 on error or bytecount
72 */
73 static int readline(char *buf,int max,struct stream_priv_s *ctl)
74 {
75 int x,retval = 0;
76 char *end,*bp=buf;
77 int eof = 0;
78
79 do {
80 if (ctl->cavail > 0) {
81 x = (max >= ctl->cavail) ? ctl->cavail : max-1;
82 end = memccpy(bp,ctl->cget,'\n',x);
83 if (end != NULL)
84 x = end - bp;
85 retval += x;
86 bp += x;
87 *bp = '\0';
88 max -= x;
89 ctl->cget += x;
90 ctl->cavail -= x;
91 if (end != NULL) {
92 bp -= 2;
93 if (strcmp(bp,"\r\n") == 0) {
94 *bp++ = '\n';
95 *bp++ = '\0';
96 --retval;
97 }
98 break;
99 }
100 }
101 if (max == 1) {
102 *buf = '\0';
103 break;
104 }
105 if (ctl->cput == ctl->cget) {
106 ctl->cput = ctl->cget = ctl->buf;
107 ctl->cavail = 0;
108 ctl->cleft = BUFSIZE;
109 }
110 if(eof) {
111 if (retval == 0)
112 retval = -1;
113 break;
114 }
115 if ((x = recv(ctl->handle,ctl->cput,ctl->cleft,0)) == -1) {
116 mp_msg(MSGT_STREAM,MSGL_ERR, "[ftp] read error: %s\n",strerror(errno));
117 retval = -1;
118 break;
119 }
120 if (x == 0)
121 eof = 1;
122 ctl->cleft -= x;
123 ctl->cavail += x;
124 ctl->cput += x;
125 } while (1);
126
127 return retval;
128 }
129
130 /*
131 * read a response from the server
132 *
133 * return 0 if first char doesn't match
134 * return 1 if first char matches
135 */
136 static int readresp(struct stream_priv_s* ctl,char* rsp)
137 {
138 static char response[256];
139 char match[5];
140 int r;
141
142 if (readline(response,256,ctl) == -1)
143 return 0;
144
145 r = atoi(response)/100;
146 if(rsp) strcpy(rsp,response);
147
148 mp_msg(MSGT_STREAM,MSGL_V, "[ftp] < %s",response);
149
150 if (response[3] == '-') {
151 strncpy(match,response,3);
152 match[3] = ' ';
153 match[4] = '\0';
154 do {
155 if (readline(response,256,ctl) == -1) {
156 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Control socket read failed\n");
157 return 0;
158 }
159 mp_msg(MSGT_OPEN,MSGL_V, "[ftp] < %s",response);
160 } while (strncmp(response,match,4));
161 }
162 return r;
163 }
164
165
166 static int FtpSendCmd(const char *cmd, struct stream_priv_s *nControl,char* rsp)
167 {
168 int l = strlen(cmd);
169
170 mp_msg(MSGT_STREAM,MSGL_V, "[ftp] > %s",cmd);
171 while(l > 0) {
172 int s = send(nControl->handle,cmd,l,0);
173
174 if(s <= 0) {
175 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] write error: %s\n",strerror(errno));
176 return 0;
177 }
178
179 cmd += s;
180 l -= s;
181 }
182
183 return readresp(nControl,rsp);
184 }
185
186 static int FtpOpenPort(struct stream_priv_s* p) {
187 int resp,fd;
188 char rsp_txt[256];
189 char* par,str[128];
190 int num[6];
191
192 resp = FtpSendCmd("PASV\r\n",p,rsp_txt);
193 if(resp != 2) {
194 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'PASV' failed: %s\n",rsp_txt);
195 return 0;
196 }
197
198 par = strchr(rsp_txt,'(');
199
200 if(!par || !par[0] || !par[1]) {
201 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] invalid server response: %s ??\n",rsp_txt);
202 return 0;
203 }
204
205 sscanf(par+1,"%u,%u,%u,%u,%u,%u",&num[0],&num[1],&num[2],
206 &num[3],&num[4],&num[5]);
207 snprintf(str,127,"%d.%d.%d.%d",num[0],num[1],num[2],num[3]);
208 fd = connect2Server(str,(num[4]<<8)+num[5],0);
209
210 if(fd <= 0) {
211 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] failed to create data connection\n");
212 return 0;
213 }
214
215 return fd;
216 }
217
218 static int fill_buffer(stream_t *s, char* buffer, int max_len){
219 fd_set fds;
220 struct timeval tv;
221 int r;
222
223 // Check if there is something to read. This avoid hang
224 // forever if the network stop responding.
225 FD_ZERO(&fds);
226 FD_SET(s->fd,&fds);
227 tv.tv_sec = 15;
228 tv.tv_usec = 0;
229
230 if(select(s->fd+1, &fds, NULL, NULL, &tv) < 1) {
231 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n");
232 return -1;
233 }
234
235 r = recv(s->fd,buffer,max_len,0);
236 return (r <= 0) ? -1 : r;
237 }
238
239 static int seek(stream_t *s,off_t newpos) {
240 struct stream_priv_s* p = s->priv;
241 int resp;
242 char str[256],rsp_txt[256];
243 fd_set fds;
244 struct timeval tv;
245
246 if(s->pos > s->end_pos) {
247 s->eof=1;
248 return 0;
249 }
250
251 FD_ZERO(&fds);
252 FD_SET(p->handle,&fds);
253 tv.tv_sec = tv.tv_usec = 0;
254
255 // Check to see if the server doesn't alredy terminated the transfert
256 if(select(p->handle+1, &fds, NULL, NULL, &tv) > 0) {
257 if(readresp(p,rsp_txt) != 2)
258 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] Warning the server didn't finished the transfert correctly: %s\n",rsp_txt);
259 close(s->fd);
260 s->fd = -1;
261 }
262
263 // Close current download
264 if(s->fd >= 0) {
265 static const char pre_cmd[]={TELNET_IAC,TELNET_IP,TELNET_IAC,TELNET_SYNCH};
266 //int fl;
267
268 // First close the fd
269 close(s->fd);
270 s->fd = 0;
271
272 // Send send the telnet sequence needed to make the server react
273
274 // Dunno if this is really needed, lftp have it. I let
275 // it here in case it turn out to be needed on some other OS
276 //fl=fcntl(p->handle,F_GETFL);
277 //fcntl(p->handle,F_SETFL,fl&~O_NONBLOCK);
278
279 // send only first byte as OOB due to OOB braindamage in many unices
280 send(p->handle,pre_cmd,1,MSG_OOB);
281 send(p->handle,pre_cmd+1,sizeof(pre_cmd)-1,0);
282
283 //fcntl(p->handle,F_SETFL,fl);
284
285 // Get the 426 Transfer aborted
286 // Or the 226 Transfer complete
287 resp = readresp(p,rsp_txt);
288 if(resp != 4 && resp != 2) {
289 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Server didn't abort correctly: %s\n",rsp_txt);
290 s->eof = 1;
291 return 0;
292 }
293 // Send the ABOR command
294 // Ignore the return code as sometimes it fail with "nothing to abort"
295 FtpSendCmd("ABOR\n\r",p,rsp_txt);
296 }
297
298 // Open a new connection
299 s->fd = FtpOpenPort(p);
300
301 if(!s->fd) return 0;
302
303 if(newpos > 0) {
304 #ifdef _LARGEFILE_SOURCE
305 snprintf(str,255,"REST %lld\r\n",newpos);
306 #else
307 snprintf(str,255,"REST %u\r\n",newpos);
308 #endif
309
310 resp = FtpSendCmd(str,p,rsp_txt);
311 if(resp != 3) {
312 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
313 newpos = 0;
314 }
315 }
316
317 // Get the file
318 snprintf(str,255,"RETR %s\r\n",p->filename);
319 resp = FtpSendCmd(str,p,rsp_txt);
320
321 if(resp != 1) {
322 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
323 return 0;
324 }
325
326 s->pos = newpos;
327 return 1;
328 }
329
330
331 static void close_f(stream_t *s) {
332 struct stream_priv_s* p = s->priv;
333
334 if(!p) return;
335
336 if(s->fd > 0) {
337 close(s->fd);
338 s->fd = 0;
339 }
340
341 FtpSendCmd("QUIT\r\n",p,NULL);
342
343 if(p->handle) close(p->handle);
344 if(p->buf) free(p->buf);
345
346 m_struct_free(&stream_opts,p);
347 }
348
349
350
351 static int open_f(stream_t *stream,int mode, void* opts, int* file_format) {
352 int len = 0,resp;
353 struct stream_priv_s* p = (struct stream_priv_s*)opts;
354 char str[256],rsp_txt[256];
355
356 if(mode != STREAM_READ) {
357 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Unknown open mode %d\n",mode);
358 m_struct_free(&stream_opts,opts);
359 return STREAM_UNSUPORTED;
360 }
361
362 if(!p->filename || !p->host) {
363 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Bad url\n");
364 m_struct_free(&stream_opts,opts);
365 return STREAM_ERROR;
366 }
367
368 // Open the control connection
369 p->handle = connect2Server(p->host,p->port,1);
370
371 if(p->handle < 0) {
372 m_struct_free(&stream_opts,opts);
373 return STREAM_ERROR;
374 }
375
376 // We got a connection, let's start serious things
377 stream->fd = -1;
378 stream->priv = p;
379 p->buf = malloc(BUFSIZE);
380
381 if (readresp(p, NULL) == 0) {
382 close_f(stream);
383 m_struct_free(&stream_opts,opts);
384 return STREAM_ERROR;
385 }
386
387 // Login
388 snprintf(str,255,"USER %s\r\n",p->user);
389 resp = FtpSendCmd(str,p,rsp_txt);
390
391 // password needed
392 if(resp == 3) {
393 snprintf(str,255,"PASS %s\r\n",p->pass);
394 resp = FtpSendCmd(str,p,rsp_txt);
395 if(resp != 2) {
396 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
397 close_f(stream);
398 return STREAM_ERROR;
399 }
400 } else if(resp != 2) {
401 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
402 close_f(stream);
403 return STREAM_ERROR;
404 }
405
406 // Set the transfert type
407 resp = FtpSendCmd("TYPE I\r\n",p,rsp_txt);
408 if(resp != 2) {
409 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'TYPE I' failed: %s\n",rsp_txt);
410 close_f(stream);
411 return STREAM_ERROR;
412 }
413
414 // Get the filesize
415 snprintf(str,255,"SIZE %s\r\n",p->filename);
416 resp = FtpSendCmd(str,p,rsp_txt);
417 if(resp != 2) {
418 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
419 } else {
420 int dummy;
421 sscanf(rsp_txt,"%d %d",&dummy,&len);
422 }
423
424 // Start the data connection
425 stream->fd = FtpOpenPort(p);
426 if(stream->fd <= 0) {
427 close_f(stream);
428 return STREAM_ERROR;
429 }
430
431 // Get the file
432 snprintf(str,255,"RETR %s\r\n",p->filename);
433 resp = FtpSendCmd(str,p,rsp_txt);
434
435 if(resp != 1) {
436 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
437 close_f(stream);
438 return STREAM_ERROR;
439 }
440
441
442 if(len > 0) {
443 stream->seek = seek;
444 stream->end_pos = len;
445 }
446
447 stream->priv = p;
448 stream->fill_buffer = fill_buffer;
449 stream->close = close_f;
450
451 return STREAM_OK;
452 }
453
454 stream_info_t stream_info_ftp = {
455 "File Transfer Protocol",
456 "ftp",
457 "Albeu",
458 "reuse a bit of code from ftplib written by Thomas Pfau",
459 open_f,
460 { "ftp", NULL },
461 &stream_opts,
462 1 // Urls are an option string
463 };
464
465 #endif