ref: e72da62915b09d5673b0c0179ba8dfe045aeb8c3
parent: 9633c9fc65a833747a24cbd51f922afcf2859efd
author: foura <james@biobuf.link>
date: Sun May 2 11:29:43 EDT 2021
ip/ftpd: Add explict and implicit FTPS support. Removed: - Challenge reponse auth. - Noworld login. - Anonymous users writing files to /incoming.
--- a/sys/man/8/ipserv
+++ b/sys/man/8/ipserv
@@ -12,9 +12,11 @@
.B ip/rexexec
.PP
.B ip/ftpd
-.RB [ -aAde ]
+.RB [ -aAdei ]
.RB [ -n
.IR namepace-file ]
+.RB [ -c
+.IR cert-path ]
.PP
.B ip/socksd
[
@@ -113,32 +115,20 @@
.IR authsrv (6)).
.PP
.I Ftpd
-runs the Internet file transfer protocol. Users may transfer
+runs the Internet file transfer protocol. It supports both
+implicit and explicit ftps. Users may transfer
files in either direction between the local and
remote machines.
-As for
-.IR telnetd ,
-there are three types of login:
-.TF anonymo
+There are two types of login:
+.TF anonymous
.TP
.I normal
-Normal users authenticate
-via the same challenge/response as for
-.IR telnetd .
+Normal users authenticate with their username and password when using tls.
.BI /usr/ username /lib/namespace.ftp
or, if that file does not exist,
.B /lib/namespace
defines the namespace.
.TP
-.I noworld
-Users in group
-.B noworld
-in
-.B /adm/users
-login using a password in the clear.
-.B /lib/namespace.noworld
-defines the namespace.
-.TP
.I anonymous
Users
.B anonymous
@@ -150,9 +140,7 @@
option (default
.IR /lib/namespace.ftp )
defines the namespace.
-Anonymous users may only store files in the subtree
-below
-.BR /incoming .
+Anonymous users may not store files.
.PD
.PP
.IR Ftpd 's
@@ -167,22 +155,17 @@
anonymous access
.TP
.B d
-write debugging output to standard error
+write debugging output to the log
.TP
.B e
treat any user as anonymous
.TP
+.B c
+the certificate to use for serving ftps. The key must be stored in factotum.
+.TP
.B n
the namespace for anonymous users (default
.BR /lib/namespace.ftp )
-.PP
-To preserve intended protections in shared file trees,
-any directory containing a file
-.I .httplogin
-is locked by
-.IR ftpd;
-see
-.IR httpd (8).
.PP
.I Socksd
is a SOCKS4 and SOCKS5
--- a/sys/src/cmd/ip/ftpd.c
+++ b/sys/src/cmd/ip/ftpd.c
@@ -1,893 +1,440 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
-#include <auth.h>
#include <ip.h>
#include <libsec.h>
-#include <String.h>
+#include <auth.h>
+#include <String.h>
#include "glob.h"
-enum
-{- /* telnet control character */
- Iac= 255,
+enum {+ Tascii,
+ Timage,
- /* representation types */
- Tascii= 0,
- Timage= 1,
+ Maxpath = 512,
+ Maxwait = 1000 * 60 * 30, /* 30 minutes */
+};
- /* transmission modes */
- Mstream= 0,
- Mblock= 1,
- Mpage= 2,
+typedef struct Passive Passive;
+typedef struct Ftpd Ftpd;
+typedef struct Cmd Cmd;
- /* file structure */
- Sfile= 0,
- Sblock= 1,
- Scompressed= 2,
-
- /* read/write buffer size */
- Nbuf= 4096,
-
- /* maximum ms we'll wait for a command */
- Maxwait= 1000*60*30, /* inactive for 30 minutes, we hang up */
-
- Maxpath= 512,
+struct Passive {+ int inuse;
+ char adir[40];
+ int afd;
+ int port;
+ uchar ipaddr[IPaddrlen];
};
-int abortcmd(char*);
-int appendcmd(char*);
-int cdupcmd(char*);
-int cwdcmd(char*);
-int delcmd(char*);
-int helpcmd(char*);
-int listcmd(char*);
-int mdtmcmd(char*);
-int mkdircmd(char*);
-int modecmd(char*);
-int namelistcmd(char*);
-int nopcmd(char*);
-int optscmd(char*);
-int passcmd(char*);
-int pasvcmd(char*);
-int portcmd(char*);
-int pwdcmd(char*);
-int quitcmd(char*);
-int rnfrcmd(char*);
-int rntocmd(char*);
-int reply(char*, ...);
-int restartcmd(char*);
-int retrievecmd(char*);
-int sitecmd(char*);
-int sizecmd(char*);
-int storecmd(char*);
-int storeucmd(char*);
-int structcmd(char*);
-int systemcmd(char*);
-int typecmd(char*);
-int usercmd(char*);
+struct Ftpd {+ Biobuf *in, *out;
-int dialdata(void);
-char* abspath(char*);
-int crlfwrite(int, char*, int);
-int sodoff(void);
-int accessok(char*);
+ struct conn {+ int tlson, tlsondata;
+ NetConnInfo *nci;
+ TLSconn *tls;
+ uchar *cert;
+ int certlen;
+ char data[64];
+ Passive pasv;
+ } conn;
-typedef struct Cmd Cmd;
-struct Cmd
-{- char *name;
- int (*f)(char*);
- int needlogin;
+ struct user {+ char cwd[Maxpath];
+ char name[Maxpath];
+ int loggedin;
+ int isnone;
+ } user;
+
+ int type;
+ vlong offset;
+ int cmdpid;
+ char *renamefrom;
};
-Cmd cmdtab[] =
-{- { "abor", abortcmd, 0, },- { "allo", nopcmd, 1, },- { "appe", appendcmd, 1, },- { "cdup", cdupcmd, 1, },- { "cwd", cwdcmd, 1, },- { "dele", delcmd, 1, },- { "help", helpcmd, 0, },- { "list", listcmd, 1, },- { "mdtm", mdtmcmd, 1, },- { "mkd", mkdircmd, 1, },- { "mode", modecmd, 0, },- { "nlst", namelistcmd, 1, },- { "noop", nopcmd, 0, },- { "opts", optscmd, 0, },- { "pass", passcmd, 0, },- { "pasv", pasvcmd, 1, },- { "pwd", pwdcmd, 0, },- { "port", portcmd, 1, },- { "quit", quitcmd, 0, },- { "rest", restartcmd, 1, },- { "retr", retrievecmd, 1, },- { "rmd", delcmd, 1, },- { "rnfr", rnfrcmd, 1, },- { "rnto", rntocmd, 1, },- { "site", sitecmd, 1, },- { "size", sizecmd, 1, },- { "stor", storecmd, 1, },- { "stou", storeucmd, 1, },- { "stru", structcmd, 1, },- { "syst", systemcmd, 0, },- { "type", typecmd, 0, },- { "user", usercmd, 0, },- { 0, 0, 0 },+struct Cmd {+ char *name;
+ int (*fn)(Ftpd *, char *);
+ int needlogin;
+ int needtls;
+ int asproc;
};
-#define NONENS "/lib/namespace.ftp" /* default ns for none */
+char *certpath;
+char *namespace = "/lib/namespace.ftp";
+int implicittls;
+int debug;
+int anonok;
+int anononly;
+int anonall;
-char user[Maxpath]; /* logged in user */
-char curdir[Maxpath]; /* current directory path */
-Chalstate *ch;
-int loggedin;
-int type; /* transmission type */
-int mode; /* transmission mode */
-int structure; /* file structure */
-char data[64]; /* data address */
-int pid; /* transfer process */
-int encryption; /* encryption state */
-int isnone, anon_ok, anon_only, anon_everybody;
-char cputype[Maxpath]; /* the environment variable of the same name */
-char bindir[Maxpath]; /* bin directory for this architecture */
-char mailaddr[Maxpath];
-char *namespace = NONENS;
-int debug;
-NetConnInfo *nci;
-int createperm = 0660;
-int isnoworld;
-vlong offset; /* from restart command */
+void
+dprint(char *fmt, ...)
+{+ char *msg;
+ va_list arg;
-ulong id;
+ if(!debug) return;
-typedef struct Passive Passive;
-struct Passive
-{- int inuse;
- char adir[40];
- int afd;
- int port;
- uchar ipaddr[IPaddrlen];
-} passive;
+ va_start(arg, fmt);
+ msg = vsmprint(fmt, arg);
+ va_end(arg);
-#define FTPLOG "ftp"
+ syslog(0, "ftp", msg);
+ free(msg);
+}
-void
+void
logit(char *fmt, ...)
{- char buf[8192];
+ char *msg;
va_list arg;
va_start(arg, fmt);
- vseprint(buf, buf+sizeof(buf), fmt, arg);
+ msg = vsmprint(fmt, arg);
va_end(arg);
- syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
-}
-static void
-usage(void)
-{- syslog(0, "ftp", "usage: %s [-aAde] [-n nsfile]", argv0);
- fprint(2, "usage: %s [-aAde] [-n nsfile]\n", argv0);
- exits("usage");+ syslog(0, "ftp", msg);
+ free(msg);
}
-/*
- * read commands from the control stream and dispatch
- */
-void
-main(int argc, char **argv)
+int
+reply(Biobuf *bio, char *fmt, ...)
{- char *cmd;
- char *arg;
- char *p;
- Cmd *t;
- Biobuf in;
- int i;
-
- ARGBEGIN{- case 'a': /* anonymous OK */
- anon_ok = 1;
- break;
- case 'A':
- anon_ok = 1;
- anon_only = 1;
- break;
- case 'd':
- debug++;
- break;
- case 'e':
- anon_ok = 1;
- anon_everybody = 1;
- break;
- case 'n':
- namespace = EARGF(usage());
- break;
- default:
- usage();
- }ARGEND
-
- /* open log file before doing a newns */
- syslog(0, FTPLOG, nil);
-
- /* find out who is calling */
- if(argc < 1)
- nci = getnetconninfo(nil, 0);
- else
- nci = getnetconninfo(argv[argc-1], 0);
- if(nci == nil)
- sysfatal("ftpd needs a network address");-
- strcpy(mailaddr, "?");
- id = getpid();
-
- /* figure out which binaries to bind in later (only for none) */
- arg = getenv("cputype");- if(arg)
- strecpy(cputype, cputype+sizeof cputype, arg);
- else
- strcpy(cputype, "mips");
- /* shurely /%s/bin */
- snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
-
- Binit(&in, 0, OREAD);
- reply("220 Plan 9 FTP server ready");- alarm(Maxwait);
- while(cmd = Brdline(&in, '\n')){- alarm(0);
-
- /*
- * strip out trailing cr's & lf and delimit with null
- */
- i = Blinelen(&in)-1;
- cmd[i] = 0;
- if(debug)
- logit("%s", cmd);- while(i > 0 && cmd[i-1] == '\r')
- cmd[--i] = 0;
-
- /*
- * hack for GatorFTP+, look for a 0x10 used as a delimiter
- */
- p = strchr(cmd, 0x10);
- if(p)
- *p = 0;
-
- /*
- * get rid of telnet control sequences (we don't need them)
- */
- while(*cmd && (uchar)*cmd == Iac){- cmd++;
- if(*cmd)
- cmd++;
- }
-
- /*
- * parse the message (command arg)
- */
- arg = strchr(cmd, ' ');
- if(arg){- *arg++ = 0;
- while(*arg == ' ')
- arg++;
- }
-
- /*
- * ignore blank commands
- */
- if(*cmd == 0)
- continue;
-
- /*
- * lookup the command and do it
- */
- for(p = cmd; *p; p++)
- *p = tolower(*p);
- for(t = cmdtab; t->name; t++)
- if(strcmp(cmd, t->name) == 0){- if(t->needlogin && !loggedin)
- sodoff();
- else if((*t->f)(arg) < 0)
- exits(0);
- break;
- }
- if(t->f != restartcmd){- /*
- * the file offset is set to zero following
- * all commands except the restart command
- */
- offset = 0;
- }
- if(t->name == 0){- /*
- * the OOB bytes preceding an abort from UCB machines
- * comes out as something unrecognizable instead of
- * IAC's. Certainly a Plan 9 bug but I can't find it.
- * This is a major hack to avoid the problem. -- presotto
- */
- i = strlen(cmd);
- if(i > 4 && strcmp(cmd+i-4, "abor") == 0){- abortcmd(0);
- } else{- logit("%s (%s) command not implemented", cmd, arg?arg:"");- reply("502 %s command not implemented", cmd);- }
- }
- alarm(Maxwait);
- }
- if(pid)
- postnote(PNPROC, pid, "kill");
-}
-
-/*
- * reply to a command
- */
-int
-reply(char *fmt, ...)
-{va_list arg;
- char buf[8192], *s;
+ char buf[Maxpath], *s;
va_start(arg, fmt);
- s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
+ s = vseprint(buf, buf + sizeof(buf) - 3, fmt, arg);
va_end(arg);
- if(debug){- *s = 0;
- logit("%s", buf);- }
+
+ dprint("rpl: %s", buf);+
*s++ = '\r';
*s++ = '\n';
- write(1, buf, s - buf);
+ Bwrite(bio, buf, s - buf);
+ Bflush(bio);
+
return 0;
}
-int
-sodoff(void)
+void
+asproc(Ftpd *ftpd, int (*f)(Ftpd *, char *), char *arg)
{- return reply("530 Sod off, service requires login");-}
-
-/*
- * run a command in a separate process
- */
-int
-asproc(void (*f)(char*, int), char *arg, int arg2)
-{int i;
- if(pid){- /* wait for previous command to finish */
- for(;;){+ if(ftpd->cmdpid) {+ for(;;) {i = waitpid();
- if(i == pid || i < 0)
+ if(i == ftpd->cmdpid || i < 0)
break;
}
}
- switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){+ switch(ftpd->cmdpid = rfork(RFFDG|RFPROC|RFNOTEG)){case -1:
- return reply("450 Out of processes: %r");+ reply(ftpd->out, "450 Out of processes: %r");
+ return;
case 0:
- (*f)(arg, arg2);
- exits(0);
+ (*f)(ftpd, arg);
+ dprint("proc exiting");+ exits(nil);
default:
break;
}
- return 0;
}
-/*
- * run a command to filter a tail
- */
-int
-transfer(char *cmd, char *a1, char *a2, char *a3, int image)
+int
+mountnet(Ftpd *ftpd)
{- int n, dfd, fd, bytes, eofs, pid;
- int pfd[2];
- char buf[Nbuf], *p;
- Waitmsg *w;
-
- reply("150 Opening data connection for %s (%s)", cmd, data);- dfd = dialdata();
- if(dfd < 0)
- return reply("425 Error opening data connection: %r");-
- if(pipe(pfd) < 0)
- return reply("520 Internal Error: %r");-
- bytes = 0;
- switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){- case -1:
- return reply("450 Out of processes: %r");- case 0:
- logit("running %s %s %s %s pid %d",- cmd, a1?a1:"", a2?a2:"" , a3?a3:"",getpid());
- close(pfd[1]);
- close(dfd);
- dup(pfd[0], 1);
- dup(pfd[0], 2);
- if(isnone){- fd = open("#s/boot", ORDWR);- if(fd < 0
- || bind("#/", "/", MAFTER) == -1- || amount(fd, "/bin", MREPL, "") == -1
- || bind("#c", "/dev", MAFTER) == -1- || bind(bindir, "/bin", MREPL) == -1)
- exits("building name space");- close(fd);
- }
- execl(cmd, cmd, a1, a2, a3, nil);
- exits(cmd);
- default:
- close(pfd[0]);
- eofs = 0;
- while((n = read(pfd[1], buf, sizeof buf)) >= 0){- if(n == 0){- if(eofs++ > 5)
- break;
- else
- continue;
- }
- eofs = 0;
- p = buf;
- if(offset > 0){- if(n > offset){- p = buf+offset;
- n -= offset;
- offset = 0;
- } else {- offset -= n;
- continue;
- }
- }
- if(!image)
- n = crlfwrite(dfd, p, n);
- else
- n = write(dfd, p, n);
- if(n < 0){- postnote(PNPROC, pid, "kill");
- bytes = -1;
- break;
- }
- bytes += n;
- }
- close(pfd[1]);
- close(dfd);
- break;
+ if(bind("#/", "/", MAFTER) == -1) {+ reply(ftpd->out, "500 can't bind #/ to /: %r");
+ return -1;
}
- /* wait for this command to finish */
- for(;;){- w = wait();
- if(w == nil || w->pid == pid)
- break;
- free(w);
+ if(bind(ftpd->conn.nci->spec, "/net", MBEFORE) == -1) {+ reply(ftpd->out, "500 can't bind %s to /net: %r", ftpd->conn.nci->spec);
+ unmount("#/", "/");+ return -1;
}
- if(w != nil && w->msg != nil && w->msg[0] != 0){- bytes = -1;
- logit("%s", w->msg);- logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);- }
- free(w);
- reply("226 Transfer complete");- return bytes;
-}
-int
-optscmd(char *arg)
-{- char *p;
-
- if(arg == 0 || *arg == 0){- reply("501 Syntax error in parameters or arguments");- return 0;
- }
- if(p = strchr(arg, ' '))
- *p = 0;
- if(cistrcmp(arg, "UTF-8") == 0 || cistrcmp(arg, "UTF8") == 0){- reply("200 Command okay");- return 0;
- }
- reply("502 %s option not implemented", arg);return 0;
}
-/*
- * just reply OK
- */
-int
-nopcmd(char *arg)
+void
+unmountnet(void)
{- USED(arg);
- reply("510 Plan 9 FTP daemon still alive");- return 0;
+ unmount(nil, "/net");
+ unmount("#/", "/");}
-/*
- * login as user
- */
-int
-loginuser(char *user, char *nsfile, int gotoslash)
+Biobuf *
+dialdata(Ftpd *ftpd, int read)
{- logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);- if(nsfile != nil && newns(user, nsfile) < 0){- logit("namespace file %s does not exist", nsfile);- return reply("530 Not logged in: login out of service");+ Biobuf *bio;
+ TLSconn *tls;
+ int fd, cfd;
+ char ldir[40];
+
+ if(mountnet(ftpd) < 0)
+ return nil;
+
+ if(!ftpd->conn.pasv.inuse) {+ fd = dial(ftpd->conn.data, "20", 0, 0);
+ } else {+ fd = -1;
+ alarm(30 * 1000); /* wait 30 seconds */
+ dprint("dbg: waiting for passive connection");+ cfd = listen(ftpd->conn.pasv.adir, ldir);
+ alarm(0);
+
+ if(cfd >= 0) {+ fd = accept(cfd, ldir);
+ close(cfd);
+ }
}
- getwd(curdir, sizeof(curdir));
- if(gotoslash){- chdir("/");- strcpy(curdir, "/");
+
+ if(fd < 0) {+ reply(ftpd->out, "425 Error opening data connection");
+ unmountnet();
+ return nil;
}
- putenv("service", "ftp");- loggedin = 1;
- if(debug == 0)
- reply("230- If you have problems, send mail to 'postmaster'.");- return reply("230 Logged in");-}
-static void
-slowdown(void)
-{- static ulong pause;
+ reply(ftpd->out, "150 Opened data connection");
- if (pause) {- sleep(pause); /* deter guessers */
- if (pause < (1UL << 20))
- pause *= 2;
- } else
- pause = 1000;
-}
+ tls = nil;
+ if(ftpd->conn.tlsondata) {+ dprint("dbg: using tls on data channel");-/*
- * get a user id, reply with a challenge. The users 'anonymous'
- * and 'ftp' are equivalent to 'none'. The user 'none' requires
- * no challenge.
- */
-int
-usercmd(char *name)
-{- slowdown();
+ tls = mallocz(sizeof(TLSconn), 1);
+ tls->cert = malloc(ftpd->conn.certlen);
+ memcpy(tls->cert, ftpd->conn.cert, ftpd->conn.certlen);
+ tls->certlen = ftpd->conn.certlen;
+ fd = tlsServer(fd, tls);
- logit("user %s %s", name, nci->rsys);- if(loggedin)
- return reply("530 Already logged in as %s", user);- if(name == 0 || *name == 0)
- return reply("530 user command needs user name");- isnoworld = 0;
- if(*name == ':'){- debug = 1;
- name++;
- }
- strncpy(user, name, sizeof(user));
- if(debug)
- logit("debugging");- user[sizeof(user)-1] = 0;
- if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
- strcpy(user, "none");
- else if(anon_everybody)
- strcpy(user,"none");
+ if(fd < 0) {+ reply(ftpd->out, "425 TLS on data connection failed");
+ unmountnet();
+ return nil;
+ }
- if(strcmp(user, "Administrator") == 0 || strcmp(user, "admin") == 0)
- return reply("530 go away, script kiddie");- else if(strcmp(user, "*none") == 0){- if(!anon_ok)
- return reply("530 Not logged in: anonymous disallowed");- return loginuser("none", namespace, 1);+ dprint("dbg: tlsserver done");}
- else if(strcmp(user, "none") == 0){- if(!anon_ok)
- return reply("530 Not logged in: anonymous disallowed");- return reply("331 Send email address as password");- }
- else if(anon_only)
- return reply("530 Not logged in: anonymous access only");- isnoworld = noworld(name);
- if(isnoworld)
- return reply("331 OK");+ unmountnet();
+ if(read)
+ bio = Bfdopen(fd, OREAD);
+ else
+ bio = Bfdopen(fd, OWRITE);
+ bio->aux = tls;
- /* consult the auth server */
- if(ch)
- auth_freechal(ch);
- if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)- return reply("421 %r");- return reply("331 encrypt challenge, %s, as a password", ch->chal);+ return bio;
}
-/*
- * get a password, set up user if it works.
- */
-int
-passcmd(char *response)
+void
+closedata(Ftpd *ftpd, Biobuf *bio, int fail)
{- char namefile[128];
- AuthInfo *ai;
- Dir nd;
+ TLSconn *conn;
- if(response == nil)
- response = "";
+ conn = bio->aux;
- if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){- /* for none, accept anything as a password */
- isnone = 1;
- strncpy(mailaddr, response, sizeof(mailaddr)-1);
- return loginuser("none", namespace, 1);- }
+ Bflush(bio);
+ Bterm(bio);
+ if(!fail)
+ reply(ftpd->out, "226 Transfer complete");
- if(isnoworld){- /* noworld gets a password in the clear */
- if(login(user, response, "/lib/namespace.noworld") < 0)
- return reply("530 Not logged in");- createperm = 0664;
- /* login has already setup the namespace */
- return loginuser(user, nil, 0);
- } else {- /* for everyone else, do challenge response */
- if(ch == nil)
- return reply("531 Send user id before encrypted challenge");- ch->resp = response;
- ch->nresp = strlen(response);
- ai = auth_response(ch);
- if(ai == nil || auth_chuid(ai, nil) < 0) {- auth_freeAI(ai);
- slowdown();
- return reply("530 Not logged in: %r");- }
- /* chown network connection */
- nulldir(&nd);
- nd.mode = 0660;
- nd.uid = ai->cuid;
- dirfwstat(0, &nd);
-
- auth_freeAI(ai);
- auth_freechal(ch);
- ch = nil;
-
- /* if the user has specified a namespace for ftp, use it */
- snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
- strcpy(mailaddr, user);
- createperm = 0660;
- if(access(namefile, 0) == 0)
- return loginuser(user, namefile, 0);
- else
- return loginuser(user, "/lib/namespace", 0);
+ if(conn) {+ free(conn->cert);
+ free(conn);
}
}
-/*
- * print working directory
- */
-int
-pwdcmd(char *arg)
+int
+starttls(Ftpd *ftpd)
{- if(arg)
- return reply("550 Pwd takes no argument");- return reply("257 \"%s\" is the current directory", curdir);-}
+ int fd;
-/*
- * chdir
- */
-int
-cwdcmd(char *dir)
-{- char *rp;
- char buf[Maxpath];
+ fd = tlsServer(0, ftpd->conn.tls);
+ if(fd < 0)
+ return -1;
- /* shell cd semantics */
- if(dir == 0 || *dir == 0){- if(isnone)
- rp = "/";
- else {- snprint(buf, sizeof buf, "/usr/%s", user);
- rp = buf;
- }
- if(accessok(rp) == 0)
- rp = nil;
- } else
- rp = abspath(dir);
+ dup(fd, 0);
+ dup(fd, 1);
+ ftpd->conn.tlson = 1;
- if(rp == nil)
- return reply("550 Permission denied");-
- if(chdir(rp) < 0)
- return reply("550 Cwd failed: %r");- strcpy(curdir, rp);
- return reply("250 directory changed to %s", curdir);+ return 0;
}
-/*
- * chdir ..
- */
int
-cdupcmd(char *dp)
+abortcmd(Ftpd *ftpd, char *arg)
{- USED(dp);
- return cwdcmd("..");-}
-
-int
-quitcmd(char *arg)
-{USED(arg);
- reply("200 Bye");- if(pid)
- postnote(PNPROC, pid, "kill");
- return -1;
+
+ if(ftpd->cmdpid){+ if(postnote(PNPROC, ftpd->cmdpid, "kill") == 0)
+ reply(ftpd->out, "426 Command aborted");
+ else
+ logit("postnote pid %d %r", ftpd->cmdpid);+ }
+ return reply(ftpd->out, "226 Abort processed");
}
-int
-typecmd(char *arg)
+int
+authcmd(Ftpd *ftpd, char *arg)
{- int c;
- char *x;
+ if((cistrcmp(arg, "TLS") == 0) || (cistrcmp(arg, "TLS-C") == 0) || (cistrcmp(arg, "SSL") == 0)) {- x = arg;
- if(arg == 0)
- return reply("501 Type command needs arguments");+ if(!ftpd->conn.tls)
+ return reply(ftpd->out, "431 tls not enabled");
- while(c = *arg++){- switch(tolower(c)){- case 'a':
- type = Tascii;
- break;
- case 'i':
- case 'l':
- type = Timage;
- break;
- case '8':
- case ' ':
- case 'n':
- case 't':
- case 'c':
- break;
- default:
- return reply("501 Unimplemented type %s", x);- }
+ reply(ftpd->out, "234 starting tls");
+ if(starttls(ftpd) < 0)
+ return reply(ftpd->out, "431 tls failed");
+ } else {+ return reply(ftpd->out, "502 security method %s not understood", arg);
}
- return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");+
+ return 0;
}
-int
-modecmd(char *arg)
+int
+cwdcmd(Ftpd *ftpd, char *arg)
{- if(arg == 0)
- return reply("501 Mode command needs arguments");- while(*arg){- switch(tolower(*arg)){- case 's':
- mode = Mstream;
- break;
- default:
- return reply("501 Unimplemented mode %c", *arg);- }
- arg++;
+ char buf[Maxpath];
+
+ if(!arg || *arg == '\0') {+ if(ftpd->user.isnone)
+ snprint(buf, Maxpath, "/");
+ else
+ snprint(buf, Maxpath, "/usr/%s", ftpd->user.name);
+ } else {+ strncpy(buf, arg, Maxpath);
+ cleanname(buf);
}
- return reply("200 Stream mode");+
+ if(chdir(buf) < 0)
+ return reply(ftpd->out, "550 CWD failed: %r");
+
+ getwd(ftpd->user.cwd, Maxpath);
+ return reply(ftpd->out, "200 Directory changed to %s", ftpd->user.cwd);
}
-int
-structcmd(char *arg)
+int
+deletecmd(Ftpd *ftpd, char *arg)
{- if(arg == 0)
- return reply("501 Struct command needs arguments");- for(; *arg; arg++){- switch(tolower(*arg)){- case 'f':
- structure = Sfile;
- break;
- default:
- return reply("501 Unimplemented structure %c", *arg);- }
- }
- return reply("200 File structure");+ if(!arg)
+ return reply(ftpd->out, "501 Rmdir/Delete command needs an argument");
+ if(ftpd->user.isnone)
+ return reply(ftpd->out, "550 Permission denied");
+ if(remove(cleanname(arg)) < 0)
+ return reply(ftpd->out, "550 Can't remove %s: %r", arg);
+ else
+ return reply(ftpd->out, "226 \"%s\" removed", arg);
}
-int
-portcmd(char *arg)
+int
+featcmd(Ftpd *ftpd, char *arg)
{- char *field[7];
- int n;
-
- if(arg == 0)
- return reply("501 Port command needs arguments");- n = getfields(arg, field, 7, 0, ", ");
- if(n != 6)
- return reply("501 Incorrect port specification");- snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
- field[3], atoi(field[4])*256 + atoi(field[5]));
- return reply("200 Data port is %s", data);+ USED(arg);
+ reply(ftpd->out, "211-Features supported");
+ reply(ftpd->out, " UTF8");
+ reply(ftpd->out, " PBSZ");
+ reply(ftpd->out, " PROT");
+ reply(ftpd->out, " AUTH TLS");
+ reply(ftpd->out, " MLST Type*;Size*;Modify*;Unix.groupname*;UNIX.ownername*;");
+ return reply(ftpd->out, "211 End");
}
-int
-mountnet(void)
+int
+dircmp(void *va, void *vb)
{- int rv;
+ Dir *a, *b;
- rv = 0;
+ a = va;
+ b = vb;
- if(bind("#/", "/", MAFTER) == -1){- logit("can't bind #/ to /: %r");- return reply("500 can't bind #/ to /: %r");- }
-
- if(bind(nci->spec, "/net", MBEFORE) == -1){- logit("can't bind %s to /net: %r", nci->spec);- rv = reply("500 can't bind %s to /net: %r", nci->spec);- unmount("#/", "/");- }
-
- return rv;
+ return strcmp(a->name, b->name);
}
void
-unmountnet(void)
+listdir(Ftpd *ftpd, Biobuf *data, char *path, void (*fn)(Biobuf *, Dir *d, char *dirname))
{- unmount(0, "/net");
- unmount("#/", "/");+ Dir *dirbuf;
+ int fd;
+ long ndirs;
+ long i;
+
+ fd = open(path, OREAD);
+ if(!fd)
+ return;
+
+ ndirs = dirreadall(fd, &dirbuf);
+ if(ndirs < 1)
+ return;
+ close(fd);
+
+ qsort(dirbuf, ndirs, sizeof(Dir), dircmp);
+ for(i=0;i<ndirs;i++)
+ (*fn)(data, &dirbuf[i], (strcmp(path, ftpd->user.cwd) == 0 ? nil : path));
+
+ free(dirbuf);
}
int
-pasvcmd(char *arg)
+list(Ftpd *ftpd, char *arg, void (*fn)(Biobuf *, Dir *d, char *dirname))
{- NetConnInfo *nnci;
- Passive *p;
+ Biobuf *data;
+ int argc, i;
+ char *argv[32];
+ Globlist *gl;
+ char *path;
+ Dir *d;
- USED(arg);
- p = &passive;
-
- if(p->inuse){- close(p->afd);
- p->inuse = 0;
+ if(arg) {+ argc = getfields(arg, argv, sizeof(argv)-1, 1, " \t");
+ } else {+ argc = 1;
+ argv[0] = ftpd->user.cwd;
}
- if(mountnet() < 0)
- return 0;
+ data = dialdata(ftpd, 0);
+ if(!data)
+ return reply(ftpd->out, "500 List failed: couldn't dial data");
- p->afd = announce("tcp!*!0", passive.adir);- if(p->afd < 0){- unmountnet();
- return reply("500 No free ports");- }
- nnci = getnetconninfo(p->adir, -1);
- unmountnet();
+ for(i=0;i<argc;i++) {+ gl = glob(argv[i]);
+ if(!gl)
+ continue;
- /* parse the local address */
- if(debug)
- logit("local sys is %s", nci->lsys);- parseip(p->ipaddr, nci->lsys);
- if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
- parseip(p->ipaddr, nci->lsys);
- p->port = atoi(nnci->lserv);
+ while(path = globiter(gl)) {+ cleanname(path);
- freenetconninfo(nnci);
- p->inuse = 1;
+ logit("list: path %s user %s", path, ftpd->user.name);- return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",- p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
- p->port>>8, p->port&0xff);
-}
+ d = dirstat(path);
+ if(d->mode & DMDIR)
+ listdir(ftpd, data, path, fn);
+ else
+ (*fn)(data, d, nil);
-enum
-{- Narg=32,
-};
-int Cflag, rflag, tflag, Rflag;
-int maxnamelen;
-int col;
+ free(d);
+ }
+ }
-char*
+ closedata(ftpd, data, 0);
+
+ return 0;
+}
+
+char *
mode2asc(int m)
{- static char asc[12];
+ char *asc;
char *p;
- strcpy(asc, "----------");
+ asc = strdup("----------");if(DMDIR & m)
asc[0] = 'd';
if(DMAPPEND & m)
@@ -895,7 +442,7 @@
else if(DMEXCL & m)
asc[3] = 'l';
- for(p = asc+1; p < asc + 10; p += 3, m<<=3){+ for(p = asc + 1; p < asc + 10; p += 3, m <<= 3) {if(m & 0400)
p[0] = 'r';
if(m & 0200)
@@ -903,1038 +450,675 @@
if(m & 0100)
p[2] = 'x';
}
+
return asc;
}
-void
-listfile(Biobufhdr *b, char *name, int lflag, char *dname)
-{- char ts[32];
- int n, links, pad;
- long now;
- char *x;
- Dir *d;
- x = abspath(name);
- if(x == nil)
- return;
- d = dirstat(x);
- if(d == nil)
- return;
- if(isnone){- if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)- d->mode &= ~0222;
- d->uid = "none";
- d->gid = "none";
- }
-
- strcpy(ts, ctime(d->mtime));
- ts[16] = 0;
- now = time(0);
- if(now - d->mtime > 6*30*24*60*60)
- memmove(ts+11, ts+23, 5);
- if(lflag){- /* Unix style long listing */
- if(DMDIR&d->mode){- links = 2;
- d->length = 512;
- } else
- links = 1;
-
- Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
- mode2asc(d->mode), links,
- d->uid, d->gid, d->length, ts+4);
- }
- if(Cflag && maxnamelen < 40){- n = strlen(name);
- pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
- if(pad+maxnamelen+1 < 60){- Bprint(b, "%*s", pad-col+n, name);
- col = pad+n;
- }
- else{- Bprint(b, "\r\n%s", name);
- col = n;
- }
- }
- else{- if(dname)
- Bprint(b, "%s/", dname);
- Bprint(b, "%s\r\n", name);
- }
- free(d);
-}
-int
-dircomp(void *va, void *vb)
+void
+listprint(Biobuf *data, Dir *d, char *dirname)
{- int rv;
- Dir *a, *b;
+ char *ts, *mode;
- a = va;
- b = vb;
+ ts = strdup(ctime(d->mtime));
+ ts[16] = '\0';
+ if(time(0) - d->mtime > 6 * 30 * 24 * 60 * 60)
+ memmove(ts + 11, ts + 23, 5);
- if(tflag)
- rv = b->mtime - a->mtime;
+ mode = mode2asc(d->mode);
+
+ if(dirname)
+ reply(data, "%s %3d %-8s %-8s %7lld %s %s/%s",
+ mode, 1, d->uid, d->gid, d->length, ts + 4, dirname, d->name);
else
- rv = strcmp(a->name, b->name);
- return (rflag?-1:1)*rv;
+ reply(data, "%s %3d %-8s %-8s %7lld %s %s",
+ mode, 1, d->uid, d->gid, d->length, ts + 4, d->name);
+
+ free(mode);
+ free(ts);
}
-void
-listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
-{- Dir *p;
- int fd, n, i, l;
- char *dname;
- uvlong total;
- col = 0;
-
- fd = open(name, OREAD);
- if(fd < 0){- Bprint(b, "can't read %s: %r\r\n", name);
- return;
- }
- dname = 0;
- if(*printname){- if(Rflag || lflag)
- Bprint(b, "\r\n%s:\r\n", name);
- else
- dname = name;
- }
- n = dirreadall(fd, &p);
- close(fd);
- if(Cflag){- for(i = 0; i < n; i++){- l = strlen(p[i].name);
- if(l > maxnamelen)
- maxnamelen = l;
- }
- }
-
- /* Unix style total line */
- if(lflag){- total = 0;
- for(i = 0; i < n; i++){- if(p[i].qid.type & QTDIR)
- total += 512;
- else
- total += p[i].length;
- }
- Bprint(b, "total %ulld\r\n", total/512);
- }
-
- qsort(p, n, sizeof(Dir), dircomp);
- for(i = 0; i < n; i++){- if(Rflag && (p[i].qid.type & QTDIR)){- *printname = 1;
- globadd(gl, name, p[i].name);
- }
- listfile(b, p[i].name, lflag, dname);
- }
- free(p);
+int
+listcmd(Ftpd *ftpd, char *arg)
+{+ return list(ftpd, arg, listprint);
}
-void
-list(char *arg, int lflag)
+
+int
+loginuser(Ftpd *ftpd, char *pass, char *nsfile)
{- Dir *d;
- Globlist *gl;
- Glob *g;
- int dfd, printname;
- int i, n, argc;
- char *alist[Narg];
- char **argv;
- Biobufhdr bh;
- uchar buf[512];
- char *p, *s;
+ char *user;
- if(arg == 0)
- arg = "";
+ user = ftpd->user.name;
- if(debug)
- logit("ls %s (. = %s)", arg, curdir);-
- /* process arguments, understand /bin/ls -l option */
- argv = alist;
- argv[0] = "/bin/ls";
- argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
- argv[argc] = 0;
- rflag = 0;
- tflag = 0;
- Rflag = 0;
- Cflag = 0;
- col = 0;
- ARGBEGIN{- case 'l':
- lflag++;
- break;
- case 'R':
- Rflag++;
- break;
- case 'C':
- Cflag++;
- break;
- case 'r':
- rflag++;
- break;
- case 't':
- tflag++;
- break;
- }ARGEND;
- if(Cflag)
- lflag = 0;
-
- dfd = dialdata();
- if(dfd < 0){- reply("425 Error opening data connection: %r");- return;
+ putenv("service", "ftp");+ if(!ftpd->user.isnone) {+ if(login(user, pass, nsfile) < 0)
+ return reply(ftpd->out, "530 Not logged in: bad password");
+ } else {+ if(newns(user, nsfile) < 0)
+ return reply(ftpd->out, "530 Not logged in: user out of service");
}
- reply("150 Opened data connection (%s)", data);- Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
- if(argc == 0){- argc = 1;
- argv = alist;
- argv[0] = ".";
- }
+ getwd(ftpd->user.cwd, Maxpath);
- for(i = 0; i < argc; i++){- chdir(curdir);
- gl = glob(argv[i]);
- if(gl == nil)
- continue;
+ logit("login: %s in dir %s with ns %s",+ ftpd->user.name,
+ ftpd->user.cwd,
+ nsfile);
- printname = gl->first != nil && gl->first->next != nil;
- maxnamelen = 8;
+ ftpd->user.loggedin = 1;
+ if(ftpd->user.isnone)
+ return reply(ftpd->out, "230 Logged in: anonymous access");
+ else
+ return reply(ftpd->out, "230 Logged in");
+}
- if(Cflag)
- for(g = gl->first; g; g = g->next)
- if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
- maxnamelen = n;
- while(s = globiter(gl)){- if(debug)
- logit("glob %s", s);- p = abspath(s);
- if(p == nil){- free(s);
- continue;
- }
- d = dirstat(p);
- if(d == nil){- free(s);
- continue;
- }
- if(d->qid.type & QTDIR)
- listdir(s, &bh, lflag, &printname, gl);
- else
- listfile(&bh, s, lflag, 0);
- free(s);
- free(d);
- }
- globlistfree(gl);
- }
- if(Cflag)
- Bprint(&bh, "\r\n");
- Bflush(&bh);
- close(dfd);
-
- reply("226 Transfer complete (list %s)", arg);+void
+nlistprint(Biobuf *data, Dir *d, char*)
+{+ reply(data, "%s", d->name);
}
-int
-namelistcmd(char *arg)
+
+int
+nlistcmd(Ftpd *ftpd, char *arg)
{- return asproc(list, arg, 0);
+ return list(ftpd, arg, nlistprint);
}
-int
-listcmd(char *arg)
+
+int
+noopcmd(Ftpd *ftpd, char *arg)
{- return asproc(list, arg, 1);
+ USED(arg);
+ return reply(ftpd->out, "200 Plan 9 FTP Server still alive");
}
-/*
- * fuse compatability
- */
int
-oksiteuser(void)
+mkdircmd(Ftpd *ftpd, char *arg)
{- char buf[64];
- int fd, n;
+ int fd;
- fd = open("#c/user", OREAD);+ if(!arg)
+ reply(ftpd->out, "501 Mkdir command requires argument.");
+ if(ftpd->user.isnone)
+ reply(ftpd->out, "550 Permission denied");
+
+ cleanname(arg);
+ fd = create(arg, OREAD, DMDIR|0755);
if(fd < 0)
- return 1;
- n = read(fd, buf, sizeof buf - 1);
- if(n > 0){- buf[n] = 0;
- if(strcmp(buf, "none") == 0)
- n = -1;
- }
+ return reply(ftpd->out, "550 Can't create %s: %r", arg);
close(fd);
- return n > 0;
+
+ return reply(ftpd->out, "226 %s created", arg);
}
-int
-sitecmd(char *arg)
+void
+mlsdprint(Biobuf *data, Dir *d, char*)
{- char *f[4];
- int nf, r;
- Dir *d;
+ Tm mtime;
- if(arg == 0)
- return reply("501 bad site command");- nf = tokenize(arg, f, nelem(f));
- if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
- return reply("501 bad site command");- if(!oksiteuser())
- return reply("550 Permission denied");- d = dirstat(f[2]);
- if(d == nil)
- return reply("501 site chmod: file does not exist");- d->mode &= ~0777;
- d->mode |= strtoul(f[1], 0, 8) & 0777;
- r = dirwstat(f[2], d);
- free(d);
- if(r < 0)
- return reply("550 Permission denied %r");- return reply("200 very well, then");- }
+ tmtime(&mtime, d->mtime, nil);
+ reply(data, "Type=%s;Size=%d;Modify=%τ;Unix.groupname=%s;Unix.ownername=%s; %s",
+ (d->mode & DMDIR ? "dir" : "file"), d->length, tmfmt(&mtime, "YYYYMMDDhhmmss"),
+ d->gid, d->uid, d->name);
+}
-/*
- * return the size of the file
- */
-int
-sizecmd(char *arg)
+int
+mlsdcmd(Ftpd *ftpd, char *arg)
{- Dir *d;
- int rv;
-
- if(arg == 0)
- return reply("501 Size command requires pathname");- arg = abspath(arg);
- d = dirstat(arg);
- if(d == nil)
- return reply("501 %r accessing %s", arg);- rv = reply("213 %lld", d->length);- free(d);
- return rv;
+ return list(ftpd, arg, mlsdprint);
}
-/*
- * return the modify time of the file
- */
-int
-mdtmcmd(char *arg)
+int
+mlstcmd(Ftpd *ftpd, char *arg)
{Dir *d;
- Tm *t;
- int rv;
+ char *path;
- if(arg == 0)
- return reply("501 Mdtm command requires pathname");- if(arg == 0)
- return reply("550 Permission denied");- d = dirstat(arg);
- if(d == nil)
- return reply("501 %r accessing %s", arg);- t = gmtime(d->mtime);
- rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",- t->year+1900, t->mon+1, t->mday,
- t->hour, t->min, t->sec);
+ if(arg != nil)
+ path = arg;
+ else
+ path = ftpd->user.cwd;
+
+ d = dirstat(path);
+ if(!d)
+ return reply(ftpd->out, "500 Mlst failed: %r");
+
+ reply(ftpd->out, "250-MLST %s", arg);
+ Bprint(ftpd->out, " ");
+ mlsdprint(ftpd->out, d, nil);
free(d);
- return rv;
+
+ return reply(ftpd->out, "250 End");
}
-/*
- * set an offset to start reading a file from
- * only lasts for one command
- */
-int
-restartcmd(char *arg)
+int
+optscmd(Ftpd *ftpd, char *arg)
{- if(arg == 0)
- return reply("501 Restart command requires offset");- offset = atoll(arg);
- if(offset < 0){- offset = 0;
- return reply("501 Bad offset");- }
+ if(cistrcmp(arg, "utf8 on") == 0)
+ return reply(ftpd->out, "200 UTF8 always on");
- return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);+ return reply(ftpd->out, "501 Option not implemented");
}
-/*
- * send a file to the user
- */
-int
-crlfwrite(int fd, char *p, int n)
+int
+passcmd(Ftpd *ftpd, char *arg)
{- char *ep, *np;
- char buf[2*Nbuf];
+ char *nsfile;
- for(np = buf, ep = p + n; p < ep; p++){- if(*p == '\n')
- *np++ = '\r';
- *np++ = *p;
- }
- if(write(fd, buf, np - buf) == np - buf)
- return n;
- else
- return -1;
-}
-void
-retrievedir(char *arg)
-{- int n;
- char *p;
- String *file;
+ if(strlen(ftpd->user.name) == 0)
+ return reply(ftpd->out, "531 Specify a user first");
- if(type != Timage){- reply("550 This file requires type binary/image");- return;
- }
-
- file = s_copy(arg);
- p = strrchr(s_to_c(file), '/');
- if(p != s_to_c(file)){- *p++ = 0;
- chdir(s_to_c(file));
- } else {- chdir("/");- p = s_to_c(file)+1;
- }
-
- n = transfer("/bin/tar", "c", p, 0, 1);- if(n < 0)
- logit("get %s failed", arg);+ nsfile = smprint("/usr/%s/lib/namespace.ftp", ftpd->user.name);+ if(ftpd->user.isnone)
+ loginuser(ftpd, arg, namespace);
+ else if(access(nsfile, 0) == 0)
+ loginuser(ftpd, arg, nsfile);
else
- logit("get %s OK %d", arg, n);- s_free(file);
+ loginuser(ftpd, arg, "/lib/namespace");
+ free(nsfile);
+
+ return 0;
}
-void
-retrieve(char *arg, int arg2)
+
+int
+pasvcmd(Ftpd *ftpd, char *arg)
{- int dfd, fd, n, i, bytes;
- Dir *d;
- char buf[Nbuf];
- char *p, *ep;
+ NetConnInfo *nci;
+ Passive *p;
- USED(arg2);
+ USED(arg);
- p = strchr(arg, '\r');
- if(p){- logit("cr in file name", arg);- *p = 0;
+ p = &ftpd->conn.pasv;
+ if(p->inuse) {+ close(p->afd);
+ p->inuse = 0;
}
- fd = open(arg, OREAD);
- if(fd == -1){- n = strlen(arg);
- if(n > 4 && strcmp(arg+n-4, ".tar") == 0){- *(arg+n-4) = 0;
- d = dirstat(arg);
- if(d != nil){- if(d->qid.type & QTDIR){- retrievedir(arg);
- free(d);
- return;
- }
- free(d);
- }
- }
- logit("get %s failed", arg);- reply("550 Error opening %s: %r", arg);- return;
- }
- if(offset != 0)
- if(seek(fd, offset, 0) < 0){- reply("550 %s: seek to %lld failed", arg, offset);- close(fd);
- return;
- }
- d = dirfstat(fd);
- if(d != nil){- if(d->qid.type & QTDIR){- reply("550 %s: not a plain file.", arg);- close(fd);
- free(d);
- return;
- }
- free(d);
- }
+ if(mountnet(ftpd) < 0)
+ return 0;
- n = read(fd, buf, sizeof(buf));
- if(n < 0){- logit("get %s failed", arg, mailaddr, nci->rsys);- reply("550 Error reading %s: %r", arg);- close(fd);
- return;
+ p->afd = announce("tcp!*!0", p->adir);+ if(p->afd < 0) {+ unmountnet();
+ return reply(ftpd->out, "500 No free ports");
}
+ nci = getnetconninfo(p->adir, -1);
+ unmountnet();
- if(type != Timage)
- for(p = buf, ep = &buf[n]; p < ep; p++)
- if(*p & 0x80){- close(fd);
- reply("550 This file requires type binary/image");- return;
- }
+ parseip(p->ipaddr, ftpd->conn.nci->lsys);
+ if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
+ parseip(p->ipaddr, ftpd->conn.nci->lsys);
+ p->port = atoi(nci->lserv);
- reply("150 Opening data connection for %s (%s)", arg, data);- dfd = dialdata();
- if(dfd < 0){- reply("425 Error opening data connection: %r");- close(fd);
- return;
- }
+ freenetconninfo(nci);
+ p->inuse = 1;
- bytes = 0;
- do {- switch(type){- case Timage:
- i = write(dfd, buf, n);
- break;
- default:
- i = crlfwrite(dfd, buf, n);
- break;
- }
- if(i != n){- close(fd);
- close(dfd);
- logit("get %s %r to data connection after %d", arg, bytes);- reply("550 Error writing to data connection: %r");- return;
- }
- bytes += n;
- } while((n = read(fd, buf, sizeof(buf))) > 0);
-
- if(n < 0)
- logit("get %s %r after %d", arg, bytes);-
- close(fd);
- close(dfd);
- reply("226 Transfer complete");- logit("get %s OK %d", arg, bytes);+ dprint("dbg: pasv mode port %d", p->port);+ return reply(ftpd->out, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
+ p->ipaddr[IPv4off + 0], p->ipaddr[IPv4off + 1],
+ p->ipaddr[IPv4off + 2], p->ipaddr[IPv4off + 3],
+ p->port >> 8, p->port & 0xff);
}
-int
-retrievecmd(char *arg)
+
+int
+pbszcmd(Ftpd *ftpd, char *arg)
{- if(arg == 0)
- return reply("501 Retrieve command requires an argument");- arg = abspath(arg);
- if(arg == 0)
- return reply("550 Permission denied");+ USED(arg);
- return asproc(retrieve, arg, 0);
+ /* tls is streaming and the only method we support */
+ return reply(ftpd->out, "200 Ok.");
}
-/*
- * get a file from the user
- */
-int
-lfwrite(int fd, char *p, int n)
+int
+protcmd(Ftpd *ftpd, char *arg)
{- char *ep, *np;
- char buf[Nbuf];
+ if(!arg)
+ return reply(ftpd->out, "500 Prot command needs a level");
- for(np = buf, ep = p + n; p < ep; p++){- if(*p != '\r')
- *np++ = *p;
+ switch(arg[0]) {+ case 'p':
+ case 'P':
+ ftpd->conn.tlsondata = 1;
+ return reply(ftpd->out, "200 Protection level set");
+ case 'c':
+ case 'C':
+ ftpd->conn.tlsondata = 0;
+ return reply(ftpd->out, "200 Protection level set");
+ default:
+ return reply(ftpd->out, "504 Unknown protection level");
}
- if(write(fd, buf, np - buf) == np - buf)
- return n;
- else
- return -1;
}
-void
-store(char *arg, int fd)
+
+int
+portcmd(Ftpd *ftpd, char *arg)
{- int dfd, n, i;
- char buf[Nbuf];
+ char *field[7];
+ char data[64];
- reply("150 Opening data connection for %s (%s)", arg, data);- dfd = dialdata();
- if(dfd < 0){- reply("425 Error opening data connection: %r");- close(fd);
- return;
- }
+ if(!arg)
+ return reply(ftpd->out, "501 Port command needs arguments");
+ if(getfields(arg, field, 7, 0, ", ") != 6)
+ return reply(ftpd->out, "501 Incorrect port specification");
+
+ snprint(data, sizeof(data), "tcp!%.3s.%.3s.%.3s.%.3s!%d",
+ field[0], field[1], field[2], field[3],
+ atoi(field[4]) * 256 + atoi(field[5]));
+ strncpy(ftpd->conn.data, data, sizeof(ftpd->conn.data));
- while((n = read(dfd, buf, sizeof(buf))) > 0){- switch(type){- case Timage:
- i = write(fd, buf, n);
- break;
- default:
- i = lfwrite(fd, buf, n);
- break;
- }
- if(i != n){- close(fd);
- close(dfd);
- reply("550 Error writing file");- return;
- }
- }
- close(fd);
- close(dfd);
- logit("put %s OK", arg);- reply("226 Transfer complete");+ return reply(ftpd->out, "200 Data port is %s", data);
}
+
int
-storecmd(char *arg)
+pwdcmd(Ftpd *ftpd, char *arg)
{- int fd, rv;
+ USED(arg);
+ return reply(ftpd->out, "257 \"%s\" is the current directory", ftpd->user.cwd);
+}
- if(arg == 0)
- return reply("501 Store command requires an argument");- arg = abspath(arg);
- if(arg == 0)
- return reply("550 Permission denied");- if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))- return reply("550 Permission denied");- if(offset){- fd = open(arg, OWRITE);
- if(fd == -1)
- return reply("550 Error opening %s: %r", arg);- if(seek(fd, offset, 0) == -1)
- return reply("550 Error seeking %s to %d: %r",- arg, offset);
- } else {- fd = create(arg, OWRITE, createperm);
- if(fd == -1)
- return reply("550 Error creating %s: %r", arg);- }
+int
+quitcmd(Ftpd *ftpd, char *arg)
+{+ USED(arg);
- rv = asproc(store, arg, fd);
- close(fd);
- return rv;
+ if(ftpd->user.loggedin)
+ logit("quit: %s", ftpd->user.name);+
+ reply(ftpd->out, "200 Goodbye.");
+ return -1;
}
-int
-appendcmd(char *arg)
-{- int fd, rv;
- if(arg == 0)
- return reply("501 Append command requires an argument");- if(isnone)
- return reply("550 Permission denied");- arg = abspath(arg);
- if(arg == 0)
- return reply("550 Error creating %s: Permission denied", arg);- fd = open(arg, OWRITE);
- if(fd == -1){- fd = create(arg, OWRITE, createperm);
- if(fd == -1)
- return reply("550 Error creating %s: %r", arg);+int
+resetcmd(Ftpd *ftpd, char *arg)
+{+ if(!arg)
+ return reply(ftpd->out, "501 Restart command requires offset");
+ ftpd->offset = atoll(arg);
+ if(ftpd->offset < 0) {+ ftpd->offset = 0;
+ return reply(ftpd->out, "501 Bad offset");
}
- seek(fd, 0, 2);
- rv = asproc(store, arg, fd);
- close(fd);
- return rv;
+ return reply(ftpd->out, "350 Restarting at %lld");
}
-int
-storeucmd(char *arg)
+
+int
+retreivecmd(Ftpd *ftpd, char *arg)
{- int fd, rv;
- char name[Maxpath];
+ Dir *d;
+ Biobuf *fd, *data;
+ char *line;
+ char buf[4096];
+ long rsz;
- USED(arg);
- if(isnone)
- return reply("550 Permission denied");- strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
- mktemp(name);
- fd = create(name, OWRITE, createperm);
- if(fd == -1)
- return reply("550 Error creating %s: %r", name);+ d = dirstat(arg);
+ if(!d)
+ return reply(ftpd->out, "550 Error opening %s: %r", arg);
+ if(d->mode & DMDIR)
+ return reply(ftpd->out, "550 %s is a directory", arg);
+ free(d);
- rv = asproc(store, name, fd);
- close(fd);
- return rv;
-}
+ fd = Bopen(arg, OREAD);
+ if(!fd)
+ return reply(ftpd->out, "550 Error opening %s: %r", arg);
-int
-mkdircmd(char *name)
-{- int fd;
+ if(ftpd->offset != 0)
+ Bseek(fd, ftpd->offset, 0);
- if(name == 0)
- return reply("501 Mkdir command requires an argument");- if(isnone)
- return reply("550 Permission denied");- name = abspath(name);
- if(name == 0)
- return reply("550 Permission denied");- fd = create(name, OREAD, DMDIR|0775);
- if(fd < 0)
- return reply("550 Can't create %s: %r", name);- close(fd);
- return reply("226 %s created", name);+ data = dialdata(ftpd, 0);
+ if(ftpd->type == Tascii)
+ while(line = Brdstr(fd, '\n', 1))
+ reply(data, line);
+ else
+ while(rsz = Bread(fd, buf, sizeof(buf)))
+ if(rsz > 0)
+ Bwrite(data, buf, rsz);
+ closedata(ftpd, data, 0);
+
+ logit("retreive: user %s file %s", ftpd->user.name, arg);+
+ return 0;
}
int
-delcmd(char *name)
+renamefromcmd(Ftpd *ftpd, char *arg)
{- if(name == 0)
- return reply("501 Rmdir/delete command requires an argument");- if(isnone)
- return reply("550 Permission denied");- name = abspath(name);
- if(name == 0)
- return reply("550 Permission denied");- if(remove(name) < 0)
- return reply("550 Can't remove %s: %r", name);- else
- return reply("226 %s removed", name);+ if(!arg)
+ return reply(ftpd->out, "501 Rename command requires an argument");
+ if(ftpd->user.isnone)
+ return reply(ftpd->out, "550 Permission denied");
+
+ cleanname(arg);
+ ftpd->renamefrom = strdup(arg);
+
+ return reply(ftpd->out, "350 Rename %s to...", arg);
}
-/*
- * kill off the last transfer (if the process still exists)
- */
int
-abortcmd(char *arg)
+renametocmd(Ftpd *ftpd, char *arg)
{- USED(arg);
+ Dir *from, *to, nd;
- logit("abort pid %d", pid);- if(pid){- if(postnote(PNPROC, pid, "kill") == 0)
- reply("426 Command aborted");- else
- logit("postnote pid %d %r", pid);+ if(!arg)
+ return reply(ftpd->out, "501 Rename command requires an argument");
+ if(ftpd->user.isnone)
+ return reply(ftpd->out, "550 Permission denied");
+ if(!ftpd->renamefrom)
+ return reply(ftpd->out, "550 Rnto must be preceded by rnfr");
+
+ from = dirstat(ftpd->renamefrom);
+ if(!from) {+ free(from);
+ return reply(ftpd->out, "550 Can't stat %s", ftpd->renamefrom);
}
- return reply("226 Abort processed");+
+ to = dirstat(arg);
+ if(to) {+ free(from); free(to);
+ return reply(ftpd->out, "550 Can't rename: target %s exists", arg);
+ }
+
+ nulldir(&nd);
+ nd.name = arg;
+ if(dirwstat(ftpd->renamefrom, &nd) < 0)
+ reply(ftpd->out, "550 Can't rename %s to %s: %r", ftpd->renamefrom, arg);
+ else
+ reply(ftpd->out, "250 %s now %s", ftpd->renamefrom, arg);
+
+ free(ftpd->renamefrom);
+ ftpd->renamefrom = nil;
+ free(from);
+
+ return 0;
}
-int
-systemcmd(char *arg)
+int
+systemcmd(Ftpd *ftpd, char *arg)
{USED(arg);
- return reply("215 UNIX Type: L8 Version: Plan 9");+ reply(ftpd->out, "215 UNIX Type: L8 Version: Plan 9");
+ return 0;
}
int
-helpcmd(char *arg)
+storecmd(Ftpd *ftpd, char *arg)
{- int i;
- char buf[80];
- char *p, *e;
+ int fd;
+ Biobuf *stored, *data;
+ char *line;
+ char buf[4096];
+ long rsz;
- USED(arg);
- reply("214- the following commands are implemented:");- buf[0] = 0;
- p = buf;
- e = buf+sizeof buf;
- for(i = 0; cmdtab[i].name; i++){- if((i%8) == 0){- reply("214-%s", buf);- p = buf;
- }
- p = seprint(p, e, " %-5.5s", cmdtab[i].name);
+ if(!arg)
+ return reply(ftpd->out, "501 Store command needs an argument");
+
+ arg = cleanname(arg);
+ if(ftpd->offset){+ fd = open(arg, OWRITE);
+ if(fd < 0)
+ return reply(ftpd->out, "550 Error opening %s: %r", arg);
+ if(seek(fd, ftpd->offset, 0) < 0)
+ return reply(ftpd->out, "550 Error seeking in %s to %d: %r", arg, ftpd->offset);
+ } else {+ fd = create(arg, OWRITE, 0660);
+ if(fd < 0)
+ return reply(ftpd->out, "550 Error creating %s: %r", arg);
}
- if(p != buf)
- reply("214-%s", buf);- reply("214 ");- return 0;
-}
-/*
- * renaming a file takes two commands
- */
-static String *filepath;
+ stored = Bfdopen(fd, OWRITE);
+ data = dialdata(ftpd, 1);
-int
-rnfrcmd(char *from)
-{- if(isnone)
- return reply("550 Permission denied");- if(from == 0)
- return reply("501 Rename command requires an argument");- from = abspath(from);
- if(from == 0)
- return reply("550 Permission denied");- if(filepath == nil)
- filepath = s_copy(from);
- else{- s_reset(filepath);
- s_append(filepath, from);
+ if(ftpd->type == Tascii)
+ while(line = Brdstr(data, '\n', 1)) {+ if(line[Blinelen(data)] == '\r')
+ line[Blinelen(data)] = '\0';
+ Bprint(stored, "%s\n", line);
+ } else {+ while((rsz = Bread(data, buf, sizeof(buf))) > 0)
+ Bwrite(stored, buf, rsz);
}
- return reply("350 Rename %s to ...", s_to_c(filepath));-}
-int
-rntocmd(char *to)
-{- int r;
- Dir nd;
- char *fp, *tp;
- if(isnone)
- return reply("550 Permission denied");- if(to == 0)
- return reply("501 Rename command requires an argument");- to = abspath(to);
- if(to == 0)
- return reply("550 Permission denied");- if(filepath == nil || *(s_to_c(filepath)) == 0)
- return reply("503 Rnto must be preceeded by an rnfr");+ Bterm(stored);
+ closedata(ftpd, data, 0);
- tp = strrchr(to, '/');
- fp = strrchr(s_to_c(filepath), '/');
- if((tp && fp == 0) || (fp && tp == 0)
- || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
- return reply("550 Rename can't change directory");- if(tp)
- to = tp+1;
+ logit("store: user %s file %s", ftpd->user.name, arg);- nulldir(&nd);
- nd.name = to;
- if(dirwstat(s_to_c(filepath), &nd) < 0)
- r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);- else
- r = reply("250 %s now %s", s_to_c(filepath), to);- s_reset(filepath);
-
- return r;
+ return 0;
}
-/*
- * to dial out we need the network file system in our
- * name space.
- */
int
-dialdata(void)
+typecmd(Ftpd *ftpd, char *arg)
{- int fd, cfd;
- char ldir[40];
- char err[ERRMAX];
+ int c;
+ char *x;
- if(mountnet() < 0)
- return -1;
+ if(!arg)
+ return reply(ftpd->out, "501 Type command needs an argument");
- if(!passive.inuse)
- fd = dial(data, "20", 0, 0);
- else {- fd = -1;
- alarm(5*60*1000);
- cfd = listen(passive.adir, ldir);
- alarm(0);
- if(cfd >= 0){- fd = accept(cfd, ldir);
- close(cfd);
+ x = arg;
+ while(c = *x++) {+ switch(tolower(c)) {+ case 'a':
+ ftpd->type = Tascii;
+ break;
+ case 'i':
+ case 'l':
+ ftpd->type = Timage;
+ break;
+ case '8':
+ case ' ':
+ case 'n':
+ case 't':
+ case 'c':
+ break;
+ default:
+ return reply(ftpd->out, "501 Unimplemented type %s", arg);
}
}
- err[0] = 0;
- errstr(err, sizeof err);
- if(fd < 0)
- logit("can't dial %s: %s", data, err);- unmountnet();
- errstr(err, sizeof err);
- return fd;
+
+ return reply(ftpd->out, "200 Type %s", (ftpd->type == Tascii ? "Ascii" : "Image"));
}
int
-postnote(int group, int pid, char *note)
+usercmd(Ftpd *ftpd, char *arg)
{- char file[128];
- int f, r;
+ if(ftpd->user.loggedin)
+ return reply(ftpd->out, "530 Already logged in as %s", ftpd->user.name);
- /*
- * Use #p because /proc may not be in the namespace.
- */
- switch(group) {- case PNPROC:
- sprint(file, "#p/%d/note", pid);
- break;
- case PNGROUP:
- sprint(file, "#p/%d/notepg", pid);
- break;
- default:
- return -1;
- }
+ if(arg == nil)
+ return reply(ftpd->out, "530 User command needs username");
- f = open(file, OWRITE);
- if(f < 0)
- return -1;
+ if(anonall)
+ ftpd->user.isnone = 1;
- r = strlen(note);
- if(write(f, note, r) != r) {- close(f);
- return -1;
+ if(strcmp(arg, "anonymous") == 0 || strcmp(arg, "ftp") == 0 || strcmp(arg, "none") == 0) {+ if(!anonok && !anononly)
+ return reply(ftpd->out, "530 Not logged in: anonymous access disabled");
+
+ ftpd->user.isnone = 1;
+ strncpy(ftpd->user.name, "none", Maxpath);
+ return loginuser(ftpd, nil, namespace);
+ } else if(anononly) {+ return reply(ftpd->out, "530 Not logged in: anonymous access only");
}
- close(f);
- return 0;
+
+ strncpy(ftpd->user.name, arg, Maxpath);
+ return reply(ftpd->out, "331 Need password");
}
-/*
- * to circumscribe the accessible files we have to eliminate ..'s
- * and resolve all names from the root. We also remove any /bin/rc
- * special characters to avoid later problems with executed commands.
- */
-char *special = "`;| ";
+Cmd cmdtab[] = {+ /* cmd, fn, needlogin, needtls, asproc*/
+ {"abor", abortcmd, 0, 0, 0},+ {"allo", noopcmd, 0, 0, 0},+ {"auth", authcmd, 0, 0, 0},+ {"cwd", cwdcmd, 1, 0, 0},+ {"dele", deletecmd, 1, 0, 0},+ {"feat", featcmd, 0, 0, 0},+ {"list", listcmd, 1, 0, 1},+ {"nlst", nlistcmd, 1, 0, 1},+ {"noop", noopcmd, 0, 0, 0},+ {"mkd", mkdircmd, 1, 0, 0},+ {"mlsd", mlsdcmd, 1, 0, 0},+ {"mlst", mlstcmd, 1, 0, 1},+ {"opts", optscmd, 0, 0, 0},+ {"pass", passcmd, 0, 1, 0},+ {"pasv", pasvcmd, 0, 0, 0},+ {"pbsz", pbszcmd, 0, 1, 0},+ {"prot", protcmd, 0, 1, 0},+ {"port", portcmd, 0, 0, 0},+ {"pwd", pwdcmd, 0, 0, 0},+ {"quit", quitcmd, 0, 0, 0},+ {"rest", resetcmd, 0, 0, 0},+ {"retr", retreivecmd, 1, 0, 1},+ {"rmd", deletecmd, 1, 0, 0},+ {"rnfr", renamefromcmd, 1, 0, 0},+ {"rnto", renametocmd, 1, 0, 0},+ {"syst", systemcmd, 0, 0, 0},+ {"stor", storecmd, 1, 0, 1},+ {"type", typecmd, 0, 0, 0},+ {"user", usercmd, 0, 0, 0},+ {nil, nil, 0, 0, 0},+};
-char*
-abspath(char *origpath)
+void
+usage(void)
{- char *p, *sp, *path;
- static String *rpath;
+ fprint(2, "usage: %s [-aAdei] [-c cert-path] [-n namespace-file]\n", argv0);
+ exits("usage");+}
- if(rpath == nil)
- rpath = s_new();
- else
- s_reset(rpath);
+void
+main(int argc, char **argv)
+{+ Ftpd ftpd;
+ char *cmd, *arg;
+ Cmd *t;
- if(origpath == nil)
- s_append(rpath, curdir);
- else{- if(*origpath != '/'){- s_append(rpath, curdir);
- s_append(rpath, "/");
- }
- s_append(rpath, origpath);
- }
- path = s_to_c(rpath);
+ ARGBEGIN {+ case 'a':
+ anonok = 1;
+ break;
+ case 'A':
+ anononly = 1;
+ break;
+ case 'c':
+ certpath = EARGF(usage());
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'e':
+ anonall = 1;
+ break;
+ case 'i':
+ implicittls = 1;
+ break;
+ case 'n':
+ namespace = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND
- for(sp = special; *sp; sp++){- p = strchr(path, *sp);
- if(p)
- *p = 0;
- }
+ tmfmtinstall();
- cleanname(s_to_c(rpath));
- rpath->ptr = rpath->base+strlen(rpath->base);
+ if(argc < 1)
+ ftpd.conn.nci = getnetconninfo(nil, 0);
+ else
+ ftpd.conn.nci = getnetconninfo(argv[argc - 1], 0);
+ if(!ftpd.conn.nci)
+ sysfatal("ftpd needs a network address");- if(!accessok(s_to_c(rpath)))
- return nil;
+ ftpd.in = mallocz(sizeof(Biobuf), 1);
+ ftpd.out = mallocz(sizeof(Biobuf), 1);
+ Binit(ftpd.in, 0, OREAD);
+ Binit(ftpd.out, 1, OWRITE);
- return s_to_c(rpath);
-}
+ /* open logfile */
+ syslog(0, "ftp", nil);
-typedef struct Path Path;
-struct Path {- Path *next;
- String *path;
- int inuse;
- int ok;
-};
+ if(certpath) {+ ftpd.conn.cert = readcert(certpath, &ftpd.conn.certlen);
+ ftpd.conn.tls = mallocz(sizeof(TLSconn), 1);
-enum
-{- Maxlevel = 16,
- Maxperlevel= 8,
-};
+ /* we need a copy in case of namespace changes
+ * NOTE: the default namespace needs to leave access to the tls device
+ * or anonymous logins with tls will be broken. */
+ ftpd.conn.tls->cert = malloc(ftpd.conn.certlen);
+ memcpy(ftpd.conn.tls->cert, ftpd.conn.cert, ftpd.conn.certlen);
+ ftpd.conn.tls->certlen = ftpd.conn.certlen;
-Path *pathlevel[Maxlevel];
-
-Path*
-unlinkpath(char *path, int level)
-{- String *s;
- Path **l, *p;
- int n;
-
- n = 0;
- for(l = &pathlevel[level]; *l; l = &(*l)->next){- p = *l;
- /* hit */
- if(strcmp(s_to_c(p->path), path) == 0){- *l = p->next;
- p->next = nil;
- return p;
+ if(implicittls) {+ dprint("dbg: implicit tls mode");+ starttls(&ftpd);
}
- /* reuse */
- if(++n >= Maxperlevel){- *l = p->next;
- s = p->path;
- s_reset(p->path);
- memset(p, 0, sizeof *p);
- p->path = s_append(s, path);
- return p;
- }
}
- /* allocate */
- p = mallocz(sizeof *p, 1);
- p->path = s_copy(path);
- return p;
-}
+ reply(ftpd.out, "220 Plan 9 FTP server ready.");
+ alarm(Maxwait);
+ while(cmd = Brdstr(ftpd.in, '\n', 1)) {+ alarm(0);
-void
-linkpath(Path *p, int level)
-{- p->next = pathlevel[level];
- pathlevel[level] = p;
- p->inuse = 1;
-}
+ /* strip cr */
+ char *p = strrchr(cmd, '\r');
+ if(p)
+ *p = '\0';
-void
-addpath(Path *p, int level, int ok)
-{- p->ok = ok;
- p->next = pathlevel[level];
- pathlevel[level] = p;
-}
+ /* strip telnet control sequences */
+ while(*cmd && (uchar)*cmd == 255) {+ cmd++;
+ if(*cmd)
+ cmd++;
+ }
-int
-_accessok(String *s, int level)
-{- Path *p;
- char *cp;
- int lvl, offset;
- static char httplogin[] = "/.httplogin";
+ /* get the arguments */
+ arg = strchr(cmd, ' ');
+ if(arg) {+ *arg++ = '\0';
+ while(*arg == ' ')
+ arg++;
+ /* some clients always send a space */
+ if(*arg == '\0')
+ arg = nil;
+ }
- if(level < 0)
- return 1;
- lvl = level;
- if(lvl >= Maxlevel)
- lvl = Maxlevel - 1;
+ /* find the cmd and execute it */
+ if(*cmd == '\0')
+ continue;
- p = unlinkpath(s_to_c(s), lvl);
- if(p->inuse){- /* move to front */
- linkpath(p, lvl);
- return p->ok;
- }
- cp = strrchr(s_to_c(s), '/');
- if(cp == nil)
- offset = 0;
- else
- offset = cp - s_to_c(s);
- s_append(s, httplogin);
- if(access(s_to_c(s), AEXIST) == 0){- addpath(p, lvl, 0);
- return 0;
- }
+ for(t = cmdtab; t->name; t++)
+ if(cistrcmp(cmd, t->name) == 0) {+ if(t->needlogin && !ftpd.user.loggedin) {+ reply(ftpd.out, "530 Command requires login");
+ } else if(t->needtls && !ftpd.conn.tlson) {+ reply(ftpd.out, "534 Command requires tls");
+ } else {+ if(t->fn != passcmd)
+ dprint("cmd: %s %s", cmd, arg);+ if(t->asproc) {+ dprint("cmd %s spawned as proc");+ asproc(&ftpd, *t->fn, arg);
+ } else if((*t->fn)(&ftpd, arg) < 0)
+ goto exit;
+ }
+ break;
+ }
- /*
- * There's no way to shorten a String without
- * knowing the implementation.
- */
- s->ptr = s->base+offset;
- s_terminate(s);
- addpath(p, lvl, _accessok(s, level-1));
+ /* reset the offset unless we just set it */
+ if(t->fn != resetcmd)
+ ftpd.offset = 0;
+ if(!t->name)
+ reply(ftpd.out, "502 %s command not implemented", cmd);
- return p->ok;
-}
-
-/*
- * check for a subdirectory containing .httplogin
- * at each level of the path.
- */
-int
-accessok(char *path)
-{- int level, r;
- char *p;
- String *npath;
-
- npath = s_copy(path);
- p = s_to_c(npath)+1;
- for(level = 1; level < Maxlevel; level++){- p = strchr(p, '/');
- if(p == nil)
- break;
- p++;
+ free(cmd);
+ alarm(Maxwait);
}
- r = _accessok(npath, level-1);
- s_free(npath);
-
- return r;
+exit:
+ free(ftpd.conn.tls);
+ freenetconninfo(ftpd.conn.nci);
+ Bterm(ftpd.in);
+ Bterm(ftpd.out);
+ free(ftpd.in);
+ free(ftpd.out);
+ exits(nil);
}
--
⑨