#include #include #include #include #include #include #include #include "glob.h" enum { Tascii, Timage, Maxpath = 512, Maxwait = 1000 * 60 * 30, /* 30 minutes */ }; typedef struct Passive Passive; typedef struct Ftpd Ftpd; typedef struct Cmd Cmd; struct Passive { int inuse; char adir[40]; int afd; int port; uchar ipaddr[IPaddrlen]; }; struct Ftpd { Biobuf *in, *out; struct conn { int tlson, tlsondata; NetConnInfo *nci; TLSconn *tls; uchar *cert; int certlen; char data[64]; Passive pasv; } conn; struct user { char cwd[Maxpath]; char name[Maxpath]; int loggedin; int isnone; } user; int type; vlong offset; int cmdpid; char *renamefrom; }; struct Cmd { char *name; int (*fn)(Ftpd *, char *); int needlogin; int needtls; int asproc; }; char *certpath; char *namespace = "/lib/namespace.ftp"; int implicittls; int debug; int anonok; int anononly; int anonall; void dprint(char *fmt, ...) { char *msg; va_list arg; if(!debug) return; va_start(arg, fmt); msg = vsmprint(fmt, arg); va_end(arg); syslog(0, "ftp", msg); free(msg); } void logit(char *fmt, ...) { char *msg; va_list arg; va_start(arg, fmt); msg = vsmprint(fmt, arg); va_end(arg); syslog(0, "ftp", msg); free(msg); } int reply(Biobuf *bio, char *fmt, ...) { va_list arg; char buf[Maxpath], *s; va_start(arg, fmt); s = vseprint(buf, buf + sizeof(buf) - 3, fmt, arg); va_end(arg); dprint("rpl: %s", buf); *s++ = '\r'; *s++ = '\n'; Bwrite(bio, buf, s - buf); Bflush(bio); return 0; } void asproc(Ftpd *ftpd, int (*f)(Ftpd *, char *), char *arg) { int i; if(ftpd->cmdpid) { for(;;) { i = waitpid(); if(i == ftpd->cmdpid || i < 0) break; } } switch(ftpd->cmdpid = rfork(RFFDG|RFPROC|RFNOTEG)){ case -1: reply(ftpd->out, "450 Out of processes: %r"); return; case 0: (*f)(ftpd, arg); dprint("proc exiting"); exits(nil); default: break; } } int mountnet(Ftpd *ftpd) { if(bind("#/", "/", MAFTER) == -1) { reply(ftpd->out, "500 can't bind #/ to /: %r"); return -1; } 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; } return 0; } void unmountnet(void) { unmount(nil, "/net"); unmount("#/", "/"); } Biobuf * dialdata(Ftpd *ftpd, int read) { 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); } } if(fd < 0) { reply(ftpd->out, "425 Error opening data connection"); unmountnet(); return nil; } reply(ftpd->out, "150 Opened data connection"); tls = nil; if(ftpd->conn.tlsondata) { dprint("dbg: using tls on data channel"); 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); if(fd < 0) { reply(ftpd->out, "425 TLS on data connection failed"); unmountnet(); return nil; } dprint("dbg: tlsserver done"); } unmountnet(); if(read) bio = Bfdopen(fd, OREAD); else bio = Bfdopen(fd, OWRITE); bio->aux = tls; return bio; } void closedata(Ftpd *ftpd, Biobuf *bio, int fail) { TLSconn *conn; conn = bio->aux; Bflush(bio); Bterm(bio); if(!fail) reply(ftpd->out, "226 Transfer complete"); if(conn) { free(conn->cert); free(conn); } } int starttls(Ftpd *ftpd) { int fd; fd = tlsServer(0, ftpd->conn.tls); if(fd < 0) return -1; dup(fd, 0); dup(fd, 1); ftpd->conn.tlson = 1; return 0; } int abortcmd(Ftpd *ftpd, char *arg) { USED(arg); 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 authcmd(Ftpd *ftpd, char *arg) { if((cistrcmp(arg, "TLS") == 0) || (cistrcmp(arg, "TLS-C") == 0) || (cistrcmp(arg, "SSL") == 0)) { if(!ftpd->conn.tls) return reply(ftpd->out, "431 tls not enabled"); 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 0; } int cwdcmd(Ftpd *ftpd, char *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); } 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 deletecmd(Ftpd *ftpd, char *arg) { 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 featcmd(Ftpd *ftpd, char *arg) { 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 dircmp(void *va, void *vb) { Dir *a, *b; a = va; b = vb; return strcmp(a->name, b->name); } void listdir(Ftpd *ftpd, Biobuf *data, char *path, void (*fn)(Biobuf *, Dir *d, char *dirname)) { 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;iuser.cwd) == 0 ? nil : path)); free(dirbuf); } int list(Ftpd *ftpd, char *arg, void (*fn)(Biobuf *, Dir *d, char *dirname)) { Biobuf *data; int argc, i; char *argv[32]; Globlist *gl; char *path; Dir *d; if(arg) { argc = getfields(arg, argv, sizeof(argv)-1, 1, " \t"); } else { argc = 1; argv[0] = ftpd->user.cwd; } data = dialdata(ftpd, 0); if(!data) return reply(ftpd->out, "500 List failed: couldn't dial data"); for(i=0;iuser.name); d = dirstat(path); if(d->mode & DMDIR) listdir(ftpd, data, path, fn); else (*fn)(data, d, nil); free(d); } } closedata(ftpd, data, 0); return 0; } char * mode2asc(int m) { char *asc; char *p; asc = strdup("----------"); if(DMDIR & m) asc[0] = 'd'; if(DMAPPEND & m) asc[0] = 'a'; else if(DMEXCL & m) asc[3] = 'l'; for(p = asc + 1; p < asc + 10; p += 3, m <<= 3) { if(m & 0400) p[0] = 'r'; if(m & 0200) p[1] = 'w'; if(m & 0100) p[2] = 'x'; } return asc; } void listprint(Biobuf *data, Dir *d, char *dirname) { char *ts, *mode; ts = strdup(ctime(d->mtime)); ts[16] = '\0'; if(time(0) - d->mtime > 6 * 30 * 24 * 60 * 60) memmove(ts + 11, ts + 23, 5); 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 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); } int listcmd(Ftpd *ftpd, char *arg) { return list(ftpd, arg, listprint); } int loginuser(Ftpd *ftpd, char *pass, char *nsfile) { char *user; user = ftpd->user.name; 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"); } getwd(ftpd->user.cwd, Maxpath); logit("login: %s in dir %s with ns %s", ftpd->user.name, ftpd->user.cwd, nsfile); 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"); } void nlistprint(Biobuf *data, Dir *d, char*) { reply(data, "%s", d->name); } int nlistcmd(Ftpd *ftpd, char *arg) { return list(ftpd, arg, nlistprint); } int noopcmd(Ftpd *ftpd, char *arg) { USED(arg); return reply(ftpd->out, "200 Plan 9 FTP Server still alive"); } int mkdircmd(Ftpd *ftpd, char *arg) { int fd; 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 reply(ftpd->out, "550 Can't create %s: %r", arg); close(fd); return reply(ftpd->out, "226 %s created", arg); } void mlsdprint(Biobuf *data, Dir *d, char*) { Tm mtime; 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); } int mlsdcmd(Ftpd *ftpd, char *arg) { return list(ftpd, arg, mlsdprint); } int mlstcmd(Ftpd *ftpd, char *arg) { Dir *d; char *path; 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 reply(ftpd->out, "250 End"); } int optscmd(Ftpd *ftpd, char *arg) { if(cistrcmp(arg, "utf8 on") == 0) return reply(ftpd->out, "200 UTF8 always on"); return reply(ftpd->out, "501 Option not implemented"); } int passcmd(Ftpd *ftpd, char *arg) { char *nsfile; if(strlen(ftpd->user.name) == 0) return reply(ftpd->out, "531 Specify a user first"); 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 loginuser(ftpd, arg, "/lib/namespace"); free(nsfile); return 0; } int pasvcmd(Ftpd *ftpd, char *arg) { NetConnInfo *nci; Passive *p; USED(arg); p = &ftpd->conn.pasv; if(p->inuse) { close(p->afd); p->inuse = 0; } if(mountnet(ftpd) < 0) return 0; 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(); 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); freenetconninfo(nci); p->inuse = 1; 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 pbszcmd(Ftpd *ftpd, char *arg) { USED(arg); /* tls is streaming and the only method we support */ return reply(ftpd->out, "200 Ok."); } int protcmd(Ftpd *ftpd, char *arg) { if(!arg) return reply(ftpd->out, "500 Prot command needs a level"); 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"); } } int portcmd(Ftpd *ftpd, char *arg) { char *field[7]; char data[64]; 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)); return reply(ftpd->out, "200 Data port is %s", data); } int pwdcmd(Ftpd *ftpd, char *arg) { USED(arg); return reply(ftpd->out, "257 \"%s\" is the current directory", ftpd->user.cwd); } int quitcmd(Ftpd *ftpd, char *arg) { USED(arg); if(ftpd->user.loggedin) logit("quit: %s", ftpd->user.name); reply(ftpd->out, "200 Goodbye."); return -1; } 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"); } return reply(ftpd->out, "350 Restarting at %lld"); } int retreivecmd(Ftpd *ftpd, char *arg) { Dir *d; Biobuf *fd, *data; char *line; char buf[4096]; long rsz; 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); fd = Bopen(arg, OREAD); if(!fd) return reply(ftpd->out, "550 Error opening %s: %r", arg); if(ftpd->offset != 0) Bseek(fd, ftpd->offset, 0); 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 renamefromcmd(Ftpd *ftpd, char *arg) { 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); } int renametocmd(Ftpd *ftpd, char *arg) { Dir *from, *to, nd; 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); } 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(Ftpd *ftpd, char *arg) { USED(arg); reply(ftpd->out, "215 UNIX Type: L8 Version: Plan 9"); return 0; } int storecmd(Ftpd *ftpd, char *arg) { int fd; Biobuf *stored, *data; char *line; char buf[4096]; long rsz; 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); } stored = Bfdopen(fd, OWRITE); data = dialdata(ftpd, 1); 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); } Bterm(stored); closedata(ftpd, data, 0); logit("store: user %s file %s", ftpd->user.name, arg); return 0; } int typecmd(Ftpd *ftpd, char *arg) { int c; char *x; if(!arg) return reply(ftpd->out, "501 Type command needs an argument"); 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); } } return reply(ftpd->out, "200 Type %s", (ftpd->type == Tascii ? "Ascii" : "Image")); } int usercmd(Ftpd *ftpd, char *arg) { if(ftpd->user.loggedin) return reply(ftpd->out, "530 Already logged in as %s", ftpd->user.name); if(arg == nil) return reply(ftpd->out, "530 User command needs username"); if(anonall) ftpd->user.isnone = 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"); } strncpy(ftpd->user.name, arg, Maxpath); return reply(ftpd->out, "331 Need password"); } 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}, }; void usage(void) { fprint(2, "usage: %s [-aAdei] [-c cert-path] [-n namespace-file]\n", argv0); exits("usage"); } void main(int argc, char **argv) { Ftpd ftpd; char *cmd, *arg; Cmd *t; 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 tmfmtinstall(); 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"); ftpd.in = mallocz(sizeof(Biobuf), 1); ftpd.out = mallocz(sizeof(Biobuf), 1); Binit(ftpd.in, 0, OREAD); Binit(ftpd.out, 1, OWRITE); /* open logfile */ syslog(0, "ftp", nil); if(certpath) { ftpd.conn.cert = readcert(certpath, &ftpd.conn.certlen); ftpd.conn.tls = mallocz(sizeof(TLSconn), 1); /* 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; if(implicittls) { dprint("dbg: implicit tls mode"); starttls(&ftpd); } } reply(ftpd.out, "220 Plan 9 FTP server ready."); alarm(Maxwait); while(cmd = Brdstr(ftpd.in, '\n', 1)) { alarm(0); /* strip cr */ char *p = strrchr(cmd, '\r'); if(p) *p = '\0'; /* strip telnet control sequences */ while(*cmd && (uchar)*cmd == 255) { cmd++; if(*cmd) cmd++; } /* get the arguments */ arg = strchr(cmd, ' '); if(arg) { *arg++ = '\0'; while(*arg == ' ') arg++; /* some clients always send a space */ if(*arg == '\0') arg = nil; } /* find the cmd and execute it */ if(*cmd == '\0') continue; 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; } /* 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); free(cmd); alarm(Maxwait); } exit: free(ftpd.conn.tls); freenetconninfo(ftpd.conn.nci); Bterm(ftpd.in); Bterm(ftpd.out); free(ftpd.in); free(ftpd.out); exits(nil); }