summaryrefslogtreecommitdiff
path: root/fw/src/netfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'fw/src/netfs.c')
-rw-r--r--fw/src/netfs.c121
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);
}