blob: f5724b5db92407d58ff0ea92fe2a37ae9851de36 [file] [log] [blame]
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <stdlib.h>
#include <ctype.h>
#include <error.h>
#include <fcall.h>
#include <fcntl.h>
#include <iplib/iplib.h>
#include <ndblib/fcallfmt.h>
#include <ndblib/ndb.h>
#include <parlib/parlib.h>
#include <parlib/spinlock.h>
#include <pthread.h>
#include <ros/common.h>
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
enum {
Nreply = 20,
Maxreply = 256,
Maxrequest = 128,
Maxpath = 128,
Maxfdata = 8192,
Maxhost = 64, /* maximum host name size */
Maxservice = 64, /* maximum service name size */
Qdir = 0,
Qcs = 1,
};
int vers; /* incremented each clone/attach */
/* need to resolve the #inluce for all this stuff. */
#define DMDIR 0x80000000 /* mode bit for directories */
#define QTDIR 0x80
#define QTFILE 0
#define ERRMAX 128
struct mfile {
int busy;
char *user;
struct qid qid;
int fid;
/*
* current request
*/
char *net;
char *host;
char *serv;
char *rem;
/*
* result of the last lookup
*/
struct network *nextnet;
int nreply;
char *reply[Nreply];
int replylen[Nreply];
};
struct mlist {
struct mlist *next;
struct mfile mf;
};
/*
* active requests
*/
struct job {
struct job *next;
int flushed;
struct fcall request;
struct fcall reply;
pthread_t thread;
};
spinlock_t joblock = SPINLOCK_INITIALIZER;
struct job *joblist;
struct mlist *mlist;
int mfd[2];
int debug;
int paranoia;
int ipv6lookups = 1;
int server;
char *dbfile = "lib/ndb/local";
struct ndb *db, *netdb;
static void rversion(struct job *);
static void rflush(struct job *);
static void rattach(struct job *, struct mfile *);
static char *rwalk(struct job *, struct mfile *);
static void ropen(struct job *, struct mfile *);
static void rcreate(struct job *, struct mfile *);
static void rread(struct job *, struct mfile *);
static void rwrite(struct job *, struct mfile *);
static void rclunk(struct job *, struct mfile *);
static void rremove(struct job *, struct mfile *);
static void rstat(struct job *, struct mfile *);
static void rwstat(struct job *, struct mfile *);
static void rauth(struct job *);
static void sendmsg(struct job *, char *);
static void mountinit(char *, char *);
static void io(void);
static void ndbinit(void);
static void netinit(int);
static void netadd(char *);
static char *genquery(struct mfile *, char *);
static char *ipinfoquery(struct mfile *, char **, int);
static int needproto(struct network *, struct ndbtuple *);
static int lookup(struct mfile *);
static struct ndbtuple *reorder(struct ndbtuple *, struct ndbtuple *);
static void ipid(void);
static void readipinterfaces(void);
static void *emalloc(int);
static char *estrdup(char *);
static struct job *newjob(void);
static void freejob(struct job *);
static void setext(char *, int, char *);
static void cleanmf(struct mfile *);
spinlock_t dblock = SPINLOCK_INITIALIZER; /* mutex on database operations */
spinlock_t netlock = SPINLOCK_INITIALIZER; /* mutex for netinit() */
char *logfile = "cs";
char *paranoiafile = "cs.paranoia";
char mntpt[Maxpath];
char netndb[Maxpath];
/*
* Network specific translators
*/
static struct ndbtuple *iplookup(struct network *, char *, char *, int);
static char *iptrans(struct ndbtuple *, struct network *, char *, char *, int);
static struct ndbtuple *telcolookup(struct network *, char *, char *, int);
static char *telcotrans(struct ndbtuple *, struct network *, char *, char *,
int);
static struct ndbtuple *dnsiplookup(char *, struct ndbs *);
struct network {
char *net;
struct ndbtuple *(*lookup)(struct network *, char *, char *, int);
char *(*trans)(struct ndbtuple *, struct network *, char *, char *, int);
int considered; /* flag: ignored for "net!"? */
int fasttimeouthack; /* flag. was for IL */
struct network *next;
};
/*
* net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
*/
struct network network[] = {
{"tcp", iplookup, iptrans, 0},
{"udp", iplookup, iptrans, 1},
{"icmp", iplookup, iptrans, 1},
{"icmpv6", iplookup, iptrans, 1},
{"rudp", iplookup, iptrans, 1},
{"ssh", iplookup, iptrans, 1},
{"telco", telcolookup, telcotrans, 1}, {0},
};
spinlock_t ipifclock = SPINLOCK_INITIALIZER;
struct ipifc *ipifcs;
char eaddr[16]; /* ascii ethernet address */
char ipaddr[64]; /* ascii internet address */
uint8_t ipa[IPaddrlen]; /* binary internet address */
char *mysysname;
struct network *netlist; /* networks ordered by preference */
struct network *last;
char *argv0;
static void evnotify(int rc)
{
struct event_msg msg = { 0 };
msg.ev_type = EV_USER_IPI;
msg.ev_arg1 = rc;
sys_notify(getppid(), EV_USER_IPI, &msg);
}
static void evexit(int rc)
{
if (server)
evnotify(rc);
exit(rc);
}
static void usage(void)
{
fprintf(stderr, "CS:usage: %s [-dn] [-f ndb-file] [-x netmtpt]\n",
argv0);
fprintf(stderr, "CS:usage");
evexit(1);
}
/*
* based on libthread's threadsetname, but drags in less library code.
* actually just sets the arguments displayed.
*/
static void procsetname(char *fmt, ...)
{
/* someday ... */
#if 0
int fd;
char *cmdname;
char buf[128];
va_list arg;
va_start(arg, fmt);
cmdname = vsmprint(fmt, arg);
va_end(arg);
if (cmdname == NULL)
return;
snprintf(buf, sizeof(buf), "#proc/%d/args", getpid());
fd = open(buf, OWRITE);
if (fd >= 0) {
write(fd, cmdname, strlen(cmdname)+1);
close(fd);
}
free(cmdname);
#endif
}
int main(int argc, char *argv[])
{
int justsetname, ch;
char ext[Maxpath], servefile[Maxpath];
/* Make us an SCP with a 2LS */
parlib_wants_to_be_mcp = FALSE;
register_printf_specifier('F', printf_fcall, printf_fcall_info);
argv0 = argv[0];
justsetname = 0;
setnetmtpt(mntpt, sizeof(mntpt), NULL);
ext[0] = 0;
while ((ch = getopt(argc, argv, "4df:nSx:")) != -1) {
switch (ch) {
case '4':
ipv6lookups = 0;
break;
case 'd':
debug = 1;
break;
case 'f':
dbfile = optarg;
break;
case 'n':
justsetname = 1;
break;
case 'S':
server = 1;
break;
case 'x':
setnetmtpt(mntpt, sizeof(mntpt), optarg);
setext(ext, sizeof(ext), mntpt);
break;
default:
usage();
break;
}
}
snprintf(servefile, sizeof(servefile), "#srv/cs%s", ext);
snprintf(netndb, sizeof(netndb), "%s/ndb", mntpt);
syscall(SYS_nunmount, (unsigned long)servefile, strlen(servefile),
(unsigned long)mntpt, strlen(mntpt));
remove(servefile);
ndbinit();
netinit(0);
if (!justsetname) {
mountinit(servefile, mntpt);
if (server)
evnotify(0);
io();
}
evexit(0);
}
/*
* if a mount point is specified, set the cs extention to be the mount point
* with '_'s replacing '/'s
*/
static void setext(char *ext, int n, char *p)
{
int i, c;
n--;
for (i = 0; i < n; i++) {
c = p[i];
if (c == 0)
break;
if (c == '/')
c = '_';
ext[i] = c;
}
ext[i] = 0;
}
static void mountinit(char *service, char *mntpt)
{
int f;
int p[2];
char buf[32];
int ret;
ret = pipe(p);
if (ret < 0) {
error(1, 0, "pipe: %r");
evexit(1);
}
/*
* make a /srv/cs
* ORCLOSE means remove on last close. Handy. Not here yet.
*/
f = open(service, O_WRONLY | O_CREAT /*|ORCLOSE*/, 0666);
if (f < 0)
error(1, 0, "%s: %r", service);
snprintf(buf, sizeof(buf), "%d", p[1]);
if (write(f, buf, strlen(buf)) != strlen(buf))
error(1, 0, "Write %s: %r", service);
/* using #s: we create a pipe and drop it into #srv.
* we no longer mount. That's up to you.
* #srv will route requests to us.
*/
close(p[1]);
mfd[0] = mfd[1] = p[0];
}
static void ndbinit(void)
{
db = ndbopen(dbfile);
if (db == NULL)
error(1, 0, "%s: %r", "can't open network database");
netdb = ndbopen(netndb);
if (netdb != NULL) {
netdb->nohash = 1;
db = ndbcat(netdb, db);
}
}
static struct mfile *newfid(int fid)
{
struct mlist *f, *ff;
struct mfile *mf;
ff = 0;
for (f = mlist; f; f = f->next)
if (f->mf.busy && f->mf.fid == fid)
return &f->mf;
else if (!ff && !f->mf.busy)
ff = f;
if (ff == 0) {
ff = emalloc(sizeof(*f));
ff->next = mlist;
mlist = ff;
}
mf = &ff->mf;
memset(mf, 0, sizeof(*mf));
mf->fid = fid;
return mf;
}
static struct job *newjob(void)
{
struct job *job;
job = calloc(1, sizeof(struct job));
if (!job)
error(1, 0, "%s: %r", "job calloc");
spinlock_lock(&joblock);
job->next = joblist;
joblist = job;
job->request.tag = -1;
spinlock_unlock(&joblock);
return job;
}
static void freejob(struct job *job)
{
struct job **l;
struct job *to_free = 0;
spinlock_lock(&joblock);
for (l = &joblist; *l; l = &(*l)->next) {
if ((*l) == job) {
*l = job->next;
to_free = job;
break;
}
}
spinlock_unlock(&joblock);
if (to_free)
free(to_free);
}
static void flushjob(int tag)
{
struct job *job;
spinlock_lock(&joblock);
for (job = joblist; job; job = job->next) {
if (job->request.tag == tag && job->request.type != Tflush) {
job->flushed = 1;
break;
}
}
spinlock_unlock(&joblock);
}
static void *job_thread(void *arg)
{
struct mfile *mf;
struct job *job = arg;
spinlock_lock(&dblock);
mf = newfid(job->request.fid);
if (debug)
fprintf(stderr, "CS:%F", &job->request);
switch (job->request.type) {
default:
fprintf(stderr, "CS:unknown request type %d", job->request.type);
break;
case Tversion:
rversion(job);
break;
case Tauth:
rauth(job);
break;
case Tflush:
rflush(job);
break;
case Tattach:
rattach(job, mf);
break;
case Twalk:
rwalk(job, mf);
break;
case Topen:
ropen(job, mf);
break;
case Tcreate:
rcreate(job, mf);
break;
case Tread:
rread(job, mf);
break;
case Twrite:
rwrite(job, mf);
break;
case Tclunk:
rclunk(job, mf);
break;
case Tremove:
rremove(job, mf);
break;
case Tstat:
rstat(job, mf);
break;
case Twstat:
rwstat(job, mf);
break;
}
spinlock_unlock(&dblock);
freejob(job);
if (debug)
fprintf(stderr, "CS:Job done\n");
return 0;
}
static void io(void)
{
long n;
uint8_t mdata[IOHDRSZ + Maxfdata];
struct job *job;
pthread_attr_t pth_attr;
/*
* each request is handled via a thread. Somewhat less efficient than
* the old cs but way cleaner.
*/
pthread_attr_init(&pth_attr);
pthread_attr_setdetachstate(&pth_attr, PTHREAD_CREATE_DETACHED);
for (;;) {
n = read9pmsg(mfd[0], mdata, sizeof(mdata));
if (n <= 0)
error(1, 0, "%s: %r", "mount read");
job = newjob();
if (convM2S(mdata, n, &job->request) != n) {
fprintf(stderr,
"convM2S went south: format error %ux %ux %ux %ux %ux",
mdata[0], mdata[1], mdata[2], mdata[3],
mdata[4]);
error(1, 0, "format error %ux %ux %ux %ux %ux",
mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
freejob(job);
continue;
}
/* stash the thread in the job so we can join them all
* later if we want to.
*/
if (pthread_create(&job->thread, &pth_attr, &job_thread, job)) {
error(1, 0, "%s: %r", "Failed to create job");
continue;
}
}
}
static void rversion(struct job *job)
{
if (job->request.msize > IOHDRSZ + Maxfdata)
job->reply.msize = IOHDRSZ + Maxfdata;
else
job->reply.msize = job->request.msize;
if (strncmp(job->request.version, "9P2000", 6) != 0)
sendmsg(job, "unknown 9P version");
else {
job->reply.version = "9P2000";
sendmsg(job, 0);
}
}
static void rauth(struct job *job)
{
sendmsg(job, "cs: authentication not required");
}
/*
* don't flush till all the threads are done
*/
static void rflush(struct job *job)
{
flushjob(job->request.oldtag);
sendmsg(job, 0);
}
static void rattach(struct job *job, struct mfile *mf)
{
if (mf->busy == 0) {
mf->busy = 1;
mf->user = estrdup(job->request.uname);
}
mf->qid.vers = vers++;
mf->qid.type = QTDIR;
mf->qid.path = 0LL;
job->reply.qid = mf->qid;
sendmsg(job, 0);
}
static char *rwalk(struct job *job, struct mfile *mf)
{
char *err;
char **elems;
int nelems;
int i;
struct mfile *nmf;
struct qid qid;
err = 0;
nmf = NULL;
elems = job->request.wname;
nelems = job->request.nwname;
job->reply.nwqid = 0;
if (job->request.newfid != job->request.fid) {
/* clone fid */
nmf = newfid(job->request.newfid);
if (nmf->busy) {
nmf = NULL;
err = "clone to used channel";
goto send;
}
*nmf = *mf;
nmf->user = estrdup(mf->user);
nmf->fid = job->request.newfid;
nmf->qid.vers = vers++;
mf = nmf;
}
/* else nmf will be nil */
qid = mf->qid;
if (nelems > 0) {
/* walk fid */
for (i = 0; i < nelems && i < MAXWELEM; i++) {
if ((qid.type & QTDIR) == 0) {
err = "not a directory";
break;
}
if (strcmp(elems[i], "..") == 0
|| strcmp(elems[i], ".") == 0) {
qid.type = QTDIR;
qid.path = Qdir;
Found:
job->reply.wqid[i] = qid;
job->reply.nwqid++;
continue;
}
if (strcmp(elems[i], "cs") == 0) {
qid.type = QTFILE;
qid.path = Qcs;
goto Found;
}
err = "file does not exist";
break;
}
}
send:
if (nmf != NULL && (err != NULL || job->reply.nwqid < nelems)) {
cleanmf(nmf);
free(nmf->user);
nmf->user = 0;
nmf->busy = 0;
nmf->fid = 0;
}
if (err == NULL)
mf->qid = qid;
sendmsg(job, err);
return err;
}
static void ropen(struct job *job, struct mfile *mf)
{
int mode;
char *err;
err = 0;
mode = job->request.mode;
if (mf->qid.type & QTDIR) {
if (mode)
err = "permission denied";
}
job->reply.qid = mf->qid;
job->reply.iounit = 0;
sendmsg(job, err);
}
static void rcreate(struct job *job, struct mfile *mf)
{
sendmsg(job, "creation permission denied");
}
static void rread(struct job *job, struct mfile *mf)
{
int i, n, cnt;
long off, toff, clock;
struct dir dir;
uint8_t buf[Maxfdata];
char *err;
n = 0;
err = 0;
off = job->request.offset;
cnt = job->request.count;
if (mf->qid.type & QTDIR) {
// clock = time(0);
if (off == 0) {
memset(&dir, 0, sizeof(dir));
dir.name = "cs";
dir.qid.type = QTFILE;
dir.qid.vers = vers;
dir.qid.path = Qcs;
dir.mode = 0666;
dir.length = 0;
dir.uid = mf->user;
dir.gid = mf->user;
dir.muid = mf->user;
dir.atime = clock; /* wrong */
dir.mtime = clock; /* wrong */
n = convD2M(&dir, buf, sizeof(buf));
}
job->reply.data = (char *)buf;
} else {
for (;;) {
/* look for an answer at the right offset */
toff = 0;
for (i = 0; mf->reply[i] && i < mf->nreply; i++) {
n = mf->replylen[i];
if (off < toff + n)
break;
toff += n;
}
if (i < mf->nreply)
break; /* got something to return */
/* try looking up more answers */
if (lookup(mf) == 0) {
/* no more */
n = 0;
goto send;
}
}
/* give back a single reply (or part of one) */
job->reply.data = mf->reply[i] + (off - toff);
if (cnt > toff - off + n)
n = toff - off + n;
else
n = cnt;
}
send:
job->reply.count = n;
sendmsg(job, err);
}
static void cleanmf(struct mfile *mf)
{
int i;
if (mf->net != NULL) {
free(mf->net);
mf->net = NULL;
}
if (mf->host != NULL) {
free(mf->host);
mf->host = NULL;
}
if (mf->serv != NULL) {
free(mf->serv);
mf->serv = NULL;
}
if (mf->rem != NULL) {
free(mf->rem);
mf->rem = NULL;
}
for (i = 0; i < mf->nreply; i++) {
free(mf->reply[i]);
mf->reply[i] = NULL;
mf->replylen[i] = 0;
}
mf->nreply = 0;
mf->nextnet = netlist;
}
static void rwrite(struct job *job, struct mfile *mf)
{
int cnt, n;
char *err;
char *field[4];
char curerr[64];
err = 0;
cnt = job->request.count;
if (mf->qid.type & QTDIR) {
err = "can't write directory";
goto send;
}
if (cnt >= Maxrequest) {
err = "request too long";
goto send;
}
job->request.data[cnt] = 0;
/*
* toggle debugging
*/
if (strncmp(job->request.data, "debug", 5) == 0) {
debug ^= 1;
fprintf(stderr, "CS:debug %d", debug);
goto send;
}
/*
* toggle ipv6 lookups
*/
if (strncmp(job->request.data, "ipv6", 4) == 0) {
ipv6lookups ^= 1;
fprintf(stderr, "CS:ipv6lookups %d", ipv6lookups);
goto send;
}
/*
* toggle debugging
*/
if (strncmp(job->request.data, "paranoia", 8) == 0) {
paranoia ^= 1;
fprintf(stderr, "CS:paranoia %d", paranoia);
goto send;
}
/*
* add networks to the default list
*/
if (strncmp(job->request.data, "add ", 4) == 0) {
if (job->request.data[cnt - 1] == '\n')
job->request.data[cnt - 1] = 0;
netadd(job->request.data + 4);
readipinterfaces();
goto send;
}
/*
* refresh all state
*/
if (strncmp(job->request.data, "refresh", 7) == 0) {
netinit(0 /*1*/);
goto send;
}
/* start transaction with a clean slate */
cleanmf(mf);
/*
* look for a general query
*/
if (*job->request.data == '!') {
err = genquery(mf, job->request.data + 1);
goto send;
}
if (debug)
fprintf(stderr, "CS:write %s", job->request.data);
if (paranoia)
fprintf(stderr, "CS:write %s by %s", job->request.data,
mf->user);
/*
* break up name
*/
n = getfields(job->request.data, field, 4, 1, "!");
switch (n) {
case 1:
mf->net = strdup("net");
mf->host = strdup(field[0]);
break;
case 4:
mf->rem = strdup(field[3]);
/* fall through */
case 3:
mf->serv = strdup(field[2]);
/* fall through */
case 2:
mf->host = strdup(field[1]);
mf->net = strdup(field[0]);
break;
}
/*
* do the first net worth of lookup
*/
if (lookup(mf) == 0) {
snprintf(curerr, sizeof(curerr), "%r");
err = curerr;
}
send:
job->reply.count = cnt;
sendmsg(job, err);
}
static void rclunk(struct job *job, struct mfile *mf)
{
cleanmf(mf);
free(mf->user);
mf->user = 0;
mf->busy = 0;
mf->fid = 0;
sendmsg(job, 0);
}
static void rremove(struct job *job, struct mfile *mf)
{
sendmsg(job, "remove permission denied");
}
static void rstat(struct job *job, struct mfile *mf)
{
struct dir dir;
uint8_t buf[IOHDRSZ + Maxfdata];
memset(&dir, 0, sizeof(dir));
if (mf->qid.type & QTDIR) {
dir.name = ".";
dir.mode = DMDIR | 0555;
} else {
dir.name = "cs";
dir.mode = 0666;
}
dir.qid = mf->qid;
dir.length = 0;
dir.uid = mf->user;
dir.gid = mf->user;
dir.muid = mf->user;
// dir.atime = dir.mtime = time(0);
job->reply.nstat = convD2M(&dir, buf, sizeof(buf));
job->reply.stat = buf;
sendmsg(job, 0);
}
static void rwstat(struct job *job, struct mfile *mf)
{
sendmsg(job, "wstat permission denied");
}
static void sendmsg(struct job *job, char *err)
{
int n;
uint8_t mdata[IOHDRSZ + Maxfdata];
char ename[ERRMAX];
if (err) {
job->reply.type = Rerror;
snprintf(ename, sizeof(ename), "cs: %s", err);
job->reply.ename = ename;
} else {
job->reply.type = job->request.type + 1;
}
job->reply.tag = job->request.tag;
n = convS2M(&job->reply, mdata, sizeof(mdata));
if (n == 1) {
fprintf(stderr, "CS:sendmsg convS2M of %F returns 0",
&job->reply);
abort();
}
spinlock_lock(&joblock);
if (job->flushed == 0)
if (write(mfd[1], mdata, n) != n)
error(1, 0, "%s: %r", "mount write");
spinlock_unlock(&joblock);
if (debug)
fprintf(stderr, "CS:%F %d", &job->reply, n);
}
static int isvalidip(uint8_t *ip)
{
return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0;
}
static uint8_t loopbacknet[IPaddrlen] = {0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xff, 0xff, 127, 0, 0, 0};
static uint8_t loopbackmask[IPaddrlen] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0, 0, 0};
static void readipinterfaces(void)
{
if (myipaddr(ipa, mntpt) != 0)
ipmove(ipa, IPnoaddr);
snprintf(ipaddr, sizeof(ipaddr), "%I", ipa);
if (debug)
fprintf(stderr, "CS:dns", "ipaddr is %s\n", ipaddr);
}
/*
* get the system name
*/
static void ipid(void)
{
uint8_t addr[6];
struct ndbtuple *t, *tt;
char *p, *attr;
struct ndbs s;
int f;
char buf[Maxpath];
/* use environment, ether addr, or ipaddr to get system name */
if (mysysname == 0) {
/*
* environment has priority.
*
* on the sgi power the default system name
* is the ip address. ignore that.
*
*/
p = getenv("sysname");
if (p && *p) {
attr = ipattr(p);
if (strcmp(attr, "ip") != 0)
mysysname = strdup(p);
}
/*
* the /net/ndb contains what the network
* figured out from DHCP. use that name if
* there is one.
*/
if (mysysname == 0 && netdb != NULL) {
ndbreopen(netdb);
for (tt = t = ndbparse(netdb); t != NULL; t = t->entry)
{
if (strcmp(t->attr, "sys") == 0) {
mysysname = strdup(t->val);
break;
}
}
ndbfree(tt);
}
/* next network database, ip address, and ether address to find
* a name
*/
if (mysysname == 0) {
t = NULL;
if (isvalidip(ipa))
free(ndbgetvalue(db, &s, "ip", ipaddr, "sys",
&t));
if (t == NULL) {
for (f = 0; f < 3; f++) {
snprintf(buf, sizeof(buf), "%s/ether%d",
mntpt, f);
if (myetheraddr(addr, buf) < 0)
continue;
snprintf(eaddr, sizeof(eaddr), "%E",
addr);
free(ndbgetvalue(db, &s, "ether", eaddr,
"sys", &t));
if (t != NULL)
break;
}
}
for (tt = t; tt != NULL; tt = tt->entry) {
if (strcmp(tt->attr, "sys") == 0) {
mysysname = strdup(tt->val);
break;
}
}
ndbfree(t);
}
/* nothing else worked, use the ip address */
if (mysysname == 0 && isvalidip(ipa))
mysysname = strdup(ipaddr);
/* set /dev/sysname if we now know it */
if (mysysname) {
f = open("/dev/sysname", O_RDWR);
if (f >= 0) {
write(f, mysysname, strlen(mysysname));
close(f);
}
}
}
}
/*
* Set up a list of default networks by looking for
* /net/^*^/clone.
* For now, never background.
*/
static void netinit(int background)
{
char clone[Maxpath];
struct network *np;
static int working;
/* add the mounted networks to the default list */
for (np = network; np->net; np++) {
int fuckup;
if (np->considered)
continue;
snprintf(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
fuckup = open(clone, O_RDONLY);
if (fuckup < 0)
continue;
close(fuckup);
// if(access(clone, R_OK))
// continue;
if (netlist)
last->next = np;
else
netlist = np;
last = np;
np->next = 0;
np->considered = 1;
}
/* find out what our ip address is */
readipinterfaces();
/* set the system name if we need to, these days ip is all we have */
ipid();
if (debug)
fprintf(stderr, logfile,
"CS:mysysname %s eaddr %s ipaddr %s ipa %I\n",
mysysname ? mysysname : "???", eaddr, ipaddr, ipa);
}
/*
* add networks to the standard list
*/
static void netadd(char *p)
{
struct network *np;
char *field[12];
int i, n;
n = getfields(p, field, 12, 1, " ");
for (i = 0; i < n; i++) {
for (np = network; np->net; np++) {
if (strcmp(field[i], np->net) != 0)
continue;
if (np->considered)
break;
if (netlist)
last->next = np;
else
netlist = np;
last = np;
np->next = 0;
np->considered = 1;
}
}
}
static int lookforproto(struct ndbtuple *t, char *proto)
{
for (; t != NULL; t = t->entry)
if (strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
return 1;
return 0;
}
/*
* lookup a request. the network "net" means we should pick the
* best network to get there.
*/
static int lookup(struct mfile *mf)
{
struct network *np;
char *cp;
struct ndbtuple *nt, *t;
char reply[Maxreply];
int i, rv;
int hack;
/* open up the standard db files */
if (db == 0)
ndbinit();
if (db == 0)
error(1, 0, "%s: %r", "can't open mf->network database\n");
rv = 0;
if (mf->net == NULL)
return 0; /* must have been a genquery */
if (strcmp(mf->net, "net") == 0) {
/*
* go through set of default nets
*/
for (np = mf->nextnet; np; np = np->next) {
nt = (*np->lookup)(np, mf->host, mf->serv, 1);
if (nt == NULL)
continue;
hack = np->fasttimeouthack && !lookforproto(nt,
np->net);
for (t = nt; mf->nreply < Nreply && t; t = t->entry) {
cp = (*np->trans)(t, np, mf->serv, mf->rem,
hack);
if (!cp)
continue;
/* avoid duplicates */
for (i = 0; i < mf->nreply; i++)
if (strcmp(mf->reply[i], cp) == 0)
break;
if (i == mf->nreply) {
/* save the reply */
mf->replylen[mf->nreply] = strlen(cp);
mf->reply[mf->nreply++] = cp;
rv++;
}
}
ndbfree(nt);
np = np->next;
break;
}
mf->nextnet = np;
return rv;
}
/*
* if not /net, we only get one lookup
*/
if (mf->nreply != 0)
return 0;
/*
* look for a specific network
*/
for (np = netlist; np && np->net != NULL; np++) {
if (np->fasttimeouthack)
continue;
if (strcmp(np->net, mf->net) == 0)
break;
}
if (np && np->net != NULL) {
/*
* known network
*/
nt = (*np->lookup)(np, mf->host, mf->serv, 1);
for (t = nt; mf->nreply < Nreply && t; t = t->entry) {
cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
if (cp) {
mf->replylen[mf->nreply] = strlen(cp);
mf->reply[mf->nreply++] = cp;
rv++;
}
}
ndbfree(nt);
return rv;
}
/*
* not a known network, don't translate host or service
*/
if (mf->serv)
snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s", mntpt,
mf->net, mf->host, mf->serv);
else
snprintf(reply, sizeof(reply), "%s/%s/clone %s", mntpt, mf->net,
mf->host);
mf->reply[0] = strdup(reply);
mf->replylen[0] = strlen(reply);
mf->nreply = 1;
return 1;
}
/*
* translate an ip service name into a port number. If it's a numeric port
* number, look for restricted access.
*
* the service '*' needs no translation.
*/
static char *ipserv(struct network *np, char *name, char *buf, int blen)
{
char *p;
int alpha = 0;
int restr = 0;
char port[10];
struct ndbtuple *t, *nt;
struct ndbs s;
/* '*' means any service */
if (strcmp(name, "*") == 0) {
strlcpy(buf, name, blen);
return buf;
}
/* see if it's numeric or symbolic */
port[0] = 0;
for (p = name; *p; p++) {
if (!isdigit(*p)) {
if (isalpha(*p) || *p == '-' || *p == '$')
alpha = 1;
else
return 0;
}
}
t = NULL;
p = NULL;
if (alpha) {
p = ndbgetvalue(db, &s, np->net, name, "port", &t);
if (p == NULL)
return 0;
} else {
/* look up only for tcp ports < 1024 to get the restricted
* attribute
*/
if (atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
p = ndbgetvalue(db, &s, "port", name, "port", &t);
if (p == NULL)
p = strdup(name);
}
if (t) {
for (nt = t; nt; nt = nt->entry)
if (strcmp(nt->attr, "restricted") == 0)
restr = 1;
ndbfree(t);
}
snprintf(buf, blen, "%s%s", p, restr ? "!r" : "");
free(p);
return buf;
}
/*
* lookup an ip attribute
*/
static int ipattrlookup(struct ndb *db, char *ipa, char *attr, char *val,
int vlen)
{
struct ndbtuple *t, *nt;
char *alist[2];
alist[0] = attr;
t = ndbipinfo(db, "ip", ipa, alist, 1);
if (t == NULL)
return 0;
for (nt = t; nt != NULL; nt = nt->entry) {
if (strcmp(nt->attr, attr) == 0) {
strlcpy(val, nt->val, vlen);
ndbfree(t);
return 1;
}
}
/* we shouldn't get here */
ndbfree(t);
return 0;
}
/*
* lookup (and translate) an ip destination
*/
static struct ndbtuple *iplookup(struct network *np, char *host, char *serv,
int nolookup)
{
char *attr, *dnsname;
struct ndbtuple *t, *nt;
struct ndbs s;
char ts[Maxservice];
char dollar[Maxhost];
uint8_t ip[IPaddrlen];
uint8_t net[IPaddrlen];
uint8_t tnet[IPaddrlen];
struct ipifc *ifc;
struct iplifc *lifc;
/*
* start with the service since it's the most likely to fail
* and costs the least
*/
werrstr("can't translate address");
if (serv == 0 || ipserv(np, serv, ts, sizeof(ts)) == 0) {
werrstr("can't translate service");
return 0;
}
/* for dial strings with no host */
if (strcmp(host, "*") == 0)
return ndbnew("ip", "*");
/*
* hack till we go v6 :: = 0.0.0.0
*/
if (strcmp("::", host) == 0)
return ndbnew("ip", "*");
/*
* '$' means the rest of the name is an attribute that we
* need to search for
*/
if (*host == '$') {
if (ipattrlookup(db, ipaddr, host + 1, dollar, sizeof(dollar)))
host = dollar;
}
/*
* turn '[ip address]' into just 'ip address'
*/
if (*host == '[' && host[strlen(host) - 1] == ']') {
host++;
host[strlen(host) - 1] = 0;
}
/*
* just accept addresses
*/
attr = ipattr(host);
if (strcmp(attr, "ip") == 0)
return ndbnew("ip", host);
/*
* give the domain name server the first opportunity to
* resolve domain names. if that fails try the database.
*/
t = 0;
werrstr("can't translate address");
if (strcmp(attr, "dom") == 0)
t = dnsiplookup(host, &s);
if (t == 0)
free(ndbgetvalue(db, &s, attr, host, "ip", &t));
if (t == 0) {
dnsname = ndbgetvalue(db, &s, attr, host, "dom", NULL);
if (dnsname) {
t = dnsiplookup(dnsname, &s);
free(dnsname);
}
}
if (t == 0)
t = dnsiplookup(host, &s);
if (t == 0)
return 0;
/*
* reorder the tuple to have the matched line first and
* save that in the request structure.
*/
t = reorder(t, s.t);
/*
* reorder according to our interfaces
*/
spinlock_lock(&ipifclock);
for (ifc = ipifcs; ifc != NULL; ifc = ifc->next) {
for (lifc = ifc->lifc; lifc != NULL; lifc = lifc->next) {
maskip(lifc->ip, lifc->mask, net);
for (nt = t; nt; nt = nt->entry) {
if (strcmp(nt->attr, "ip") != 0)
continue;
parseip(ip, nt->val);
maskip(ip, lifc->mask, tnet);
if (memcmp(net, tnet, IPaddrlen) == 0) {
t = reorder(t, nt);
spinlock_unlock(&ipifclock);
return t;
}
}
}
}
spinlock_unlock(&ipifclock);
return t;
}
/*
* translate an ip address
*/
static char *iptrans(struct ndbtuple *t, struct network *np, char *serv,
char *rem, int hack)
{
char ts[Maxservice];
char reply[Maxreply];
char x[Maxservice];
if (strcmp(t->attr, "ip") != 0)
return 0;
if (serv == 0 || ipserv(np, serv, ts, sizeof(ts)) == 0) {
werrstr("can't translate service");
return 0;
}
if (rem != NULL)
snprintf(x, sizeof(x), "!%s", rem);
else
*x = 0;
if (*t->val == '*')
snprintf(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt,
np->net, ts, x);
else
snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s%s%s", mntpt,
np->net, t->val, ts, x, hack ? "!fasttimeout" : "");
return strdup(reply);
}
/*
* lookup a telephone number
*/
static struct ndbtuple *telcolookup(struct network *np, char *host, char *serv,
int nolookup)
{
struct ndbtuple *t;
struct ndbs s;
werrstr("can't translate address");
free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
if (t == 0)
return ndbnew("telco", host);
return reorder(t, s.t);
}
/*
* translate a telephone address
*/
static char *telcotrans(struct ndbtuple *t, struct network *np, char *serv,
char *rem, int unused)
{
char reply[Maxreply];
char x[Maxservice];
if (strcmp(t->attr, "telco") != 0)
return 0;
if (rem != NULL)
snprintf(x, sizeof(x), "!%s", rem);
else
*x = 0;
if (serv)
snprintf(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt,
np->net, t->val, serv, x);
else
snprintf(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt,
np->net, t->val, x);
return strdup(reply);
}
/*
* reorder the tuple to put x's line first in the entry
*/
static struct ndbtuple *reorder(struct ndbtuple *t, struct ndbtuple *x)
{
struct ndbtuple *nt;
struct ndbtuple *line;
/* find start of this entry's line */
for (line = x; line->entry == line->line; line = line->line)
;
line = line->line;
if (line == t)
return t; /* already the first line */
/* remove this line and everything after it from the entry */
for (nt = t; nt->entry != line; nt = nt->entry)
;
nt->entry = 0;
/* make that the start of the entry */
for (nt = line; nt->entry; nt = nt->entry)
;
nt->entry = t;
return line;
}
static struct ndbtuple *dnsip6lookup(char *mntpt, char *buf, struct ndbtuple *t)
{
struct ndbtuple *t6, *tt;
t6 = dnsquery(mntpt, buf, "ipv6"); /* lookup AAAA dns RRs */
if (t6 == NULL)
return t;
/* convert ipv6 attr to ip */
for (tt = t6; tt != NULL; tt = tt->entry)
if (strcmp(tt->attr, "ipv6") == 0)
strlcpy(tt->attr, "ip", sizeof(tt->attr));
if (t == NULL)
return t6;
/* append t6 list to t list */
for (tt = t; tt->entry != NULL; tt = tt->entry)
;
tt->entry = t6;
return t;
}
/*
* call the dns process and have it try to translate a name
*/
static struct ndbtuple *dnsiplookup(char *host, struct ndbs *s)
{
char buf[Maxreply];
struct ndbtuple *t;
spinlock_unlock(&dblock);
/* save the name */
snprintf(buf, sizeof(buf), "%s", host);
if (strcmp(ipattr(buf), "ip") == 0)
t = dnsquery(mntpt, buf, "ptr");
else {
t = dnsquery(mntpt, buf, "ip");
/* special case: query ipv6 (AAAA dns RR) too */
if (ipv6lookups)
t = dnsip6lookup(mntpt, buf, t);
}
s->t = t;
if (t == NULL) {
snprintf(buf, sizeof(buf), "%r");
if (strstr(buf, "exist"))
werrstr("can't translate address: %s", buf);
else if (strstr(buf, "dns failure"))
werrstr("temporary problem: %s", buf);
}
spinlock_lock(&dblock);
return t;
}
static int qmatch(struct ndbtuple *t, char **attr, char **val, int n)
{
int i, found;
struct ndbtuple *nt;
for (i = 1; i < n; i++) {
found = 0;
for (nt = t; nt; nt = nt->entry)
if (strcmp(attr[i], nt->attr) == 0)
if (strcmp(val[i], "*") == 0 ||
strcmp(val[i], nt->val) == 0) {
found = 1;
break;
}
if (found == 0)
break;
}
return i == n;
}
/* this is awful but I don't want to bring in libstring just for this.
* you want real strings don't use C
*/
static void qreply(struct mfile *mf, struct ndbtuple *t)
{
struct ndbtuple *nt;
char *s, *cur;
int len, amt;
s = malloc(4096);
cur = s;
len = 4096;
for (nt = t; mf->nreply < Nreply && nt; nt = nt->entry) {
amt = snprintf(cur, len, "%s=%s", nt->attr, nt->val);
if (amt < 0)
len = 0;
else {
len -= amt;
cur += amt;
}
if (nt->line != nt->entry) {
mf->replylen[mf->nreply] = strlen(s);
mf->reply[mf->nreply++] = strdup(s);
cur = s;
len = 4096;
} else {
amt = snprintf(cur, len, " ");
if (amt < 0)
len = 0;
else {
len -= amt;
cur += amt;
}
}
}
free(s);
}
enum {
Maxattr = 32,
};
/*
* generic query lookup. The query is of one of the following
* forms:
*
* attr1=val1 attr2=val2 attr3=val3 ...
*
* returns the matching tuple
*
* ipinfo attr=val attr1 attr2 attr3 ...
*
* is like ipinfo and returns the attr{1-n}
* associated with the ip address.
*/
static char *genquery(struct mfile *mf, char *query)
{
int i, n;
char *p;
char *attr[Maxattr];
char *val[Maxattr];
struct ndbtuple *t;
struct ndbs s;
n = getfields(query, attr, COUNT_OF(attr), 1, " ");
if (n == 0)
return "bad query";
if (strcmp(attr[0], "ipinfo") == 0)
return ipinfoquery(mf, attr, n);
/* parse pairs */
for (i = 0; i < n; i++) {
p = strchr(attr[i], '=');
if (p == 0)
return "bad query";
*p++ = 0;
val[i] = p;
}
/* give dns a chance */
if ((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0)
&& val[0]) {
t = dnsiplookup(val[0], &s);
if (t) {
if (qmatch(t, attr, val, n)) {
qreply(mf, t);
ndbfree(t);
return 0;
}
ndbfree(t);
}
}
/* first pair is always the key. It can't be a '*' */
t = ndbsearch(db, &s, attr[0], val[0]);
/* search is the and of all the pairs */
while (t) {
if (qmatch(t, attr, val, n)) {
qreply(mf, t);
ndbfree(t);
return 0;
}
ndbfree(t);
t = ndbsnext(&s, attr[0], val[0]);
}
return "no match";
}
/*
* resolve an ip address
*/
static struct ndbtuple *ipresolve(char *attr, char *host)
{
struct ndbtuple *t, *nt, **l;
t = iplookup(&network[0], host, "*", 0);
for (l = &t; *l != NULL;) {
nt = *l;
if (strcmp(nt->attr, "ip") != 0) {
*l = nt->entry;
nt->entry = NULL;
ndbfree(nt);
continue;
}
strlcpy(nt->attr, attr, sizeof(nt->attr));
l = &nt->entry;
}
return t;
}
static char *ipinfoquery(struct mfile *mf, char **list, int n)
{
int i, nresolve;
int resolve[Maxattr];
struct ndbtuple *t, *nt, **l;
char *attr, *val;
/* skip 'ipinfo' */
list++;
n--;
if (n < 1)
return "bad query";
/* get search attribute=value, or assume ip=myipaddr */
attr = *list;
val = strchr(attr, '=');
if (val != NULL) {
*val++ = 0;
list++;
n--;
} else {
attr = "ip";
val = ipaddr;
}
if (n < 1)
return "bad query";
/*
* don't let ndbipinfo resolve the addresses, we're
* better at it.
*/
nresolve = 0;
for (i = 0; i < n; i++)
if (*list[i] == '@') { /* @attr=val ? */
list[i]++;
resolve[i] = 1; /* we'll resolve it */
nresolve++;
} else
resolve[i] = 0;
t = ndbipinfo(db, attr, val, list, n);
if (t == NULL)
return "no match";
if (nresolve != 0) {
for (l = &t; *l != NULL;) {
nt = *l;
/* already an address? */
if (strcmp(ipattr(nt->val), "ip") == 0) {
l = &(*l)->entry;
continue;
}
/* user wants it resolved? */
for (i = 0; i < n; i++)
if (strcmp(list[i], nt->attr) == 0)
break;
if (i >= n || resolve[i] == 0) {
l = &(*l)->entry;
continue;
}
/* resolve address and replace entry */
*l = ipresolve(nt->attr, nt->val);
while (*l != NULL)
l = &(*l)->entry;
*l = nt->entry;
nt->entry = NULL;
ndbfree(nt);
}
}
/* make it all one line */
for (nt = t; nt != NULL; nt = nt->entry) {
if (nt->entry == NULL)
nt->line = t;
else
nt->line = nt->entry;
}
qreply(mf, t);
return NULL;
}
static void *emalloc(int size)
{
void *x;
x = calloc(size, 1);
if (x == NULL)
abort();
memset(x, 0, size);
return x;
}
static char *estrdup(char *s)
{
int size;
char *p;
size = strlen(s) + 1;
p = calloc(size, 1);
if (p == NULL)
abort();
memmove(p, s, size);
return p;
}