Back to index

avfs  1.0.1
ftp.c
Go to the documentation of this file.
00001 /*
00002     AVFS: A Virtual File System Library
00003     Copyright (C) 2000-2001  Miklos Szeredi <miklos@szeredi.hu>
00004 
00005     This program can be distributed under the terms of the GNU GPL.
00006     See the file COPYING.
00007 */
00008 
00009 /*
00010   TODO:
00011   
00012   if data connection has closed, retry
00013   don't use NOOP (if control connection has closed (421), retry) 
00014   fix absolute symlinks
00015   close unused data connections after timeout
00016   close unused control connections after timeout
00017   limit max connections to a host
00018   if second connection doesn't work wait for first and retry
00019   retrieve restart
00020   seekability
00021   selective proxying
00022 */
00023 
00024 #include "remote.h"
00025 #include "socket.h"
00026 #include "state.h"
00027 #include "filebuf.h"
00028 #include "parsels.h"
00029 
00030 #include <stdio.h>
00031 #include <stdlib.h>
00032 #include <unistd.h>
00033 #include <fcntl.h>
00034 
00035 #define USER_SEP_STR  "@"
00036 #define USER_SEP_CHAR (USER_SEP_STR[0])
00037 
00038 #define FTP_REPLY_TIMEOUT 30000
00039 #define FTP_READ_TIMEOUT 60000
00040 
00041 static AV_LOCK_DECL(ftp_lock);
00042 
00043 struct ftpconn {
00044     char *host;
00045     char *user;
00046     char *password;
00047     int busy;
00048     int sock;
00049     struct filebuf *sockfb;
00050     struct ftpconn *next;
00051     int binary;
00052     char *cwd;
00053 };
00054 
00055 struct ftpsession {
00056     char *account;
00057     char *password;
00058     struct ftpsession *next;
00059     struct ftpsession *prev;
00060 };
00061 
00062 struct ftpdata {
00063     struct ftpconn *conns;
00064     struct ftpsession sessions;
00065 };
00066 
00067 struct ftplocalfile {
00068     int fd;
00069     char *tmpfile;
00070     int sock;
00071     struct filebuf *sockfb;
00072     avoff_t numbytes;
00073     struct ftpconn *conn;
00074 };
00075 
00076 static struct ftpsession *ftp_find_session(struct ftpdata *ftd,
00077                                            const char *account)
00078 {
00079     struct ftpsession *fts;
00080 
00081     for(fts = ftd->sessions.next; fts != &ftd->sessions; fts = fts->next) {
00082         if(strcmp(account, fts->account) == 0)
00083             return fts;
00084     }
00085     
00086     return NULL;
00087 }
00088 
00089 static struct ftpsession *ftp_get_session(struct ftpdata *ftd,
00090                                           const char *account)
00091 {
00092     struct ftpsession *fts;
00093     
00094     fts = ftp_find_session(ftd, account);
00095     if(fts == NULL) {
00096         struct ftpsession *next;
00097         struct ftpsession *prev;
00098 
00099         AV_NEW(fts);
00100         fts->account = av_strdup(account);
00101         fts->password = NULL;
00102 
00103         fts->next = next = ftd->sessions.next;
00104         fts->prev = prev = &ftd->sessions;
00105         next->prev = fts;
00106         prev->next = fts;
00107     }
00108     
00109     return fts;
00110 }
00111 
00112 static void ftp_remove_session(struct ftpsession *fts)
00113 {
00114     struct ftpsession *next = fts->next;
00115     struct ftpsession *prev = fts->prev;
00116     
00117     next->prev = prev;
00118     prev->next = next;
00119 
00120     av_free(fts->account);
00121     av_free(fts->password);
00122     av_free(fts);
00123 }
00124 
00125 static void strip_crlf(char *line)
00126 {
00127     avsize_t len = strlen(line);
00128     
00129     if(len > 0 && line[len-1] == '\n') {
00130         if(len > 1 && line[len-2] == '\r')
00131             line[len-2] = '\0';
00132         else
00133             line[len-1] = '\0';
00134     }
00135 }
00136 
00137 static void ftp_release_conn(struct ftpconn *conn)
00138 {
00139     AV_LOCK(ftp_lock);
00140     conn->busy = 0;
00141     AV_UNLOCK(ftp_lock);
00142 }
00143 
00144 static void ftp_close_conn(struct ftpconn *conn)
00145 {
00146     av_unref_obj(conn->sockfb);
00147     conn->sockfb = NULL;
00148     conn->sock = -1;
00149     conn->binary = -1;
00150     conn->cwd[0] = '\0';
00151 }
00152 
00153 static int ftp_get_line(struct ftpconn *conn, char **linep)
00154 {
00155     int res;
00156     char *line;
00157     
00158     res = av_filebuf_getline(conn->sockfb, &line, FTP_REPLY_TIMEOUT);
00159     if(res <= 0 || line == NULL) {
00160         ftp_close_conn(conn);
00161         if(res < 0)
00162             return res;
00163 
00164         if(res == 0)
00165             av_log(AVLOG_ERROR, "FTP: timeout waiting for reply");
00166         else
00167             av_log(AVLOG_ERROR, "FTP: server closed ftpconn");
00168 
00169         return -EIO;
00170     }
00171 
00172     strip_crlf(line);
00173     *linep = line;
00174 
00175     return 0;
00176 }
00177 
00178 
00179 static int ftp_check_reply(struct ftpconn *conn, const char *line)
00180 {
00181     int reply;
00182 
00183     if(strlen(line) < 4 || !isdigit((int) line[0]) ||
00184        !isdigit((int) line[1]) || !isdigit((int) line[2]) ||
00185        (line[3] != ' ' && line[3] != '-')) {
00186         ftp_close_conn(conn);
00187         av_log(AVLOG_ERROR, "FTP: malformed reply: %s", line);
00188         return -EIO;
00189     }
00190 
00191     reply = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0');
00192 
00193     if(reply == 421)
00194         ftp_close_conn(conn);
00195 
00196     return reply;
00197 }
00198 
00199 static int ftp_wait_reply_code(struct ftpconn *conn)
00200 {
00201     int res;
00202     char *line;
00203     char replystr[4];
00204     int firstline = 1;
00205     int cont;
00206     int replycode = 0;
00207     
00208     do {
00209         cont = 0;
00210         res = ftp_get_line(conn, &line);
00211         if(res < 0)
00212             return res;
00213 
00214         if(firstline) {
00215             res = ftp_check_reply(conn, line);
00216             if(res < 0) {
00217                 av_free(line);
00218                 return res;
00219             }
00220 
00221             replycode = res;
00222 
00223             if(line[3] == '-') {
00224                 strncpy(replystr, line, 3);
00225                 replystr[3] = ' ';
00226                 firstline = 0;
00227                 cont = 1;
00228             }
00229         }
00230         else if(strncmp(line, replystr, 4) != 0)
00231             cont = 1;
00232 
00233         if(replycode >= 400)
00234             av_log(AVLOG_ERROR, "FTP: %s", line);
00235         else
00236             av_log(AVLOG_DEBUG, "FTP: %s", line);
00237 
00238         av_free(line);
00239     } while(cont);
00240  
00241     return replycode;
00242 }
00243 
00244 
00245 static int write_socket(int sock, const char *buf, avsize_t buflen)
00246 {
00247     int res;
00248 
00249     while(buflen > 0) {
00250         res = write(sock, buf, buflen);
00251         if(res == -1)
00252             return -errno;
00253         
00254         buf += res;
00255         buflen -= res;
00256     }
00257 
00258     return 0;
00259 }
00260 
00261 static int ftp_write_command(struct ftpconn *conn, const char *cmd)
00262 {
00263     char *line;
00264     int res;
00265 
00266     if(strncmp(cmd, "PASS ", 5) == 0)
00267         av_log(AVLOG_DEBUG, "FTP: PASS *");
00268     else
00269         av_log(AVLOG_DEBUG, "FTP: %s", cmd);
00270 
00271     line = av_stradd(NULL, cmd, "\r\n", NULL);
00272     res = write_socket(conn->sock, line, strlen(line));
00273     av_free(line);
00274 
00275     return res;
00276 }
00277 
00278 static int ftp_command(struct ftpconn *conn, const char *cmd)
00279 {
00280     int res;
00281 
00282     res = ftp_write_command(conn, cmd);
00283     if(res < 0)
00284         return res;
00285 
00286     res = ftp_wait_reply_code(conn);
00287     
00288     return res;
00289 }
00290 
00291 
00292 static int ftp_get_addrbytes(const char *line, int addrbytes[6])
00293 {
00294     int i;
00295     int j;
00296     int val;
00297     const char *s;
00298 
00299     for(s = line; *s && *s != '('; s++);
00300     if(!*s)
00301         return -1;
00302 
00303     s++;
00304     for(i = 0; i < 6; i++) {
00305         val = 0;
00306         for(j = 0; j < 3; j++) {
00307             if(!isdigit((int) *s))
00308                 return -1;
00309             val = val * 10 + (*s - '0');
00310             s++;
00311             if(*s == ',' || *s == ')')
00312                 break;
00313         }
00314         if(*s != ',' && *s != ')')
00315             return -1;
00316         addrbytes[i] = val;
00317         if(*s == ')')
00318             break;
00319         s++;        
00320     }
00321     if(i != 5 || *s != ')')
00322         return -1;
00323 
00324     return 0;
00325 }
00326 
00327 static int ftp_check_passv_reply(struct ftpconn *conn, const char *line,
00328                                  char **resp)
00329 {
00330     int res;
00331     int replycode;
00332     int addrbytes[6];
00333     char addrbuf[128];
00334     int port;
00335     
00336     res = ftp_check_reply(conn, line);
00337     if(res < 0)
00338         return res;
00339 
00340     replycode = res;
00341     
00342     if(replycode != 227) {
00343         av_log(AVLOG_ERROR, "FTP: %s", line);
00344         ftp_close_conn(conn);
00345         return -EIO;
00346     }
00347     
00348     av_log(AVLOG_DEBUG, "FTP: %s", line);
00349         
00350     if(line[3] != ' ') {
00351         av_log(AVLOG_ERROR, "FTP: Multiline reply to PASV: %s", line);
00352         ftp_close_conn(conn);
00353         return -EIO;
00354     }
00355 
00356     res = ftp_get_addrbytes(line, addrbytes);
00357     if(res < 0) {
00358         av_log(AVLOG_ERROR, "FTP: Bad reply to PASV: %s", line);
00359         ftp_close_conn(conn);
00360         return -EIO;
00361     }
00362 
00363     port = addrbytes[4] * 0x100 + addrbytes[5];
00364     sprintf(addrbuf, "%i.%i.%i.%i:%i", 
00365             addrbytes[0], addrbytes[1], addrbytes[2], addrbytes[3], port);
00366 
00367     *resp = av_strdup(addrbuf);
00368 
00369     return 0;
00370 }
00371 
00372 static int ftp_open_dataconn(struct ftpconn *conn)
00373 {
00374     int res;
00375     char *line;
00376     char *host;
00377 
00378     res = ftp_write_command(conn, "PASV");
00379     if(res < 0)
00380         return res;
00381 
00382     res = ftp_get_line(conn, &line);
00383     if(res < 0)
00384         return res;
00385 
00386     res = ftp_check_passv_reply(conn, line, &host);
00387     av_free(line);
00388     if(res < 0)
00389         return res;
00390 
00391     av_log(AVLOG_DEBUG,"FTP: remote data address: %s", host);
00392     
00393     res = av_sock_connect(host, -1);
00394     if(res >= 0)
00395        av_registerfd(res);
00396 
00397     av_free(host);
00398     
00399     return res;
00400 }
00401 
00402 static int ftp_login(struct ftpconn *conn)
00403 {
00404     int res;
00405     char *cmd;
00406 
00407     cmd = av_stradd(NULL, "USER ", conn->user, NULL);
00408     res = ftp_command(conn, cmd);
00409     av_free(cmd);
00410 
00411     if(res == 331) {
00412         cmd = av_stradd(NULL, "PASS ", conn->password, NULL);
00413         res = ftp_command(conn, cmd);
00414         av_free(cmd);
00415         if(res < 0)
00416             return res;
00417     }
00418 
00419     if(res != 230)
00420         return -EACCES;
00421 
00422     return 0;
00423 }
00424 
00425 static int ftp_init_conn(struct ftpconn *conn)
00426 {
00427     int res;
00428 
00429     res = ftp_wait_reply_code(conn);
00430     if(res == 120)
00431         res = ftp_wait_reply_code(conn);
00432     
00433     if(res < 0)
00434         return res;
00435 
00436     if(res != 220)
00437         return -EIO;
00438     
00439     res = ftp_login(conn);
00440     if(res < 0)
00441         return res;
00442 
00443     ftp_command(conn, "PWD");
00444     ftp_command(conn, "SYST");
00445 
00446     return 0;
00447 }
00448 
00449 static int ftp_open_conn(struct ftpconn *conn)
00450 {
00451     int res;
00452 
00453     if(conn->sock != -1) {
00454         res = ftp_command(conn, "NOOP");
00455         if(res < 0)
00456             return res;
00457         
00458         if(res != 421)
00459             return 0;
00460     }
00461 
00462     res = av_sock_connect(conn->host, 21);
00463     if(res < 0)
00464         return res;
00465 
00466     conn->sock = res;
00467     conn->sockfb = av_filebuf_new(conn->sock, 0);
00468 
00469     res = ftp_init_conn(conn);
00470     if(res < 0) {
00471         ftp_close_conn(conn);
00472         return res;
00473     }
00474 
00475     return 0;
00476 }
00477 
00478 static void ftp_free_dirlist(struct remdirlist *dl)
00479 {
00480     int i;
00481 
00482     for(i = 0; i < dl->num; i++) {
00483         av_free(dl->ents[i].name);
00484         av_free(dl->ents[i].linkname);
00485     }
00486 
00487     av_free(dl->ents);
00488     dl->ents = NULL;
00489     dl->num = 0;
00490 }
00491 
00492 static int ftp_read_list(struct filebuf *fb, struct remdirlist *dl,
00493                          struct lscache *lc)
00494 {
00495     int res;
00496     char *line;
00497     int eof = 0;
00498 
00499     do {
00500         res = av_filebuf_getline(fb, &line, FTP_READ_TIMEOUT);
00501         if(res < 0)
00502             return res;
00503         
00504         if(res == 0) {
00505             av_log(AVLOG_ERROR, "FTP: read timeout");
00506             return -EIO;
00507         }
00508         if(line == NULL)
00509             eof = 1;
00510         else {
00511             struct avstat stbuf;
00512             char *filename;
00513             char *linkname;
00514             strip_crlf(line);
00515             
00516             av_log(AVLOG_DEBUG, "FTP: %s", line);
00517             res = av_parse_ls(lc, line, &stbuf, &filename, &linkname);
00518             av_free(line);
00519             if(res == 1) {
00520                 av_remote_add(dl, filename, linkname, &stbuf);
00521                 av_free(filename);
00522                 av_free(linkname);
00523             }
00524         }
00525     } while(!eof);
00526 
00527     return 0;
00528 }
00529 
00530 static int ftp_set_ascii(struct ftpconn *conn)
00531 {
00532     int res;
00533 
00534     if(conn->binary != 0) {
00535         res = ftp_command(conn, "TYPE A");
00536         if(res < 0)
00537             return res;
00538 
00539         conn->binary = 0;
00540     }
00541 
00542     return 0;
00543 }
00544 
00545 static int ftp_set_binary(struct ftpconn *conn)
00546 {
00547     int res;
00548 
00549     if(conn->binary != 1) {
00550         res = ftp_command(conn, "TYPE I");
00551         if(res < 0)
00552             return res;
00553 
00554         conn->binary = 1;
00555     }
00556 
00557     return 0;
00558 }
00559 
00560 static int ftp_set_cwd(struct ftpconn *conn, const char *dir)
00561 {
00562     int res;
00563     char *cmd;
00564 
00565     if(strcmp(conn->cwd, dir) != 0) {
00566         cmd = av_stradd(NULL, "CWD ", dir, NULL);
00567         res = ftp_command(conn, cmd);
00568         av_free(cmd);
00569         if(res < 0)
00570             return res;
00571         
00572         if(res == 550)
00573             return -ENOENT;
00574         if(res / 100 != 2)
00575             return -EIO;
00576 
00577         av_free(conn->cwd);
00578         conn->cwd = av_strdup(dir);
00579     }
00580 
00581     return 0;
00582 }
00583 
00584 static int ftp_do_list(struct ftpconn *conn, const char *dir, 
00585                        struct remdirlist *dl)
00586 {
00587     int res;
00588     char *cmd;
00589     int listsock;
00590     struct filebuf *fb;
00591     struct lscache *lc;
00592 
00593     res = ftp_open_conn(conn);
00594     if(res < 0)
00595         return res;
00596 
00597     res = ftp_set_ascii(conn);
00598     if(res < 0)
00599         return res;
00600     
00601     res = ftp_set_cwd(conn, dir);
00602     if(res < 0)
00603         return res;
00604 
00605     res = ftp_open_dataconn(conn);
00606     if(res < 0)
00607         return res;
00608         
00609     listsock = res;
00610     cmd = av_strdup("LIST -al");
00611     res = ftp_command(conn, cmd);
00612     av_free(cmd);
00613     if(res >= 0 && res / 100 != 1)
00614         res = -EIO;
00615     
00616     if(res < 0) {
00617         close(listsock);
00618         return res;
00619     }
00620     
00621     fb = av_filebuf_new(listsock, 0);
00622     lc = av_new_lscache();
00623     res = ftp_read_list(fb, dl, lc);
00624     av_unref_obj(lc);
00625     av_unref_obj(fb);
00626 
00627     res = ftp_wait_reply_code(conn);
00628     if(res >= 0 && res / 100 != 2)
00629         res = -EIO;
00630 
00631     if(res < 0) {
00632         ftp_free_dirlist(dl);
00633         return res;
00634     }
00635 
00636     return 0;
00637 }
00638 
00639 static const char *ftp_get_password(struct ftpdata *ftd, const char *host,
00640                                     const char *user)
00641 {
00642     struct ftpsession *fts;
00643     char *account;
00644 
00645     account = av_stradd(NULL, user, USER_SEP_STR, host, NULL);
00646     fts = ftp_find_session(ftd, account);
00647     av_free(account);
00648     if(fts == NULL) {
00649         account = av_stradd(NULL, user, USER_SEP_STR, NULL);
00650         fts = ftp_find_session(ftd, account);
00651         av_free(account);
00652     }
00653     
00654     if(fts != NULL)
00655         return fts->password;
00656     else
00657         return NULL;
00658 }
00659 
00660 static int ftp_split_path(struct ftpdata *ftd, char *hostpart,
00661                            const char **hostp, const char **userp,
00662                            const char **passp)
00663 {
00664     char *s, *t;
00665     const char *host;
00666     const char *user;
00667     const char *pass;
00668 
00669     for(s = hostpart; *s && *s != USER_SEP_CHAR; s++);
00670     for(t = s; *t; t++) if (*t == USER_SEP_CHAR) s = t;
00671     if(*s != '\0') {
00672         *s = '\0';
00673         host = s + 1;
00674         user = hostpart;
00675         pass = ftp_get_password(ftd, host, user);
00676         if(pass == NULL)
00677             return -EACCES;
00678     }
00679     else {
00680         host = hostpart;
00681         user = "ftp";
00682         pass = "avfs@";
00683     }
00684 
00685     if(host[0] == '\0')
00686         return -ENOENT;
00687 
00688     *hostp = host;
00689     *userp = user;
00690     *passp = pass;
00691 
00692     return 0;
00693 }
00694 
00695 
00696 static struct ftpconn *ftp_find_conn(struct ftpdata *ftd, const char *host,
00697                                      const char *user, const char *password)
00698 {
00699     struct ftpconn *conn;
00700     struct ftpconn **cp;
00701 
00702     for(cp = &ftd->conns; *cp != NULL; cp = &(*cp)->next) {
00703         conn = *cp;
00704 
00705         if(strcmp(conn->host, host) == 0 && strcmp(conn->user, user) == 0 &&
00706            strcmp(conn->password, password) == 0 && !conn->busy) {
00707             conn->busy = 1;
00708             return conn;
00709         }
00710     }
00711 
00712     AV_NEW(conn);
00713     
00714     conn->host = av_strdup(host);
00715     conn->user = av_strdup(user);
00716     conn->password = av_strdup(password);
00717     conn->busy = 1;
00718     conn->sock = -1;
00719     conn->sockfb = NULL;
00720     conn->next = NULL;
00721     conn->binary = -1;
00722     conn->cwd = av_strdup("");
00723     
00724     *cp = conn;
00725     
00726     return conn;
00727 }
00728 
00729 static int ftp_get_conn(struct ftpdata *ftd, const char *userhost,
00730                         struct ftpconn **resp)
00731 {
00732     int res;
00733     char *tmps;
00734     const char *host;
00735     const char *user;
00736     const char *password;
00737     struct ftpconn *conn = NULL;
00738 
00739     AV_LOCK(ftp_lock);
00740     tmps = av_strdup(userhost);
00741     res = ftp_split_path(ftd, tmps, &host, &user, &password);
00742     if(res == 0)
00743         conn = ftp_find_conn(ftd, host, user, password);
00744 
00745     av_free(tmps);
00746     AV_UNLOCK(ftp_lock);
00747 
00748     if(res < 0)
00749         return res;
00750 
00751     *resp = conn;
00752 
00753     return 0;
00754 }
00755 
00756 static int ftp_list(struct remote *rem, struct remdirlist *dl)
00757 {
00758     int res;
00759     struct ftpdata *ftd = (struct ftpdata *) rem->data;
00760     struct ftpconn *conn;
00761 
00762     res = ftp_get_conn(ftd, dl->hostpath.host, &conn);
00763     if(res < 0)
00764         return res;
00765 
00766     res = ftp_do_list(conn, dl->hostpath.path, dl);
00767 
00768     ftp_release_conn(conn);
00769 
00770     return res;
00771 }
00772 
00773 static void ftp_free_localfile(struct ftplocalfile *lf)
00774 {
00775     if(lf->conn != NULL) {
00776         ftp_close_conn(lf->conn);
00777         ftp_release_conn(lf->conn);
00778     }
00779     av_unref_obj(lf->sockfb);
00780     close(lf->sock);
00781     close(lf->fd);
00782 }
00783 
00784 static int ftp_init_localfile(struct ftplocalfile *lf, int sock)
00785 {
00786     int res;
00787 
00788     lf->fd = -1;
00789     lf->tmpfile = NULL;
00790     lf->sock = sock;
00791     lf->sockfb = NULL;
00792     lf->numbytes = 0;
00793     lf->conn = NULL;
00794 
00795     res = av_get_tmpfile(&lf->tmpfile);
00796     if(res < 0)
00797         return res;
00798 
00799     lf->fd = open(lf->tmpfile, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0600);
00800     if(lf->fd == -1)
00801         return -errno;
00802 
00803     lf->sockfb = av_filebuf_new(lf->sock, 0);
00804 
00805     return 0;
00806 }
00807 
00808 
00809 static int ftp_do_get(struct remgetparam *gp, const char *dir,
00810                       const char *file, struct ftpconn *conn)
00811 {
00812     int res;
00813     int getsock;
00814     char *cmd;
00815     struct ftplocalfile *lf;
00816 
00817     res = ftp_open_conn(conn);
00818     if(res < 0)
00819         return res;
00820 
00821     res = ftp_set_binary(conn);
00822     if(res < 0)
00823         return res;
00824     
00825     res = ftp_set_cwd(conn, dir);
00826     if(res < 0)
00827         return res;
00828 
00829     res = ftp_open_dataconn(conn);
00830     if(res < 0)
00831         return res;
00832 
00833     getsock = res;
00834     cmd = av_stradd(NULL, "RETR ", file, NULL);
00835     res = ftp_command(conn, cmd);
00836     av_free(cmd);
00837     if(res >= 0 && res / 100 != 1)
00838         res = -EIO;
00839     
00840     if(res < 0) {
00841         close(getsock);
00842         return res;
00843     }
00844 
00845     AV_NEW_OBJ(lf, ftp_free_localfile);
00846     res = ftp_init_localfile(lf, getsock);
00847     if(res < 0) {
00848         av_unref_obj(lf);
00849         av_del_tmpfile(lf->tmpfile);
00850         return res;
00851     }
00852 
00853     lf->conn = conn;
00854 
00855     gp->data = lf;
00856     gp->localname = lf->tmpfile;
00857 
00858     return 0;
00859 }
00860 
00861 static int ftp_get(struct remote *rem, struct remgetparam *gp)
00862 {
00863     int res;
00864     struct ftpdata *ftd = (struct ftpdata *) rem->data;
00865     struct ftpconn *conn;
00866     char *dir;
00867     char *s;
00868     char *file;
00869 
00870     res = ftp_get_conn(ftd, gp->hostpath.host, &conn);
00871     if(res < 0)
00872         return res;
00873 
00874     dir = av_strdup(gp->hostpath.path);
00875     s = strrchr(dir, '/');
00876     *s = '\0';
00877     file = s + 1;
00878 
00879     res = ftp_do_get(gp, ( dir[0] == '\0' ) ? "/" : dir, file, conn);
00880     av_free(dir);
00881 
00882     if(res < 0)
00883         ftp_release_conn(conn);
00884 
00885     return res;
00886 }
00887 
00888 static int ftp_write_localfile(int fd, char *buf, avsize_t nbytes)
00889 {
00890     int res;
00891 
00892     res = write(fd, buf, nbytes);
00893     if(res == -1) {
00894         av_log(AVLOG_ERROR, "FTP: error writing to tmpfile: %s",
00895                  strerror(errno));
00896                 
00897         return -EIO;
00898     }
00899     if(res != nbytes) {
00900         av_log(AVLOG_ERROR, "FTP: short write to tmpfile (%i/%i)",
00901                  res, nbytes);
00902         return -EIO;
00903     }
00904     
00905     return 0;
00906 }
00907 
00908 #define READBUF 4096
00909 
00910 static int ftp_wait(struct remote *rem, void *data, avoff_t end)
00911 {
00912     int res;
00913     struct ftplocalfile *lf = (struct ftplocalfile *) data;
00914     char buf[READBUF];
00915     avsize_t nbytes;
00916     
00917     do {
00918         nbytes = av_filebuf_read(lf->sockfb, buf, READBUF);
00919         if(nbytes != 0) {
00920             res = ftp_write_localfile(lf->fd, buf, nbytes);
00921             if(res < 0)
00922                 return res;
00923 
00924             lf->numbytes += nbytes;
00925         }
00926         else {
00927             if(av_filebuf_eof(lf->sockfb)) {
00928                 av_unref_obj(lf->sockfb);
00929                 lf->sockfb = NULL;
00930                 close(lf->sock);
00931                 lf->sock = -1;
00932 
00933                 res = ftp_wait_reply_code(lf->conn);
00934                 if(res >= 0 && res / 100 != 2)
00935                     res = -EIO;
00936                 
00937                 if(res < 0)
00938                     return res;
00939                 
00940                 ftp_release_conn(lf->conn);
00941                 lf->conn = NULL;
00942 
00943                 return 0;
00944             }
00945             else {
00946                 res = av_filebuf_check(&lf->sockfb, 1, FTP_READ_TIMEOUT);
00947                 if(res < 0)
00948                     return res;
00949 
00950                 if(res == 0) {
00951                     av_log(AVLOG_ERROR, "FTP: read timeout");
00952                     return -EIO;
00953                 }
00954             }
00955         }
00956     } while(lf->numbytes < end);
00957 
00958     return 1;
00959 }
00960 
00961 static int ftp_password_set(struct entry *ent, const char *param,
00962                             const char *val)
00963 {
00964     struct ftpsession *fts;
00965     struct statefile *sf = (struct statefile *) av_namespace_get(ent);
00966     struct ftpdata *ftd = (struct ftpdata *) sf->data;
00967     unsigned int len;
00968 
00969     AV_LOCK(ftp_lock);
00970     fts = ftp_get_session(ftd, param);
00971     av_free(fts->password);
00972     fts->password = av_strdup(val);
00973     len = strlen(fts->password);
00974     if(len > 0) {
00975         if(fts->password[len - 1] == '\n')
00976             fts->password[len - 1] = '\0';
00977     }
00978     AV_UNLOCK(ftp_lock);
00979 
00980     return 0;
00981 }
00982 
00983 static int ftp_loggedin_get(struct entry *ent, const char *param, char **resp)
00984 {
00985     struct ftpsession *fts;
00986     struct statefile *sf = (struct statefile *) av_namespace_get(ent);
00987     struct ftpdata *ftd = (struct ftpdata *) sf->data;
00988 
00989     AV_LOCK(ftp_lock);
00990     fts = ftp_find_session(ftd, param);
00991     if(fts == NULL)
00992         *resp = av_strdup("0\n");
00993     else
00994         *resp = av_strdup("1\n");
00995     AV_UNLOCK(ftp_lock);
00996 
00997     return 0;
00998 }
00999 
01000 static int ftp_loggedin_val(const char *val, int *resp)
01001 {
01002     char *end;
01003     int ival;
01004 
01005     ival = strtol(val, &end, 10);
01006     if(end == val)
01007         return -EINVAL;
01008 
01009     if(*end == '\n')
01010         end++;
01011     if(*end != '\0')
01012         return -EINVAL;
01013 
01014     if(ival < 0 || ival > 1)
01015         return -EINVAL;
01016     
01017     *resp = ival;
01018 
01019     return 0;
01020 }
01021 
01022 static int ftp_loggedin_set(struct entry *ent, const char *param,
01023                             const char *val)
01024 {
01025     int res;
01026     struct ftpsession *fts;
01027     struct statefile *sf = (struct statefile *) av_namespace_get(ent);
01028     struct ftpdata *ftd = (struct ftpdata *) sf->data;
01029 
01030     AV_LOCK(ftp_lock);
01031     fts = ftp_find_session(ftd, param);
01032     if(fts == NULL)
01033         res = -EACCES;
01034     else {
01035         int ival;
01036 
01037         res = ftp_loggedin_val(val, &ival);
01038         if(res == 0 && ival == 0) {
01039             /* FIXME: end connections using this session */
01040             ftp_remove_session(fts);
01041         }
01042     }
01043     AV_UNLOCK(ftp_lock);
01044 
01045     return res;
01046 }
01047 
01048 static int ftp_init_ctl(struct vmodule *module, struct ftpdata *ftd)
01049 {
01050     int res;
01051     struct namespace *ns;
01052     struct statefile *stf;
01053     struct entry *ent;
01054     struct avfs *avfs;
01055     
01056     res = av_state_new(module, "ftp_ctl", &ns, &avfs);
01057     if(res < 0)
01058         return res;
01059     
01060     ent = av_namespace_lookup(ns, NULL, "password");
01061     AV_NEW(stf);
01062     stf->data = ftd;
01063     stf->get = NULL;
01064     stf->set = ftp_password_set;
01065     av_namespace_set(ent, stf);
01066 
01067     ent = av_namespace_lookup(ns, NULL, "loggedin");
01068     AV_NEW(stf);
01069     stf->data = ftd;
01070     stf->get = ftp_loggedin_get;
01071     stf->set = ftp_loggedin_set;
01072     av_namespace_set(ent, stf);
01073     
01074     av_unref_obj(ns);
01075 
01076     return 0;
01077 }    
01078 
01079 static void ftp_destroy(struct remote *rem)
01080 {
01081     struct ftpdata *ftd = (struct ftpdata *) rem->data;
01082     struct ftpconn *conn;
01083     struct ftpconn *nextconn;
01084 
01085     for(conn = ftd->conns; conn != NULL; conn = nextconn) {
01086         nextconn = conn->next;
01087 
01088         ftp_close_conn(conn);
01089         av_free(conn->host);
01090         av_free(conn->user);
01091         av_free(conn->password);
01092         av_free(conn->cwd);
01093         av_free(conn);
01094         
01095         conn = nextconn;
01096     }
01097 
01098     AV_LOCK(ftp_lock);
01099     while(ftd->sessions.next != &ftd->sessions)
01100         ftp_remove_session(ftd->sessions.next);
01101     AV_UNLOCK(ftp_lock);
01102 
01103     av_free(ftd);
01104 
01105     av_free(rem->name);
01106     av_free(rem);
01107 }
01108     
01109 extern int av_init_module_ftp(struct vmodule *module);
01110 
01111 int av_init_module_ftp(struct vmodule *module)
01112 {
01113     int res;
01114     struct remote *rem;
01115     struct ftpdata *ftd;
01116     struct avfs *avfs;
01117 
01118     AV_NEW(ftd);
01119     ftd->conns = NULL;
01120     ftd->sessions.next = &ftd->sessions;
01121     ftd->sessions.prev = &ftd->sessions;
01122 
01123     AV_NEW(rem);
01124 
01125     rem->data    = ftd;
01126     rem->flags   = REM_DIR_ONLY;
01127     rem->name    = av_strdup("ftp");
01128     rem->list    = ftp_list;
01129     rem->get     = ftp_get;
01130     rem->wait    = ftp_wait;
01131     rem->destroy = ftp_destroy;
01132     
01133     res = av_remote_init(module, rem, &avfs);
01134     if(res == 0) {
01135         res = ftp_init_ctl(module, ftd);
01136         if(res < 0)
01137             av_unref_obj(avfs);
01138     }
01139 
01140     return res;
01141 }