diff options
Diffstat (limited to 'fw/src/netfs.c')
| -rw-r--r-- | fw/src/netfs.c | 121 |
1 files changed, 120 insertions, 1 deletions
diff --git a/fw/src/netfs.c b/fw/src/netfs.c index 1300717..f60d5bc 100644 --- a/fw/src/netfs.c +++ b/fw/src/netfs.c @@ -341,6 +341,113 @@ static char *okverbs[] = { }; /* + * If path is a protocol listen file - "tcp/1/listen" - return the + * protocol name. Opening one blocks until somebody connects, and the + * fd it yields is the ctl file of the connection that arrived. + */ +static char* +listenproto(char *path) +{ + char buf[64], *p, *q; + int n; + + if((p = strchr(path, '/')) == nil) + return nil; + n = p - path; + if(n <= 0 || n >= sizeof buf) + return nil; + memmove(buf, path, n); + buf[n] = '\0'; + if((q = strchr(p+1, '/')) == nil || strcmp(q+1, "listen") != 0) + return nil; + if(!isproto(buf)) + return nil; + return estrdup(buf); +} + +/* the address side of "10.9.9.1!1234", read out of local or remote */ +static int +convaddr(char *proto, int conv, char *which, uchar *ip, int *port) +{ + char path[256], buf[128], *p; + int fd, n; + + snprint(path, sizeof path, "%s/%s/%d/%s", orig, proto, conv, which); + if((fd = open(path, OREAD)) < 0) + return -1; + n = read(fd, buf, sizeof buf - 1); + close(fd); + if(n <= 0) + return -1; + buf[n] = '\0'; + if((p = strchr(buf, '\n')) != nil) + *p = '\0'; + if((p = strrchr(buf, '!')) == nil) + return -1; + *p++ = '\0'; + *port = atoi(p); + return parseip(ip, buf); +} + +/* + * A listen has returned a connection. Ask about the peer, now that + * there is one. + * + * The announce could not be matched against a peer - at that moment + * nobody had called - so any rule naming an address was skipped, and + * "deny=in ip=..." meant nothing here while meaning something at the + * packet layer. A rule that silently does nothing is the failure this + * program refuses to accept from a mistyped attribute, and it should + * not accept it from itself. + * + * The handshake has already happened: the kernel answered before listen + * returned, and no filter at this altitude can prevent that. What it + * can do is refuse the connection to the program and hang it up, which + * is the difference between a rule that is late and a rule that is + * decorative. + */ +static char* +checklisten(Match *m, char *proto, int fd) +{ + uchar ip[IPaddrlen], lip[IPaddrlen]; + char buf[64]; + int n, conv, lport; + + /* + * fd is the new conversation's ctl file and its number is what + * reading it gives. At offset 0, so the program's own read - the + * one listen(2) makes to learn the same number - still sees it. + */ + if((n = pread(fd, buf, sizeof buf - 1, 0)) <= 0) + return nil; + buf[n] = '\0'; + conv = atoi(buf); + + memset(m, 0, sizeof *m); + m->count = 1; + m->verb = Vin; + m->proto = proto; + m->ip = ip; + m->port = -1; + m->lport = -1; + if(convaddr(proto, conv, "remote", ip, &m->port) < 0) + return nil; /* gone already; let it be */ + if(convaddr(proto, conv, "local", lip, &lport) >= 0) + m->lport = lport; /* the port announced, for an lport rule */ + + if(matchrule(m)){ + if(m->log) + syslog(0, "fw", "allow %s listen %I!%d", proto, ip, m->port); + return nil; + } + syslog(0, "fw", "deny %s listen %I!%d: %s", proto, ip, m->port, m->err); + fprint(fd, "hangup"); + snprint(buf, sizeof buf, "%s", m->err); + snprint(m->err, sizeof m->err, "fw: %s", buf); + return m->err; +} + +/* * connect takes addr!port with optional trailing fields; announce takes * a bare port, or addr!port with addr often "*". */ @@ -590,7 +697,7 @@ static void fsopen(Req *r) { Fnode *f; - char *rp, *e; + char *rp, *e, *proto; int mode; f = r->fid->aux; @@ -615,6 +722,18 @@ fsopen(Req *r) responderror(r); return; } + if((proto = listenproto(f->path)) != nil){ + Match m; + + e = checklisten(&m, proto, f->fd); + free(proto); + if(e != nil){ + close(f->fd); + f->fd = -1; + respond(r, e); /* m outlives the respond, which packs it */ + return; + } + } respond(r, nil); } |
