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