/* ** Copyright 2000-2004 University of Illinois Board of Trustees ** Copyright 2000-2004 Mark D. Roth ** All rights reserved. ** ** handle.c - code to initialize an FTP handle for libfget ** ** Mark D. Roth */ #include #include #include #ifdef STDC_HEADERS # include # include # include #endif #include const char libfget_version[] = PACKAGE_VERSION; const int libfget_is_thread_safe = #if defined(HAVE_GETHOSTBYNAME_R) && defined(HAVE_GETSERVBYNAME_R) && defined(HAVE_LOCALTIME_R) 1; #else 0; #endif /* ** ftp_connect() - open a connection to hostname */ int ftp_connect(FTP **ftp, char *hostname, char *banner, size_t bannersize, unsigned short flags, ...) { va_list args; int code; #ifdef DEBUG printf("==> ftp_connect(ftp=0x%lx, hostname=\"%s\", banner=0x%lx, " "bannersize=%lu, flags=%hu, ...)\n", ftp, hostname, banner, (unsigned long)bannersize, flags); #endif /* ** allocate and initialize FTP handle */ *ftp = (FTP *)calloc(1, sizeof(FTP)); if (*ftp == NULL) return -1; _ftp_dircache_init(*ftp); _ftp_init_options(*ftp); /* process stdarg options */ va_start(args, flags); _vftp_set_options(*ftp, args); va_end(args); /* ** establish control connection */ if (fget_netio_new(&((*ftp)->ftp_control), FTPBUFSIZE, hostname, (*ftp)->ftp_io_timeout, (BIT_ISSET(flags, FTP_CONNECT_DNS_RR) ? NETIO_CONNECT_DNS_RR : 0), NETIO_OPT_DEFAULT_SERVICE, "ftp", NETIO_OPT_DEFAULT_PORT, (unsigned short)21, NETIO_OPT_SEND_HOOK, (*ftp)->ftp_send_hook, NETIO_OPT_RECV_HOOK, (*ftp)->ftp_recv_hook, NETIO_OPT_HOOK_HANDLE, *ftp, NETIO_OPT_HOOK_DATA, (*ftp)->ftp_hook_data, 0) == -1) goto connect_error; /* ** read banner from server */ do { if (_ftp_get_response(*ftp, &code, banner, bannersize) == -1) goto connect_error; } while (code == 120); if (code != 220) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; goto connect_error; } return 0; connect_error: if (*ftp != NULL) { if ((*ftp)->ftp_control != NULL) fget_netio_free((*ftp)->ftp_control); free(*ftp); *ftp = NULL; } return -1; } /* check for server features */ static int _ftp_feat(FTP *ftp) { char buf[FTPBUFSIZE]; char *linep, *nextp = buf; int code; /* check if the server supports the FEAT command */ if (_ftp_send_command(ftp, "FEAT") == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code == 211) { BIT_SET(ftp->ftp_features, FTP_FEAT_FEAT); while ((linep = strsep(&nextp, "\n")) != NULL) { if (*linep != ' ') continue; #ifdef DEBUG printf(" _ftp_feat(): linep=\"%s\"\n", linep); #endif /* skip leading space */ linep++; if (strncasecmp(linep, "SIZE", 4) == 0) BIT_SET(ftp->ftp_features, FTP_FEAT_SIZE); else if (strncasecmp(linep, "MDTM", 4) == 0) BIT_SET(ftp->ftp_features, FTP_FEAT_MDTM); else if (strncasecmp(linep, "REST STREAM", 11) == 0) BIT_SET(ftp->ftp_features, FTP_FEAT_REST_STREAM); else if (strncasecmp(linep, "TVFS", 4) == 0) BIT_SET(ftp->ftp_features, FTP_FEAT_TVFS); else if (strncasecmp(linep, "MLST", 4) == 0) BIT_SET(ftp->ftp_features, FTP_FEAT_MLST); } } else if (code == 421) { errno = ECONNRESET; return -1; } return 0; } /* option negotiation */ int _ftp_opts(FTP *ftp, char *command, char *options) { char buf[FTPBUFSIZE]; int code; if (!BIT_ISSET(ftp->ftp_features, FTP_FEAT_FEAT)) { errno = EINVAL; return -1; } if (_ftp_send_command(ftp, "OPTS %s%s%s", command, (options ? " " : ""), (options ? options : "")) == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code != 200) { if (code == 421) errno = ECONNRESET; else if (code == 451) errno = EAGAIN; else if (code == 500 || code == 502) /* ** if we get 500 or 502, that means ** that the server doesn't support the OPTS ** command. this should never happen, because ** RFC-2389 requires that if you support FEAT, ** you also support OPTS. however, Pure-FTPd ** is known to violate this requirement. */ errno = ENOSYS; else errno = EINVAL; return -1; } return 0; } /* ** _ftp_init_session() - initialize session parameters ** ** This initialization must be done after authentication, because the ** server responses may be different (or may fail) for non-authenticated ** connections. Thus, this function is called from the end of ftp_login(). ** ** Note: In the future, new functions to support RFC-2228-style ** authentication mechansims may be added as alternatives to ** ftp_login(). Any such functions will have to call ** _ftp_init_session() after a successful authentication, just ** as ftp_login() does. */ static int _ftp_init_session(FTP *ftp) { /* check for features */ if (_ftp_feat(ftp) == -1) return -1; /* ** get remote system type. ** Note: old SunOS 4.x FTP servers don't grok SYST, ** so don't bitch if this fails. */ ftp_systype(ftp); /* get current working directory */ if (ftp_getcwd(ftp) == NULL) return -1; /* set TYPE to binary by default */ if (ftp_type(ftp, FTP_TYPE_BINARY) == -1) return -1; return 0; } /* ** ftp_login() - login to the FTP server ** ** Note: This function is seperated from ftp_connect() for two main reasons: ** ** 1. Applications that interactively prompt the user for a password ** may need to retry if the user mistyped their password on the first ** attempt, and it is useful to be able to do that without trying to ** establish a second connection. ** ** 2. In the future, new functions to support RFC-2228-style ** authentication mechansims may be added as alternatives to ** ftp_login(). Keeping this seperate from ftp_connect() will ** allow those new functions to be added without changing the ** existing API. */ int ftp_login(FTP *ftp, char *login, char *pass) { int code; char buf[FTPBUFSIZE]; if (ftp->ftp_login[0] != '\0') { if (_ftp_send_command(ftp, "REIN") == -1) return -1; do { if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; } while (code == 120); if (code != 220) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } ftp->ftp_login[0] = '\0'; } if (_ftp_send_command(ftp, "USER %s", login) == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; #ifdef DEBUG printf(" ftp_login(): got response from USER command (code == %d)\n", code); #endif if (code != 230) { if (code != 331) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } if (_ftp_send_command(ftp, "PASS %s", pass) == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code != 230) { if (code == 530) errno = EACCES; else if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } } strlcpy(ftp->ftp_login, login, sizeof(ftp->ftp_login)); return _ftp_init_session(ftp); } /* close ftp connection */ int ftp_quit(FTP *ftp, unsigned short flags) { char buf[FTPBUFSIZE]; int code, retval = 0; if (ftp->ftp_data_listen != NULL) fget_netio_free(ftp->ftp_data_listen); if (ftp->ftp_data != NULL) { if (BIT_ISSET(flags, FTP_QUIT_FAST)) fget_netio_free(ftp->ftp_data); else _ftp_data_close(ftp); } if (! BIT_ISSET(flags, FTP_QUIT_FAST)) { if (_ftp_send_command(ftp, "QUIT") == -1) return -1; _ftp_get_response(ftp, &code, buf, sizeof(buf)); } retval = fget_netio_free(ftp->ftp_control); _ftp_dircache_free(ftp); free(ftp); return retval; } /* return file descriptor for FTP control connection */ int ftp_fd(FTP *ftp) { return fget_netio_fd(ftp->ftp_control); } /* return file descriptor for FTP data connection */ int ftp_data_fd(FTP *ftp) { return (ftp->ftp_data == NULL ? -1 : fget_netio_fd(ftp->ftp_data)); } /* return hostname of FTP server */ char * ftp_get_host(FTP *ftp) { return fget_netio_get_host(ftp->ftp_control); } /* return login used to authenticate to FTP server */ char * ftp_whoami(FTP *ftp) { return ftp->ftp_login; }