chimera2/proto/ftp.c

970 lines
18 KiB
C

/*
* ftp.c
*
* Copyright 1993-1997, John Kilburg (john@cs.unlv.edu)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of 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.
*
* 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 the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "port_before.h"
#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include "port_after.h"
#include "Chimera.h"
#include "ChimeraStream.h"
#include "ChimeraSource.h"
#include "ChimeraAuth.h"
#include "mime.h"
#define PRINT_RATE 10
#define FM_OPEN 0
#define FM_RU 1
#define FM_RK 2
#define FM_DONE 3
#define FM_SEND 4
#define FM_WAITING 5
#define MSGLEN 1024
#define REQLEN 1024
static struct ftp_message
{
char *name;
char *def;
} ftp_messages[] =
{
{ "ftp.open", "Connecting to " },
{ "ftp.read_unknown", " bytes read so far." },
{ "ftp.read_known", " bytes remaining." },
{ "ftp.done", "FTP read finished." },
{ "ftp.send", "Sending request..." },
{ "ftp.waiting", "Waiting for a response from " },
{ NULL, NULL },
};
typedef struct
{
char *username;
char *password;
char *hostname;
int port;
bool good;
} FTPPassword;
typedef struct
{
MemPool mp;
char *header;
char *trailer;
GList passwords;
} FTPClass;
typedef struct FTPInfoP FTPInfo;
typedef void (*FTPProc) _ArgProto((FTPInfo *));
static void FTPDestroy _ArgProto((void *));
static void FTPData _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPDirData _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPSimpleRead _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPDummy _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPDirRead _ArgProto((FTPInfo *));
static void FTPNlst _ArgProto((FTPInfo *));
static void FTPCwd _ArgProto((FTPInfo *));
static void FTPRetrieve _ArgProto((FTPInfo *));
static void FTPPassive _ArgProto((FTPInfo *));
static void FTPSize _ArgProto((FTPInfo *));
static void FTPType _ArgProto((FTPInfo *));
static void FTPUser _ArgProto((FTPInfo *));
static void FTPPass _ArgProto((FTPInfo *));
static void *FTPInit _ArgProto((ChimeraSource, ChimeraRequest *, void *));
static void FTPClassDestroy _ArgProto((void *));
static void FTPCancel _ArgProto((void *));
static void FTPGetData _ArgProto((void *, byte **, size_t *, MIMEHeader *));
static int ftp_strcmp _ArgProto((const void *, const void *));
static void FTPAuthCallback _ArgProto((void *, char *, char *));
void InitModule_FTP _ArgProto((ChimeraResources));
struct FTPInfoP
{
MemPool mp;
ChimeraSource ws;
ChimeraResources cres;
ChimeraRequest *wr;
FTPClass *fc;
FTPProc rfunc;
char msgbuf[MSGLEN];
char request[REQLEN];
char *msg[sizeof(ftp_messages) / sizeof(ftp_messages[0])];
int rcount;
FTPPassword *fp;
ChimeraAuth wa;
/* control stream */
ChimeraStream cs;
byte *cb; /* control buffer */
size_t cblen; /* control buffer content length */
size_t cbsize; /* control buffer size */
bool ignore_err; /* recognition of control errors */
/* data stream */
ChimeraStream ds;
byte *db; /* data buffer */
size_t dblen; /* data buffer content length */
size_t dbsize; /* data buffer size */
size_t dbmax; /* data buffer maximum */
MIMEHeader mh;
};
static char *FTPDirToHTML _ArgProto((FTPInfo *));
static void FTPWrite _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPDestroyStream _ArgProto((FTPInfo *));
static void FTPReadData _ArgProto((FTPInfo *, ChimeraStreamCallback));
static void FTPReadControl _ArgProto((FTPInfo *, ChimeraStreamCallback));
static int FTPParseResponse _ArgProto((FTPInfo *));
static void FTPDummy _ArgProto((ChimeraStream, ssize_t, void *));
static void FTPFailure _ArgProto((FTPInfo *));
static void FTPSimple _ArgProto((FTPInfo *, FTPProc, bool));
static void FTPAddPassword _ArgProto((FTPInfo *, char *, char *));
static FTPPassword *FTPFindPassword _ArgProto((FTPInfo *, char *));
/*
* FTPFindPassword
*/
static FTPPassword *
FTPFindPassword(fi, username)
FTPInfo *fi;
char *username;
{
FTPPassword *fp;
char *hostname;
int port;
hostname = fi->wr->up->hostname;
port = fi->wr->up->port == 0 ? 21:fi->wr->up->port;
for (fp = (FTPPassword *)GListGetHead(fi->fc->passwords); fp != NULL;
fp = (FTPPassword *)GListGetNext(fi->fc->passwords))
{
if (strcmp(username, fp->username) == 0 &&
strcasecmp(hostname, fp->hostname) == 0 &&
port == fp->port &&
fp->good)
{
return(fp);
}
}
return(NULL);
}
/*
* FTPAddPassword
*/
static void
FTPAddPassword(fi, username, password)
FTPInfo *fi;
char *username;
char *password;
{
FTPPassword *fp;
MemPool mp;
if (username == NULL) return;
if (fi->wr->up->hostname == NULL) return;
mp = fi->fc->mp;
fp = (FTPPassword *)MPCGet(mp, sizeof(FTPPassword));
fp->username = MPStrDup(mp, username);
if (password != NULL) fp->password = MPStrDup(mp, password);
else fp->password = MPStrDup(mp, "");
fp->hostname = MPStrDup(mp, fi->wr->up->hostname);
fp->port = fi->wr->up->port == 0 ? 21:fi->wr->up->port;
GListAddHead(fi->fc->passwords, fp);
fi->fp = fp;
return;
}
/*
* FTPFailure
*/
static void
FTPFailure(fi)
FTPInfo *fi;
{
SourceStop(fi->ws, "ftp failure");
FTPDestroyStream(fi);
return;
}
/*
* FTPDestroyStream
*/
static void
FTPDestroyStream(fi)
FTPInfo *fi;
{
if (fi->ds != NULL)
{
StreamDestroy(fi->ds);
fi->ds = NULL;
}
if (fi->cs != NULL)
{
StreamDestroy(fi->cs);
fi->cs = NULL;
}
return;
}
/*
* FTPDestroy
*/
static void
FTPDestroy(closure)
void *closure;
{
FTPInfo *fi = (FTPInfo *)closure;
FTPDestroyStream(fi);
if (fi->db != NULL) free_mem(fi->db);
if (fi->cb != NULL) free_mem(fi->cb);
if (fi->mh != NULL) MIMEDestroyHeader(fi->mh);
MPDestroy(fi->mp);
return;
}
/*
* FTPReadData
*/
static void
FTPReadData(fi, func)
FTPInfo *fi;
ChimeraStreamCallback func;
{
size_t len;
if (fi->dbmax > 0)
{
len = fi->dbmax - fi->dblen;
if (len > BUFSIZ) len = BUFSIZ;
}
else len = BUFSIZ;
if (len > 0)
{
if (fi->db == NULL) fi->db = (byte *)alloc_mem(len);
else fi->db = (byte *)realloc_mem(fi->db, fi->dbsize + len);
fi->dbsize += len;
}
StreamRead(fi->ds, fi->db + fi->dblen, len, func, fi);
return;
}
/*
* FTPReadControl
*/
static void
FTPReadControl(fi, func)
FTPInfo *fi;
ChimeraStreamCallback func;
{
if (fi->cblen + BUFSIZ > fi->cbsize)
{
if (fi->cb == NULL) fi->cb = (byte *)alloc_mem(BUFSIZ);
else fi->cb = (byte *)realloc_mem(fi->cb, fi->cbsize + BUFSIZ);
fi->cbsize += BUFSIZ;
}
StreamRead(fi->cs, fi->cb + fi->cblen, BUFSIZ, func, fi);
return;
}
/*
* FTPData
*/
static void
FTPData(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
FTPInfo *fi = (FTPInfo *)closure;
if (len < 0)
{
FTPFailure(fi);
return;
}
if (len == 0)
{
SourceSendMessage(fi->ws, fi->msg[FM_DONE]);
FTPCancel(fi);
SourceEnd(fi->ws);
}
else
{
fi->dblen += len;
if (fi->dbmax > 0)
{
if (fi->rcount++ % PRINT_RATE == 0)
{
snprintf (fi->msgbuf, sizeof(fi->msgbuf),
"%ld %s", (long)(fi->dbmax - fi->dblen), fi->msg[FM_RK]);
SourceSendMessage(fi->ws, fi->msgbuf);
}
SourceAdd(fi->ws);
}
else
{
if (fi->rcount++ % PRINT_RATE == 0)
{
snprintf (fi->msgbuf, sizeof(fi->msgbuf),
"%ld %s", (long)fi->dblen, fi->msg[FM_RU]);
SourceSendMessage(fi->ws, fi->msgbuf);
}
}
FTPReadData(fi, FTPData);
}
return;
}
/*
* ftp_dirdata
*/
static void
FTPDirData(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
FTPInfo *fi = (FTPInfo *)closure;
if (len > 0)
{
fi->dblen += len;
FTPReadData(fi, FTPDirData);
return;
}
else if (len < 0)
{
FTPFailure(fi);
return;
}
MIMEAddField(fi->mh, "content-type", "text/html");
MIMEAddField(fi->mh, "x-url", fi->wr->url);
fi->db = FTPDirToHTML(fi);
fi->dblen = strlen(fi->db);
FTPDestroyStream(fi);
SourceInit(fi->ws, fi->fp == NULL);
SourceEnd(fi->ws);
return;
}
/*
* FTPParseResponse
*/
static int
FTPParseResponse(fi)
FTPInfo *fi;
{
char *cp;
char *b = (char *)fi->cb;
size_t len = fi->cblen;
if (b[len - 1] == '\n')
{
if (*(b + 3) == ' ') return(atoi(b));
for (cp = b + len - 2; cp >= b; cp--)
{
if (*cp == '\n' && *(cp + 4) == ' ') return(atoi(cp + 1));
}
}
return(-1);
}
/*
* FTPSimpleRead
*/
static void
FTPSimpleRead(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
int ecode;
FTPInfo *fi = (FTPInfo *)closure;
if (len < 0)
{
FTPFailure(fi);
return;
}
fi->cblen += len;
if ((ecode = FTPParseResponse(fi)) == -1)
{
FTPReadControl(fi, FTPSimpleRead);
return;
}
if (ecode < 400 || fi->ignore_err)
{
(fi->rfunc)(fi);
fi->cblen = 0;
}
else FTPFailure(fi);
return;
}
/*
* FTPWrite
*/
static void
FTPWrite(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
FTPReadControl((FTPInfo *)closure, FTPSimpleRead);
return;
}
/*
* FTPSimple
*/
static void
FTPSimple(fi, rfunc, ignore_err)
FTPInfo *fi;
FTPProc rfunc;
bool ignore_err;
{
fi->rfunc = rfunc;
fi->ignore_err = ignore_err;
SourceSendMessage(fi->ws, fi->msg[FM_SEND]);
StreamWrite(fi->cs, (byte *)fi->request, strlen(fi->request),
FTPWrite, fi);
return;
}
/*
* FTPDirRead
*/
static void
FTPDirRead(fi)
FTPInfo *fi;
{
FTPReadData(fi, FTPDirData);
return;
}
/*
* FTPNlst
*/
static void
FTPNlst(fi)
FTPInfo *fi;
{
snprintf (fi->request, sizeof(fi->request), "NLST\r\n");
FTPSimple(fi, FTPDirRead, false);
return;
}
/*
* FTPDummy
*/
static void
FTPDummy(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
return;
}
/*
* FTPCwd
*/
static void
FTPCwd(fi)
FTPInfo *fi;
{
int ecode;
char *filename;
filename = fi->wr->up->filename;
sscanf((char*)fi->cb, "%d", &ecode);
if (ecode < 400)
{
char *content;
if ((content = ChimeraExt2Content(fi->cres, filename)) == NULL)
{
content = "text/plain";
}
MIMEAddField(fi->mh, "content-type", content);
MIMEAddField(fi->mh, "x-url", fi->wr->url);
SourceInit(fi->ws, fi->fp == NULL);
FTPReadData(fi, FTPData);
FTPReadControl(fi, FTPDummy);
}
else
{
snprintf (fi->request, sizeof(fi->request), "CWD %s\r\n", filename);
FTPSimple(fi, FTPNlst, false);
}
return;
}
/*
* FTPRetrieve
*/
static void
FTPRetrieve(fi)
FTPInfo *fi;
{
int h0, h1, h2, h3, p0, p1, reply, n;
const char *format = "RETR %s\r\n";
char dhost[BUFSIZ];
int dport;
n = sscanf((char *)fi->cb, "%d %*[^(] (%d,%d,%d,%d,%d,%d)",
&reply, &h0, &h1, &h2, &h3, &p0, &p1);
if (n != 7 || reply != 227)
{
/* error */
return;
}
snprintf (dhost, sizeof(dhost), "%d.%d.%d.%d", h0, h1, h2, h3);
dport = (p0 << 8) + p1;
/*
* Check for error here.
*/
if ((fi->ds = StreamCreateINet(fi->cres, dhost, dport)) == NULL)
{
return;
}
snprintf (fi->request, sizeof(fi->request), format, fi->wr->up->filename);
FTPSimple(fi, FTPCwd, true);
return;
}
/*
* FTPPassive
*/
static void
FTPPassive(fi)
FTPInfo *fi;
{
int ecode;
long size;
sscanf((char *)fi->cb, "%d %ld", &ecode, &size);
fi->dbmax = (size_t)size;
if (ecode >= 400) fi->dbmax = 0;
else
{
fi->db = (byte *)realloc_mem(fi->db, fi->dbmax);
fi->dbsize = fi->dbmax;
}
snprintf (fi->request, sizeof(fi->request), "PASV\r\n");
FTPSimple(fi, FTPRetrieve, true);
return;
}
/*
* FTPSize
*/
static void
FTPSize(fi)
FTPInfo *fi;
{
snprintf (fi->request, sizeof(fi->request),
"SIZE %s\r\n", fi->wr->up->filename);
FTPSimple(fi, FTPPassive, true);
return;
}
/*
* FTPType
*/
static void
FTPType(fi)
FTPInfo *fi;
{
/* Now we know the password succeeded so check it as OK */
if (fi->fp != NULL) fi->fp->good = true;
snprintf (fi->request, sizeof(fi->request), "TYPE I\r\n");
FTPSimple(fi, FTPSize, false);
return;
}
/*
* FTPPass
*/
static void
FTPPass(fi)
FTPInfo *fi;
{
char *uname;
const char *pformat = "PASS %s\r\n";
if (fi->fp != NULL)
{
snprintf (fi->request, sizeof(fi->request), pformat, fi->fp->password);
}
else if (fi->wr->up->password != NULL)
{
snprintf (fi->request, sizeof(fi->request), pformat, fi->wr->up->password);
}
else if ((uname = getenv("EMAIL")) != NULL)
{
snprintf (fi->request, sizeof(fi->request), pformat, uname);
}
else
{
snprintf (fi->request, sizeof(fi->request),
"PASS -nobody@nowhere.org\r\n");
}
FTPSimple(fi, FTPType, false);
return;
}
/*
* FTPAuthCallback
*/
static void
FTPAuthCallback(closure, username, password)
void *closure;
char *username;
char *password;
{
FTPInfo *fi = (FTPInfo *)closure;
if (username == NULL || password == NULL)
{
FTPCancel(fi);
return;
}
FTPAddPassword(fi, username, password);
AuthDestroy(fi->wa);
fi->wa = NULL;
FTPUser(fi);
return;
}
/*
* FTPUser
*/
static void
FTPUser(fi)
FTPInfo *fi;
{
const char *uformat = "USER %s\r\n";
if (fi->fp != NULL)
{
snprintf (fi->request, sizeof(fi->request), uformat, fi->fp->username);
}
else if (fi->wr->up->username != NULL)
{
if (fi->wr->up->password == NULL)
{
if ((fi->fp = FTPFindPassword(fi, fi->wr->up->username)) == NULL)
{
fi->wa = AuthCreate(fi->cres, "Enter password", fi->wr->up->username,
FTPAuthCallback, fi);
if (fi->wa != NULL) return;
}
}
snprintf (fi->request, sizeof(fi->request), uformat, fi->wr->up->username);
}
else
{
snprintf (fi->request, sizeof(fi->request), "USER anonymous\r\n");
}
FTPSimple(fi, FTPPass, false);
return;
}
/*
* FTPInit
*/
static void *
FTPInit(ws, wr, class_closure)
ChimeraSource ws;
ChimeraRequest *wr;
void *class_closure;
{
FTPInfo *fi;
MemPool mp;
size_t mlen, tlen;
int i;
mp = MPCreate();
fi = (FTPInfo *)MPCGet(mp, sizeof(FTPInfo));
fi->mp = mp;
fi->ws = ws;
fi->wr = wr;
fi->cres = SourceToResources(ws);
fi->fc = (FTPClass *)class_closure;
fi->mh = MIMECreateHeader();
/* get the status messages and allocate a message work buffer */
mlen = 0;
for (i = 0; ftp_messages[i].name != NULL; i++)
{
if ((fi->msg[i] = ResourceGetString(fi->cres,
ftp_messages[0].name)) == NULL)
{
fi->msg[i] = ftp_messages[i].def;
}
if ((tlen = strlen(fi->msg[i])) > mlen) mlen = tlen;
}
snprintf (fi->msgbuf, sizeof(fi->msgbuf),
"%s %s", fi->msg[FM_OPEN], wr->up->hostname);
SourceSendMessage(fi->ws, fi->msgbuf);
fi->cs = StreamCreateINet(fi->cres,
wr->up->hostname,
wr->up->port == 0 ? 21:wr->up->port);
if (fi->cs == NULL)
{
FTPDestroy(fi);
return(NULL);
}
fi->rfunc = FTPUser;
FTPReadControl(fi, FTPSimpleRead);
return(fi);
}
/*
* FTPClassDestroy
*/
static void
FTPClassDestroy(closure)
void *closure;
{
FTPClass *fc = (FTPClass *)closure;
MPDestroy(fc->mp);
return;
}
/*
* FTPCancel
*/
static void
FTPCancel(closure)
void *closure;
{
FTPInfo *fi = (FTPInfo *)closure;
if (fi->wa != NULL)
{
AuthDestroy(fi->wa);
fi->wa = NULL;
}
FTPDestroyStream(fi);
return;
}
static void
FTPGetData(closure, data, len, mh)
void *closure;
byte **data;
size_t *len;
MIMEHeader *mh;
{
FTPInfo *fi = (FTPInfo *)closure;
*data = fi->db;
*len = fi->dblen;
*mh = fi->mh;
return;
}
/*
* InitModule_FTP
*/
void
InitModule_FTP(cres)
ChimeraResources cres;
{
ChimeraSourceHooks ph;
FTPClass *fc;
MemPool mp;
mp = MPCreate();
fc = (FTPClass *)MPCGet(mp, sizeof(FTPClass));
fc->mp = mp;
fc->passwords = GListCreateX(mp);
fc->header = ResourceGetString(cres, "ftp.dirheader");
if (fc->header == NULL)
{
fc->header = "<html><body><h2>FTP Directory</h2><ul>";
}
fc->trailer = ResourceGetString(cres, "ftp.dirtrailer");
if (fc->trailer == NULL) fc->trailer = "</ul></body></html>";
memset(&ph, 0, sizeof(ph));
ph.class_closure = fc;
ph.class_destroy = FTPClassDestroy;
ph.name = "ftp";
ph.init = FTPInit;
ph.destroy = FTPDestroy;
ph.stop = FTPCancel;
ph.getdata = FTPGetData;
SourceAddHooks(cres, &ph);
return;
}
static int
ftp_strcmp(a, b)
const void *a, *b;
{
return(strcmp(*((char **)a), *((char **)b)));
}
/*
* FTPDirToHTML
*
* Convert directory list to HTML
*/
static char *
FTPDirToHTML(fi)
FTPInfo *fi;
{
char *f;
int i;
char *hostname;
char *filename;
const char *entry = "<li><a href=ftp://%s:%d%s/%s>%s</a>\n";
byte *cp, *lastcp, *dname;
int sacount;
char **sa;
int olen, hlen, flen, elen;
FTPClass *fc = fi->fc;
if (fi->wr->up->username != NULL)
{
size_t t;
t = strlen(fi->wr->up->username);
t += strlen("@");
t += strlen(fi->wr->up->hostname);
hostname = MPGet(fi->mp, t + 1);
strcpy(hostname, fi->wr->up->username);
strcat(hostname, "@");
strcat(hostname, fi->wr->up->hostname);
}
else
{
hostname = fi->wr->up->hostname;
}
filename = fi->wr->up->filename;
hlen = strlen(hostname);
flen = strlen(filename);
elen = strlen(entry);
sacount = 0;
for (cp = fi->db, lastcp = cp + fi->dblen; cp < lastcp; cp++)
{
if (*cp == '\n') sacount++;
}
sa = (char **)MPGet(fi->mp, sizeof(char *) * sacount);
dname = fi->db;
olen = 0;
for (i = 0, cp = fi->db, lastcp = cp + fi->dblen; cp < lastcp; cp++)
{
if (*cp == '\n')
{
sa[i] = (char *)dname;
*cp = '\0';
dname = (byte *)cp + 1;
olen += hlen + 20 + flen + elen + strlen(sa[i]) * 2;
i++;
}
}
qsort(sa, sacount, sizeof(char *), ftp_strcmp);
olen += strlen(fc->header) + 2 * flen + hlen + strlen(fc->trailer) + 1;
f = (char *)alloc_mem(olen);
snprintf (f, olen, fc->header, filename, hostname, filename);
if (filename[0] != '\0' && filename[1] == '\0') filename = "";
for (i = 0; i < sacount; i++)
{
snprintf(f + strlen(f), olen - strlen(f), entry,
hostname,
fi->wr->up->port == 0 ? 21:fi->wr->up->port,
filename,
sa[i], sa[i]);
}
strcat(f, fc->trailer);
return(f);
}