/* +----------------------------------------------------------------------+ | PHP HTML Embedded Scripting Language Version 3.0 | +----------------------------------------------------------------------+ | Copyright (c) 1997-1999 PHP Development Team (See Credits file) | +----------------------------------------------------------------------+ | This program is free software; you can redistribute it and/or modify | | it under the terms of one of the following licenses: | | | | A) the GNU General Public License as published by the Free Software | | Foundation; either version 2 of the License, or (at your option) | | any later version. | | | | B) the PHP License as published by the PHP Development Team and | | included in the distribution in the file: LICENSE | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU General Public License for more details. | | | | You should have received a copy of both licenses referred to here. | | If you did not, or have any questions about PHP licensing, please | | contact core@php.net. | +----------------------------------------------------------------------+ | Authors: | | Andrew Skalski | +----------------------------------------------------------------------+ */ /* $Id$ */ #include "php.h" #if HAVE_FTP #include #include #include #include #include #include #include #include #include "ftp.h" /* reads an ftp response, returns true on success, false on error */ static int ftp_getresp(ftpbuf_t *ftp); /* sets the ftp transfer type */ static int ftp_type(ftpbuf_t *ftp, ftptype_t type); /* opens up a data stream */ static databuf_t* ftp_getdata(ftpbuf_t *ftp); /* accepts the data connection, returns updated data buffer */ static databuf_t* data_accept(databuf_t *data); /* closes the data connection, returns NULL */ static databuf_t* data_close(databuf_t *data); /* generic file lister */ static char** ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path); /* IP and port conversion box */ union ipbox { unsigned long l[2]; unsigned short s[4]; unsigned char c[8]; }; ftpbuf_t* ftp_open(const char *host, short port) { int fd = -1; ftpbuf_t *ftp; struct sockaddr_in addr; struct hostent *he; int size; /* set up the address */ if ((he = gethostbyname(host)) == NULL) { herror("gethostbyname"); return NULL; } memset(&addr, 0, sizeof(addr)); memcpy(&addr.sin_addr, he->h_addr, he->h_length); addr.sin_family = AF_INET; addr.sin_port = port ? port : htons(21); /* alloc the ftp structure */ ftp = calloc(1, sizeof(*ftp)); if (ftp == NULL) { perror("calloc"); return NULL; } /* connect */ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); goto bail; } if (connect(fd, (struct sockaddr*) &addr, sizeof(addr)) == -1) { perror("connect"); goto bail; } size = sizeof(addr); if (getsockname(fd, (struct sockaddr*) &addr, &size) == -1) { perror("getsockname"); goto bail; } ftp->localaddr = addr.sin_addr; if ((ftp->fp = fdopen(fd, "r+")) == NULL) { perror("fdopen"); goto bail; } if (!ftp_getresp(ftp) || ftp->resp != 220) { goto bail; } return ftp; bail: if (ftp->fp) fclose(ftp->fp); else if (fd != -1) close(fd); free(ftp); return NULL; } ftpbuf_t* ftp_close(ftpbuf_t *ftp) { if (ftp == NULL) return NULL; if (ftp->fp) fclose(ftp->fp); ftp_gc(ftp); free(ftp); return NULL; } void ftp_gc(ftpbuf_t *ftp) { if (ftp == NULL) return; free(ftp->pwd); ftp->pwd = NULL; free(ftp->syst); ftp->syst = NULL; } int ftp_quit(ftpbuf_t *ftp) { if (ftp == NULL) return 0; fprintf(ftp->fp, "QUIT\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 221) return 0; free(ftp->pwd); ftp->pwd = NULL; return 1; } int ftp_login(ftpbuf_t *ftp, const char *user, const char *pass) { if (ftp == NULL) return 0; fprintf(ftp->fp, "USER %s\r\n", user); if (!ftp_getresp(ftp)) return 0; if (ftp->resp == 230) return 1; if (ftp->resp != 331) return 0; fprintf(ftp->fp, "PASS %s\r\n", pass); if (!ftp_getresp(ftp)) return 0; return (ftp->resp == 230); } int ftp_reinit(ftpbuf_t *ftp) { if (ftp == NULL) return 0; ftp_gc(ftp); fprintf(ftp->fp, "REIN\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 220) return 0; return 1; } const char* ftp_syst(ftpbuf_t *ftp) { char *syst, *end; if (ftp == NULL) return NULL; /* default to cached value */ if (ftp->syst) return ftp->syst; fprintf(ftp->fp, "SYST\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 215) return NULL; syst = ftp->inbuf; if ((end = strchr(syst, ' '))) *end = 0; ftp->syst = strdup(syst); if (end) *end = ' '; return ftp->syst; } const char* ftp_pwd(ftpbuf_t *ftp) { char *pwd, *end; if (ftp == NULL) return NULL; /* default to cached value */ if (ftp->pwd) return ftp->pwd; fprintf(ftp->fp, "PWD\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 257) return NULL; /* copy out the pwd from response */ if ((pwd = strchr(ftp->inbuf, '"')) == NULL) return NULL; end = strrchr(++pwd, '"'); *end = 0; ftp->pwd = strdup(pwd); *end = '"'; return ftp->pwd; } int ftp_chdir(ftpbuf_t *ftp, const char *dir) { if (ftp == NULL) return 0; free(ftp->pwd); ftp->pwd = NULL; fprintf(ftp->fp, "CWD %s\r\n", dir); if (!ftp_getresp(ftp) || ftp->resp != 250) return 0; return 1; } int ftp_cdup(ftpbuf_t *ftp) { if (ftp == NULL) return 0; free(ftp->pwd); ftp->pwd = NULL; fprintf(ftp->fp, "CDUP\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 250) return 0; return 1; } char* ftp_mkdir(ftpbuf_t *ftp, const char *dir) { char *mkd, *end; if (ftp == NULL) return NULL; fprintf(ftp->fp, "MKD %s\r\n", dir); if (!ftp_getresp(ftp) || ftp->resp != 257) return NULL; /* copy out the dir from response */ if ((mkd = strchr(ftp->inbuf, '"')) == NULL) return NULL; end = strrchr(++mkd, '"'); *end = 0; mkd = strdup(mkd); *end = '"'; return mkd; } int ftp_rmdir(ftpbuf_t *ftp, const char *dir) { if (ftp == NULL) return 0; fprintf(ftp->fp, "RMD %s\r\n", dir); if (!ftp_getresp(ftp) || ftp->resp != 250) return 0; return 1; } char** ftp_nlist(ftpbuf_t *ftp, const char *path) { return ftp_genlist(ftp, "NLST", path); } char** ftp_list(ftpbuf_t *ftp, const char *path) { return ftp_genlist(ftp, "LIST", path); } int ftp_getresp(ftpbuf_t *ftp) { char tag[4]; int ch; char *buf; char *ptr; if (ftp == NULL) return 0; buf = ftp->inbuf; ftp->resp = 0; do { if (!fread(tag, 4, 1, ftp->fp)) return 0; if (tag[3] == '-') { while ((ch = getc(ftp->fp)) != '\n') if (ch == EOF) { return 0; } } else if (tag[3] == ' ') { ptr = fgets(buf, FTP_BUFSIZE, ftp->fp); if (!ptr || !(ptr = strchr(buf, '\n'))) return 0; if (ptr > buf && ptr[-1] == '\r') ptr--; *ptr = 0; } else { return 0; } } while (tag[3] == '-'); /* translate the tag */ if (!isdigit(tag[0]) || !isdigit(tag[1]) || !isdigit(tag[2])) return 0; ftp->resp = 100 * (tag[0] - '0') + 10 * (tag[1] - '0') + (tag[2] - '0'); return 1; } int ftp_type(ftpbuf_t *ftp, ftptype_t type) { char typechar; if (ftp == NULL) return 0; if (type == ftp->type) return 1; if (type == FTPTYPE_ASCII) typechar = 'A'; else if (type == FTPTYPE_IMAGE) typechar = 'I'; else return 0; fprintf(ftp->fp, "TYPE %c\r\n", typechar); if (!ftp_getresp(ftp) || ftp->resp != 200) return 0; ftp->type = type; return 1; } int ftp_pasv(ftpbuf_t *ftp, int pasv) { char *ptr; union ipbox ipbox; unsigned long b[6]; int n; if (ftp == NULL) return 0; if (pasv && ftp->pasv == 2) return 1; ftp->pasv = 0; if (!pasv) return 1; fprintf(ftp->fp, "PASV\r\n"); if (!ftp_getresp(ftp) || ftp->resp != 227) return 0; /* parse out the IP and port */ for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); n = sscanf(ptr, "%u,%u,%u,%u,%u,%u", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]); if (n != 6) return 0; for (n=0; n<6; n++) ipbox.c[n] = b[n]; memset(&ftp->pasvaddr, 0, sizeof(ftp->pasvaddr)); ftp->pasvaddr.sin_family = AF_INET; ftp->pasvaddr.sin_addr.s_addr = ipbox.l[0]; ftp->pasvaddr.sin_port = ipbox.s[2]; ftp->pasv = 2; return 1; } int ftp_get(ftpbuf_t *ftp, FILE *outfp, const char *path, ftptype_t type) { databuf_t *data = NULL; int ch, lastch; if (ftp == NULL) return 0; if (!ftp_type(ftp, type)) goto bail; if ((data = ftp_getdata(ftp)) == NULL) goto bail; fprintf(ftp->fp, "RETR %s\r\n", path); if (!ftp_getresp(ftp) || ftp->resp != 150) goto bail; if ((data = data_accept(data)) == NULL) goto bail; lastch = 0; while ((ch = getc(data->fp)) != EOF) { if (type == FTPTYPE_ASCII) { if (lastch == '\r' && ch != '\n') putc('\r', outfp); if (ch != '\r') putc(ch, outfp); lastch = ch; } else { putc(ch, outfp); } } if (type == FTPTYPE_ASCII && lastch == '\r') putc('\r', outfp); if (ferror(data->fp) || ferror(outfp)) goto bail; data = data_close(data); if (!ftp_getresp(ftp) || ftp->resp != 226) goto bail; return 1; bail: data_close(data); return 0; } int ftp_put(ftpbuf_t *ftp, const char *path, FILE *infp, ftptype_t type) { databuf_t *data = NULL; int ch; if (ftp == NULL) return 0; if (!ftp_type(ftp, type)) goto bail; if ((data = ftp_getdata(ftp)) == NULL) goto bail; fprintf(ftp->fp, "STOR %s\r\n", path); if (!ftp_getresp(ftp) || ftp->resp != 150) goto bail; if ((data = data_accept(data)) == NULL) goto bail; while ((ch = getc(infp)) != EOF) { if (type == FTPTYPE_ASCII && ch == '\n') putc('\r', data->fp); putc(ch, data->fp); } if (ferror(data->fp) || ferror(infp)) goto bail; data = data_close(data); if (!ftp_getresp(ftp) || ftp->resp != 226) goto bail; return 1; bail: data_close(data); return 0; } int ftp_size(ftpbuf_t *ftp, const char *path) { if (ftp == NULL) return -1; fprintf(ftp->fp, "SIZE %s\r\n", path); if (!ftp_getresp(ftp) || ftp->resp != 213) return -1; return atoi(ftp->inbuf); } time_t ftp_mdtm(ftpbuf_t *ftp, const char *path) { time_t stamp; struct tm *gmt; struct tm tm; char *ptr; int n; if (ftp == NULL) return -1; fprintf(ftp->fp, "MDTM %s\r\n", path); if (!ftp_getresp(ftp) || ftp->resp != 213) return -1; /* parse out the timestamp */ for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); n = sscanf(ptr, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (n != 6) return -1; tm.tm_year -= 1900; tm.tm_mon--; tm.tm_isdst = -1; /* figure out the GMT offset */ stamp = time(NULL); gmt = gmtime(&stamp); gmt->tm_isdst = -1; /* apply the GMT offset */ tm.tm_sec += stamp - mktime(gmt); tm.tm_isdst = gmt->tm_isdst; stamp = mktime(&tm); return stamp; } int ftp_delete(ftpbuf_t *ftp, const char *path) { if (ftp == NULL) return 0; fprintf(ftp->fp, "DELE %s\r\n", path); if (!ftp_getresp(ftp) || ftp->resp != 250) return 0; return 1; } int ftp_rename(ftpbuf_t *ftp, const char *src, const char *dest) { if (ftp == NULL) return 0; fprintf(ftp->fp, "RNFR %s\r\n", src); if (!ftp_getresp(ftp) || ftp->resp != 350) return 0; fprintf(ftp->fp, "RNTO %s\r\n", dest); if (!ftp_getresp(ftp) || ftp->resp != 250) return 0; return 1; } /* static functions */ databuf_t* ftp_getdata(ftpbuf_t *ftp) { int fd = -1; databuf_t *data; struct sockaddr_in addr; int size; union ipbox ipbox; /* ask for a passive connection if we need one */ if (ftp->pasv && !ftp_pasv(ftp, 1)) return NULL; /* alloc the data structure */ data = calloc(1, sizeof(*data)); if (data == NULL) { perror("calloc"); return NULL; } data->listener = -1; data->type = ftp->type; /* bind/listen */ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); goto bail; } /* passive connection handler */ if (ftp->pasv) { /* clear the ready status */ ftp->pasv = 1; /* connect */ if (connect(fd, (struct sockaddr*) &ftp->pasvaddr, sizeof(ftp->pasvaddr)) == -1) { perror("connect"); close(fd); free(data); return NULL; } /* wrap fd in a FILE stream */ data->fp = fdopen(fd, "r+"); if (data->fp == NULL) { perror("fdopen"); close(fd); free(data); return NULL; } return data; } /* active (normal) connection */ /* bind to a local address */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = 0; if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) == -1) { perror("bind"); goto bail; } size = sizeof(addr); if (getsockname(fd, (struct sockaddr*) &addr, &size) == -1) { perror("getsockname"); goto bail; } if (listen(fd, 5) == -1) { perror("listen"); goto bail; } data->listener = fd; /* send the PORT */ ipbox.l[0] = ftp->localaddr.s_addr; ipbox.s[2] = addr.sin_port; fprintf(ftp->fp, "PORT %u,%u,%u,%u,%u,%u\r\n", ipbox.c[0], ipbox.c[1], ipbox.c[2], ipbox.c[3], ipbox.c[4], ipbox.c[5]); if (!ftp_getresp(ftp) || ftp->resp != 200) goto bail; return data; bail: if (fd != -1) close(fd); free(data); return NULL; } databuf_t* data_accept(databuf_t *data) { struct sockaddr_in addr; int size; int fd; if (data->fp) return data; size = sizeof(addr); fd = accept(data->listener, (struct sockaddr*) &addr, &size); close(data->listener); data->listener = -1; if (fd == -1) { free(data); return NULL; } if ((data->fp = fdopen(fd, "r+")) == NULL) { close(fd); free(data); return NULL; } return data; } databuf_t* data_close(databuf_t *data) { if (data == NULL) return NULL; if (data->listener != -1) close(data->listener); if (data->fp) fclose(data->fp); free(data); return NULL; } char** ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path) { FILE *tmpfp = NULL; databuf_t *data = NULL; int ch, lastch; int size; int lines; char **ret = NULL; char **entry; char *text; if ((tmpfp = tmpfile()) == NULL) return NULL; if (!ftp_type(ftp, FTPTYPE_ASCII)) goto bail; if ((data = ftp_getdata(ftp)) == NULL) goto bail; if (path) fprintf(ftp->fp, "%s %s\r\n", cmd, path); else fprintf(ftp->fp, "%s\r\n", cmd); if (!ftp_getresp(ftp) || ftp->resp != 150) goto bail; /* pull data buffer into tmpfile */ if ((data = data_accept(data)) == NULL) goto bail; size = 0; lines = 0; lastch = 0; while ((ch = getc(data->fp)) != EOF) { if (ch == '\n' && lastch == '\r') lines++; else size++; putc(ch, tmpfp); lastch = ch; } data = data_close(data); if (ferror(tmpfp)) goto bail; rewind(tmpfp); ret = malloc((lines + 1) * sizeof(char**) + size * sizeof(char*)); if (ret == NULL) { perror("malloc"); goto bail; } entry = ret; text = (char*) (ret + lines + 1); *entry = text; lastch = 0; while ((ch = getc(tmpfp)) != EOF) { if (ch == '\n' && lastch == '\r') { *(text - 1) = 0; *++entry = text; } else { *text++ = ch; } lastch = ch; } *entry = NULL; if (ferror(tmpfp)) goto bail; fclose(tmpfp); if (!ftp_getresp(ftp) || ftp->resp != 226) { free(ret); return NULL; } return ret; bail: data_close(data); fclose(tmpfp); free(ret); return NULL; } #endif /* HAVE_FTP */