http://prdownloads.sourceforge.net/lufs/lufs-0.9.7.tar.gz?download
[lufs.git] / filesystems / ftpfs / ftplib.cpp
1 /*
2  * ftplib.cpp
3  * Copyright (C) 2002 Florin Malita <mali@go.ro>
4  *
5  * This file is part of LUFS, a free userspace filesystem implementation.
6  * See http://lufs.sourceforge.net/ for updates.
7  *
8  * LUFS is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * LUFS is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include <unistd.h>
24 #include <netdb.h>
25 #include <stdio.h>
26 #include <errno.h>
27
28 #include <sys/types.h>
29 #include <sys/socket.h>
30
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33
34 #include <lufs/fs.h>
35
36 #include <string>
37
38 #include "ftplib.h"
39
40
41 FTPConnection::FTPConnection(int act, char *h, unsigned short p, char *u, char *pw){
42     TRACE("in constructor");
43
44     host = string(h);
45     port = p;
46     user = string(u);
47     pass = string(pw);
48     last_cmd = string("");
49     active = act;
50     csock = dsock = 0;
51     cfd = dfd = NULL;
52 }
53
54 FTPConnection::~FTPConnection(){
55     TRACE("in destructor");
56     disconnect();
57 }
58
59 void
60 FTPConnection::disconnect(){
61     if(dfd)
62         fclose(dfd);
63     if(dsock)
64         close(dsock);
65     if(cfd)
66         fclose(cfd);
67     if(csock)
68         close(csock);
69
70     csock = dsock = 0;
71     cfd = dfd = 0;
72 }
73
74 int
75 FTPConnection::connect(){
76     struct hostent *hst;
77     struct sockaddr_in addr;
78     int res, i;
79     
80     disconnect();
81
82     if(!(hst = gethostbyname(host.c_str()))){
83         ERROR("could not resolve host " << host);
84         return -1;
85     }
86
87     if((csock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
88         ERROR("socket call failed!");
89         return -1;
90     }
91
92
93     memset(&addr, 0, sizeof(struct sockaddr_in));
94     addr.sin_family = AF_INET;
95     addr.sin_port = htons(port);
96     
97     for(i = 0; hst->h_addr_list[i]; i++){
98         memcpy(&addr.sin_addr.s_addr, hst->h_addr_list[i], 4);
99
100         TRACE("trying to connect to "<<inet_ntoa(*(struct in_addr*)hst->h_addr_list[i])<<"...");
101         if(!::connect(csock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))){
102             TRACE("connect succeded...");
103             break;
104         }
105     }
106     
107     if(! hst->h_addr_list[i]){
108         ERROR("could not connect to server!");
109         goto error;
110     }
111     
112     if(!(cfd = fdopen(csock, "r+"))){
113         WARN("could not create filestream!");
114         goto error;
115     }
116
117     if(get_response() != 220){
118         WARN("protocol error!");
119         goto error;
120     }
121
122     if(execute(string("USER ") + user, 0, 0) < 0){
123         WARN("USER failed!");
124         goto error;
125     }
126
127     res = get_response();
128
129     if((res < 0) || ((res != 331) && (res != 230))){
130         WARN("invalid user!");
131         goto error;
132     }
133
134     if((res == 331) && (execute(string("PASS ") + pass, 230, 0) < 0)){
135         WARN("login failed!");
136         goto error;
137     }
138
139     if((res = execute(string("SYST"), 0, 0)) < 0){
140         WARN("SYST command failed!");
141         goto error;
142     }
143
144     if(! fgets(buf, FTP_MAXLINE, cfd)){
145         WARN("no response to SYST!");
146         goto error;
147     }
148
149     if((sscanf(buf, "%u %32s", &res, system) != 2) || (res != 215)){
150         WARN("bad response to SYST!");
151         goto error;
152     }
153     
154     
155
156     TRACE("logged in. system type is " << system << ".");
157
158     return 0;
159
160   error:
161     disconnect();
162     return -1;
163 }
164
165 int
166 FTPConnection::get_response(){
167     int res = 0;
168     unsigned multiline = 0;
169
170     if(!cfd)
171         return -1;
172
173     if(!fgets(buf, FTP_MAXLINE, cfd)){
174         WARN("broken control socket!");
175         return -1;
176     }
177     
178     TRACE("line: " << buf);
179
180     if(buf[3] == '-'){
181         if(!sscanf(buf, "%u-", &multiline)){
182             WARN("bad response!");
183             return -1;
184         }
185         TRACE("multiline: " << multiline);
186     }
187     
188     if(multiline)
189         do {
190             if(!fgets(buf, FTP_MAXLINE, cfd)){
191                 WARN("broken control socket!");
192                 return -1;
193             }
194
195             TRACE("line: " << buf);
196
197             if(buf[3] == ' ')
198                 sscanf(buf, "%u ", &res);
199         }while((unsigned)res != multiline);
200
201     else if(!sscanf(buf, "%u", &res)){
202             WARN("bad response!");
203             return -1;
204         }
205                 
206     return res;    
207 }
208
209 int
210 FTPConnection::close_data(){
211     if(dfd){
212         fclose(dfd);
213         dfd = NULL;
214     }
215
216     if(dsock){
217         close(dsock);
218         dsock = 0;
219         return get_response();
220     }
221
222     return 0;
223 }
224
225 int
226 FTPConnection::execute_retry(string cmd, int r, int reconnect){
227     int res, tries = 0;
228
229     do {
230         res = execute(cmd, r, reconnect);
231     }while((res == -EAGAIN) && (tries++ < FTP_MAXTRIES));
232
233     return res;
234 }
235
236 int
237 FTPConnection::execute(string cmd, int r, int reconnect){
238     int res;
239
240     close_data();
241
242     if(cfd == NULL){
243         if((!reconnect) || (connect() < 0)){
244             WARN("could not connect!");
245             return -1;
246         }
247     }
248     TRACE("executing " << cmd);
249
250     cmd += "\r\n";
251
252     if((res = fwrite(cmd.c_str(), 1, cmd.size(), cfd)) != (int)cmd.size() || (fflush(cfd))){
253         WARN("write error!");
254         if((!reconnect) || ((res = connect()) < 0)){
255             WARN("could not reconnect!");
256             return res;
257         }
258     }
259
260     if(r){
261         if((res = get_response()) != r){
262             WARN("command failed!");
263             if((reconnect) && ((res < 0) || (res == 421))){
264                 if((res = connect()) < 0)
265                     return res;
266                 else
267                     return -EAGAIN;
268             }else
269                 return -1;
270         }
271     }
272     
273     return 0;
274 }
275
276 int 
277 FTPConnection::execute_open(string cmd, string type, long long offset){
278     
279     if((!csock) || (!cfd)){
280         TRACE("control socket not connected.");
281         disconnect();
282         if(connect() < 0)
283             return -1;
284     }
285
286     if(active)
287         return execute_open_active(cmd, type, offset);
288     else
289         return execute_open_passive(cmd, type, offset);
290 }
291
292 int 
293 FTPConnection::execute_open_active(string cmd, string type, long long offset){
294     struct sockaddr_in addr, ctrl;
295     int res, ssock, tries = 0;
296     
297
298     if((dsock == 0) || (dfd == NULL) || (offset != last_off) || (cmd != last_cmd)){
299
300       again:
301         
302         if(tries++ > FTP_MAXTRIES){
303             WARN("too many failures!");
304             return -1;
305         }
306
307         close_data();
308         memset(&addr, 0, sizeof(struct sockaddr_in));
309         addr.sin_addr.s_addr = INADDR_ANY;
310         addr.sin_port = 0;
311         addr.sin_family = AF_INET;
312
313         if((ssock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
314             WARN("socket call failed!");
315             return ssock;
316         }
317
318         if((res = bind(ssock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) < 0){
319             WARN("bind call failed!");
320             close(ssock);
321             return res;
322         }
323
324         if((res = listen(ssock, 2)) < 0){
325             WARN("listen call failed!");
326             close(ssock);
327             return res;
328         }
329
330         res = sizeof(struct sockaddr_in);
331         if((res = getsockname(csock, (struct sockaddr*)&ctrl, (socklen_t*)&res)) < 0){
332             WARN("getsockname call failed!");
333             close(ssock);
334             return res;
335         }
336
337         res = sizeof(struct sockaddr_in);
338         if((res = getsockname(ssock, (struct sockaddr*)&addr, (socklen_t*)&res)) < 0){
339             WARN("getsockname call failed!");
340             close(ssock);
341             return res;
342         }
343
344         sprintf(buf, "PORT %u,%u,%u,%u,%u,%u",
345                 ctrl.sin_addr.s_addr & 0xff,
346                 (ctrl.sin_addr.s_addr >> 8) & 0xff,
347                 (ctrl.sin_addr.s_addr >> 16) & 0xff,
348                 (ctrl.sin_addr.s_addr >> 24) & 0xff,
349                 ntohs(addr.sin_port) >> 8,
350                 ntohs(addr.sin_port) & 0xff);
351         TRACE("cmd: " << buf);
352
353         if((res = execute(string(buf), 200, 1)) < 0){
354             close(ssock);
355             if(res == -EAGAIN)
356                 goto again;
357             return res;
358         }
359
360         if((res = execute(string("TYPE ") + type, 200, 1)) < 0){
361             close(ssock);
362             if(res == -EAGAIN)
363                 goto again;
364             return res;
365         }
366
367         sprintf(buf, "REST %llu", offset);
368         if(offset && ((res = execute(string(buf), 350,1)) < 0)){
369             close(ssock);
370             if(res == -EAGAIN)
371                 goto again;
372             return res;
373         }
374         
375         if((res = execute(cmd, 150, 1)) < 0){
376             close(ssock);
377             if(res == -EAGAIN)
378                 goto again;
379             return res;
380         }
381
382         res = sizeof(struct sockaddr_in);
383         if((res = accept(ssock, (struct sockaddr*)&addr, (socklen_t*)&res)) < 0){
384             WARN("accept call failed!");
385             close(ssock);
386             return res;
387         }
388         
389         close(ssock);
390
391         dsock = res;
392         if((dfd = fdopen(dsock, "r+")) == NULL){
393             WARN("fdopen failed!");
394             return -1;
395         }
396
397         last_cmd = cmd;
398         last_off = offset;
399     
400     }else
401         TRACE("keepalive...");
402
403     return 0;
404 }
405
406 int
407 getIP(char *buf, unsigned long *ip, unsigned short *port){
408     int res;
409     unsigned char i0,i1, i2, i3, p0, p1;
410     
411     if((res = sscanf(buf," (%hhu,%hhu,%hhu,%hhu,%hhu,%hhu)", &i0, &i1, &i2, &i3, &p0, &p1)) != 6){
412         WARN("bad format, res=" << res);
413         return -1;
414     }
415
416     TRACE("buf: " << buf);
417     TRACE("(i0,i1,i2,i3,p0,p1)=("<<(int)i0<<","<<(int)i1<<","<<(int)i2<<","<<(int)i3<<","<<(int)p0<<","<<(int)p1<<")");
418
419     *ip = ntohl((unsigned)i0 + (((unsigned)i1) << 8) + (((unsigned)i2) << 16) + (((unsigned)i3) << 24));
420     *port = ntohs((unsigned)p0 + (((unsigned)p1) << 8));
421
422     TRACE("IP: " << *ip << "(" << inet_ntoa(*(struct in_addr*)ip) << ")");
423     TRACE("port: " << *port);
424
425     return 0;
426 }
427
428 int 
429 FTPConnection::execute_open_passive(string cmd, string type, long long offset){
430     int res, tries = 0;
431     unsigned long ip;
432     unsigned short port;
433     struct sockaddr_in addr;
434
435     TRACE("dsock: "<<dsock<<",dfd: "<<dfd<<",last_off: "<<last_off<<",last_cmd: "<<last_cmd);
436
437     if((dsock == 0) || (dfd == NULL) || (offset != last_off) || (last_cmd != cmd)){
438
439         close_data();
440
441       again:
442
443         if(tries++ > FTP_MAXTRIES){
444             WARN("too many failures!");
445             return -1;
446         }
447
448         TRACE("reopening data connection...");
449
450         if((res = execute(string("PASV"), 0, 1)) < 0){
451             WARN("PASV command failed!");
452
453             if(res == -EAGAIN)
454                 goto again;
455             return res;
456         }
457
458         if(!fgets(buf, FTP_MAXLINE, cfd)){
459             WARN("no response!");
460             goto again;
461         }
462
463         if((!sscanf(buf, "%u", &res)) || (res != 227)){
464             WARN("bad response!");
465             goto again;
466         }
467
468         if(getIP(strchr(buf, '('), &ip, &port) < 0){
469             WARN("could not extract ip/port information!");
470             goto again;
471         }
472         
473         if((res = execute(string("TYPE ") + type, 200, 1)) < 0){
474             if(res == -EAGAIN)
475                 goto again;
476             return res;
477         }
478
479         sprintf(buf, "REST %llu", offset);
480         if(offset && (res = execute(string(buf), 350, 1)) < 0){
481             WARN("could not set offset!");
482             if(res == -EAGAIN)
483                 goto again;
484             return res;
485         } 
486         
487         if((res = execute(cmd, 0, 1)) < 0){
488             if(res == -EAGAIN)
489                 goto again;
490             return res;
491         }
492
493         if((dsock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
494             WARN("socket call failed!");
495             return dsock;
496         }
497
498         memset(&addr, 0, sizeof(struct sockaddr_in));
499         addr.sin_family = AF_INET;
500         addr.sin_addr.s_addr = htonl(ip);
501         addr.sin_port = htons(port);
502
503         if(::connect(dsock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0){
504             WARN("could not connect to server!");
505             return -1;
506         }
507
508         if(get_response() != 150){
509             WARN("bad server response!");
510             close(dsock);
511             dsock = 0;
512             return -1;
513         }
514
515         if((dfd = fdopen(dsock, "r+")) == NULL){
516             WARN("fdopen failed!");
517             close_data();
518             return -1;
519         }
520
521         last_cmd = cmd;
522         last_off = offset;
523     }else
524         TRACE("keepalive...");
525
526     return 0;
527 }
528