ref: c58df62d069d2a90714fc46be4a7c44ddfffba51
dir: /sys/src/cmd/ip/ftpd.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <auth.h> #include <ip.h> #include <libsec.h> #include <String.h> #include "glob.h" enum { /* telnet control character */ Iac= 255, /* representation types */ Tascii= 0, Timage= 1, /* transmission modes */ Mstream= 0, Mblock= 1, Mpage= 2, /* 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, }; 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*); int dialdata(void); char* abspath(char*); int crlfwrite(int, char*, int); int sodoff(void); int accessok(char*); typedef struct Cmd Cmd; struct Cmd { char *name; int (*f)(char*); int needlogin; }; Cmd cmdtab[] = { { "abor", abortcmd, 0, }, { "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 }, }; #define NONENS "/lib/namespace.ftp" /* default ns for none */ 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 */ ulong id; typedef struct Passive Passive; struct Passive { int inuse; char adir[40]; int afd; int port; uchar ipaddr[IPaddrlen]; } passive; #define FTPLOG "ftp" void logit(char *fmt, ...) { char buf[8192]; va_list arg; va_start(arg, fmt); vseprint(buf, buf+sizeof(buf), 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"); } /* * read commands from the control stream and dispatch */ void main(int argc, char **argv) { 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; va_start(arg, fmt); s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg); va_end(arg); if(debug){ *s = 0; logit("%s", buf); } *s++ = '\r'; *s++ = '\n'; write(1, buf, s - buf); return 0; } int sodoff(void) { 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(;;){ i = waitpid(); if(i == pid || i < 0) break; } } switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){ case -1: return reply("450 Out of processes: %r"); case 0: (*f)(arg, arg2); exits(0); default: break; } return 0; } /* * run a command to filter a tail */ int transfer(char *cmd, char *a1, char *a2, char *a3, int image) { 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) < 0 || amount(fd, "/bin", MREPL, "") < 0 || bind("#c", "/dev", MAFTER) < 0 || bind(bindir, "/bin", MREPL) < 0) 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; } /* wait for this command to finish */ for(;;){ w = wait(); if(w == nil || w->pid == pid) break; free(w); } 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) { USED(arg); reply("510 Plan 9 FTP daemon still alive"); return 0; } /* * login as user */ int loginuser(char *user, char *nsfile, int gotoslash) { 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"); } getwd(curdir, sizeof(curdir)); if(gotoslash){ chdir("/"); strcpy(curdir, "/"); } 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; if (pause) { sleep(pause); /* deter guessers */ if (pause < (1UL << 20)) pause *= 2; } else pause = 1000; } /* * 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(); 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(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); } 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"); /* 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); } /* * get a password, set up user if it works. */ int passcmd(char *response) { char namefile[128]; AuthInfo *ai; if(response == nil) response = ""; 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); } 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) { slowdown(); return reply("530 Not logged in: %r"); } 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); } } /* * print working directory */ int pwdcmd(char *arg) { if(arg) return reply("550 Pwd takes no argument"); return reply("257 \"%s\" is the current directory", curdir); } /* * chdir */ int cwdcmd(char *dir) { char *rp; char buf[Maxpath]; /* 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); 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); } /* * chdir .. */ int cdupcmd(char *dp) { USED(dp); return cwdcmd(".."); } int quitcmd(char *arg) { USED(arg); reply("200 Bye"); if(pid) postnote(PNPROC, pid, "kill"); return -1; } int typecmd(char *arg) { int c; char *x; x = arg; if(arg == 0) return reply("501 Type command needs arguments"); 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); } } return reply("200 Type %s", type==Tascii ? "Ascii" : "Image"); } int modecmd(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++; } return reply("200 Stream mode"); } int structcmd(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"); } int portcmd(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); } int mountnet(void) { int rv; rv = 0; if(bind("#/", "/", MAFTER) < 0){ logit("can't bind #/ to /: %r"); return reply("500 can't bind #/ to /: %r"); } if(bind(nci->spec, "/net", MBEFORE) < 0){ 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; } void unmountnet(void) { unmount(0, "/net"); unmount("#/", "/"); } int pasvcmd(char *arg) { NetConnInfo *nnci; Passive *p; USED(arg); p = &passive; if(p->inuse){ close(p->afd); p->inuse = 0; } if(mountnet() < 0) return 0; p->afd = announce("tcp!*!0", passive.adir); if(p->afd < 0){ unmountnet(); return reply("500 No free ports"); } nnci = getnetconninfo(p->adir, -1); unmountnet(); /* 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); freenetconninfo(nnci); p->inuse = 1; 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); } enum { Narg=32, }; int Cflag, rflag, tflag, Rflag; int maxnamelen; int col; char* mode2asc(int m) { static char asc[12]; char *p; strcpy(asc, "----------"); 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 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) { int rv; Dir *a, *b; a = va; b = vb; if(tflag) rv = b->mtime - a->mtime; else rv = strcmp(a->name, b->name); return (rflag?-1:1)*rv; } 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); } void list(char *arg, int lflag) { 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; if(arg == 0) arg = ""; 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; } reply("150 Opened data connection (%s)", data); Binits(&bh, dfd, OWRITE, buf, sizeof(buf)); if(argc == 0){ argc = 1; argv = alist; argv[0] = "."; } for(i = 0; i < argc; i++){ chdir(curdir); gl = glob(argv[i]); if(gl == nil) continue; printname = gl->first != nil && gl->first->next != nil; maxnamelen = 8; 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); } int namelistcmd(char *arg) { return asproc(list, arg, 0); } int listcmd(char *arg) { return asproc(list, arg, 1); } /* * fuse compatability */ int oksiteuser(void) { char buf[64]; int fd, n; fd = open("#c/user", OREAD); 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; } close(fd); return n > 0; } int sitecmd(char *arg) { char *f[4]; int nf, r; Dir *d; 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"); } /* * return the size of the file */ int sizecmd(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 the modify time of the file */ int mdtmcmd(char *arg) { Dir *d; Tm *t; int rv; 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); free(d); return rv; } /* * set an offset to start reading a file from * only lasts for one command */ int restartcmd(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"); } return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset); } /* * send a file to the user */ int crlfwrite(int fd, char *p, int n) { char *ep, *np; char buf[2*Nbuf]; 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(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); else logit("get %s OK %d", arg, n); s_free(file); } void retrieve(char *arg, int arg2) { int dfd, fd, n, i, bytes; Dir *d; char buf[Nbuf]; char *p, *ep; USED(arg2); p = strchr(arg, '\r'); if(p){ logit("cr in file name", arg); *p = 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); } 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; } 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; } 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; } 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); } int retrievecmd(char *arg) { if(arg == 0) return reply("501 Retrieve command requires an argument"); arg = abspath(arg); if(arg == 0) return reply("550 Permission denied"); return asproc(retrieve, arg, 0); } /* * get a file from the user */ int lfwrite(int fd, char *p, int n) { char *ep, *np; char buf[Nbuf]; for(np = buf, ep = p + n; p < ep; p++){ if(*p != '\r') *np++ = *p; } if(write(fd, buf, np - buf) == np - buf) return n; else return -1; } void store(char *arg, int fd) { int dfd, n, i; char buf[Nbuf]; 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; } 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"); } int storecmd(char *arg) { int fd, rv; 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); } rv = asproc(store, arg, fd); close(fd); return rv; } 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); } seek(fd, 0, 2); rv = asproc(store, arg, fd); close(fd); return rv; } int storeucmd(char *arg) { int fd, rv; char name[Maxpath]; 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); rv = asproc(store, name, fd); close(fd); return rv; } int mkdircmd(char *name) { int fd; 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); } int delcmd(char *name) { 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); } /* * kill off the last transfer (if the process still exists) */ int abortcmd(char *arg) { USED(arg); logit("abort pid %d", pid); if(pid){ if(postnote(PNPROC, pid, "kill") == 0) reply("426 Command aborted"); else logit("postnote pid %d %r", pid); } return reply("226 Abort processed"); } int systemcmd(char *arg) { USED(arg); return reply("215 UNIX Type: L8 Version: Plan 9"); } int helpcmd(char *arg) { int i; char buf[80]; char *p, *e; 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(p != buf) reply("214-%s", buf); reply("214 "); return 0; } /* * renaming a file takes two commands */ static String *filepath; 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); } 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"); 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; 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; } /* * to dial out we need the network file system in our * name space. */ int dialdata(void) { int fd, cfd; char ldir[40]; char err[ERRMAX]; if(mountnet() < 0) return -1; 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); } } 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; } int postnote(int group, int pid, char *note) { char file[128]; int f, r; /* * 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; } f = open(file, OWRITE); if(f < 0) return -1; r = strlen(note); if(write(f, note, r) != r) { close(f); return -1; } close(f); return 0; } /* * 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 = "`;| "; char* abspath(char *origpath) { char *p, *sp, *path; static String *rpath; if(rpath == nil) rpath = s_new(); else s_reset(rpath); 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); for(sp = special; *sp; sp++){ p = strchr(path, *sp); if(p) *p = 0; } cleanname(s_to_c(rpath)); rpath->ptr = rpath->base+strlen(rpath->base); if(!accessok(s_to_c(rpath))) return nil; return s_to_c(rpath); } typedef struct Path Path; struct Path { Path *next; String *path; int inuse; int ok; }; enum { Maxlevel = 16, Maxperlevel= 8, }; 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; } /* 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; } void linkpath(Path *p, int level) { p->next = pathlevel[level]; pathlevel[level] = p; p->inuse = 1; } void addpath(Path *p, int level, int ok) { p->ok = ok; p->next = pathlevel[level]; pathlevel[level] = p; } int _accessok(String *s, int level) { Path *p; char *cp; int lvl, offset; static char httplogin[] = "/.httplogin"; if(level < 0) return 1; lvl = level; if(lvl >= Maxlevel) lvl = Maxlevel - 1; 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; } /* * 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)); 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++; } r = _accessok(npath, level-1); s_free(npath); return r; }