/* $Id: ftp.c,v 1.15 2002/11/18 18:26:13 ukai Exp $ */ #include #include #include #include #include #include #include "fm.h" #include "html.h" #include "myctype.h" #ifdef DEBUG #include #endif /* DEBUG */ #include #if defined(FTPPASS_HOSTNAMEGEN) || defined(INET6) #include #include #include #endif typedef struct _FTP { FILE *rcontrol; FILE *wcontrol; FILE *data; } *FTP; #define FtpError(status) ((status)<0) #define FTPDATA(ftp) ((ftp)->data) typedef int STATUS; static FTP current_ftp; static Str read_response1(FTP ftp) { char c; Str buf = Strnew(); while (1) { c = getc(ftp->rcontrol); if (c == '\r') { c = getc(ftp->rcontrol); if (c == '\n') { Strcat_charp(buf, "\r\n"); break; } else { Strcat_char(buf, '\r'); Strcat_char(buf, c); } } else if (c == '\n') { Strcat_charp(buf, "\r\n"); break; } else if (feof(ftp->rcontrol)) break; else Strcat_char(buf, c); } return buf; } Str read_response(FTP ftp) { Str tmp; tmp = read_response1(ftp); if (feof(ftp->rcontrol)) { return tmp; } if (tmp->ptr[3] == '-') { /* RFC959 4.2 FTP REPLIES */ /* multi-line response start */ /* * Thus the format for multi-line replies is that the * first line will begin with the exact required reply * code, followed immediately by a Hyphen, "-" (also known * as Minus), followed by text. The last line will begin * with the same code, followed immediately by Space , * optionally some text, and the Telnet end-of-line code. */ while (1) { tmp = read_response1(ftp); if (feof(ftp->rcontrol)) { break; } if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) && IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ') { break; } } } return tmp; } int FtpLogin(FTP * ftp_return, char *host, char *user, char *pass) { Str tmp; FTP ftp = New(struct _FTP); int fd; *ftp_return = current_ftp = ftp; fd = openSocket(host, "ftp", 21); if (fd < 0) return -1; #ifdef FTPPASS_HOSTNAMEGEN if (ftppass_hostnamegen && !strcmp(user, "anonymous")) { size_t n = strlen(pass); if (n > 0 && pass[n - 1] == '@') { struct sockaddr_in sockname; int socknamelen = sizeof(sockname); if (!getsockname(fd, (struct sockaddr *)&sockname, &socknamelen)) { struct hostent *sockent; Str tmp2 = Strnew_charp(pass); if ((sockent = gethostbyaddr((char *)&sockname.sin_addr, sizeof(sockname.sin_addr), sockname.sin_family))) Strcat_charp(tmp2, sockent->h_name); else Strcat_m_charp(tmp2, "[", inet_ntoa(sockname.sin_addr), "]", NULL); pass = tmp2->ptr; } } } #endif ftp->rcontrol = fdopen(fd, "rb"); ftp->wcontrol = fdopen(dup(fd), "wb"); ftp->data = NULL; tmp = read_response(ftp); if (atoi(tmp->ptr) != 220) return -1; if (fmInitialized) { message(Sprintf("Sending FTP username (%s) to remote server.", user)-> ptr, 0, 0); refresh(); } tmp = Sprintf("USER %s\r\n", user); fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); /* * Some ftp daemons(e.g. publicfile) return code 230 for user command. */ if (atoi(tmp->ptr) == 230) goto succeed; if (atoi(tmp->ptr) != 331) return -1; if (fmInitialized) { message(Sprintf("Sending FTP password to remote server.")->ptr, 0, 0); refresh(); } tmp = Sprintf("PASS %s\r\n", pass); fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (atoi(tmp->ptr) != 230) return -1; succeed: return 0; } int FtpBinary(FTP ftp) { Str tmp; fwrite("TYPE I\r\n", 8, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (atoi(tmp->ptr) != 200) return -1; return 0; } int ftp_pasv(FTP ftp) { int n1, n2, n3, n4, p1, p2; int data_s; char *p; Str tmp; int family; #ifdef INET6 struct sockaddr_storage sockaddr; int sockaddrlen, port; unsigned char d1, d2, d3, d4; char abuf[INET6_ADDRSTRLEN]; #endif #ifdef INET6 sockaddrlen = sizeof(sockaddr); if (getpeername(fileno(ftp->wcontrol), (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) return -1; family = sockaddr.ss_family; #else family = AF_INET; #endif switch (family) { #ifdef INET6 case AF_INET6: fwrite("EPSV\r\n", 6, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (atoi(tmp->ptr) != 229) return -1; for (p = tmp->ptr + 4; *p && *p != '('; p++) ; if (*p == '\0') return -1; if (sscanf(++p, "%c%c%c%d%c", &d1, &d2, &d3, &port, &d4) != 5 || d1 != d2 || d1 != d3 || d1 != d4) return -1; if (getnameinfo((struct sockaddr *)&sockaddr, sockaddrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST) != 0) return -1; tmp = Sprintf("%s", abuf); data_s = openSocket(tmp->ptr, "", port); break; #endif case AF_INET: fwrite("PASV\r\n", 6, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (atoi(tmp->ptr) != 227) return -1; for (p = tmp->ptr + 4; *p && !IS_DIGIT(*p); p++) ; if (*p == '\0') return -1; sscanf(p, "%d,%d,%d,%d,%d,%d", &n1, &n2, &n3, &n4, &p1, &p2); tmp = Sprintf("%d.%d.%d.%d", n1, n2, n3, n4); data_s = openSocket(tmp->ptr, "", p1 * 256 + p2); break; default: return -1; } if (data_s < 0) return -1; ftp->data = fdopen(data_s, "rb"); return 0; } static void ftp_fclose(FTP ftp) { int control_closed = 0; if (ftp->rcontrol != NULL) { fclose(ftp->rcontrol); ftp->rcontrol = NULL; control_closed = 1; } if (ftp->wcontrol != NULL) { fclose(ftp->wcontrol); ftp->wcontrol = NULL; control_closed = 1; } if (control_closed && ftp->data != NULL) { fclose(ftp->data); ftp->data = NULL; } return; } int FtpCwd(FTP ftp, char *path) { Str tmp; tmp = Sprintf("CWD %s\r\n", path); fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (tmp->ptr[0] == '5') { return -1; } return 0; } int FtpOpenReadBody(FTP ftp, char *path) { Str tmp; tmp = Sprintf("RETR %s\r\n", path); fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (tmp->ptr[0] == '5') { return -1; } return 0; } int FtpOpenRead(FTP ftp, char *path) { if (ftp_pasv(ftp) < 0) return -1; if (FtpOpenReadBody(ftp, path) < 0) { fclose(ftp->data); ftp->data = NULL; return -1; } return 0; } int Ftpfclose(FILE * f) { fclose(f); if (f == current_ftp->data) current_ftp->data = NULL; ftp_fclose(current_ftp); return 0; } int FtpDataBody(FTP ftp, char *cmd, char *arg, char *mode) { Str tmp; tmp = Sprintf(cmd, arg); Strcat_charp(tmp, "\r\n"); fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (tmp->ptr[0] == '5') { fclose(ftp->data); ftp->data = NULL; return -1; } return 0; } int FtpData(FTP ftp, char *cmd, char *arg, char *mode) { if (ftp_pasv(ftp) < 0) return -1; return FtpDataBody(ftp, cmd, arg, mode); } int FtpClose(FTP ftp) { Str tmp; fclose(ftp->data); ftp->data = NULL; tmp = read_response(ftp); if (atoi(tmp->ptr) != 226) return -1; return 0; } int FtpBye(FTP ftp) { Str tmp; int ret_val; fwrite("QUIT\r\n", 6, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (atoi(tmp->ptr) != 221) ret_val = -1; else ret_val = 0; ftp_fclose(ftp); return ret_val; } static int ex_ftpdir_name_size_date(char *, char **, char **, char **); static int ftp_system(FTP); #define SERVER_NONE 0 #define UNIXLIKE_SERVER 1 #define FTPDIR_NONE 0 #define FTPDIR_DIR 1 #define FTPDIR_LINK 2 #define FTPDIR_FILE 3 FILE * openFTP(ParsedURL *pu) { Str tmp2 = Strnew(); Str tmp3 = Strnew(); STATUS s; char *user = NULL; char *pass = NULL; Str pwd = NULL; int add_auth_cookie_flag; char *realpathname = NULL; #ifdef JP_CHARSET char code = '\0', ic; Str pathStr; #endif add_auth_cookie_flag = 0; if (pu->user == NULL && pu->pass == NULL) { Str uname, pwd; if (find_auth_user_passwd(pu, NULL, &uname, &pwd, 0)) { if (uname) user = uname->ptr; if (pwd) pass = pwd->ptr; } } if (user) /* do nothing */ ; else if (pu->user) user = pu->user; else { Strcat_charp(tmp3, "anonymous"); user = tmp3->ptr; } if (pass) /* do nothing */ ; else if (pu->pass) pass = pu->pass; else if (pu->user) { pwd = find_auth_cookie(pu->host, pu->port, pu->file, pu->user); if (pwd == NULL) { if (fmInitialized) { term_raw(); pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD)); pwd = Str_conv_to_system(pwd); term_cbreak(); } else { pwd = Strnew_charp((char *)getpass("Password: ")); } add_auth_cookie_flag = 1; } pass = pwd->ptr; } else if (ftppasswd != NULL && *ftppasswd != '\0') pass = ftppasswd; else { struct passwd *mypw = getpwuid(getuid()); if (mypw == NULL) Strcat_charp(tmp2, "anonymous"); else Strcat_charp(tmp2, mypw->pw_name); Strcat_char(tmp2, '@'); pass = tmp2->ptr; } s = FtpLogin(¤t_ftp, pu->host, user, pass); if (FtpError(s)) return NULL; if (add_auth_cookie_flag) add_auth_cookie(pu->host, pu->port, pu->file, pu->user, pwd); if (pu->file == NULL || *pu->file == '\0') goto ftp_dir; else realpathname = file_unquote(pu->file); if (pu->file[strlen(pu->file) - 1] == '/') goto ftp_dir; /* Get file */ FtpBinary(current_ftp); if (ftp_pasv(current_ftp) < 0) { FtpBye(current_ftp); return NULL; } s = FtpOpenReadBody(current_ftp, realpathname); if (!FtpError(s)) { #ifdef JP_CHARSET pathStr = Strnew_charp(realpathname); if ((ic = checkShiftCode(pathStr, code)) != '\0') { pathStr = conv_str(pathStr, (code = ic), InnerCode); realpathname = pathStr->ptr; } #endif /* JP_CHARSET */ pu->file = realpathname; return FTPDATA(current_ftp); } goto ftp_dir1; /* Get directory */ ftp_dir: if (ftp_pasv(current_ftp) < 0) { FtpBye(current_ftp); return NULL; } ftp_dir1: pu->scheme = SCM_FTPDIR; return NULL; } Str readFTPDir(ParsedURL *pu) { Str FTPDIRtmp = Strnew(); Str host; Str curdir; char *fn; char *qdir; char **flist; int i, nfile, nfile_max = 100; int sv_type; STATUS s; char *realpathname = NULL; Str tmp2 = Strnew(); if (current_ftp->data == NULL) return FTPDIRtmp; sv_type = ftp_system(current_ftp); if (pu->file == NULL || *pu->file == '\0') { if (sv_type == UNIXLIKE_SERVER) { s = FtpDataBody(current_ftp, "LIST", NULL, "r"); } else { s = FtpDataBody(current_ftp, "NLST", NULL, "r"); } curdir = Strnew_charp("/"); } else { realpathname = file_unquote(pu->file); if (sv_type == UNIXLIKE_SERVER) { s = FtpCwd(current_ftp, realpathname); if (!FtpError(s)) { s = FtpDataBody(current_ftp, "LIST", NULL, "r"); } } else { s = FtpDataBody(current_ftp, "NLST %s", realpathname, "r"); } if (realpathname[0] == '/') curdir = Strnew_charp(realpathname); else curdir = Sprintf("/%s", realpathname); if (Strlastchar(curdir) != '/') Strcat_char(curdir, '/'); } if (FtpError(s)) { FtpBye(current_ftp); return NULL; } host = Strnew_charp("ftp://"); if (pu->user) { Strcat_m_charp(host, pu->user, "@", NULL); } Strcat_charp(host, pu->host); if (Strlastchar(host) == '/') Strshrink(host, 1); qdir = html_quote(curdir->ptr); FTPDIRtmp = Sprintf ("%s%s

