php-src/ext/ftp/ftp.c
1999-10-04 18:30:37 +00:00

909 lines
16 KiB
C

/*
+----------------------------------------------------------------------+
| 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 <askalski@chek.com> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#include "php.h"
#if HAVE_FTP
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#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 */