| /* | 
 |  * 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; | 
 | } |