Index of %s%s

\n", host->ptr, qdir, host->ptr, qdir); curdir = Strnew_charp(file_quote(curdir->ptr)); qdir = curdir->ptr; tmp2 = Strdup(curdir); if (Strcmp_charp(curdir, "/") != 0) { Strshrink(tmp2, 1); while (Strlastchar(tmp2) != '/' && tmp2->length > 0) Strshrink(tmp2, 1); } if (sv_type == UNIXLIKE_SERVER) { Strcat_charp(FTPDIRtmp, "
  • ptr, html_quote(tmp2->ptr), "\">[Upper Directory]\n", NULL); flist = New_N(char *, nfile_max); nfile = 0; if (sv_type == UNIXLIKE_SERVER) { char *name, *date, *size, *type_str; int ftype, max_len, len, j; Str line_tmp; max_len = 0; while (tmp2 = Strfgets(FTPDATA(current_ftp)), tmp2->length > 0) { Strchop(tmp2); if ((ftype = ex_ftpdir_name_size_date(tmp2->ptr, &name, &date, &size)) == FTPDIR_NONE) { continue; } if (!strcmp(".", name) || !strcmp("..", name)) { continue; } len = strlen(name); if (!len) continue; if (ftype == FTPDIR_DIR) { len++; type_str = "/"; } else if (ftype == FTPDIR_LINK) { len++; type_str = "@"; } else { type_str = ""; } if (max_len < len) max_len = len; line_tmp = Sprintf("%s%s %-12.12s %6.6s", name, type_str, date, size); flist[nfile++] = line_tmp->ptr; if (nfile == nfile_max) { nfile_max *= 2; flist = New_Reuse(char *, flist, nfile_max); } } qsort(flist, nfile, sizeof(char *), strCmp); for (j = 0; j < nfile; j++) { fn = flist[j]; date = fn + strlen(fn) - 20; if (*(date - 1) == '/') { ftype = FTPDIR_DIR; *(date - 1) = '\0'; } else if (*(date - 1) == '@') { ftype = FTPDIR_LINK; *(date - 1) = '\0'; } else { ftype = FTPDIR_FILE; *date = '\0'; } date++; len = strlen(fn); Strcat_m_charp(FTPDIRtmp, "ptr, qdir, html_quote(file_quote(fn)), "\">", html_quote(fn), NULL); if (ftype == FTPDIR_DIR) { Strcat_charp(FTPDIRtmp, "/"); len++; } else if (ftype == FTPDIR_LINK) { Strcat_charp(FTPDIRtmp, "@"); len++; } Strcat_charp(FTPDIRtmp, ""); for (i = len; i <= max_len; i++) { if ((max_len % 2 + i) % 2) { Strcat_charp(FTPDIRtmp, "."); } else { Strcat_charp(FTPDIRtmp, " "); } } Strcat_m_charp(FTPDIRtmp, date, "\n", NULL); } Strcat_charp(FTPDIRtmp, "
  • \n"); } else { while (tmp2 = Strfgets(FTPDATA(current_ftp)), tmp2->length > 0) { Strchop(tmp2); flist[nfile++] = mybasename(tmp2->ptr); if (nfile == nfile_max) { nfile_max *= 2; flist = New_Reuse(char *, flist, nfile_max); } } qsort(flist, nfile, sizeof(char *), strCmp); for (i = 0; i < nfile; i++) { fn = flist[i]; Strcat_m_charp(FTPDIRtmp, "
  • ptr, qdir, html_quote(file_quote(fn)), "\">", html_quote(fn), "\n", NULL); } Strcat_charp(FTPDIRtmp, "\n"); } FtpClose(current_ftp); FtpBye(current_ftp); return FTPDIRtmp; } static int ftp_system(FTP ftp) { int sv_type = SERVER_NONE; Str tmp; fwrite("SYST\r\n", 6, sizeof(char), ftp->wcontrol); fflush(ftp->wcontrol); tmp = read_response(ftp); if (strstr(tmp->ptr, "UNIX") != NULL || !strncmp(tmp->ptr + 4, "Windows_NT", 10)) { /* :-) */ sv_type = UNIXLIKE_SERVER; } return (sv_type); } #define XD_CTOD(c) {\ if (c >= '0' && c <= '9') {\ c -= (unsigned char)'0';\ } else if (c >= 'a' && c <= 'f') {\ c = c - (unsigned char)'a' + (unsigned char)10;\ } else if (c >= 'A' && c <= 'F') {\ c = c - (unsigned char)'A' + (unsigned char)10;\ } else {\ goto skip;\ }\ } #define EX_SKIP_SPACE(cp) {\ while (IS_SPACE(*cp) && *cp != '\0') cp++;\ if (*cp == '\0') {\ goto done;\ }\ } #define EX_SKIP_NONE_SPACE(cp) {\ while (!IS_SPACE(*cp) && *cp != '\0') cp++;\ if (*cp == '\0') {\ goto done;\ }\ } static Str size_int2str(clen_t); static int ex_ftpdir_name_size_date(char *line, char **name, char **date, char **sizep) { int ftype = FTPDIR_NONE; char *cp, *endp; Str date_str, name_str, size_str; clen_t size; if (strlen(line) < 11) { goto done; } /* skip permission */ if (!IS_SPACE(line[10])) { goto done; } cp = line + 11; /* skip link count */ EX_SKIP_SPACE(cp) while (IS_DIGIT(*cp) && *cp != '\0') cp++; if (!IS_SPACE(*cp) || *cp == '\0') { goto done; } cp++; /* skip owner string */ EX_SKIP_SPACE(cp) EX_SKIP_NONE_SPACE(cp) cp++; /* skip group string */ EX_SKIP_SPACE(cp) EX_SKIP_NONE_SPACE(cp) cp++; /* extract size */ EX_SKIP_SPACE(cp) size = 0; while (*cp && IS_DIGIT(*cp)) { size = size * 10 + *(cp++) - '0'; } if (*cp == '\0') { goto done; } /* extract date */ EX_SKIP_SPACE(cp) if (IS_ALPHA(cp[0]) && IS_ALPHA(cp[1]) && IS_ALPHA(cp[2]) && IS_SPACE(cp[3]) && (IS_SPACE(cp[4]) || IS_DIGIT(cp[4])) && IS_DIGIT(cp[5]) && IS_SPACE(cp[6]) && (IS_SPACE(cp[7]) || IS_DIGIT(cp[7])) && IS_DIGIT(cp[8]) && (cp[9] == ':' || IS_DIGIT(cp[9])) && IS_DIGIT(cp[10]) && (IS_DIGIT(cp[11]) || IS_SPACE(cp[11])) && IS_SPACE(cp[12])) { cp[12] = '\0'; date_str = Strnew_charp(cp); cp += 13; } else { goto done; } /* extract file name */ EX_SKIP_SPACE(cp) if (line[0] == 'l') { if ((endp = strstr(cp, " -> ")) == NULL) { goto done; } *endp = '\0'; size_str = Strnew_charp("-"); ftype = FTPDIR_LINK; } else if (line[0] == 'd') { size_str = Strnew_charp("-"); ftype = FTPDIR_DIR; } else { size_str = size_int2str(size); ftype = FTPDIR_FILE; } name_str = Strnew_charp(cp); *date = date_str->ptr; *name = name_str->ptr; *sizep = size_str->ptr; done: return (ftype); } static Str size_int2str(clen_t size) { Str size_str; int unit; double dtmp; char *size_format, *unit_str; dtmp = (double)size; for (unit = 0; unit < 3; unit++) { if (dtmp < 1024) { break; } dtmp /= 1024; } if (!unit || dtmp > 100) { size_format = "%.0f%s"; } else if (dtmp > 10) { size_format = "%.1f%s"; } else { size_format = "%.2f%s"; } switch (unit) { case 3: unit_str = "G"; break; case 2: unit_str = "M"; break; case 1: unit_str = "K"; break; default: unit_str = ""; break; } size_str = Sprintf(size_format, dtmp, unit_str); return (size_str); } void closeFTP(FILE * f) { if (f) { fclose(f); if (f == current_ftp->data) current_ftp->data = NULL; } FtpBye(current_ftp); }