From 7279bb68ef0363cd751abedfb391764a493bc714 Mon Sep 17 00:00:00 2001 From: Jorrit Herder Date: Fri, 17 Jun 2005 13:00:04 +0000 Subject: [PATCH] Applied MINIX 2.0.4 pathes provides by Al Woodhull. --- commands/cron/crontab.c | 3 + commands/cron/tab.c | 2 + commands/ftpd200/Makefile | 42 ++ commands/ftpd200/README | 35 + commands/ftpd200/README2 | 34 + commands/ftpd200/access.c | 134 ++++ commands/ftpd200/access.h | 12 + commands/ftpd200/file.c | 1252 ++++++++++++++++++++++++++++++++ commands/ftpd200/file.h | 32 + commands/ftpd200/ftpd.8 | 161 ++++ commands/ftpd200/ftpd.c | 405 +++++++++++ commands/ftpd200/ftpd.h | 37 + commands/ftpd200/ftpdsh | 14 + commands/ftpd200/net.c | 431 +++++++++++ commands/ftpd200/net.h | 13 + commands/ftpd200/setup.anonftp | 75 ++ commands/ps/ps.c | 7 +- drivers/libpci/pci_table.c | 20 +- kernel/clock.c | 2 +- kernel/proc.c | 43 +- kernel/proto.h | 1 - kernel/system.c | 2 +- lib/ansi/misc.c | 2 +- man/man1/ash.1 | 5 +- man/man1/eject.1 | 10 +- man/man1/elvis.1 | 3 +- man/man1/flex.1 | 3 +- man/man1/flexdoc.1 | 3 +- man/man1/fsck.1 | 5 +- man/man1/join.1 | 5 +- man/man1/makewhatis.1 | 3 +- man/man1/ps.1 | 17 +- man/man1/tar.1 | 3 +- man/man1/touch.1 | 28 +- 34 files changed, 2789 insertions(+), 55 deletions(-) create mode 100644 commands/ftpd200/Makefile create mode 100644 commands/ftpd200/README create mode 100644 commands/ftpd200/README2 create mode 100644 commands/ftpd200/access.c create mode 100644 commands/ftpd200/access.h create mode 100644 commands/ftpd200/file.c create mode 100644 commands/ftpd200/file.h create mode 100644 commands/ftpd200/ftpd.8 create mode 100644 commands/ftpd200/ftpd.c create mode 100644 commands/ftpd200/ftpd.h create mode 100755 commands/ftpd200/ftpdsh create mode 100644 commands/ftpd200/net.c create mode 100644 commands/ftpd200/net.h create mode 100755 commands/ftpd200/setup.anonftp diff --git a/commands/cron/crontab.c b/commands/cron/crontab.c index 13da7d390..6797ae28c 100755 --- a/commands/cron/crontab.c +++ b/commands/cron/crontab.c @@ -189,6 +189,9 @@ int main(int argc, char **argv) pflag= 1; } + /* Initialize current Time */ + time(&now); + if (cflag) { int fd1, fd2; diff --git a/commands/cron/tab.c b/commands/cron/tab.c index 858d12418..2cdce435b 100755 --- a/commands/cron/tab.c +++ b/commands/cron/tab.c @@ -158,6 +158,7 @@ void tab_reschedule(cronjob_t *job) job->rtime= NEVER; return; } + tmptm= *localtime(&job->rtime); if (tmptm.tm_hour != nexttm.tm_hour || tmptm.tm_min != nexttm.tm_min) { @@ -182,6 +183,7 @@ void tab_reschedule(cronjob_t *job) job->rtime= NEVER; return; } + tmptm= *localtime(&job->rtime); if (tmptm.tm_hour != nexttm.tm_hour || tmptm.tm_min != nexttm.tm_min) { diff --git a/commands/ftpd200/Makefile b/commands/ftpd200/Makefile new file mode 100644 index 000000000..02876581d --- /dev/null +++ b/commands/ftpd200/Makefile @@ -0,0 +1,42 @@ +# Makefile for ftpd +# +# 01/25/96 Initial Release Michael Temari, +# 2005-02-25 version 2.00 + +CFLAGS= -O -D_MINIX -D_POSIX_SOURCE -m +LDFLAGS=-i +BINDIR= /usr/bin +PROG= in.ftpd +MANDIR= /usr/man/man8 +MANPAGE=ftpd.8 + +OBJS= ftpd.o access.o file.o net.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) + install -S 8kw $@ + +clean: + rm -f $(PROG) $(OBJS) + +install: $(BINDIR)/$(PROG) $(BINDIR)/setup.anonftp $(BINDIR)/ftpdsh + +$(BINDIR)/$(PROG): $(PROG) + install -cs -o bin $? $@ + +$(BINDIR)/setup.anonftp: setup.anonftp + install -c -o bin $? $@ + +$(BINDIR)/ftpdsh: ftpdsh + install -c -o bin $? $@ + +ftpd.o: ftpd.c ftpd.h access.h file.h net.h +access.o: access.c ftpd.h access.h +file.o: file.c ftpd.h access.h file.h net.h +net.o: net.c ftpd.h net.h + +installman: $(MANDIR)/$(MANPAGE) + cp $(MANPAGE) $(MANDIR) + echo "You may need to run makewhatis to update man page index" diff --git a/commands/ftpd200/README b/commands/ftpd200/README new file mode 100644 index 000000000..0d267c10a --- /dev/null +++ b/commands/ftpd200/README @@ -0,0 +1,35 @@ +ftpd200 --- FTP server program for Minix 2.0 +written by Michael Temari release 2.00 2005-02-25 + +Full download: ftpd200.tar.Z + +Ftpd is the File Transfer Protocol (FTP) server. + +Important: Release 2.00 incorporates an improved mechanism to restrict +execution of commands on the server. This is done through use of a +shell script, ftpdsh. Any earlier ftpd version in use on a system +accessible from the Internet should be upgraded at least to version 1.01, +version 2.00 is preferable. + +Installation: unpack the tarball in /usr/local/src or another directory +of your choice: +zcat < ftpd200.tar.Z | tar xvfp - + +The ftpd200 directory will be created. Read the Makefile to see how +the program is compiled and installed: + +make (or make ftpd) -- compiles the binary +make install -- installs /usr/bin/in.ftpd, and ftpdsh. Also installs + setup.anonftp script. +make installman -- installs new ftpd.8 man page in /usr/local/man/man8 + +The shell script setup.anonftp sets up and verifies configuration for +anonymous ftp. If you provide anonymous ftp you are letting anyone in +the whole wide world execute a program on your computer. You want to +make sure it's set up correctly so outsiders can't mess with things +they shouldn't. + +This file is included as README in the source directory. For more +notes on compiling and installing, also please see the file README2. + +notes updated by asw 2005-02-25 diff --git a/commands/ftpd200/README2 b/commands/ftpd200/README2 new file mode 100644 index 000000000..40e035baf --- /dev/null +++ b/commands/ftpd200/README2 @@ -0,0 +1,34 @@ +README2: additional notes on compiling and installing ftpd. + +Note that the Makefile install options will replace files in /usr/bin +and /usr/man that were installed with the Minix distribution. If you +are not sure you want to do this you can either rename the original +in.ftpd binary, the anonftp.* scripts, and the ftpd.8 man page to +prevent them from being replaced, or you can edit the Makefile to +change the directory values: + BINDIR= /usr/local/bin + MANDIR= /usr/local/man/man8 +ASW's practice is to rename binaries with a suffix that indicates the +original distribution from which they were obtained, i.e., in.ftpd.203 +for the version distributed with Minix 2.0.3, or with a date code or a +version number. + +If you are sure you want to replace the original ftpd provided with your +distribution you may want to copy the contents of the unpacked tarball +to the main directory tree, in this case /usr/src/commands/ftpd, so that +a new version will be compiled if you do a general recompilation of all +commands using "make all" or "make compile" in /usr/src. ASW's practice +is generally to make a directory in /usr/local/src for new versions of +major programs. +Also note that if you create a new man page where one did not exist +previously you will need to run makewhatis to rebuild the whatis +database, i.e.: + makewhatis /usr/man +or + makewhatis /usr/local/man + +Important: the scripts for setting up and maintaining an anonymous ftp +installation haven't been checked for a long time, I would appreciate +comments. + +ASW 2005-02-06 diff --git a/commands/ftpd200/access.c b/commands/ftpd200/access.c new file mode 100644 index 000000000..0e686bbb0 --- /dev/null +++ b/commands/ftpd200/access.c @@ -0,0 +1,134 @@ +/* access.c Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * This file handles: + * + * USER PASS QUIT + * + * + * 01/25/96 Initial Release Michael Temari, + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftpd.h" +#include "access.h" + +_PROTOTYPE(static int AreWeIn, (char *name, struct passwd *pwd)); + +static char *msg530 = "530 Not logged in.\r\n"; + +/* Returns -1 = not logged in, 0 = loggedin */ +int ChkLoggedIn() +{ + if(!loggedin) { + printf(msg530); + return(-1); + } else + return(0); +} + +/* what a USER! */ +int doUSER(buff) +char *buff; +{ + loggedin = 0; + gotuser = 0; + strncpy(username, buff, sizeof(username)); + username[sizeof(username)-1] = '\0'; + + if(*username == '\0') { + printf("501 Bad user name.\r\n"); + return(GOOD); + } + + gotuser = 1; + + printf("331 Password required for %s.\r\n", username); + + return(GOOD); +} + +/* secret, secret, secret */ +int doPASS(buff) +char *buff; +{ +char *name; +struct passwd *pwd; +int bad=0; + + name = username; + + if(!strcmp(name, "anonymous")) + name = "ftp"; + + if(!gotuser || ((pwd = getpwnam(name)) == (struct passwd *)0)) + bad = 1; + else + if(strcmp(name, "ftp")) { + if(!strcmp(pwd->pw_passwd, crypt("", pwd->pw_passwd))) + bad = 1; + if(strcmp(pwd->pw_passwd, crypt(buff, pwd->pw_passwd))) + bad = 1; + } else { + strncpy(anonpass, buff, sizeof(anonpass)); + anonpass[sizeof(anonpass)-1] = '\0'; + } + + if(bad) { + logit("LOGIN", "FAIL"); + printf(msg530); + return(GOOD); + } + + return(AreWeIn(name, pwd)); +} + +/* bye, bye don't let the door hit you in the butt on the way out */ +int doQUIT(buff) +char *buff; +{ + printf("221 Service closing, don't be a stranger.\r\n"); + + return(BAD); +} + +/* see if this user is okay */ +static int AreWeIn(name, pwd) +char *name; +struct passwd *pwd; +{ + if(!strcmp(name, "ftp")) { + if(chroot(pwd->pw_dir)) { + logit("LOGIN", "FAIL"); + printf("530 Not logged in, could not chroot.\r\n"); + return(GOOD); + } + strncpy(newroot, pwd->pw_dir, sizeof(newroot)); + newroot[sizeof(newroot)-1] = '\0'; + anonymous = 1; + strcpy(pwd->pw_dir, "/"); + } + + if(setgid(pwd->pw_gid) || setuid(pwd->pw_uid) || chdir(pwd->pw_dir)) { + logit("LOGIN", "FAIL"); + printf(msg530); + anonymous = 0; + } else { + logit("LOGIN", "PASS"); + showmsg("230", (char *)NULL); + printf("230 User %s logged in, directory %s.\r\n", + username, pwd->pw_dir); + loggedin = 1; + } + + return(GOOD); +} diff --git a/commands/ftpd200/access.h b/commands/ftpd200/access.h new file mode 100644 index 000000000..f2f446289 --- /dev/null +++ b/commands/ftpd200/access.h @@ -0,0 +1,12 @@ +/* ftpd.h + * + * This file is part of ftpd. + * + * + * 01/25/96 Initial Release Michael Temari, + */ + +_PROTOTYPE(int ChkLoggedIn, (void)); +_PROTOTYPE(int doUSER, (char *buff)); +_PROTOTYPE(int doPASS, (char *buff)); +_PROTOTYPE(int doQUIT, (char *buff)); diff --git a/commands/ftpd200/file.c b/commands/ftpd200/file.c new file mode 100644 index 000000000..381cfdbea --- /dev/null +++ b/commands/ftpd200/file.c @@ -0,0 +1,1252 @@ +/* file.c Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * This file handles: + * + * ALLO APPE CDUP CWD DELE LIST MDTM MODE MKD NLST PWD REST RETR + * RMD RNFR RNTO SITE SIZE STAT STOR STOU STRU SYST TYPE + * + * 01/25/96 Initial Release Michael Temari + * 03/09/00 Michael Temari, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftpd.h" +#include "access.h" +#include "file.h" +#include "net.h" + +_PROTOTYPE(static int fdxcmd, (int cmd, char *arg)); +_PROTOTYPE(static int endfdxcmd, (int fd)); +_PROTOTYPE(static int asciisize, (char *filename, unsigned long *filesize)); +_PROTOTYPE(static int cnvtfile, (char *name, char **name2)); +_PROTOTYPE(static int procfile, (char *name)); +_PROTOTYPE(static unsigned long fsize, (char *fname)); +_PROTOTYPE(static int sendfile, (char *name, int xmode)); +_PROTOTYPE(static int recvfile, (char *name, int xmode)); +_PROTOTYPE(static char *uniqname, (void)); +_PROTOTYPE(static int docrc, (char *buff, int xmode)); +_PROTOTYPE(static int dofdet, (char *buff)); +_PROTOTYPE(static char *path, (char *fname)); + +#define SEND_FILE 0 +#define SEND_NLST 1 +#define SEND_LIST 2 + +#define RECV_FILE 0 +#define RECV_APND 1 +#define RECV_UNIQ 2 + +#define CNVT_ERROR 0 +#define CNVT_NONE 1 +#define CNVT_TAR 2 +#define CNVT_TAR_Z 3 +#define CNVT_COMP 4 +#define CNVT_TAR_GZ 5 +#define CNVT_GZIP 6 +#define CNVT_UNCOMP 7 + + +#define PROG_FTPDSH "ftpdsh" +#define CMD_NLST 1 +#define CMD_LIST 2 +#define CMD_CRC 3 + +static char *msg550 = "550 %s %s.\r\n"; + +static unsigned long file_restart = 0; + +static char rnfr[256]; +static char buffer[8192]; +static char bufout[8192]; + +static cmdpid = -1; + +/* allocate, we don't need no stink'n allocate */ +int doALLO(buff) +char *buff; +{ + printf("202 ALLO command not needed at this site.\r\n"); + + return(GOOD); +} + +/* append to a file if it exists */ +int doAPPE(buff) +char *buff; +{ + return(recvfile(buff, RECV_APND)); +} + +/* change to parent directory */ +int doCDUP(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + return(doCWD("..")); +} + +/* change directory */ +int doCWD(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(chdir(buff)) + printf(msg550, buff, strerror(errno)); + else { + showmsg("250", ".ftpd_msg"); + printf("250 %s command okay.\r\n", line); + } + + return(GOOD); +} + +/* remove a file */ +int doDELE(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(anonymous) { + printf("550 Command not allowed for anonymous user\r\n"); + return(GOOD); + } + + if(unlink(buff)) + printf(msg550, buff, strerror(errno)); + else { + printf("250 File \"%s\" deleted.\r\n", buff); + logit("DELE", path(buff)); + } + + return(GOOD); +} + +/* directory listing */ +int doLIST(buff) +char *buff; +{ + file_restart = 0; + + return(sendfile(buff, SEND_LIST)); +} + +/* file modification time, btw when will this be put into an RFC */ +int doMDTM(buff) +char *buff; +{ +struct stat st; +struct tm *t; + + if(ChkLoggedIn()) + return(GOOD); + + if(stat(buff, &st)) { + printf(msg550, buff, strerror(errno)); + return(GOOD); + } + + if((st.st_mode & S_IFMT) != S_IFREG) { + printf("550 Not a regular file.\r\n"); + return(GOOD); + } + + t = gmtime(&st.st_mtime); + + printf("215 %04d%02d%02d%02d%02d%02d\r\n", + t->tm_year+1900, t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + + return(GOOD); +} + +/* mode */ +int doMODE(buff) +char *buff; +{ + switch(*buff) { + case 'b': + case 'B': + printf("200 Mode set to %c.\r\n", *buff); + mode = MODE_B; + break; + case 's': + case 'S': + printf("200 Mode set to %c.\r\n", *buff); + mode = MODE_S; + break; + default: + printf("501 Unknown mode %c.\r\n", *buff); + } + + return(GOOD); +} + +/* make a directory */ +int doMKD(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(anonymous) { + printf("550 Command not allowed for anonymous user\r\n"); + return(GOOD); + } + + if(mkdir(buff, 0777)) + printf(msg550, buff, strerror(errno)); + else { + printf("257 \"%s\" directory created.\r\n", buff); + logit("MKD ", path(buff)); + } + + return(GOOD); +} + +/* name listing */ +int doNLST(buff) +char *buff; +{ + file_restart = 0; + + return(sendfile(buff, SEND_NLST)); +} + +/* where are we */ +int doPWD(buff) +char *buff; +{ +char dir[128]; + + if(ChkLoggedIn()) + return(GOOD); + + if(getcwd(dir, sizeof(dir)) == (char *)NULL) + printf(msg550, buff, strerror(errno)); + else + printf("257 \"%s\" is current directory.\r\n", dir); + + return(GOOD); +} + +/* restart command */ +int doREST(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + file_restart = atol(buff); + + printf("350 Next file transfer will restart at %lu.\r\n", file_restart); + + return(GOOD); +} + +/* they want a file */ +int doRETR(buff) +char *buff; +{ + return(sendfile(buff, SEND_FILE)); +} + +/* remove a directory */ +int doRMD(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(anonymous) { + printf("550 Command not allowed for anonymous user\r\n"); + return(GOOD); + } + + if(rmdir(buff)) + printf(msg550, buff, strerror(errno)); + else { + printf("250 Directory \"%s\" deleted.\r\n", buff); + logit("RMD ", path(buff)); + } + + return(GOOD); +} + +/* rename from */ +int doRNFR(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(anonymous) { + printf("550 Command not allowed for anonymous user\r\n"); + return(GOOD); + } + + strncpy(rnfr, buff, sizeof(rnfr)); + rnfr[sizeof(rnfr)-1] = '\0'; + + printf("350 Got RNFR waiting for RNTO.\r\n"); + + return(GOOD); +} + +/* rename to */ +int doRNTO(buff) +char *buff; +{ + if(ChkLoggedIn()) + return(GOOD); + + if(anonymous) { + printf("550 Command not allowed for anonymous user\r\n"); + return(GOOD); + } + + if(rnfr[0] == '\0') { + printf("550 Rename failed.\r\n"); + return(GOOD); + } + + if(rename(rnfr, buff) < 0) + printf("550 Rename failed. Error %s\r\n", strerror(errno)); + else { + printf("250 Renamed %s to %s.\r\n", rnfr, buff); + logit("RNFR", path(rnfr)); + logit("RNTO", path(buff)); + } + + rnfr[0] = '\0'; + + return(GOOD); +} + +/* xmode = 0 for multiline crc, xmode <> 0 for single file single line crc */ +static int docrc(buff, xmode) +char *buff; +int xmode; +{ +unsigned short cs; +long fs; +int fd; +int s; +char *p; + + if((fd = fdxcmd(CMD_CRC, buff)) < 0) { + printf("501 Could not obtain CRC.\r\n"); + return(GOOD); + } + + if(xmode == 0) + printf("202-SITE CRC \"%s\"\r\n", buff); + + while(1) { + p = buffer; + while(1) { + if((s = read(fd, p, 1)) != 1) { + if(xmode == 0) + printf("202 SITE CRC DONE.\r\n"); + else + printf("501 Could not obtain CRC.\r\n"); + endfdxcmd(fd); + return(GOOD); + } + if(*p == '\n') { + *p++ = '\r'; + *p++ = '\n'; + *p = '\0'; + break; + } + p++; + } + if(xmode != 0) + break; + printf(" %s", buffer); + } + + cs = atoi(buffer); + + fs = atol(buffer+6); + + printf("202 CRC %05u %ld.\r\n", cs, fs); + + endfdxcmd(fd); + + return(GOOD); +} + +/* site specific */ +int doSITE(buff) +char *buff; +{ +char *args; + + if(ChkLoggedIn()) + return(GOOD); + + + strncpy(line, buff, sizeof(line)); + line[sizeof(line)-1] = '\0'; + + cvtline(&args); + + if(!strcmp(line, "CRC") || !strcmp(line, "CCRC")) + return(docrc(args, strcmp(line, "CRC"))); + + if(!strcmp(line, "FDET")) + return(dofdet(args)); + + printf("501 Unknown SITE command %s.\r\n", line); + + return(GOOD); +} + +static unsigned long fsize(fname) +char *fname; +{ +struct stat st; +unsigned long fs = 0L; + + if(stat(fname, &st)) + return(fs); + + if((st.st_mode & S_IFMT) != S_IFREG) + return(fs); + + if(type == TYPE_A) + return(fs); + + fs = st.st_size; + + return(fs); +} + +/* file size, btw when will this be put into an RFC */ +int doSIZE(buff) +char *buff; +{ +struct stat st; +unsigned long filesize; + + if(ChkLoggedIn()) + return(GOOD); + + if(stat(buff, &st)) { + printf(msg550, buff, strerror(errno)); + return(GOOD); + } + + if((st.st_mode & S_IFMT) != S_IFREG) { + printf("550 Not a regular file.\r\n"); + return(GOOD); + } + + filesize = st.st_size; + + if(type == TYPE_A) + if(asciisize(buff, &filesize)) + return(GOOD); + + printf("215 %lu\r\n", filesize); + + return(GOOD); +} + +/* server status, or file status */ +int doSTAT(buff) +char *buff; +{ +time_t now; +struct tm *tm; +int fd; +int s; + + if(!*buff) { + (void) time(&now); + tm = localtime(&now); + printf("211-%s(%s:%u) FTP server status:\r\n", + myhostname, inet_ntoa(myipaddr), ntohs(myport)); + printf(" Version %s ", FtpdVersion); + printf("%s, %02d %s %d %02d:%02d:%02d %s\r\n", days[tm->tm_wday], + tm->tm_mday, months[tm->tm_mon], 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, tzname[tm->tm_isdst]); + printf(" Connected to %s:%u\r\n", inet_ntoa(rmtipaddr), ntohs(rmtport)); + if(!loggedin) + printf(" Not logged in\r\n"); + else + printf(" Logged in %s\r\n", username); + printf(" MODE: %s\r\n",(mode == MODE_B) ? "Block" : "Stream"); + printf(" TYPE: %s\r\n",(type == TYPE_A) ? "Ascii" : "Binary"); + printf("211 End of status\r\n"); + return(GOOD); + } + + if(ChkLoggedIn()) + return(GOOD); + + printf("211-Status of %s:\r\n", buff); + + if((fd = fdxcmd(CMD_LIST, buff)) < 0) + printf(" Could not retrieve status"); + else { + while((s = read(fd, buffer, 1)) == 1) { + if(*buffer == '\n') + printf("\r\n"); + else + printf("%c", *buffer); + } + endfdxcmd(fd); + } + + printf("211 End of status\r\n"); + + return(GOOD); +} + +/* hey look, we're getting a file */ +int doSTOR(buff) +char *buff; +{ + return(recvfile(buff, RECV_FILE)); +} + +/* hey, get a file unique */ +int doSTOU(buff) +char *buff; +{ + return(recvfile(buff, RECV_UNIQ)); +} + +/* structure */ +int doSTRU(buff) +char *buff; +{ + switch(*buff) { + case 'f': + case 'F': + printf("200 Structure set to %c.\r\n", *buff); + break; + default: + printf("501 Unknown structure %c.\r\n", *buff); + } + + return(GOOD); +} + +/* we're UNIX and proud of it! */ +int doSYST(buff) +char *buff; +{ + printf("215 UNIX Type: L8\r\n"); + + return(GOOD); +} + +/* change transfer type */ +int doTYPE(buff) +char *buff; +{ + if(*(buff+1) != '\0') { + printf("501 Syntax error in parameters.\r\n"); + return(GOOD); + } + + switch(*buff) { + case 'A': + case 'a': + type = TYPE_A; + printf("200 Type set to A.\r\n"); + break; + case 'I': + case 'i': + type = TYPE_I; + printf("200 Type set to I.\r\n"); + break; + default: + printf("501 Invalid type %c.\r\n", *buff); + } + + return(GOOD); +} + +static int fdxcmd(cmd, arg) +int cmd; +char *arg; +{ +char xcmd[3]; +char *argv[5]; +int fds[2]; +char *smallenv[] = { "PATH=/bin:/usr/bin:/usr/local/bin", NULL, NULL }; + + if((smallenv[1] = getenv("TZ")) != NULL) smallenv[1] -= 3; /* ouch... */ + + sprintf(xcmd, "%d", cmd); + + argv[0] = PROG_FTPDSH; + argv[1] = xcmd; + argv[2] = arg; + argv[3] = (char *)NULL; + + if(pipe(fds) < 0) + return(-1); + + if((cmdpid = fork()) < 0) { + close(fds[0]); + close(fds[1]); + return(-1); + } + + if(cmdpid == 0) { /* Child */ + close(fds[0]); + close(0); + open("/dev/null", O_RDONLY); + dup2(fds[1], 1); + dup2(fds[1], 2); + close(fds[1]); + sprintf(argv[0], "/bin/%s", PROG_FTPDSH); + execve(argv[0], argv, smallenv); + sprintf(argv[0], "/usr/bin/%s", PROG_FTPDSH); + execve(argv[0], argv, smallenv); + sprintf(argv[0], "/usr/local/bin/%s", PROG_FTPDSH); + execve(argv[0], argv, smallenv); + exit(0); + } + + close(fds[1]); + + return(fds[0]); +} + +/* Same as close if not cmd child started */ +static int endfdxcmd(fd) +int fd; +{ +int s; +int cs; + + close(fd); + + if(cmdpid == -1) + return(0); + + s = waitpid(cmdpid, &cs, 0); + + cmdpid = -1; + + return(0); +} + +/* returns -1 = size could not be determined, */ +/* 0 = size determined and in filesize */ +static int asciisize(filename, filesize) +char *filename; +unsigned long *filesize; +{ +unsigned long count; +int fd; +char *p, *pp; +int cnt; + + if((fd = open(filename, O_RDONLY)) < 0) { + printf(msg550, filename, strerror(errno)); + return(-1); + } + + count = 0; + + while((cnt = read(fd, buffer, sizeof(buffer))) > 0) { + count += cnt; + p = buffer; + while(cnt > 0) + if((pp = memchr(p, '\n', cnt)) != (char *)NULL) { + count++; + cnt = cnt - 1 - (pp - p); + p = pp + 1; + } else + break; + } + + if(cnt == 0) { + *filesize = count; + close(fd); + return(0); + } + + printf(msg550, filename, strerror(errno)); + + close(fd); + + return(-1); +} + +/* see if we need to run a command to convert the file */ +static int cnvtfile(name, name2) +char *name; +char **name2; +{ +struct stat st; +static char fname[256]; +char *p; +int cmode; + + if(!stat(name, &st)) /* file exists can't be a conversion */ + if((st.st_mode & S_IFMT) != S_IFREG) { /* must be regular file */ + printf("550 Not a regular file.\r\n"); + return(CNVT_ERROR); + } else + return(CNVT_NONE); + + if(errno != ENOENT) { /* doesn't exist is okay, others are not */ + printf(msg550, name, strerror(errno)); + return(CNVT_ERROR); + } + + /* find out what kind of conversion */ + strncpy(fname, name, sizeof(fname)); + fname[sizeof(fname)-1] = '\0'; + + p = fname + strlen(fname); + cmode = CNVT_ERROR; + while(p > fname && cmode == CNVT_ERROR) { + if(*p == '.') { + if(!strcmp(p, ".tar")) + cmode = CNVT_TAR; + else + if(!strcmp(p, ".tar.Z")) + cmode = CNVT_TAR_Z; + else + if(!strcmp(p, ".Z")) + cmode = CNVT_COMP; + else + if(!strcmp(p, ".tar.gz")) + cmode = CNVT_TAR_GZ; + else + if(!strcmp(p, ".gz")) + cmode = CNVT_GZIP; + + if (cmode != CNVT_ERROR) { + /* is there a file to convert? */ + *p = '\0'; + if (!stat(fname, &st)) break; + *p = '.'; + cmode = CNVT_ERROR; + } + } + p--; + } + + if(cmode == CNVT_ERROR) { + printf(msg550, fname, strerror(errno)); + return(CNVT_ERROR); + } + + if(cmode == CNVT_COMP || cmode == CNVT_GZIP || cmode == CNVT_UNCOMP) + if((st.st_mode & S_IFMT) != S_IFREG) { + printf("550 Not a regular file.\r\n"); + return(CNVT_ERROR); + } + + *name2 = fname; + + return(cmode); +} + +static int procfile(name) +char *name; +{ +int cmd; +int fd; +char *name2; + + cmd = cnvtfile(name, &name2); + + switch(cmd) { + case CNVT_TAR: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_TAR_Z: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_COMP: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_TAR_GZ: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_GZIP: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_UNCOMP: + fd = fdxcmd(cmd + 10, name2); + break; + case CNVT_NONE: + fd = open(name, O_RDONLY); + break; + case CNVT_ERROR: + default: + return(-1); + } + + if(fd < 0) + printf(msg550, name, strerror(errno)); + + return(fd); +} + +/* oh no, they're taking a file */ +static int sendfile(name, xmode) +char *name; +int xmode; +{ +char *fname; +int fd, s; +time_t datastart, dataend; +unsigned long datacount; +long kbs; +char c; +char *p; +char *op, *ope; +off_t sp; +int doascii; +unsigned long fs; +char block[3]; + + if(ChkLoggedIn()) + return(GOOD); + + switch(xmode) { + case SEND_NLST: + fname = "NLST"; + fd = fdxcmd(CMD_NLST, name); + if(fd < 0) + printf(msg550, name, strerror(errno)); + break; + case SEND_LIST: + fname = "LIST"; + fd = fdxcmd(CMD_LIST, name); + if(fd < 0) + printf(msg550, name, strerror(errno)); + break; + default: + fname = name; + fd = procfile(name); + if(fd < 0) + logit("FAIL", path(fname)); + else + logit("SEND", path(fname)); + } + + if(fd < 0) + return(GOOD); + + /* set file position at approriate spot */ + if(file_restart) { + if(type == TYPE_A) { + sp = 0; + while(sp < file_restart) { + sp++; + s = read(fd, buffer, 1); + if(s < 0) { + printf(msg550, fname, strerror(errno)); + endfdxcmd(fd); + file_restart = 0; + return(GOOD); + } + if(s == 0) break; + if(*buffer == '\n') + sp++; + } + } else { + sp = lseek(fd, file_restart, SEEK_SET); + if(sp == -1) { + printf(msg550, fname, strerror(errno)); + endfdxcmd(fd); + file_restart = 0; + return(GOOD); + } + } + if(sp != file_restart) { + printf("550 File restart point error. %lu not %lu\r\n", sp, file_restart); + endfdxcmd(fd); + file_restart = 0; + return(GOOD); + } + } + file_restart = 0; + + fs = fsize(fname); + if(fs == 0L) + printf("%03d File %s okay. Opening data connection.\r\n", + ftpdata_fd >= 0 ? 125 : 150, fname); + else + printf("%03d Opening %s mode data connection for %s (%ld bytes).\r\n", + ftpdata_fd >= 0 ? 125 : 150, + type == TYPE_A ? "ASCII" : "BINARY", + fname, fs); + fflush(stdout); + +#ifdef DEBUG + fprintf(logfile, "After 125/150 b4 DataConnect\n"); + fflush(logfile); +#endif + + if(DataConnect()) { + endfdxcmd(fd); + return(GOOD); + } + +#ifdef DEBUG + fprintf(logfile, "After DataConnect\n"); + fflush(logfile); + fprintf(logfile, "ftpd: parent %d start sendfile \n", getpid()); + fflush(logfile); +#endif + + /* start transfer */ + doascii = (type == TYPE_A) || + ((xmode == SEND_LIST) || (xmode == SEND_NLST)); /* per RFC1123 4.1.2.7 */ + datacount = 0; + time(&datastart); + op = bufout; ope = bufout + sizeof(bufout) - 3; + while((s = read(fd, buffer, sizeof(buffer))) > 0) { +#ifdef DEBUG + fprintf(logfile, "sendfile read %d\n", s); fflush(logfile); +#endif + datacount += s; + if(doascii) { + p = buffer; + while(s-- > 0) { + c = *p++; + if(c == '\n') { + *op++ = '\r'; + datacount++; + } + *op++ = c; + if(op >= ope) { + if(mode == MODE_B) { + block[0] = '\0'; + *(u16_t *)&block[1] = htons(op - bufout); + write(ftpdata_fd, block, sizeof(block)); + } + write(ftpdata_fd, bufout, op - bufout); + op = bufout; + } + } + } else { + if(mode == MODE_B) { + block[0] = '\0'; + *(u16_t *)&block[1] = htons(s); + write(ftpdata_fd, block, sizeof(block)); + } + s = write(ftpdata_fd, buffer, s); + } + } + if(op > bufout) { + if(mode == MODE_B) { + block[0] = MODE_B_EOF; + *(u16_t *)&block[1] = htons(op - bufout); + write(ftpdata_fd, block, sizeof(block)); + } + write(ftpdata_fd, bufout, op - bufout); + } else + if(mode == MODE_B) { + block[0] = MODE_B_EOF; + *(u16_t *)&block[1] = htons(0); + write(ftpdata_fd, block, sizeof(block)); + } + time(&dataend); + +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d end sendfile \n", getpid()); + fflush(logfile); +#endif + + endfdxcmd(fd); + if(mode != MODE_B) { + close(ftpdata_fd); + ftpdata_fd = -1; + } + + if(dataend == datastart) dataend++; + kbs = (datacount * 100 / (dataend - datastart)) / 1024; + + if(s < 0) + printf("451 Transfer aborted.\r\n"); + else + printf("%03d Transfer finished successfully. %ld.%02d KB/s\r\n", + mode == MODE_B ? 250 : 226, + (long)(kbs / 100), (int)(kbs % 100)); + + return(GOOD); +} + +static int recvfile(name, xmode) +char *name; +int xmode; +{ +char *fname; +time_t datastart, dataend; +unsigned long datacount; +long kbs; +char c; +char *p; +char *op, *ope; +int fd, oflag; +int s; +int gotcr; +off_t sp; +char block[3]; +unsigned short cnt; + + if(ChkLoggedIn()) + return(GOOD); + + fname = name; + + switch(xmode) { + case RECV_APND: + oflag = O_WRONLY | O_APPEND; + break; + case RECV_UNIQ: + fname = uniqname(); + oflag = O_WRONLY | O_CREAT; + break; + default: + oflag = O_WRONLY | O_CREAT | O_TRUNC; + } + + if(file_restart) + oflag = O_RDWR; + + fd = open(fname, oflag, (anonymous ? 0000:0600)); + + if(fd < 0) { + printf(msg550, fname, strerror(errno)); + return(GOOD); + } + + /* log the received file */ + logit("RECV", path(fname)); + + /* set file position at approriate spot */ + if(file_restart) { + if(type == TYPE_A) { + sp = 0; + while(sp < file_restart) { + sp++; + s = read(fd, buffer, 1); + if(s < 0) { + printf(msg550, fname, strerror(errno)); + close(fd); + file_restart = 0; + return(GOOD); + } + if(s == 0) break; + if(*buffer == '\n') + sp++; + } + } else { + sp = lseek(fd, file_restart, SEEK_SET); + if(sp == -1) { + printf(msg550, fname, strerror(errno)); + close(fd); + file_restart = 0; + return(GOOD); + } + } + if(sp != file_restart) { + printf("550 File restart point error. %lu not %lu\r\n", sp, file_restart); + close(fd); + file_restart = 0; + return(GOOD); + } + } + file_restart = 0; + + if(xmode == RECV_UNIQ) + printf("%03d FILE: %s\r\n", + ftpdata_fd >= 0 ? 125 : 150, fname); /* per RFC1123 4.1.2.9 */ + else + printf("%03d File %s okay. Opening data connection.\r\n", + ftpdata_fd >= 0 ? 125 : 150, fname); + fflush(stdout); + + if(DataConnect()) { + close(fd); + return(GOOD); + } + +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d start recvfile \n", getpid()); + fflush(logfile); +#endif + + /* start receiving file */ + datacount = 0; + gotcr = 0; + op = bufout; ope = bufout + sizeof(bufout) - 3; + cnt = 0; + time(&datastart); + while(1) { + if(mode != MODE_B) + cnt = sizeof(buffer); + else + if(cnt == 0) { + s = read(ftpdata_fd, block, sizeof(block)); + cnt = ntohs(*(u16_t *)&block[1]); + s = 0; + if(cnt == 0 && block[0] & MODE_B_EOF) + break; + } + s = read(ftpdata_fd, buffer, cnt > sizeof(buffer) ? sizeof(buffer) : cnt); + if(s <= 0) break; + cnt -= s; + datacount += (long)s; + if(type == TYPE_A) { + p = buffer; + while(s-- > 0) { + c = *p++; + if(gotcr) { + gotcr = 0; + if(c != '\n') + *op++ = '\r'; + } + if(c == '\r') + gotcr = 1; + else + *op++ = c; + if(op >= ope) { + write(fd, bufout, op - bufout); + op = bufout; + } + } + } else + write(fd, buffer, s); + if(cnt == 0 && mode == MODE_B && block[0] & MODE_B_EOF) { + s = 0; + break; + } + } + if(gotcr) + *op++ = '\r'; + if(op > bufout) + write(fd, bufout, op - bufout); + time(&dataend); + +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d end recvfile \n", getpid()); + fflush(logfile); +#endif + + close(fd); + if(mode != MODE_B) { + close(ftpdata_fd); + ftpdata_fd = -1; + } + + if(dataend == datastart) dataend++; + kbs = (datacount * 100 / (dataend - datastart)) / 1024; + + if((mode == MODE_B && cnt != 0) || s != 0) + printf("451 Transfer aborted.\r\n"); + else { + printf("%03d Transfer finished successfully. ", + mode == MODE_B ? 250 : 226); + if(xmode == RECV_UNIQ) + printf("Unique file %s. ", fname); + printf("%ld.%02d KB/s\r\n", (long)(kbs / 100), (int)(kbs % 100)); + } + + return(GOOD); +} + +static char *uniqname() +{ +static char uniq[32]; +int i; +struct stat st; + + for(i = 0; i < 1000; i++) { + sprintf(uniq, "ftpd%d%d", getpid(), i); + if(stat(uniq, &st) == -1) + return(uniq); + } + return(uniq); +} + +static char *spath[256]; +static char *path(fname) +char *fname; +{ +char dir[128]; + + if(getcwd(dir, sizeof(dir)) == (char *)NULL) + sprintf(dir, "???"); + + if(fname[0] == '/') + sprintf((char *)spath, "%s%s", newroot, fname); + else + if(dir[1] == '\0') + sprintf((char *)spath, "%s%s%s", newroot, dir, fname); + else + sprintf((char *)spath, "%s%s/%s", newroot, dir, fname); + + return((char *)spath); +} + +/* do file detail */ +static int dofdet(buff) +char *buff; +{ +struct stat st; +char ft; + + if(ChkLoggedIn()) + return(GOOD); + + if(stat(buff, &st)) { + printf("501 Could not obtain file detail.\r\n"); + return(GOOD); + } + switch(st.st_mode & S_IFMT) { + case S_IFIFO: ft = 'p'; break; + case S_IFCHR: ft = 'c'; break; + case S_IFDIR: ft = 'd'; break; + case S_IFBLK: ft = 'b'; break; + case S_IFREG: ft = 'f'; break; + default: ft = '?'; break; + } + printf("202 %c %u %u %u %u %u %lu %lu\r\n", + ft, /* file type */ + st.st_rdev >> 8, /* Major */ + st.st_rdev & 0xff, /* Minor */ + st.st_uid, /* UID */ + st.st_gid, /* GID */ + st.st_mode, /* File Modes */ + st.st_size, /* SIZE */ + st.st_mtime); /* Mod Time */ + + return(GOOD); +} diff --git a/commands/ftpd200/file.h b/commands/ftpd200/file.h new file mode 100644 index 000000000..fdce03919 --- /dev/null +++ b/commands/ftpd200/file.h @@ -0,0 +1,32 @@ +/* file.h Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * + * 01/25/96 Initial Release Michael Temari, + */ + +_PROTOTYPE(int doALLO, (char *buff)); +_PROTOTYPE(int doAPPE, (char *buff)); +_PROTOTYPE(int doCDUP, (char *buff)); +_PROTOTYPE(int doCWD, (char *buff)); +_PROTOTYPE(int doDELE, (char *buff)); +_PROTOTYPE(int doLIST, (char *buff)); +_PROTOTYPE(int doMDTM, (char *buff)); +_PROTOTYPE(int doMODE, (char *buff)); +_PROTOTYPE(int doMKD, (char *buff)); +_PROTOTYPE(int doNLST, (char *buff)); +_PROTOTYPE(int doPWD, (char *buff)); +_PROTOTYPE(int doREST, (char *buff)); +_PROTOTYPE(int doRETR, (char *buff)); +_PROTOTYPE(int doRMD, (char *buff)); +_PROTOTYPE(int doRNFR, (char *buff)); +_PROTOTYPE(int doRNTO, (char *buff)); +_PROTOTYPE(int doSITE, (char *buff)); +_PROTOTYPE(int doSIZE, (char *buff)); +_PROTOTYPE(int doSTAT, (char *buff)); +_PROTOTYPE(int doSTOR, (char *buff)); +_PROTOTYPE(int doSTOU, (char *buff)); +_PROTOTYPE(int doSTRU, (char *buff)); +_PROTOTYPE(int doSYST, (char *buff)); +_PROTOTYPE(int doTYPE, (char *buff)); diff --git a/commands/ftpd200/ftpd.8 b/commands/ftpd200/ftpd.8 new file mode 100644 index 000000000..bae175717 --- /dev/null +++ b/commands/ftpd200/ftpd.8 @@ -0,0 +1,161 @@ +.\" Copyright (c) 1985 Regents of the University of California. +.\" All rights reserved. The Berkeley software License Agreement +.\" specifies the terms and conditions for redistribution. +.\" +.\" @(#)ftpd.8c 6.4 (Berkeley) 5/28/86 +.\" +.TH FTPD 8 +.SH NAME +ftpd, in.ftpd, ftpdsh, setup.anonftp \- DARPA Internet File Transfer Protocol server +.SH SYNOPSIS +.B "ftp stream tcp nowait root /usr/bin/in.ftpd in.ftpd" +.br +.B "tcpd ftp /usr/bin/in.ftpd" +.SH DESCRIPTION +.B Ftpd +is the DARPA Internet File Transfer Prototocol +server process. The server uses the TCP protocol +and listens at the port specified in the ``ftp'' +service specification; see +.BR services (5). +.PP +The ftp server currently supports the following ftp +requests; case is not distinguished. +.PP +.nf +.ta \w'Request 'u +\fBRequest Description\fP +ABOR abort previous command +ACCT specify account (ignored) +ALLO allocate storage (vacuously) +APPE append to a file +CDUP change to parent of current working directory +CWD change working directory +DELE delete a file +HELP give help information +LIST give list files in a directory (``ls -lA'') +MKD make a directory +MODE specify data transfer \fImode\fP +NLST give name list of files in directory (``ls'') +NOOP do nothing +PASS specify password +PASV prepare for server-to-server transfer +PORT specify data connection port +PWD print the current working directory +QUIT terminate session +RETR retrieve a file +RMD remove a directory +RNFR specify rename-from file name +RNTO specify rename-to file name +STOR store a file +STOU store a file with a unique name +STRU specify data transfer \fIstructure\fP +TYPE specify data transfer \fItype\fP +USER specify user name +XCUP change to parent of current working directory +XCWD change working directory +XMKD make a directory +XPWD print the current working directory +XRMD remove a directory +.fi +.PP +The remaining ftp requests specified in Internet RFC 959 are +recognized, but not implemented. +.PP +The ftp server will abort an active file transfer only when the +ABOR command is preceded by a Telnet "Interrupt Process" (IP) +signal and a Telnet "Synch" signal in the command Telnet stream, +as described in Internet RFC 959. +.PP +.B Ftpd +interprets file names according to the ``globbing'' +conventions used by +.BR csh (1). +This allows users to utilize the metacharacters ``*?[]{}~''. +.PP +.B Ftpd +authenticates users according to two rules. +.IP 1) +The user name must be in the password data base, +.BR /etc/passwd , +and not have a null password. In this case a password +must be provided by the client before any file operations +may be performed. +.IP 2) +If the user name is ``anonymous'' or ``ftp'', an +anonymous ftp account must be present in the password +file (user ``ftp''). In this case the user is allowed +to log in by specifying any password (by convention this +is given as the client host's name). +.PP +In the last case, +.B ftpd +takes special measures to restrict the client's access privileges. +The server performs a +.BR chroot (2) +command to the home directory of the ``ftp'' user. +In order that system security is not breached, it is recommended +that the ``ftp'' subtree be constructed with care; the following +rules are recommended. +.IP ~ftp) +Make the home directory owned by ``ftp'' and unwritable by anyone. +.IP ~ftp/bin) +Make this directory owned by the super-user and unwritable by +anyone. The program +.BR ls (1) +must be present to support the list commands. +Also, +.BR crc (1) +must be present to support generating crcs using the site command, +.BR tar (1) +and +.BR compress (1) +must be present to support on-the-fly generation of .tar and .tar.Z archives, +.BR gzip (1) +must be present to support gzip compression, and +.BR sh (1) +must be present to support +.BR ftpdsh (8) +which also must be present. +.BR ftpdsh controls which binaries can be used. +These programs should all have mode 111. +.IP ~ftp/etc) +Make this directory owned by the super-user and unwritable by +anyone. The files +.BR passwd (5) +and +.BR group (5) +must be present for the +.B ls +command to work properly. These files should be mode 444. They can (and +should) be stripped down versions so as not to reveal names of users who +are not owners of files in the ~ftp/pub directory tree. +.IP ~ftp/pub) +Make this directory mode 755 and owned by the super-user. Create +directories in it owned by users if those users want to manage an +anonymous ftp directory. +.IP ~ftp/pub/incoming) +Optionally create this directory for anonymous uploads. Make it mode +777. The FTP daemon will create files with mode 266, so remote users +can write a file, but only local users can do something with it. +.PP +The script +.B setup.anonftp +can be used to create or check an anonymous FTP tree. +.SH "SEE ALSO" +.BR ftp (1). +.SH BUGS +The anonymous account is inherently dangerous and should +avoided when possible. +.ig \" Minix doesn't have privileged port numbers (yet?) +.PP +The server must run as the super-user +to create sockets with privileged port numbers. It maintains +an effective user id of the logged in user, reverting to +the super-user only when binding addresses to sockets. The +possible security holes have been extensively +scrutinized, but are possibly incomplete. +.. +.\" man page updated by Al Woodhull 2005-02-25 + + diff --git a/commands/ftpd200/ftpd.c b/commands/ftpd200/ftpd.c new file mode 100644 index 000000000..5727f1b6d --- /dev/null +++ b/commands/ftpd200/ftpd.c @@ -0,0 +1,405 @@ +/* ftpd.c Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * ftpd An FTP server program for use with Minix. + * + * Usage: Minix usage: tcpd ftp ftpd + * + * 06/14/92 Tnet Release Michael Temari + * 01/15/96 0.30 Michael Temari + * 01/25/96 0.90 Michael Temari + * 03/17/96 0.91 Michael Temari + * 06/27/96 0.92 Michael Temari + * 07/02/96 0.93 Michael Temari + * 07/15/96 0.94 Michael Temari + * 08/27/96 0.95 Michael Temari + * 02/09/97 0.96 Michael Temari + * 02/10/97 0.97 Michael Temari + * 09/25/97 0.98 Michael Temari + * 03/10/00 0.99 Michael Temari, + * 12/12/03 1.00 Michael Temari, + * 02/06/05 1.01 Michael Temari, + * 02/12/05 2.00 Michael Temari, + */ + +char *FtpdVersion = "2.00"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftpd.h" +#include "access.h" +#include "file.h" +#include "net.h" + +_PROTOTYPE(static void init, (void)); +_PROTOTYPE(static int doHELP, (char *buff)); +_PROTOTYPE(static int doNOOP, (char *buff)); +_PROTOTYPE(static int doUNIMP, (char *buff)); +_PROTOTYPE(static int getline, (char *line, int len)); + +FILE *msgfile = (FILE *)NULL; + +/* The following defines the inactivity timeout in seconds */ +#define INACTIVITY_TIMEOUT 60*5 + +char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + +char line[512]; + +int type, format, mode, structure; +int ftpdata_fd = -1; +int loggedin, gotuser, anonymous; +char username[80]; +char anonpass[128]; +char newroot[128]; + +ipaddr_t myipaddr, rmtipaddr, dataaddr; +tcpport_t myport, rmtport, dataport; + +char myhostname[256], rmthostname[256]; + +#define FTPD_LOG "/usr/adm/ftpd.log" +#define FTPD_MSG "/etc/ftpd_msg" + +FILE *logfile; + +int timeout = 0; + +_PROTOTYPE(static int doHELP, (char *buff)); +_PROTOTYPE(int readline, (char **args)); +_PROTOTYPE(void Timeout, (int sig)); +_PROTOTYPE(int main, (int argc, char *argv[])); + +struct commands { + char *name; + _PROTOTYPE(int (*func), (char *buff)); +}; + +struct commands commands[] = { + "ABOR", doUNIMP, + "ACCT", doUNIMP, + "ALLO", doALLO, + "APPE", doAPPE, + "CDUP", doCDUP, + "CWD", doCWD, + "DELE", doDELE, + "HELP", doHELP, + "LIST", doLIST, + "MDTM", doMDTM, + "MKD", doMKD, + "MODE", doMODE, + "NLST", doNLST, + "NOOP", doNOOP, + "PASS", doPASS, + "PASV", doPASV, + "PORT", doPORT, + "PWD", doPWD, + "QUIT", doQUIT, + "REIN", doUNIMP, + "REST", doREST, + "RETR", doRETR, + "RMD", doRMD, + "RNFR", doRNFR, + "RNTO", doRNTO, + "SITE", doSITE, + "SIZE", doSIZE, + "SMNT", doUNIMP, + "STAT", doSTAT, + "STOR", doSTOR, + "STOU", doSTOU, + "STRU", doSTRU, + "SYST", doSYST, + "TYPE", doTYPE, + "USER", doUSER, + "XCUP", doCDUP, + "XCWD", doCWD, + "XMKD", doMKD, + "XPWD", doPWD, + "XRMD", doRMD, + "", (int (*)())0 +}; + +static void init() +{ + loggedin = 0; + gotuser = 0; + anonymous = 0; + newroot[0] = '\0'; + type = TYPE_A; + format = 0; + mode = MODE_S; + structure = 0; + ftpdata_fd = -1; + username[0] = '\0'; + anonpass[0] = '\0'; +} + +/* nothing, nada, zilch... */ +static int doNOOP(buff) +char *buff; +{ + printf("200 NOOP to you too!\r\n"); + + return(GOOD); +} + +/* giv'em help, what a USER! */ +static int doHELP(buff) +char *buff; +{ +struct commands *cmd; +char star; +int i; +char *space = " "; + + printf("214-Here is a list of available ftp commands\r\n"); + printf(" Those with '*' are not yet implemented.\r\n"); + + i = 0; + for(cmd = commands; *cmd->name != '\0'; cmd++) { + if(cmd->func == doUNIMP) + star = '*'; + else + star = ' '; + printf(" %s%c%s", cmd->name, star, space + strlen(cmd->name)); + if(++i == 6) { + printf("\r\n"); + i = 0; + } + } + + if(i) + printf("\r\n"); + + printf("214 That's all the help you get.\r\n"); + + return(GOOD); +} + +/* not implemented */ +static int doUNIMP(buff) +char *buff; +{ + printf("502 Command \"%s\" not implemented!\r\n", line); + + return(GOOD); +} + +/* convert line for use */ +void cvtline(args) +char **args; +{ +char *p; + + p = line + strlen(line); + while(--p >= line) + if(*p == '\r' || *p == '\n' || isspace(*p)) + *p = '\0'; + else + break; + + p = line; + +#ifdef DEBUG + logit("COMMAND", line); +#endif + + while(*p && !isspace(*p)) { + *p = toupper(*p); + p++; + } + + if(*p) { + *p = '\0'; + p++; + while(*p && isspace(*p)) + p++; + } + + *args = p; + + return; +} + +static int getline(line, len) +char *line; +int len; +{ +int s; +int gotcr; + + /* leave room for at end for null */ + len--; + + /* got to be able to put in at least 1 character */ + if(len < 1) + return(-1); + + gotcr = 0; + while(len-- > 0) { + s = read(0, line, 1); + if(s != 1) + return(-1); + if(*line == '\n') + break; + gotcr = (*line == '\r'); + line++; + } + if(gotcr) + --line; + + *line = '\0'; + + return(0); +} + +int readline(args) +char **args; +{ + if(getline(line, sizeof(line))) + return(BAD); + + cvtline(args); + + return(GOOD); +} + +/* signal handler for inactivity timeout */ +void Timeout(sig) +int sig; +{ + timeout = 1; + + printf("421 Inactivity timer expired.\r\n"); +} + +/* logit */ +void logit(type, parm) +char *type; +char *parm; +{ +time_t now; +struct tm *tm; + + if(logfile == (FILE *)NULL) + return; + + time(&now); + tm = localtime(&now); + fprintf(logfile, "%4d%02d%02d%02d%02d%02d ", + 1900+tm->tm_year, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + fprintf(logfile, "%s %s %s %s %s\n", + rmthostname, username, anonymous ? anonpass : username, type, parm); + fflush(logfile); +} + +void showmsg(reply, filename) +char *reply; +char *filename; +{ +FILE *mfp; +char *pe; +static char mline[256]; + + if(filename == (char *)NULL) + mfp = msgfile; + else + mfp = fopen(filename, "r"); + + if(mfp == (FILE *)NULL) + return; + + while(fgets(mline, sizeof(mline), mfp) != (char *)NULL) { + pe = mline + strlen(mline); + while(--pe >= mline) + if(*pe == '\r' || *pe == '\n') + *pe = '\0'; + else + break; + printf("%s- %s\r\n", reply, mline); + } + + if(filename != (char *)NULL) + fclose(mfp); +} + +int main(argc, argv) +int argc; +char *argv[]; +{ +struct commands *cmd; +char *args; +int status; +time_t now; +struct tm *tm; +int s; + + GetNetInfo(); + + /* open transfer log file if it exists */ + if((logfile = fopen(FTPD_LOG, "r")) != (FILE *)NULL) { + fclose(logfile); + logfile = fopen(FTPD_LOG, "a"); + } + + /* open login msg file */ + msgfile = fopen(FTPD_MSG, "r"); + + /* Let's initialize some stuff */ + init(); + + /* Log the connection */ + logit("CONNECT", ""); + + /* Tell 'em we are ready */ + time(&now); + tm = localtime(&now); + printf("220 FTP service (Ftpd %s) ready on %s at ", + FtpdVersion, myhostname); + printf("%s, %02d %s %d %02d:%02d:%02d %s\r\n", days[tm->tm_wday], + tm->tm_mday, months[tm->tm_mon], 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, + tzname[tm->tm_isdst]); + fflush(stdout); + + /* Loop here getting commands */ + while(1) { + signal(SIGALRM, Timeout); + alarm(INACTIVITY_TIMEOUT); + if(readline(&args) != GOOD) { + if(!timeout) + printf("221 Control connection closing (EOF).\r\n"); + break; + } + alarm(0); + for(cmd = commands; *cmd->name != '\0'; cmd++) + if(!strcmp(line, cmd->name)) + break; + if(*cmd->name != '\0') + status = (*cmd->func)(args); + else { + printf("500 Command \"%s\" not recognized.\r\n", line); + status = GOOD; + } + fflush(stdout); + if(status != GOOD) + break; + } + + CleanUpPasv(); + + return(-1); +} diff --git a/commands/ftpd200/ftpd.h b/commands/ftpd200/ftpd.h new file mode 100644 index 000000000..373709e64 --- /dev/null +++ b/commands/ftpd200/ftpd.h @@ -0,0 +1,37 @@ +/* ftpd.h Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * + * 01/25/96 Initial Release Michael Temari, + */ + +#define GOOD 0 +#define BAD 1 + +#define TYPE_A 0 +#define TYPE_I 1 + +#define MODE_S 0 +#define MODE_B 1 + +#define MODE_B_EOF 64 + +extern char *FtpdVersion; +extern int type, format, mode, structure; +extern ipaddr_t myipaddr, rmtipaddr, dataaddr; +extern tcpport_t myport, rmtport, dataport; +extern int ftpdata_fd; +extern int loggedin, gotuser, anonymous; +extern char newroot[128]; +extern char *days[], *months[]; +extern char username[80]; +extern char anonpass[128]; +extern char myhostname[256], rmthostname[256]; +extern char line[512]; + +extern FILE *logfile; + +_PROTOTYPE(void cvtline, (char **args)); +_PROTOTYPE(void logit, (char *type, char *parm)); +_PROTOTYPE(void showmsg, (char *reply, char *filename)); diff --git a/commands/ftpd200/ftpdsh b/commands/ftpd200/ftpdsh new file mode 100755 index 000000000..6ca54e3e9 --- /dev/null +++ b/commands/ftpd200/ftpdsh @@ -0,0 +1,14 @@ +#!/bin/sh + +case $1 in + 1) ls -A $2 ;; + 2) ls -la $2 ;; + 3) crc $2 ;; + 12) tar cf - $2 ;; + 13) tar cf - $2 | compress -q ;; + 14) compress -cq $2 ;; + 15) tar cf - $2 | gzip ;; + 16) tar -c $2 ;; + 17) compress -dcq $2 ;; +esac +exit diff --git a/commands/ftpd200/net.c b/commands/ftpd200/net.c new file mode 100644 index 000000000..0c5755d59 --- /dev/null +++ b/commands/ftpd200/net.c @@ -0,0 +1,431 @@ +/* net.c Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * This file handles: + * + * PASV PORT + * + * + * 01/25/1995 Initial Release Michael Temari, + * 02/09/2005 Initial Release Michael Temari, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftpd.h" +#include "access.h" +#include "net.h" + +_PROTOTYPE(static void timeout, (int sig)); + +static char *msg425 = "425-Could not open data connection.\r\n"; +static char *msg501 = "501 Syntax error in parameters.\r\n"; + +static int gottimeout = 0; +static int lpid = -1; +static int didpassive = 0; + +/* they must be behind a firewall or using a web browser */ +int doPASV(buff) +char *buff; +{ +nwio_tcpconf_t tcpconf; +nwio_tcpcl_t tcplopt; +char *tcp_device; +ipaddr_t ipaddr; +tcpport_t lport; +int s; +time_t starttime; +int retry; + + if(ChkLoggedIn()) + return(GOOD); + + CleanUpPasv(); + + /* here we set up a connection to listen on */ + if((tcp_device = getenv("TCP_DEVICE")) == NULL) + tcp_device = TCP_DEVICE; + + if(ftpdata_fd >= 0) { + close(ftpdata_fd); + ftpdata_fd = -1; + } + + if((ftpdata_fd = open(tcp_device, O_RDWR)) < 0) { + printf(msg425); + printf("425 Could not open tcp_device. Error %s\r\n", strerror(errno)); + return(GOOD); + } + + tcpconf.nwtc_flags = NWTC_LP_SEL | NWTC_SET_RA | NWTC_UNSET_RP; + + tcpconf.nwtc_remaddr = rmtipaddr; + tcpconf.nwtc_remport = htons(0); + tcpconf.nwtc_locport = htons(0); + + s = ioctl(ftpdata_fd, NWIOSTCPCONF, &tcpconf); + if(s < 0) { + printf(msg425); + printf("425 Could not ioctl NWIOSTCPCONF. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(GOOD); + } + + s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf); + if(s < 0) { + printf(msg425); + printf("425 Could not NWIOGTCPCONF. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(GOOD); + } + ipaddr = tcpconf.nwtc_locaddr; + lport = tcpconf.nwtc_locport; + + /* Now lets fork a child to do the listening :-( */ + + tcplopt.nwtcl_flags = 0; + + lpid = fork(); + if(lpid < 0) { + printf(msg425); + printf("425 Could not fork listener. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(GOOD); + } else if(lpid == 0) { + retry = 0; + while(1) { +#ifdef DEBUG + fprintf(logfile, "ftpd: child %d parent %d listen try %d\n", getpid(), getppid(), retry); + fflush(logfile); +#endif + s = ioctl(ftpdata_fd, NWIOTCPLISTEN, &tcplopt); + if(!(s == -1 && errno == EAGAIN)) break; + if(retry++ > 10) break; + sleep(1); + } +#ifdef DEBUG + fprintf(logfile, "ftpd: child %d s %d errno %d\n", getpid(), s, errno); + fflush(logfile); +#endif + if(s < 0) + exit(errno); /* tells parent listen failed */ + else + exit(0); /* tells parent listen okay */ + } + +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d wait for %d\n", getpid(), lpid); + fflush(logfile); +#endif + + /* wait for child to be listening, no more than serveral seconds */ + (void) time(&starttime); + while(1) { + if(time((time_t *)NULL) > (starttime + 15)) break; + signal(SIGALRM, timeout); + alarm(1); + s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf); +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d s %d errno %d start %ld now %ld\n", + getpid(), lpid, s, errno, starttime, time((time_t *)NULL)); + fflush(logfile); +#endif + alarm(0); + if(s == -1) break; + sleep(1); + } + +#define hiword(x) ((u16_t)((x) >> 16)) +#define loword(x) ((u16_t)(x & 0xffff)) +#define hibyte(x) (((x) >> 8) & 0xff) +#define lobyte(x) ((x) & 0xff) + + printf("227 Entering Passive Mode (%u,%u,%u,%u,%u,%u).\r\n", + hibyte(hiword(htonl(ipaddr))), lobyte(hiword(htonl(ipaddr))), + hibyte(loword(htonl(ipaddr))), lobyte(loword(htonl(ipaddr))), + hibyte(htons(lport)), lobyte(htons(lport))); + +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d send 227\n", getpid(), lpid); + fflush(logfile); +#endif + + didpassive = -1; + + return(GOOD); +} + +/* they want us to connect here */ +int doPORT(buff) +char *buff; +{ +u32_t ipaddr; +u16_t port; +int i; + + if(ftpdata_fd >= 0) { + close(ftpdata_fd); + ftpdata_fd = -1; + } + + ipaddr = (u32_t)0; + for(i = 0; i < 4; i++) { + ipaddr = (ipaddr << 8) + (u32_t)atoi(buff); + if((buff = strchr(buff, ',')) == (char *)0) { + printf(msg501); + return(GOOD); + } + buff++; + } + port = (u16_t)atoi(buff); + if((buff = strchr(buff, ',')) == (char *)0) { + printf(msg501); + return(0); + } + buff++; + port = (port << 8) + (u16_t)atoi(buff); + + dataaddr = htonl(ipaddr); + dataport = htons(port); + if(dataaddr != rmtipaddr) { + printf(msg501); + return(GOOD); + } + + printf("200 Port command okay.\r\n"); + + return(GOOD); +} + +/* connect, huh? */ +int DataConnect() +{ +nwio_tcpconf_t tcpconf; +nwio_tcpcl_t tcpcopt; +nwio_tcpcl_t tcplopt; +char *tcp_device; +int s, cs; +int retry; + + if(didpassive && ftpdata_fd >= 0) { + didpassive = 0; + gottimeout = 0; + signal(SIGALRM, timeout); + alarm(10); + while(!gottimeout) { + s = waitpid(lpid, &cs, 0); + if((s == lpid) || (s < 0 && errno == ECHILD)) break; +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d waitpid s %d cs %04x errno %d\n", getpid(), lpid, s, cs, errno); + fflush(logfile); +#endif + } + alarm(0); +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d waitpid s %d cs %04x errno %d\n", getpid(), lpid, s, cs, errno); + fflush(logfile); +#endif + if(gottimeout) { +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d got timeout\n", getpid(), lpid); + fflush(logfile); +#endif + kill(lpid, SIGKILL); + s = waitpid(lpid, &cs, 0); + } +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d continuing\n", getpid(), lpid); + fflush(logfile); +#endif + lpid = -1; + if(gottimeout) { + printf(msg425); + printf("425 Child listener timeout.\r\n"); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + if(s < 0) { + printf(msg425); + printf("425 Child listener vanished.\r\n"); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + if((cs & 0x00ff)) { + printf(msg425); + printf("425 Child listener failed %04x\r\n", cs); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + cs = (cs >> 8) & 0x00ff; + if(cs) { + printf(msg425); + printf("425 Child listener error %s\r\n", strerror(cs)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } +#ifdef DEBUG + fprintf(logfile, "ftpd: parent %d child %d pasv done\n", getpid(), lpid); + fflush(logfile); +#endif + return(GOOD); + } + + if(ftpdata_fd >= 0) + return(GOOD); + + if((tcp_device = getenv("TCP_DEVICE")) == NULL) + tcp_device = TCP_DEVICE; + + if((ftpdata_fd = open(tcp_device, O_RDWR)) < 0) { + printf(msg425); + printf("425 Could not open tcp_device. Error %s\r\n", strerror(errno)); + return(BAD); + } + + tcpconf.nwtc_flags = NWTC_LP_SET | NWTC_SET_RA | NWTC_SET_RP; + tcpconf.nwtc_remaddr = dataaddr; + tcpconf.nwtc_remport = dataport; + tcpconf.nwtc_locport = htons(20); + + s = ioctl(ftpdata_fd, NWIOSTCPCONF, &tcpconf); + if(s < 0) { + printf(msg425); + printf("425 Could not ioctl NWIOSTCPCONF. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + + s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf); + if(s < 0) { + printf(msg425); + printf("425 Could not ioctl NWIOGTCPCONF. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + + tcpcopt.nwtcl_flags = 0; + + retry = 0; + do { +#ifdef DEBUG + fprintf(logfile, "try connect\n"); fflush(logfile); + fflush(logfile); +#endif + sleep(2); + s = ioctl(ftpdata_fd, NWIOTCPCONN, &tcpcopt); +#ifdef DEBUG + fprintf(logfile, "after connect %d %d\n", s, errno); + fflush(logfile); +#endif + if(!(s == -1 && errno == EAGAIN)) break; + if(retry++ > 10) break; + sleep(1); + } while(1); + if(s < 0) { + printf(msg425); + printf("425 Could not ioctl NWIOTCPCONN. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + + s = ioctl(ftpdata_fd, NWIOGTCPCONF, &tcpconf); + if(s < 0) { + printf(msg425); + printf("425 Could not ioctl NWIOGTCPCONF. Error %s\r\n", strerror(errno)); + close(ftpdata_fd); + ftpdata_fd = -1; + return(BAD); + } + + return(GOOD); +} + +/* Clean up stuff we did to get a Pasv connection going */ +int CleanUpPasv() +{ +int s, cs; + + if(lpid >= 0) { + kill(lpid, SIGKILL); + while(1) { + s = waitpid(lpid, &cs, 0); + if(s == lpid || (s == -1 && errno == ECHILD)) + break; + } + } + + lpid = -1; + + didpassive = 0; + + return(GOOD); +} + +void GetNetInfo() +{ +nwio_tcpconf_t tcpconf; +int s; +struct hostent *hostent; + + /* Ask the system what our hostname is. */ + if(gethostname(myhostname, sizeof(myhostname)) < 0) + strcpy(myhostname, "unknown"); + + /* lets get our ip address and the clients ip address */ + s = ioctl(0, NWIOGTCPCONF, &tcpconf); + if(s < 0) { + printf("421 FTP service unable to get remote ip address. Closing.\r\n"); + fflush(stdout); + exit(1); + } + + myipaddr = tcpconf.nwtc_locaddr; + myport = tcpconf.nwtc_locport; + rmtipaddr = tcpconf.nwtc_remaddr; + rmtport = tcpconf.nwtc_remport; + + /* Look up the host name of the remote host. */ + hostent = gethostbyaddr((char *) &rmtipaddr, sizeof(rmtipaddr), AF_INET); + if(!hostent) + strcpy(rmthostname, inet_ntoa(rmtipaddr)); + else { + strncpy(rmthostname, hostent->h_name, sizeof(rmthostname)-1); + rmthostname[sizeof(rmthostname)-1] = '\0'; + } +} + +static void timeout(sig) +int sig; +{ + gottimeout = 1; +} diff --git a/commands/ftpd200/net.h b/commands/ftpd200/net.h new file mode 100644 index 000000000..2bedf9f89 --- /dev/null +++ b/commands/ftpd200/net.h @@ -0,0 +1,13 @@ +/* net.h Copyright 1992-2000 by Michael Temari All Rights Reserved + * + * This file is part of ftpd. + * + * + * 01/25/96 Initial Release Michael Temari, + */ + +_PROTOTYPE(int doPASV, (char *buff)); +_PROTOTYPE(int doPORT, (char *buff)); +_PROTOTYPE(int DataConnect, (void)); +_PROTOTYPE(int CleanUpPasv, (void)); +_PROTOTYPE(void GetNetInfo, (void)); diff --git a/commands/ftpd200/setup.anonftp b/commands/ftpd200/setup.anonftp new file mode 100755 index 000000000..8dbb0fbe7 --- /dev/null +++ b/commands/ftpd200/setup.anonftp @@ -0,0 +1,75 @@ +#!/bin/sh +# setup.anonftp - Anonymous FTP setup and maintenance. +# +# 01/22/96 Initial Release Al Woodhul, +# 01/25/96 Michael Temari, +# + +# What is needed for anon ftp + +# ref: Hunt TCP/IP Net Admin pp. 338++ +# ref: Nemeth et al UNIX System Admin Handbook p. 295 +# ref: mail from M. Temari 18.01.96 + +# programs possibly used by ftpd +PROGS="sh ls crc tar compress gzip" + +echo Checking /etc/passwd +if grep '^ftp:[^:]*:[1-9][0-9]*:[1-9][0-9]*:[^:]*:/[^:]*:[^:]*$' \ + /etc/passwd >/dev/null +then + echo -n "OK, ftp entry found: " + grep '^ftp:' /etc/passwd +else + echo "Found no entry for ftp in /etc/passwd, please add one with the" + echo "home directory pointing to the anonymous FTP directory" + exit 1 +fi + +# ftp directory +FTPDIR="`sed '/^ftp:/!d; s/^.*:\\([^:]*\\):[^:]*/\\1/' /etc/passwd`" + +if [ `whoami` != root ] +then + echo You must be root to do this + exit 1 +fi + +echo Setting up for anonymous ftp + +echo Making $FTPDIR and subdirectories +install -d -m 755 -o root -g operator $FTPDIR +install -d -m 751 -o root -g operator $FTPDIR/bin +install -d -m 751 -o root -g operator $FTPDIR/dev +install -d -m 751 -o root -g operator $FTPDIR/etc +install -d -m 755 -o root -g operator $FTPDIR/pub +incoming= +if [ -d $FTPDIR/pub/incoming ] +then + incoming=t +elif [ -t 0 ] +then + echo -n "Create \"incoming\" directory? [n] "; read yn + case "$yn" in + [yY]*|ok|sure) incoming=t + esac +fi +test "$incoming" && install -d -m 777 -o root -g operator $FTPDIR/pub/incoming + +echo Copying files +for PROG in $PROGS +do + test -f /usr/bin/$PROG && install -lcs /usr/bin/$PROG $FTPDIR/bin +done +cp -rp /dev/tcp $FTPDIR/dev/tcp +install -lcs ftpdsh $FTPDIR/bin + +echo Copying a minimum of the password and group files +sed 's/^\([^:]*\):[^:]*:\([^:]*:[^:]*\):.*$/\1:*:\2:::/' \ + /etc/passwd >$FTPDIR/etc/passwd +sed 's/^\([^:]*\):[^:]*:\([^:]*\):.*$/\1:*:\2:/' \ + /etc/group >$FTPDIR/etc/group +chown root:operator $FTPDIR/etc/* +chmod 444 $FTPDIR/etc/* + +echo "Anonymous ftp setup complete" diff --git a/commands/ps/ps.c b/commands/ps/ps.c index df857d20e..2c97f4e29 100644 --- a/commands/ps/ps.c +++ b/commands/ps/ps.c @@ -7,9 +7,12 @@ * If you want to compile this for non-IBM PC architectures, the header files * require that you have your CHIP, MACHINE etc. defined. * Full syntax: - * ps [-][alx] + * ps [-][aeflx] * Option `a' gives all processes, `l' for detailed info, `x' includes even * processes without a terminal. + * The `f' and `e' options were added by Kees Bot for the convenience of + * Solaris users accustomed to these options. The `e' option is equivalent to + * `a' and `f' is equivalent to -l. These do not appear in the usage message. * * VERY IMPORTANT NOTE: * To compile ps, the kernel/, fs/ and pm/ source directories must be in @@ -549,7 +552,7 @@ int nbytes; void usage(pname) char *pname; { - fprintf(stderr, "Usage: %s [-][alx]\n", pname); + fprintf(stderr, "Usage: %s [-][aeflx]\n", pname); exit(1); } diff --git a/drivers/libpci/pci_table.c b/drivers/libpci/pci_table.c index 172676826..9e430c36f 100644 --- a/drivers/libpci/pci_table.c +++ b/drivers/libpci/pci_table.c @@ -55,6 +55,7 @@ struct pci_device pci_device_table[]= { 0x100B, 0xD001, "Nat. Semi. 87410" }, { 0x1013, 0x00B8, "Cirrus Logic GD 5446" }, { 0x1013, 0x6003, "Cirrus Logic CS4614/22/24 CrystalClear" }, + { 0x1022, 0x2000, "AMD Lance/PCI" }, { 0x1022, 0x700C, "AMD-762 CPU to PCI Bridge (SMP chipset)" }, { 0x1022, 0x700D, "AMD-762 CPU to PCI Bridge (AGP 4x)" }, { 0x1022, 0x7410, "AMD-766 PCI to ISA/LPC Bridge" }, @@ -100,20 +101,27 @@ struct pci_device pci_device_table[]= { 0x5333, 0x88d0, "S3 Vision 964 vers 0" }, { 0x5333, 0x8a01, "S3 Virge/DX or /GX" }, { 0x8086, 0x1004, "Intel 82543GC Gigabit Ethernet Controller" }, - { 0x8086, 0x1229, "Intel 82557" }, + { 0x8086, 0x1029, "Intel EtherExpressPro100 ID1029" }, + { 0x8086, 0x1030, "Intel Corporation 82559 InBusiness 10/100" }, + { 0x8086, 0x1209, "Intel EtherExpressPro100 82559ER" }, + { 0x8086, 0x1229, "Intel EtherExpressPro100 82557/8/9" }, { 0x8086, 0x122D, "Intel 82437FX" }, { 0x8086, 0x122E, "Intel 82371FB (PIIX)" }, { 0x8086, 0x1230, "Intel 82371FB (IDE)" }, { 0x8086, 0x1237, "Intel 82441FX (440FX)" }, { 0x8086, 0x1250, "Intel 82439HX" }, + { 0x8086, 0x2449, "Intel EtherExpressPro100 82562EM" }, { 0x8086, 0x7000, "Intel 82371SB" }, { 0x8086, 0x7010, "Intel 82371SB (IDE)" }, { 0x8086, 0x7020, "Intel 82371SB (USB)" }, + { 0x8086, 0x7030, "Intel 82437VX" }, /* asw 2005-03-02 */ + { 0x8086, 0x7100, "Intel 82371AB" }, /* asw 2004-07-31 */ { 0x8086, 0x7100, "Intel 82371AB" }, { 0x8086, 0x7110, "Intel 82371AB (PIIX4)" }, { 0x8086, 0x7111, "Intel 82371AB (IDE)" }, { 0x8086, 0x7112, "Intel 82371AB (USB)" }, { 0x8086, 0x7113, "Intel 82371AB (Power)" }, + { 0x8086, 0x7124, "Intel 82801AA" }, /* asw 2004-11-09 */ { 0x8086, 0x7190, "Intel 82443BX" }, { 0x8086, 0x7191, "Intel 82443BX (AGP bridge)" }, { 0x9004, 0x8178, "Adaptec AHA-2940U/2940UW Ultra/Ultra-Wide SCSI Ctrlr" }, @@ -216,7 +224,9 @@ struct pci_intel_ctrl pci_intel_ctrl[]= { 0x8086, 0x122D, }, /* Intel 82437FX */ { 0x8086, 0x1237, }, /* Intel 82441FX */ { 0x8086, 0x1250, }, /* Intel 82439HX */ - { 0x8086, 0x7100, }, /* Intel 82371AB */ + { 0x8086, 0x7030, }, /* Intel 82437VX (asw 2005-03-02) */ + { 0x8086, 0x7100, }, /* Intel 82371AB (asw 2004-07-31) */ + { 0x8086, 0x7124, }, /* Intel 82801AA (asw 2004-11-09) */ { 0x8086, 0x7190, }, /* Intel 82443BX */ { 0x0000, 0x0000, }, }; @@ -231,8 +241,10 @@ struct pci_isabridge pci_isabridge[]= { 0x1106, 0x3227, 1, PCI_IB_VIA, }, /* VIA */ { 0x8086, 0x122E, 1, PCI_IB_PIIX, }, /* Intel 82371FB */ { 0x8086, 0x7000, 1, PCI_IB_PIIX, }, /* Intel 82371SB */ - { 0x8086, 0x7100, 1, PCI_IB_PIIX, }, /* Intel 82371AB */ - { 0x8086, 0x7110, 1, PCI_IB_PIIX, }, /* Intel PIIX4 */ + { 0x8086, 0x7030, 1, PCI_IB_PIIX, }, /* Intel 82437VX (asw 2005-03-02) */ + { 0x8086, 0x7100, 1, PCI_IB_PIIX, }, /* Intel 82371AB (asw 2004-07-31) */ + { 0x8086, 0x7110, 1, PCI_IB_PIIX, }, /* Intel PIIX4 */ + { 0x8086, 0x7124, 1, PCI_IB_PIIX, }, /* Intel 82801AA (asw 2004-11-09) */ { 0x0000, 0x0000, 0, 0, }, }; diff --git a/kernel/clock.c b/kernel/clock.c index 6615de3a0..1f1c8e623 100755 --- a/kernel/clock.c +++ b/kernel/clock.c @@ -267,7 +267,7 @@ irq_hook_t *hook; && rdy_head[PPRI_USER] != NIL_PROC)) { m.NOTIFY_TYPE = HARD_INT; - int_notify(CLOCK, &m); + lock_notify(CLOCK, &m); } else if (--sched_ticks <= 0) { sched_ticks = SCHED_RATE; /* reset the quantum */ diff --git a/kernel/proc.c b/kernel/proc.c index 54cb8ed09..ece2bb7a6 100755 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -10,10 +10,9 @@ * * As well as several entry points used from the interrupt and task level: * - * lock_notify: send a notification to inform a process of a system event - * int_notify: same as above, but from an interrupt handler (no locking) + * lock_notify: notify a process of a system event * lock_send: send a message to a process - * lock_ready: put a process on one of the ready queues so it can be run + * lock_ready: put a process on one of the ready queues * lock_unready: remove a process from the ready queues * lock_sched: a process has run too long; schedule another one * @@ -438,37 +437,29 @@ PUBLIC int lock_notify(dst, m_ptr) int dst; /* to whom is message being sent? */ message *m_ptr; /* pointer to message buffer */ { -/* Safe gateway to mini_notify() for tasks. Don't use this function from the - * interrupt level, as it will reenable interrupts (because of the unlock() - * call). For interrupt handlers, int_notify() is available. +/* Safe gateway to mini_notify() for tasks and interrupt handlers. MINIX + * kernel is not reentrant, which means to interrupts are disabled after + * the first kernel entry (hardware interrupt, trap, or exception). Locking + * work is done by temporarily disabling interrupts. */ int result; - register struct proc *caller_ptr; - lock(0, "notify"); - caller_ptr = (k_reenter >= 0) ? proc_addr(HARDWARE) : proc_ptr; - result = mini_notify(caller_ptr, dst, m_ptr); - unlock(0); - return(result); -} + /* Exception or interrupt occurred, thus already locked. */ + if (k_reenter >= 0) { + result = mini_notify(proc_addr(HARDWARE), dst, m_ptr); + } -/*==========================================================================* - * int_notify * - *==========================================================================*/ -PUBLIC int int_notify(dst, m_ptr) -int dst; /* to whom is message being sent? */ -message *m_ptr; /* pointer to message buffer */ -{ -/* Gateway to mini_notify() for interrupt handlers. This function doesn't - * use lock() and unlock() because interrupts are already disabled. - */ - int result; - register struct proc *caller_ptr = proc_addr(HARDWARE); - result = mini_notify(caller_ptr, dst, m_ptr); + /* Call from task level, locking is required. */ + else { + lock(0, "notify"); + result = mini_notify(proc_ptr, dst, m_ptr); + unlock(0); + } return(result); } + /*===========================================================================* * pick_proc * *===========================================================================*/ diff --git a/kernel/proto.h b/kernel/proto.h index 32e6e1e1d..52f5c361e 100755 --- a/kernel/proto.h +++ b/kernel/proto.h @@ -41,7 +41,6 @@ _PROTOTYPE( void free_bit, (bit_t nr, bitchunk_t *map, bit_t nr_bits) ); /* proc.c */ _PROTOTYPE( int sys_call, (int function, int src_dest, message *m_ptr) ); _PROTOTYPE( int lock_notify, (int dst, message *m_ptr) ); -_PROTOTYPE( int int_notify, (int dst, message *m_ptr) ); _PROTOTYPE( int lock_send, (int dst, message *m_ptr) ); _PROTOTYPE( void lock_ready, (struct proc *rp) ); _PROTOTYPE( void lock_sched, (int queue) ); diff --git a/kernel/system.c b/kernel/system.c index a040d802a..1a9f7db30 100755 --- a/kernel/system.c +++ b/kernel/system.c @@ -296,7 +296,7 @@ irq_hook_t *hook; /* Build notification message and return. */ m.NOTIFY_TYPE = HARD_INT; m.NOTIFY_ARG = hook->irq; - int_notify(hook->proc_nr, &m); + lock_notify(hook->proc_nr, &m); return(hook->policy & IRQ_REENABLE); } diff --git a/lib/ansi/misc.c b/lib/ansi/misc.c index bce1d8f1b..d687c8395 100755 --- a/lib/ansi/misc.c +++ b/lib/ansi/misc.c @@ -352,7 +352,7 @@ date_of(register struct dsttype *dst, struct tm *timep) tmpday = day; day += (dst->ds_date[2] - firstday + 7) % 7 + 7 * (dst->ds_date[1] - 1); - if (day >= tmpday + _ytab[leap][month]) day -= 7; + if (day >= tmpday + _ytab[leap][month-1]) day -= 7; return day; } diff --git a/man/man1/ash.1 b/man/man1/ash.1 index 7c5009f14..2fd48527a 100644 --- a/man/man1/ash.1 +++ b/man/man1/ash.1 @@ -323,7 +323,7 @@ declared inside not to the global variable named .BR x . .PP -The only special parameter than can be made local is ``\fB-\fR''. +The only special parameter that can be made local is ``\fB-\fR''. Making ``\fB-\fR'' local any shell options that are changed via the .I set command inside the function to be restored to their original values @@ -1103,7 +1103,7 @@ echo(1), expr(1), line(1), pwd(1), true(1). .SH BUGS When command substitution occurs inside a here document, the commands inside the here document are run with their standard input closed. For example, -the following will not word because the standard input of the +the following will not work because the standard input of the .I line command will be closed when the command is run: .d @@ -1121,3 +1121,4 @@ as well as by a line containing the terminator word which follows the ``<<''. What this means is that if you mistype the terminator line, the shell will silently swallow up the rest of your shell script and stick it in the here document. +.\" several minor typos corrected -- ASW 2005-01-15 diff --git a/man/man1/eject.1 b/man/man1/eject.1 index 5c3f15001..fa83cae1f 100644 --- a/man/man1/eject.1 +++ b/man/man1/eject.1 @@ -18,7 +18,13 @@ Tapes can't be unloaded with this command, use instead. .SH "SEE ALSO" .BR mt (1), -.BR hd (4), -.BR sd (4). +.BR disk (4), +.BR tape (4). .SH AUTHOR Kees J. Bot (kjb@cs.vu.nl) + +.\" hd, sd changed to disk, tape -- ASW 2004-12-13 + + + + diff --git a/man/man1/elvis.1 b/man/man1/elvis.1 index 80a75b828..7a3a6ec91 100644 --- a/man/man1/elvis.1 +++ b/man/man1/elvis.1 @@ -75,7 +75,7 @@ there, the initialization file is called "elvis.rc" instead. .SH "SEE ALSO" .BR ctags (1), .BR ref (1), -.BR virec (1), +.BR elvrec (1), .BR elvis (9). .PP \fIElvis - A Clone of Vi/Ex\fP, the complete \fBelvis\fP documentation. @@ -98,3 +98,4 @@ kirkenda@cs.pdx.edu Many other people have worked to port \fBelvis\fP to various operating systems. To see who deserves credit, run the \fB:version\fP command from within \fBelvis\fP, or look in the system-specific section of the complete documentation. +.\" ref to virec chnaged to elvrec -- ASW 2004-12-13 diff --git a/man/man1/flex.1 b/man/man1/flex.1 index b89f90c1f..a37fb19df 100644 --- a/man/man1/flex.1 +++ b/man/man1/flex.1 @@ -631,7 +631,7 @@ on some systems). library with which to link the scanners. .SH "SEE ALSO" .LP -flexdoc(1), lex(1), yacc(1), sed(1), awk(1). +flexdoc(1), lex(1), yacc(1), sed(1), awk(9). .LP M. E. Lesk and E. Schmidt, .I LEX - Lexical Analyzer Generator @@ -777,3 +777,4 @@ required for POSIX-compliance. The .I flex internal algorithms need documentation. +.\" ref. to awk(9) man page corrected -- ASW 2005-01-15 diff --git a/man/man1/flexdoc.1 b/man/man1/flexdoc.1 index a38a14c36..ca93b4f0f 100644 --- a/man/man1/flexdoc.1 +++ b/man/man1/flexdoc.1 @@ -2380,7 +2380,7 @@ requires that at least one of the classes share characters. See flex(1). .SH "SEE ALSO" .LP -flex(1), lex(1), yacc(1), sed(1), awk(1). +flex(1), lex(1), yacc(1), sed(1), awk(9). .LP M. E. Lesk and E. Schmidt, .I LEX - Lexical Analyzer Generator @@ -2439,3 +2439,4 @@ Send comments to: decvax!cornell!vern .fi +.\" ref. to awk(9) man page corrected -- ASW 2005-01-15 diff --git a/man/man1/fsck.1 b/man/man1/fsck.1 index 11bca3488..31ce4c1f2 100644 --- a/man/man1/fsck.1 +++ b/man/man1/fsck.1 @@ -21,10 +21,10 @@ fsck, fsck1 \- perform file system consistency check .FL "\-r" "Prompt user for repairs if inconsistencies are found .FL "\-s" "List the superblock of the file system" .SH EXAMPLES -.EX "fsck /dev/hd4" "Check file system on \fI/dev/hd4\fR" +.EX "fsck /dev/c0d0p3" "Check file system on \fI/dev/c0d0p3\fR" .EX "fsck \-a /dev/at0" "Automatically fix errors on \fI/dev/at0\fR" .EX "fsck \-l /dev/fd0" "List the contents of \fI/dev/fd0\fR" -.EX "fsck \-c 2 3 /dev/hd3" "Check and list \fI/dev/hd3\fR i-nodes 2 & 3" +.EX "fsck \-c 2 3 /dev/c0d0p2" "Check and list \fI/dev/c0d0p2\fR i-nodes 2 & 3" .SH DESCRIPTION .PP \fIFsck\fR performs consistency checks on the file systems which reside @@ -49,3 +49,4 @@ simply be unmounted before it is checked. .SH "SEE ALSO" .BR mkfs (1), .BR mount (1). +.\" disk name refs corrected, i.e., old hd1 now c0d0p0 -- ASW 2005-01-15 diff --git a/man/man1/join.1 b/man/man1/join.1 index afa9c99a7..fb3ffdfc6 100644 --- a/man/man1/join.1 +++ b/man/man1/join.1 @@ -98,7 +98,7 @@ in a line is significant. .SH "SEE ALSO" .BR sort (1), .BR comm (1), -.BR awk (1). +.BR awk (9). .SH BUGS With default field separation, the collating sequence is that of @@ -114,5 +114,6 @@ The conventions of .BR uniq , .BR look and -.BR awk (1) +.BR awk (9) are wildly incongruous. +.\" ref. to awk(9) man page corrected -- ASW 2005-01-15 diff --git a/man/man1/makewhatis.1 b/man/man1/makewhatis.1 index dce01769b..a56962d36 100644 --- a/man/man1/makewhatis.1 +++ b/man/man1/makewhatis.1 @@ -14,7 +14,7 @@ to map titles to manual page names and by .BR whatis (1) to give one line descriptions. See .BR whatis (5) -for a desciption of what a whatis database should look like and the +for a description of what a whatis database should look like and the restrictions that are placed on the NAME sections so that .B makewhatis can make whatis lines out of the manual pages. @@ -25,3 +25,4 @@ Removing only font and size changes from the NAME section is often not enough. .SH AUTHOR Kees J. Bot (kjb@cs.vu.nl) +.\" minor correction -- ASW 2005-01-15 diff --git a/man/man1/ps.1 b/man/man1/ps.1 index 7971c4df8..839d20a78 100644 --- a/man/man1/ps.1 +++ b/man/man1/ps.1 @@ -2,7 +2,7 @@ .SH NAME ps \- process status .SH SYNOPSIS -\fBps \fR[\fB\-alxU\fR] [\fBkernel mm fs\fR]\fR +\fBps \fR[\fR[\fB\-\fR]\fBalx\fR] .br .de FL .TP @@ -19,7 +19,9 @@ ps \- process status .FL "\-l" "Give long listing" .FL "\-x" "Include processes without a terminal" .SH EXAMPLES +.EX "ps " "Show user's own processes in short format" .EX "ps \-axl" "Print all processes and tasks in long format" +.EX "ps \axl" "Same -- the '\-' is optional" .SH DESCRIPTION .PP \fIPs\fR prints the status of active processes. Normally only the caller's own @@ -66,3 +68,16 @@ The files \fI/dev/{mem,kmem}\fR are used to read the system tables and command line arguments from. Terminal names in \fI/dev\fR are used to generate the mnemonic names in the TTY column, so \fIps\fR is independent of terminal naming conventions. +.SH NOTES +The '\-' option prefix is not required. +For marginal compatibility with System V usage, the hidden option +.B \-e +means the same as +.BR \-ax , +and +.B \-f +is the same as +.BR \-l . + +.\" edited by ASW 2004-12-14 + diff --git a/man/man1/tar.1 b/man/man1/tar.1 index 1a58c9a4b..fbe55d254 100644 --- a/man/man1/tar.1 +++ b/man/man1/tar.1 @@ -27,7 +27,7 @@ tar \- tape archiver .SH EXAMPLES .EX "tar c /dev/fd1 ." "Back up current directory to \fI/dev/fd1\fR" .EX "tar xv /dev/fd1 file1 file2" "Extract two files from the archive" -.EX "tar cf \- | (cd dest; tar xf \-)" "Copy current directory to \fIdest\fR" +.EX "tar cf \- . | (cd dest; tar xf \-)" "Copy current directory to \fIdest\fR" .SH DESCRIPTION .PP \fITar\fR is a POSIX-compatible archiver, except that it does not use tape. @@ -47,3 +47,4 @@ try .SH "SEE ALSO" .BR compress (1), .BR vol (1). +.\" minor correction ASW 2005-01-15 diff --git a/man/man1/touch.1 b/man/man1/touch.1 index 36d40905f..284fda089 100644 --- a/man/man1/touch.1 +++ b/man/man1/touch.1 @@ -1,8 +1,8 @@ .TH TOUCH 1 .SH NAME -touch \- update a file's time of last modification +touch \- change file access and modification times .SH SYNOPSIS -\fBtouch\fR [\fB\-c\fR] \fIfile\fR ...\fR +\fBtouch\fR [\fB\-c\fR] [\fB\-a\fR] [\fB\-m\fR] [\fB\-r\fR file] [\fB\-t\fR [CC[YY]]MMDDhhmm[.ss]] [MMDDhhmm[YY]] \fIfile\fR ...\fR .br .de FL .TP @@ -15,17 +15,35 @@ touch \- update a file's time of last modification # \\$2 .. .SH OPTIONS -.FL "\-c" "Do not create the file" +.FL "\-c" "Do not create the file if it doesn't already exist" +.FL "\-a" "Change access time" +.FL "\-m" "Change modification time" +.FL "\-r file" "Apply time of specified file" +.FL "\-t [CC[YY]]MMDDhhmm[.ss]]" "Apply time specified" +.FL "\-t [MMDDhhmm[YY]]" "Apply time specified (alternate form)" .SH EXAMPLES .EX "touch *.h" "Make the \fI.h\fP files look recent" +.EX "touch -t 199610010000 *" "Change date and time of all files in current directory to midnight Oct 1, 1996" .SH DESCRIPTION -############ NEXT ENTRY HAS NOT BEEN CHECKED ############# .PP -The time of last modification is set to the current time. +With no options specified, the times of last modification and last access +are set to the current time. This command is mostly used to trick .I make into thinking that a file is more recent than it really is. If the file being touched does not exist, it is created, unless the \fB\-c\fR flag is present. +.PP +The \fB\-a\fR or \fB\-m\fR flag may be used to change only the access or +modification time. The \fB\-r\fR or \fB\-t\fR flag may be used to change +the times to match the times of another file or to a specified time. .SH "SEE ALSO" .BR utime (2). +.SH "AUTHOR" +.PP +Original author unknown. Rewritten for POSIX by Peter Holzer +(hp@vmars.tuwien.ac.at). +.\" man page updated by A. S. Woodhull 2005-01-15 + + + -- 2.44.0