1 /* $Id: ftp.c,v 1.39 2007/05/31 01:19:50 inu Exp $ */
3 #ifndef __MINGW32_VERSION
5 #endif /* __MINGW32_VERSION */
19 #ifndef __MINGW32_VERSION
20 #include <sys/socket.h>
21 #include <netinet/in.h>
23 #include <arpa/inet.h>
26 #endif /* __MINGW32_VERSION */
38 static struct _FTP current_ftp = {
39 NULL, 0, NULL, NULL, NULL, NULL, NULL
42 static JMP_BUF AbortLoading;
44 static MySignalHandler
47 LONGJMP(AbortLoading, 1);
52 ftp_command(FTP ftp, char *cmd, char *arg, int *status)
60 tmp = Sprintf("%s %s\r\n", cmd, arg);
62 tmp = Sprintf("%s\r\n", cmd);
63 fwrite(tmp->ptr, sizeof(char), tmp->length, ftp->wf);
68 *status = -1; /* error */
69 tmp = StrISgets(ftp->rf);
70 if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
71 IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ')
72 sscanf(tmp->ptr, "%d", status);
74 if (tmp->ptr[3] != '-')
76 /* RFC959 4.2 FTP REPLIES */
77 /* multi-line response start */
79 * Thus the format for multi-line replies is that the
80 * first line will begin with the exact required reply
81 * code, followed immediately by a Hyphen, "-" (also known
82 * as Minus), followed by text. The last line will begin
83 * with the same code, followed immediately by Space <SP>,
84 * optionally some text, and the Telnet end-of-line code. */
86 tmp = StrISgets(ftp->rf);
87 if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
88 IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ') {
89 sscanf(tmp->ptr, "%d", status);
102 IStype(ftp->rf) &= ~IST_UNCLOSE;
123 sock = openSocket(ftp->host, "ftp", 21);
126 if (ftppass_hostnamegen && !strcmp(ftp->user, "anonymous")) {
127 size_t n = strlen(ftp->pass);
129 if (n > 0 && ftp->pass[n - 1] == '@') {
130 struct sockaddr_in sockname;
131 int socknamelen = sizeof(sockname);
133 if (!getsockname(sock, (struct sockaddr *)&sockname, &socknamelen)) {
134 struct hostent *sockent;
135 Str tmp = Strnew_charp(ftp->pass);
137 if ((sockent = gethostbyaddr((char *)&sockname.sin_addr,
138 sizeof(sockname.sin_addr),
139 sockname.sin_family)))
140 Strcat_charp(tmp, sockent->h_name);
142 Strcat_m_charp(tmp, "[", inet_ntoa(sockname.sin_addr),
145 ftp->pass = tmp->ptr;
149 ftp->rf = newInputStream(sock);
150 ftp->wf = fdopen(dup(sock), "wb");
151 if (!ftp->rf || !ftp->wf)
153 IStype(ftp->rf) |= IST_UNCLOSE;
154 ftp_command(ftp, NULL, NULL, &status);
158 message(Sprintf("Sending FTP username (%s) to remote server.",
159 ftp->user)->ptr, 0, 0);
162 ftp_command(ftp, "USER", ftp->user, &status);
164 * Some ftp daemons(e.g. publicfile) return code 230 for user command.
171 message("Sending FTP password to remote server.", 0, 0);
174 ftp_command(ftp, "PASS", ftp->pass, &status);
188 int n1, n2, n3, n4, p1, p2;
194 struct sockaddr_storage sockaddr;
195 int sockaddrlen, port;
196 unsigned char d1, d2, d3, d4;
197 char abuf[INET6_ADDRSTRLEN];
201 sockaddrlen = sizeof(sockaddr);
202 if (getpeername(fileno(ftp->wf),
203 (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
205 #ifdef HAVE_OLD_SS_FAMILY
206 family = sockaddr.__ss_family;
208 family = sockaddr.ss_family;
216 tmp = ftp_command(ftp, "EPSV", NULL, &status);
219 for (p = tmp->ptr + 4; *p && *p != '('; p++) ;
222 if (sscanf(++p, "%c%c%c%d%c", &d1, &d2, &d3, &port, &d4) != 5
223 || d1 != d2 || d1 != d3 || d1 != d4)
225 if (getnameinfo((struct sockaddr *)&sockaddr, sockaddrlen,
226 abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST) != 0)
228 data = openSocket(abuf, "", port);
232 tmp = ftp_command(ftp, "PASV", NULL, &status);
235 for (p = tmp->ptr + 4; *p && !IS_DIGIT(*p); p++) ;
238 sscanf(p, "%d,%d,%d,%d,%d,%d", &n1, &n2, &n3, &n4, &p1, &p2);
239 tmp = Sprintf("%d.%d.%d.%d", n1, n2, n3, n4);
240 data = openSocket(tmp->ptr, "", p1 * 256 + p2);
247 ftp->data = fdopen(data, "rb");
252 ftp_modtime(FTP ftp, char *path)
260 tmp = ftp_command(ftp, "MDTM", path, &status);
263 for (p = tmp->ptr + 4; *p && *p == ' '; p++) ;
264 memset(&tm, 0, sizeof(struct tm));
265 if (sscanf(p, "%04d%02d%02d%02d%02d%02d",
266 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
267 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) < 6)
272 lt = mktime(localtime(&t));
273 gt = mktime(gmtime(&t));
274 return t + (lt - gt);
282 * ftp_command(ftp, "QUIT", NULL, &status);
287 ftp_command(ftp, "QUIT", NULL, NULL);
292 static int ex_ftpdir_name_size_date(char *, char **, char **, char **,
295 #define SERVER_NONE 0
296 #define UNIXLIKE_SERVER 1
298 #define FTPDIR_NONE 0
300 #define FTPDIR_LINK 2
301 #define FTPDIR_FILE 3
304 closeFTPdata(FILE * f)
309 if (f == current_ftp.data)
310 current_ftp.data = NULL;
312 ftp_command(¤t_ftp, NULL, NULL, &status);
319 ftp_close(¤t_ftp);
323 openFTPStream(ParsedURL *pu, URLFile *uf)
331 int add_auth_cookie_flag = FALSE;
332 char *realpathname = NULL;
337 if (pu->user == NULL && pu->pass == NULL) {
338 if (find_auth_user_passwd(pu, NULL, &uname, &pwd, 0)) {
352 if (current_ftp.host) {
353 if (!strcmp(current_ftp.host, pu->host) &&
354 current_ftp.port == pu->port && !strcmp(current_ftp.user, user)) {
355 ftp_command(¤t_ftp, "NOOP", NULL, &status);
357 ftp_close(¤t_ftp);
362 ftp_quit(¤t_ftp);
371 find_auth_user_passwd(pu, NULL, &uname, &pwd, 0);
375 pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
376 pwd = Str_conv_to_system(pwd);
380 #ifndef __MINGW32_VERSION
381 pwd = Strnew_charp((char *)getpass("Password: "));
384 pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
385 pwd = Str_conv_to_system(pwd);
387 #endif /* __MINGW32_VERSION */
389 add_auth_cookie_flag = TRUE;
393 else if (ftppasswd != NULL && *ftppasswd != '\0')
396 #ifndef __MINGW32_VERSION
397 struct passwd *mypw = getpwuid(getuid());
398 tmp = Strnew_charp(mypw ? mypw->pw_name : "anonymous");
400 tmp = Strnew_charp("anonymous");
401 #endif /* __MINGW32_VERSION */
402 Strcat_char(tmp, '@');
406 if (!current_ftp.host) {
407 current_ftp.host = allocStr(pu->host, -1);
408 current_ftp.port = pu->port;
409 current_ftp.user = allocStr(user, -1);
410 current_ftp.pass = allocStr(pass, -1);
411 if (!ftp_login(¤t_ftp))
414 if (add_auth_cookie_flag)
415 add_auth_user_passwd(pu, NULL, uname, pwd, 0);
418 ftp_command(¤t_ftp, "TYPE", "I", &status);
419 if (ftp_pasv(¤t_ftp) < 0) {
420 ftp_quit(¤t_ftp);
423 if (pu->file == NULL || *pu->file == '\0' ||
424 pu->file[strlen(pu->file) - 1] == '/')
427 realpathname = file_unquote(pu->file);
428 if (*realpathname == '/' && *(realpathname + 1) == '~')
431 uf->modtime = ftp_modtime(¤t_ftp, realpathname);
432 ftp_command(¤t_ftp, "RETR", realpathname, &status);
433 if (status == 125 || status == 150)
434 return newFileStream(current_ftp.data, (void (*)())closeFTPdata);
437 pu->scheme = SCM_FTPDIR;
443 loadFTPDir(ParsedURL *pu, wc_ces * charset)
446 loadFTPDir0(ParsedURL *pu)
452 volatile int sv_type;
453 char *realpathname, *fn, *q;
455 int i, nfile, nfile_max;
456 MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
458 wc_ces doc_charset = DocumentCharset;
460 *charset = WC_CES_US_ASCII;
462 if (current_ftp.data == NULL)
464 tmp = ftp_command(¤t_ftp, "SYST", NULL, &status);
465 if (strstr(tmp->ptr, "UNIX") != NULL || !strncmp(tmp->ptr + 4, "Windows_NT", 10)) /* :-) */
466 sv_type = UNIXLIKE_SERVER;
468 sv_type = SERVER_NONE;
469 if (pu->file == NULL || *pu->file == '\0') {
470 if (sv_type == UNIXLIKE_SERVER)
471 ftp_command(¤t_ftp, "LIST", NULL, &status);
473 ftp_command(¤t_ftp, "NLST", NULL, &status);
477 realpathname = file_unquote(pu->file);
478 if (*realpathname == '/' && *(realpathname + 1) == '~')
480 if (sv_type == UNIXLIKE_SERVER) {
481 ftp_command(¤t_ftp, "CWD", realpathname, &status);
483 ftp_command(¤t_ftp, "LIST", NULL, &status);
486 ftp_command(¤t_ftp, "NLST", realpathname, &status);
488 if (status != 125 && status != 150) {
489 fclose(current_ftp.data);
490 current_ftp.data = NULL;
493 tmp = parsedURL2Str(pu);
494 if (Strlastchar(tmp) != '/')
495 Strcat_char(tmp, '/');
496 fn = html_quote(tmp->ptr);
498 convertLine(NULL, Strnew_charp(file_unquote(tmp->ptr)), RAW_MODE,
499 charset, doc_charset);
500 q = html_quote(tmp->ptr);
501 FTPDIRtmp = Strnew_m_charp("<html>\n<head>\n<base href=\"", fn,
503 "</title>\n</head>\n<body>\n<h1>Index of ", q,
506 if (SETJMP(AbortLoading) != 0) {
507 if (sv_type == UNIXLIKE_SERVER)
508 Strcat_charp(FTPDIRtmp, "</a></pre>\n");
510 Strcat_charp(FTPDIRtmp, "</a></ul>\n");
511 Strcat_charp(FTPDIRtmp, "<p>Transfer Interrupted!\n");
516 if (sv_type == UNIXLIKE_SERVER)
517 Strcat_charp(FTPDIRtmp, "<pre>\n");
519 Strcat_charp(FTPDIRtmp, "<ul>\n<li>");
520 Strcat_charp(FTPDIRtmp, "<a href=\"..\">[Upper Directory]</a>\n");
523 flist = New_N(char *, nfile_max);
525 if (sv_type == UNIXLIKE_SERVER) {
526 char *name, *link, *date, *size, *type_str;
527 int ftype, max_len, len, j;
530 while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
533 ex_ftpdir_name_size_date(tmp->ptr, &name, &link, &date,
534 &size)) == FTPDIR_NONE)
536 if (!strcmp(".", name) || !strcmp("..", name))
541 if (ftype == FTPDIR_DIR) {
545 else if (ftype == FTPDIR_LINK) {
554 flist[nfile++] = Sprintf("%s%s\n%s %5s%s", name, type_str, date,
556 if (nfile == nfile_max) {
558 flist = New_Reuse(char *, flist, nfile_max);
561 qsort(flist, nfile, sizeof(char *), strCmp);
562 for (j = 0; j < nfile; j++) {
564 date = strchr(fn, '\n');
565 if (*(date - 1) == '/') {
569 else if (*(date - 1) == '@') {
578 tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
580 if (ftype == FTPDIR_LINK)
581 Strcat_char(tmp, '@');
582 Strcat_m_charp(FTPDIRtmp, "<a href=\"", html_quote(file_quote(fn)),
583 "\">", html_quote(tmp->ptr), "</a>", NULL);
584 for (i = get_Str_strwidth(tmp); i <= max_len; i++) {
585 if ((max_len % 2 + i) % 2)
586 Strcat_char(FTPDIRtmp, '.');
588 Strcat_char(FTPDIRtmp, ' ');
590 tmp = convertLine(NULL, Strnew_charp(date), RAW_MODE, charset,
592 Strcat_m_charp(FTPDIRtmp, html_quote(tmp->ptr), "\n", NULL);
594 Strcat_charp(FTPDIRtmp, "</pre>\n");
597 while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
599 flist[nfile++] = mybasename(tmp->ptr);
600 if (nfile == nfile_max) {
602 flist = New_Reuse(char *, flist, nfile_max);
605 qsort(flist, nfile, sizeof(char *), strCmp);
606 for (i = 0; i < nfile; i++) {
608 tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
610 Strcat_m_charp(FTPDIRtmp, "<li><a href=\"",
611 html_quote(file_quote(fn)), "\">",
612 html_quote(tmp->ptr), "</a>\n", NULL);
614 Strcat_charp(FTPDIRtmp, "</ul>\n");
618 Strcat_charp(FTPDIRtmp, "</body>\n</html>\n");
620 closeFTPdata(current_ftp.data);
627 ftp_quit(¤t_ftp);
630 #define EX_SKIP_SPACE(cp) {\
631 while (IS_SPACE(*cp) && *cp != '\0') cp++;\
635 #define EX_SKIP_NONE_SPACE(cp) {\
636 while (!IS_SPACE(*cp) && *cp != '\0') cp++;\
640 #define EX_COUNT_DIGIT(cp) {\
642 while (*cp && IS_DIGIT(*cp))\
643 size = size * 10 + *(cp++) - '0';\
648 static Str size_int2str(clen_t);
651 ex_ftpdir_name_size_date(char *line, char **name, char **link, char **date,
654 int ftype = FTPDIR_NONE;
660 /* skip permission */
666 /* skip link count */
671 /* skip owner string */
673 EX_SKIP_NONE_SPACE(cp);
676 /* skip group string */
678 EX_SKIP_NONE_SPACE(cp);
685 if (*cp == ',') { /* device file ? */
688 EX_SKIP_NONE_SPACE(cp);
689 *sizep = allocStr(p, cp - p);
692 *sizep = size_int2str(size)->ptr;
697 /* loose check for i18n server */
700 EX_SKIP_NONE_SPACE(cp); /* month ? */
702 EX_SKIP_NONE_SPACE(cp); /* day ? */
704 EX_SKIP_NONE_SPACE(cp); /* year or time ? */
705 *date = allocStr(p, cp - p);
708 /* extract file name */
713 if ((p = strstr(cp, " -> ")) == NULL)
715 *name = allocStr(cp, p - cp);
716 *link = allocStr(p, -1);
721 *name = allocStr(cp, -1);
727 *name = allocStr(cp, -1);
737 size_int2str(clen_t size)
742 char *size_format, *unit_str;
745 for (unit = 0; unit < 3; unit++) {
751 if (!unit || dtmp > 100) {
752 size_format = "%.0f%s";
754 else if (dtmp > 10) {
755 size_format = "%.1f%s";
758 size_format = "%.2f%s";
774 size_str = Sprintf(size_format, dtmp, unit_str);