/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for ENDIAN defines */ #include #include #include #include #include /* * This version of Berkeley's rwhod has been modified to use IP multicast * datagrams, under control of a new command-line option: * * rwhod -m causes rwhod to use IP multicast (instead of * broadcast or unicast) on all interfaces that have * the IFF_MULTICAST flag set in their "ifnet" structs * (excluding the loopback interface). The multicast * reports are sent with a time-to-live of 1, to prevent * forwarding beyond the directly-connected subnet(s). * * rwhod -m causes rwhod to send IP multicast datagrams with a * time-to-live of , via a SINGLE interface rather * than all interfaces. must be between 0 and * MAX_MULTICAST_SCOPE, defined below. Note that "-m 1" * is different than "-m", in that "-m 1" specifies * transmission on one interface only. * * When "-m" is used without a argument, the program accepts multicast * rwhod reports from all multicast-capable interfaces. If a argument * is given, it accepts multicast reports from only one interface, the one * on which reports are sent (which may be controlled via the host's routing * table). Regardless of the "-m" option, the program accepts broadcast or * unicast reports from all interfaces. Thus, this program will hear the * reports of old, non-multicasting rwhods, but, if multicasting is used, * those old rwhods won't hear the reports generated by this program. * * -- Steve Deering, Stanford University, February 1989 */ #define NO_MULTICAST 0 /* multicast modes */ #define PER_INTERFACE_MULTICAST 1 #define SCOPED_MULTICAST 2 #define MAX_MULTICAST_SCOPE 32 /* "site-wide", by convention */ #define INADDR_WHOD_GROUP (ulong_t)0xe0000103 /* 224.0.1.3 */ /* (belongs in protocols/rwhod.h) */ static int multicast_mode = NO_MULTICAST; static int multicast_scope; static struct sockaddr_in multicast_addr = { AF_INET }; /* * Alarm interval. Don't forget to change the down time check in ruptime * if this is changed. */ #define AL_INTERVAL (3 * 60) static struct sockaddr_in sin = { AF_INET }; static char myname[MAXHOSTNAMELEN]; /* * We communicate with each neighbor in * a list constructed at the time we're * started up. Neighbors are currently * directly connected via a hardware interface. */ struct neighbor { struct neighbor *n_next; char *n_name; /* interface name */ char *n_addr; /* who to send to */ int n_addrlen; /* size of address */ ulong_t n_subnet; /* AF_INET subnet */ uint_t n_flags; /* should forward?, interface flags */ }; static struct neighbor *neighbors; static struct whod mywd; static struct servent *sp; static int s; #define WHDRSIZE (sizeof (mywd) - sizeof (mywd.wd_we)) #define RWHODIR "/var/spool/rwho" static void onalrm(void); static void getkmem(void); static boolean_t configure(int); static int verify(const struct whod *); void main(int argc, char *argv[]) { struct sockaddr_in from; struct stat st; char path[64]; struct hostent *hp; int on = 1; char *cp; struct stat sb; if (getuid()) { (void) fprintf(stderr, "in.rwhod: not super user\n"); exit(1); } sp = getservbyname("who", "udp"); if (sp == NULL) { (void) fprintf(stderr, "in.rwhod: udp/who: unknown service\n"); exit(1); } argv++; argc--; while (argc > 0 && *argv[0] == '-') { if (strcmp(*argv, "-m") == 0) { if (argc > 1 && isdigit(*(argv + 1)[0])) { argv++; argc--; multicast_mode = SCOPED_MULTICAST; multicast_scope = atoi(*argv); if (multicast_scope > MAX_MULTICAST_SCOPE) { (void) fprintf(stderr, "in.rwhod: " "ttl must not exceed %u\n", MAX_MULTICAST_SCOPE); exit(1); } } else { multicast_mode = PER_INTERFACE_MULTICAST; } } else { goto usage; } argv++; argc--; } if (argc > 0) goto usage; if (chdir(RWHODIR) < 0) { perror(RWHODIR); exit(1); } #ifndef DEBUG if (fork()) exit(0); /* CSTYLED */ { (void) close(0); (void) close(1); (void) close(2); (void) open("/", 0); (void) dup2(0, 1); (void) dup2(0, 2); (void) setsid(); } #endif (void) sigset(SIGHUP, (void (*)())getkmem); openlog("in.rwhod", LOG_PID, LOG_DAEMON); /* * Establish host name as returned by system. */ if (gethostname(myname, sizeof (myname) - 1) < 0) { syslog(LOG_ERR, "main: gethostname: %m"); exit(1); } if ((cp = index(myname, '.')) != NULL) *cp = '\0'; (void) strlcpy(mywd.wd_hostname, myname, sizeof (mywd.wd_hostname)); if (stat(UTMPX_FILE, &sb) < 0) { syslog(LOG_ERR, "main: stat: %s: %m", UTMPX_FILE); exit(1); } getkmem(); if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "main: socket: %m"); exit(1); } if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof (on)) < 0) { syslog(LOG_ERR, "main: setsockopt SO_BROADCAST: %m"); exit(1); } hp = gethostbyname(myname); if (hp == NULL) { syslog(LOG_ERR, "main: %s: don't know my own name\n", myname); exit(1); } sin.sin_family = hp->h_addrtype; sin.sin_port = sp->s_port; if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { syslog(LOG_ERR, "main: bind: %m"); exit(1); } if (!configure(s)) exit(1); (void) sigset(SIGALRM, (void (*)())onalrm); onalrm(); for (;;) { struct whod wd; int cc, whod; socklen_t len = sizeof (from); cc = recvfrom(s, &wd, sizeof (struct whod), 0, (struct sockaddr *)&from, &len); if (cc <= 0) { if (cc < 0 && errno != EINTR) syslog(LOG_WARNING, "main: recvfrom: %m"); continue; } if (from.sin_port != sp->s_port) { syslog(LOG_WARNING, "main: %d: bad from port", ntohs(from.sin_port)); continue; } #ifdef notdef if (gethostbyname(wd.wd_hostname) == 0) { syslog(LOG_WARNING, "main: %s: unknown host", wd.wd_hostname); continue; } #endif if (wd.wd_vers != WHODVERSION) continue; if (wd.wd_type != WHODTYPE_STATUS) continue; if (!verify(&wd)) { syslog(LOG_WARNING, "main: malformed host name from %x", from.sin_addr.s_addr); continue; } (void) sprintf(path, "whod.%s", wd.wd_hostname); /* * Rather than truncating and growing the file each time, * use ftruncate if size is less than previous size. */ whod = open(path, O_WRONLY | O_CREAT, 0644); if (whod < 0) { syslog(LOG_WARNING, "main: open: %s: %m", path); continue; } #if defined(_LITTLE_ENDIAN) /* CSTYLED */ { int i, n = (cc - WHDRSIZE)/sizeof (struct whoent); struct whoent *we; /* undo header byte swapping before writing to file */ wd.wd_sendtime = ntohl(wd.wd_sendtime); for (i = 0; i < 3; i++) wd.wd_loadav[i] = ntohl(wd.wd_loadav[i]); wd.wd_boottime = ntohl(wd.wd_boottime); we = wd.wd_we; for (i = 0; i < n; i++) { we->we_idle = ntohl(we->we_idle); we->we_utmp.out_time = ntohl(we->we_utmp.out_time); we++; } } #endif (void) time((time_t *)&wd.wd_recvtime); (void) write(whod, &wd, cc); if (fstat(whod, &st) < 0 || st.st_size > cc) (void) ftruncate(whod, cc); (void) close(whod); } /* NOTREACHED */ usage: (void) fprintf(stderr, "usage: in.rwhod [ -m [ ttl ] ]\n"); exit(1); } /* * Check out host name for unprintables * and other funnies before allowing a file * to be created. Sorry, but blanks aren't allowed. */ static int verify(const struct whod *wd) { int size = 0; const char *name = wd->wd_hostname; /* * We shouldn't assume the name is NUL terminated, so bound the * checks at the size of the whod structures wd_hostname field. */ while ((size < sizeof (wd->wd_hostname)) && (*name != '\0')) { if (*name == '/' || !isascii(*name) || !(isalnum(*name) || ispunct(*name))) return (0); name++, size++; } /* * Fail the verification if NULL name or it wasn't NUL terminated. */ return ((size > 0) && (size < sizeof (wd->wd_hostname))); } static int utmpxtime; static int utmpxent; static int alarmcount; struct utmpx *utmpx; static void onalrm(void) { int i; struct stat stb; int utmpxsize = 0; int entries; struct utmpx *utp; struct utmpx *utmpxbegin; struct whoent *we = mywd.wd_we, *wlast; int cc, cnt; double avenrun[3]; time_t now = time(0); struct neighbor *np; if (alarmcount % 10 == 0) getkmem(); alarmcount++; (void) stat(UTMPX_FILE, &stb); entries = stb.st_size / sizeof (struct futmpx); if ((stb.st_mtime != utmpxtime) || (entries > utmpxent)) { utmpxtime = stb.st_mtime; if (entries > utmpxent) { utmpxent = entries; utmpxsize = utmpxent * sizeof (struct utmpx); utmpx = realloc(utmpx, utmpxsize); if (utmpx == NULL) { syslog(LOG_ERR, "onalrm: realloc: %m"); utmpxsize = 0; goto done; } } utmpxbegin = utmpx; setutxent(); cnt = 0; while (cnt++ < utmpxent && (utp = getutxent()) != NULL) (void) memcpy(utmpxbegin++, utp, sizeof (struct utmpx)); endutxent(); wlast = &mywd.wd_we[1024 / sizeof (struct whoent) - 1]; for (i = 0; i < utmpxent; i++) { if (utmpx[i].ut_name[0] && utmpx[i].ut_type == USER_PROCESS) { /* * XXX - utmpx name and line lengths should * be here */ bcopy(utmpx[i].ut_line, we->we_utmp.out_line, sizeof (we->we_utmp.out_line)); bcopy(utmpx[i].ut_name, we->we_utmp.out_name, sizeof (we->we_utmp.out_name)); we->we_utmp.out_time = htonl(utmpx[i].ut_xtime); if (we >= wlast) break; we++; } } utmpxent = we - mywd.wd_we; } /* * The test on utmpxent looks silly---after all, if no one is * logged on, why worry about efficiency?---but is useful on * (e.g.) compute servers. */ if (utmpxent > 0 && chdir("/dev") == -1) { syslog(LOG_ERR, "onalrm: chdir /dev: %m"); exit(1); } we = mywd.wd_we; for (i = 0; i < utmpxent; i++) { if (stat(we->we_utmp.out_line, &stb) >= 0) we->we_idle = htonl(now - stb.st_atime); we++; } if (getloadavg(avenrun, 3) == -1) { syslog(LOG_ERR, "onalrm: getloadavg: %m"); exit(1); } for (i = 0; i < 3; i++) mywd.wd_loadav[i] = htonl((ulong_t)(avenrun[i] * 100)); cc = (char *)we - (char *)&mywd; mywd.wd_sendtime = htonl(time(0)); mywd.wd_vers = WHODVERSION; mywd.wd_type = WHODTYPE_STATUS; if (multicast_mode == SCOPED_MULTICAST) { (void) sendto(s, &mywd, cc, 0, (struct sockaddr *)&multicast_addr, sizeof (multicast_addr)); } else for (np = neighbors; np != NULL; np = np->n_next) { if (multicast_mode == PER_INTERFACE_MULTICAST && np->n_flags & IFF_MULTICAST) { /* * Select the outgoing interface for the multicast. */ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &(((struct sockaddr_in *)np->n_addr)->sin_addr), sizeof (struct in_addr)) < 0) { syslog(LOG_ERR, "onalrm: setsockopt IP_MULTICAST_IF: %m"); exit(1); } (void) sendto(s, &mywd, cc, 0, (struct sockaddr *)&multicast_addr, sizeof (multicast_addr)); } else { (void) sendto(s, &mywd, cc, 0, (struct sockaddr *)np->n_addr, np->n_addrlen); } } if (utmpxent > 0 && chdir(RWHODIR) == -1) { syslog(LOG_ERR, "onalrm: chdir %s: %m", RWHODIR); exit(1); } done: (void) alarm(AL_INTERVAL); } static void getkmem(void) { struct utmpx *utmpx, utmpx_id; utmpx_id.ut_type = BOOT_TIME; if ((utmpx = getutxid(&utmpx_id)) != NULL) mywd.wd_boottime = utmpx->ut_xtime; endutxent(); mywd.wd_boottime = htonl(mywd.wd_boottime); } /* * Figure out device configuration and select * networks which deserve status information. */ static boolean_t configure(int s) { char *buf; struct ifconf ifc; struct ifreq ifreq, *ifr; struct sockaddr_in *sin; struct neighbor *np; struct neighbor *np2; int n; int numifs; unsigned bufsize; if (multicast_mode == SCOPED_MULTICAST) { struct ip_mreq mreq; unsigned char ttl; mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP); mreq.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof (mreq)) < 0) { syslog(LOG_ERR, "configure: setsockopt IP_ADD_MEMBERSHIP: %m"); return (B_FALSE); } ttl = multicast_scope; if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof (ttl)) < 0) { syslog(LOG_ERR, "configure: setsockopt IP_MULTICAST_TTL: %m"); return (B_FALSE); } multicast_addr.sin_addr.s_addr = htonl(INADDR_WHOD_GROUP); multicast_addr.sin_port = sp->s_port; return (B_TRUE); } if (ioctl(s, SIOCGIFNUM, (char *)&numifs) < 0) { syslog(LOG_ERR, "configure: ioctl SIOCGIFNUM: %m"); return (B_FALSE); } bufsize = numifs * sizeof (struct ifreq); buf = malloc(bufsize); if (buf == NULL) { syslog(LOG_ERR, "configure: malloc: %m"); return (B_FALSE); } ifc.ifc_len = bufsize; ifc.ifc_buf = buf; if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0) { syslog(LOG_ERR, "configure: ioctl (get interface configuration): %m"); (void) free(buf); return (B_FALSE); } ifr = ifc.ifc_req; for (n = ifc.ifc_len / sizeof (struct ifreq); n > 0; n--, ifr++) { /* Skip all logical interfaces */ if (index(ifr->ifr_name, ':') != NULL) continue; for (np = neighbors; np != NULL; np = np->n_next) { if (np->n_name && strcmp(ifr->ifr_name, np->n_name) == 0) break; } if (np != NULL) continue; ifreq = *ifr; np = (struct neighbor *)malloc(sizeof (*np)); if (np == NULL) continue; np->n_name = malloc(strlen(ifr->ifr_name) + 1); if (np->n_name == NULL) { free(np); continue; } (void) strcpy(np->n_name, ifr->ifr_name); np->n_addrlen = sizeof (ifr->ifr_addr); np->n_addr = malloc(np->n_addrlen); if (np->n_addr == NULL) { free(np->n_name); free(np); continue; } bcopy(&ifr->ifr_addr, np->n_addr, np->n_addrlen); if (ioctl(s, SIOCGIFFLAGS, (char *)&ifreq) < 0) { syslog(LOG_ERR, "configure: ioctl (get interface flags): %m"); free(np->n_addr); free(np->n_name); free(np); continue; } np->n_flags = ifreq.ifr_flags; if (((struct sockaddr_in *)np->n_addr)->sin_family == AF_INET && ioctl(s, SIOCGIFNETMASK, (char *)&ifreq) >= 0) { sin = (struct sockaddr_in *)np->n_addr; np->n_subnet = sin->sin_addr.s_addr & ((struct sockaddr_in *)&ifreq.ifr_addr)-> sin_addr.s_addr; } if (multicast_mode == PER_INTERFACE_MULTICAST && (np->n_flags & IFF_UP) && (np->n_flags & IFF_MULTICAST) && !(np->n_flags & IFF_LOOPBACK)) { struct ip_mreq mreq; /* * Skip interfaces that have matching subnets i.e. * (addr & netmask) are identical. * Such interfaces are connected to the same * physical wire. */ for (np2 = neighbors; np2 != NULL; np2 = np2->n_next) { if (!(np->n_flags & IFF_POINTOPOINT) && !(np2->n_flags & IFF_POINTOPOINT) && (np->n_subnet == np2->n_subnet)) { free(np->n_addr); free(np->n_name); free(np); break; } } if (np2 != NULL) continue; mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP); mreq.imr_interface.s_addr = ((struct sockaddr_in *)np->n_addr)->sin_addr.s_addr; if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof (mreq)) < 0) { syslog(LOG_ERR, "configure: " "setsockopt IP_ADD_MEMBERSHIP: %m"); free(np->n_addr); free(np->n_name); free(np); continue; } multicast_addr.sin_addr.s_addr = htonl(INADDR_WHOD_GROUP); multicast_addr.sin_port = sp->s_port; np->n_next = neighbors; neighbors = np; continue; } if ((np->n_flags & IFF_UP) == 0 || (np->n_flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0) { free(np->n_addr); free(np->n_name); free(np); continue; } if (np->n_flags & IFF_POINTOPOINT) { if (ioctl(s, SIOCGIFDSTADDR, (char *)&ifreq) < 0) { syslog(LOG_ERR, "configure: ioctl (get dstaddr): %m"); free(np->n_addr); free(np->n_name); free(np); continue; } /* we assume addresses are all the same size */ bcopy(&ifreq.ifr_dstaddr, np->n_addr, np->n_addrlen); } if (np->n_flags & IFF_BROADCAST) { if (ioctl(s, SIOCGIFBRDADDR, (char *)&ifreq) < 0) { syslog(LOG_ERR, "configure: ioctl (get broadaddr): %m"); free(np->n_addr); free(np->n_name); free(np); continue; } /* we assume addresses are all the same size */ bcopy(&ifreq.ifr_broadaddr, np->n_addr, np->n_addrlen); } /* gag, wish we could get rid of Internet dependencies */ sin = (struct sockaddr_in *)np->n_addr; sin->sin_port = sp->s_port; /* * Avoid adding duplicate broadcast and pt-pt destinations * to the list. */ for (np2 = neighbors; np2 != NULL; np2 = np2->n_next) { struct sockaddr_in *sin2; sin2 = (struct sockaddr_in *)np2->n_addr; if (sin2->sin_addr.s_addr == sin->sin_addr.s_addr) { free(np->n_addr); free(np->n_name); free(np); break; } } if (np2 != NULL) continue; np->n_next = neighbors; neighbors = np; } (void) free(buf); return (B_TRUE); } #ifdef DEBUG static char *interval(uint_t, char *); /* ARGSUSED */ static ssize_t sendto(int s, const void *buf, size_t cc, int flags, const struct sockaddr *to, socklen_t tolen) { struct whod *w = (struct whod *)buf; struct whoent *we; struct sockaddr_in *sin = (struct sockaddr_in *)to; int nsz; (void) printf("sendto %x.%d\n", ntohl(sin->sin_addr.s_addr), ntohs(sin->sin_port)); (void) printf("hostname %s %s\n", w->wd_hostname, interval(ntohl(w->wd_sendtime) - ntohl(w->wd_boottime), " up")); (void) printf("load %4.2f, %4.2f, %4.2f\n", ntohl(w->wd_loadav[0]) / 100.0, ntohl(w->wd_loadav[1]) / 100.0, ntohl(w->wd_loadav[2]) / 100.0); cc -= WHDRSIZE; for (we = w->wd_we, cc /= sizeof (struct whoent); cc > 0; cc--, we++) { time_t t = ntohl(we->we_utmp.out_time); nsz = sizeof (we->we_utmp.out_name); (void) printf("%-*.*s %s:%s %.12s", nsz, nsz, we->we_utmp.out_name, w->wd_hostname, we->we_utmp.out_line, ctime(&t)+4); we->we_idle = ntohl(we->we_idle) / 60; if (we->we_idle) { if (we->we_idle >= 100*60) we->we_idle = 100*60 - 1; if (we->we_idle >= 60) (void) printf(" %2d", we->we_idle / 60); else (void) printf(" "); (void) printf(":%02d", we->we_idle % 60); } (void) printf("\n"); } return (0); } static char * interval(uint_t time, char *updown) { static char resbuf[32]; int days, hours, minutes; if (time > 3*30*24*60*60) { (void) sprintf(resbuf, " %s ??:??", updown); return (resbuf); } minutes = (time + 59) / 60; /* round to minutes */ hours = minutes / 60; minutes %= 60; days = hours / 24; hours %= 24; if (days > 0) { (void) sprintf(resbuf, "%s %2d+%02d:%02d", updown, days, hours, minutes); } else { (void) sprintf(resbuf, "%s %2d:%02d", updown, hours, minutes); } return (resbuf); } #endif