rwhod.c revision 1.28
1/* $NetBSD: rwhod.c,v 1.28 2005/07/01 13:07:21 christos Exp $ */ 2 3/* 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\ 35 The Regents of the University of California. All rights reserved.\n"); 36#endif /* not lint */ 37 38#ifndef lint 39#if 0 40static char sccsid[] = "@(#)rwhod.c 8.1 (Berkeley) 6/6/93"; 41#else 42__RCSID("$NetBSD: rwhod.c,v 1.28 2005/07/01 13:07:21 christos Exp $"); 43#endif 44#endif /* not lint */ 45 46#include <sys/param.h> 47#include <sys/socket.h> 48#include <sys/stat.h> 49#include <sys/signal.h> 50#include <sys/ioctl.h> 51#include <sys/sysctl.h> 52 53#include <net/if.h> 54#include <net/if_dl.h> 55#include <net/route.h> 56#include <netinet/in.h> 57#include <protocols/rwhod.h> 58#include <arpa/inet.h> 59 60#include <ctype.h> 61#include <err.h> 62#include <errno.h> 63#include <fcntl.h> 64#include <netdb.h> 65#include <paths.h> 66#include <poll.h> 67#include <stdio.h> 68#include <stdlib.h> 69#include <string.h> 70#include <syslog.h> 71#include <unistd.h> 72#include <util.h> 73 74#include "utmpentry.h" 75/* 76 * Check interval. Don't forget to change the down time check in ruptime 77 * if this is changed. 78 */ 79#define CHECK_INTERVAL (3 * 60) 80 81static char myname[MAXHOSTNAMELEN + 1]; 82 83/* 84 * We communicate with each neighbor in a list constructed at the time we're 85 * started up. Neighbors are currently directly connected via a hardware 86 * interface. 87 */ 88struct neighbor { 89 struct neighbor *n_next; 90 char *n_name; /* interface name */ 91 struct sockaddr *n_addr; /* who to send to */ 92 int n_addrlen; /* size of address */ 93 int n_flags; /* should forward?, interface flags */ 94}; 95 96static struct neighbor *neighbors; 97static struct whod mywd; 98static struct servent *sp; 99static volatile sig_atomic_t onsighup; 100 101#define WHDRSIZE (sizeof(mywd) - sizeof(mywd.wd_we)) 102 103static int configure(int); 104static void getboottime(void); 105static void send_host_information(int); 106static void sighup(int); 107static void handleread(int); 108static void quit(const char *); 109static void rt_xaddrs(void *, void *, struct rt_addrinfo *); 110static void usage(void) __attribute__((__noreturn__)); 111static int verify(const char *); 112#ifdef DEBUG 113static char *interval(int, const char *); 114#define sendto Sendto 115static ssize_t Sendto(int, const void *, size_t, int, 116 const struct sockaddr *, socklen_t); 117#endif 118 119int 120main(int argc, char *argv[]) 121{ 122 int s, ch; 123 int time_interval = 180; /* Default time (180 seconds) */ 124 char *cp, *ep; 125 socklen_t on = 1; 126 struct sockaddr_in sasin; 127 struct pollfd pfd[1]; 128 struct timeval delta, next, now; 129 130 setprogname(argv[0]); 131 132 if (getuid()) 133 errx(EXIT_FAILURE, "not super user"); 134 135 while ((ch = getopt(argc, argv, "g:")) != -1) { 136 switch (ch) { 137 case 'g': 138 time_interval = (int)strtol(optarg, &ep, 10); 139 140 switch (*ep) { 141 case '\0': 142 break; 143 case 'm': 144 case 'M': 145 /* Time in minutes. */ 146 time_interval *= 60; 147 if (ep[1] == '\0') 148 break; 149 /*FALLTHROUGH*/ 150 default: 151 errx(1, "Invalid argument: `%s'", optarg); 152 } 153 154 if (time_interval <= 0) 155 errx(1, "Time must be greater than 0"); 156 157 if (time_interval > 180) 158 errx(1, "Cannot be greater than 180 seconds"); 159 break; 160 default: 161 usage(); 162 } 163 } 164 165 sp = getservbyname("who", "udp"); 166 if (sp == NULL) 167 errx(EXIT_FAILURE, "udp/who: unknown service"); 168#ifndef DEBUG 169 (void)daemon(1, 0); 170 (void)pidfile(NULL); 171#endif 172 if (chdir(_PATH_RWHODIR) < 0) 173 err(EXIT_FAILURE, "%s", _PATH_RWHODIR); 174 (void)signal(SIGHUP, sighup); 175 openlog("rwhod", LOG_PID, LOG_DAEMON); 176 /* 177 * Establish host name as returned by system. 178 */ 179 if (gethostname(myname, sizeof(myname) - 1) < 0) { 180 syslog(LOG_ERR, "gethostname: %m"); 181 exit(EXIT_FAILURE); 182 } 183 myname[sizeof(myname) - 1] = '\0'; 184 if ((cp = strchr(myname, '.')) != NULL) 185 *cp = '\0'; 186 (void)strncpy(mywd.wd_hostname, myname, sizeof(mywd.wd_hostname) - 1); 187 getboottime(); 188 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 189 syslog(LOG_ERR, "socket: %m"); 190 exit(EXIT_FAILURE); 191 } 192 if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { 193 syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m"); 194 exit(EXIT_FAILURE); 195 } 196 (void)memset(&sasin, 0, sizeof(sasin)); 197 sasin.sin_family = AF_INET; 198 sasin.sin_port = sp->s_port; 199 if (bind(s, (struct sockaddr *)&sasin, sizeof(sasin)) < 0) { 200 syslog(LOG_ERR, "bind: %m"); 201 exit(EXIT_FAILURE); 202 } 203 if (!configure(s)) 204 exit(EXIT_FAILURE); 205 206 send_host_information(s); 207 delta.tv_sec = time_interval; 208 delta.tv_usec = 0; 209 gettimeofday(&now, NULL); 210 timeradd(&now, &delta, &next); 211 212 pfd[0].fd = s; 213 pfd[0].events = POLLIN; 214 215 for (;;) { 216 int n; 217 218 n = poll(pfd, 1, 1000); 219 220 if (onsighup) { 221 onsighup = 0; 222 getboottime(); 223 } 224 225 if (n == 1) 226 handleread(s); 227 228 (void)gettimeofday(&now, NULL); 229 if (timercmp(&now, &next, >)) { 230 send_host_information(s); 231 timeradd(&now, &delta, &next); 232 } 233 } 234 235 /* NOTREACHED */ 236 return 0; 237} 238 239static void 240sighup(int signo __unused) 241{ 242 onsighup = 1; 243} 244 245static void 246handleread(int s) 247{ 248 struct sockaddr_in from; 249 struct stat st; 250 char path[64]; 251 struct whod wd; 252 int cc, whod; 253 socklen_t len = sizeof(from); 254 255 cc = recvfrom(s, (char *)&wd, sizeof(struct whod), 0, 256 (struct sockaddr *)&from, &len); 257 if (cc <= 0) { 258 if (cc < 0 && errno != EINTR) 259 syslog(LOG_WARNING, "recv: %m"); 260 return; 261 } 262 if (from.sin_port != sp->s_port) { 263 syslog(LOG_WARNING, "%d: bad from port", 264 ntohs(from.sin_port)); 265 return; 266 } 267 if (cc < WHDRSIZE) { 268 syslog(LOG_WARNING, "Short packet from %s", 269 inet_ntoa(from.sin_addr)); 270 return; 271 } 272 273 if (wd.wd_vers != WHODVERSION) 274 return; 275 if (wd.wd_type != WHODTYPE_STATUS) 276 return; 277 /* 278 * Ensure null termination of the name within the packet. 279 * Otherwise we might overflow or read past the end. 280 */ 281 wd.wd_hostname[sizeof(wd.wd_hostname)-1] = 0; 282 if (!verify(wd.wd_hostname)) { 283 syslog(LOG_WARNING, "malformed host name from %s", 284 inet_ntoa(from.sin_addr)); 285 return; 286 } 287 (void)snprintf(path, sizeof(path), "whod.%s", wd.wd_hostname); 288 /* 289 * Rather than truncating and growing the file each time, 290 * use ftruncate if size is less than previous size. 291 */ 292 whod = open(path, O_WRONLY | O_CREAT, 0644); 293 if (whod < 0) { 294 syslog(LOG_WARNING, "%s: %m", path); 295 return; 296 } 297#if ENDIAN != BIG_ENDIAN 298 { 299 int i, n = (cc - WHDRSIZE) / sizeof(struct whoent); 300 struct whoent *we; 301 302 /* undo header byte swapping before writing to file */ 303 wd.wd_sendtime = ntohl(wd.wd_sendtime); 304 for (i = 0; i < 3; i++) 305 wd.wd_loadav[i] = ntohl(wd.wd_loadav[i]); 306 wd.wd_boottime = ntohl(wd.wd_boottime); 307 we = wd.wd_we; 308 for (i = 0; i < n; i++) { 309 we->we_idle = ntohl(we->we_idle); 310 we->we_utmp.out_time = 311 ntohl(we->we_utmp.out_time); 312 we++; 313 } 314 } 315#endif 316 (void)time((time_t *)&wd.wd_recvtime); 317 (void)write(whod, (char *)&wd, cc); 318 if (fstat(whod, &st) < 0 || st.st_size > cc) 319 (void)ftruncate(whod, cc); 320 (void)close(whod); 321} 322 323/* 324 * Check out host name for unprintables 325 * and other funnies before allowing a file 326 * to be created. Sorry, but blanks aren't allowed. 327 */ 328static int 329verify(const char *name) 330{ 331 int size = 0; 332 333 while (*name) { 334 if (!isascii((unsigned char)*name) || 335 !(isalnum((unsigned char)*name) || 336 ispunct((unsigned char)*name))) 337 return 0; 338 name++, size++; 339 } 340 return size > 0; 341} 342 343static void 344send_host_information(int s) 345{ 346 struct neighbor *np; 347 struct whoent *we = mywd.wd_we, *wlast; 348 int i, cc, utmpent = 0; 349 struct stat stb; 350 double avenrun[3]; 351 time_t now; 352 static struct utmpentry *ohead = NULL; 353 struct utmpentry *ep; 354 static int count = 0; 355 356 now = time(NULL); 357 if (count % 10 == 0) 358 getboottime(); 359 count++; 360 361 (void)getutentries(NULL, &ep); 362 if (ep != ohead) { 363 freeutentries(ep); 364 wlast = &mywd.wd_we[1024 / sizeof(struct whoent) - 1]; 365 for (; ep; ep = ep->next) { 366 (void)strncpy(we->we_utmp.out_line, ep->line, 367 sizeof(we->we_utmp.out_line) - 1); 368 (void)strncpy(we->we_utmp.out_name, ep->name, 369 sizeof(we->we_utmp.out_name) - 1); 370 we->we_utmp.out_time = htonl(ep->tv.tv_sec); 371 if (we >= wlast) 372 break; 373 we++; 374 } 375 utmpent = we - mywd.wd_we; 376 } 377 378 /* 379 * The test on utmpent looks silly---after all, if no one is 380 * logged on, why worry about efficiency?---but is useful on 381 * (e.g.) compute servers. 382 */ 383 if (utmpent && chdir(_PATH_DEV)) { 384 syslog(LOG_ERR, "chdir(%s): %m", _PATH_DEV); 385 exit(EXIT_FAILURE); 386 } 387 we = mywd.wd_we; 388 for (i = 0; i < utmpent; i++) { 389 if (stat(we->we_utmp.out_line, &stb) >= 0) 390 we->we_idle = htonl(now - stb.st_atime); 391 we++; 392 } 393 (void)getloadavg(avenrun, sizeof(avenrun)/sizeof(avenrun[0])); 394 for (i = 0; i < 3; i++) 395 mywd.wd_loadav[i] = htonl((u_long)(avenrun[i] * 100)); 396 cc = (char *)we - (char *)&mywd; 397 mywd.wd_sendtime = htonl(time(0)); 398 mywd.wd_vers = WHODVERSION; 399 mywd.wd_type = WHODTYPE_STATUS; 400 for (np = neighbors; np != NULL; np = np->n_next) 401 (void)sendto(s, (char *)&mywd, cc, 0, 402 np->n_addr, np->n_addrlen); 403 if (utmpent && chdir(_PATH_RWHODIR)) { 404 syslog(LOG_ERR, "chdir(%s): %m", _PATH_RWHODIR); 405 exit(EXIT_FAILURE); 406 } 407} 408 409static void 410getboottime(void) 411{ 412 int mib[2]; 413 size_t size; 414 struct timeval tm; 415 416 mib[0] = CTL_KERN; 417 mib[1] = KERN_BOOTTIME; 418 size = sizeof(tm); 419 if (sysctl(mib, 2, &tm, &size, NULL, 0) == -1) { 420 syslog(LOG_ERR, "cannot get boottime: %m"); 421 exit(EXIT_FAILURE); 422 } 423 mywd.wd_boottime = htonl(tm.tv_sec); 424} 425 426static void 427quit(const char *msg) 428{ 429 syslog(LOG_ERR, "%s", msg); 430 exit(EXIT_FAILURE); 431} 432 433#define ROUNDUP(a) \ 434 ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) 435#define ADVANCE(x, n) ((char *)(x) + ROUNDUP((n)->sa_len)) 436 437static void 438rt_xaddrs(void *cp, void *cplim, struct rt_addrinfo *rtinfo) 439{ 440 struct sockaddr *sa; 441 int i; 442 443 (void)memset(rtinfo->rti_info, 0, sizeof(rtinfo->rti_info)); 444 for (i = 0; (i < RTAX_MAX) && (cp < cplim); i++) { 445 if ((rtinfo->rti_addrs & (1 << i)) == 0) 446 continue; 447 rtinfo->rti_info[i] = sa = (struct sockaddr *)cp; 448 cp = ADVANCE(cp, sa); 449 } 450} 451 452/* 453 * Figure out device configuration and select 454 * networks which deserve status information. 455 */ 456static int 457configure(int s) 458{ 459 struct neighbor *np; 460 struct if_msghdr *ifm; 461 struct ifa_msghdr *ifam; 462 struct sockaddr_dl *sdl; 463 size_t needed; 464 int mib[6], flags = 0, len; 465 char *buf, *lim, *next; 466 struct rt_addrinfo info; 467 struct sockaddr_in dstaddr; 468 469 mib[0] = CTL_NET; 470 mib[1] = PF_ROUTE; 471 mib[2] = 0; 472 mib[3] = AF_INET; 473 mib[4] = NET_RT_IFLIST; 474 mib[5] = 0; 475 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 476 quit("route-sysctl-estimate"); 477 if ((buf = malloc(needed)) == NULL) 478 quit("malloc"); 479 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 480 quit("actual retrieval of interface table"); 481 lim = buf + needed; 482 483 sdl = NULL; /* XXX just to keep gcc -Wall happy */ 484 for (next = buf; next < lim; next += ifm->ifm_msglen) { 485 ifm = (struct if_msghdr *)next; 486 if (ifm->ifm_type == RTM_IFINFO) { 487 sdl = (struct sockaddr_dl *)(ifm + 1); 488 flags = ifm->ifm_flags; 489 continue; 490 } 491 if ((flags & IFF_UP) == 0 || 492 (flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0) 493 continue; 494 if (ifm->ifm_type != RTM_NEWADDR) 495 quit("out of sync parsing NET_RT_IFLIST"); 496 ifam = (struct ifa_msghdr *)ifm; 497 info.rti_addrs = ifam->ifam_addrs; 498 rt_xaddrs((ifam + 1), ifam->ifam_msglen + (char *)ifam, &info); 499 /* gag, wish we could get rid of Internet dependencies */ 500 if (info.rti_info[RTAX_BRD] == NULL || 501 info.rti_info[RTAX_BRD]->sa_family != AF_INET) 502 continue; 503 (void)memcpy(&dstaddr, info.rti_info[RTAX_BRD], 504 sizeof(dstaddr)); 505#define IPADDR_SA(x) ((struct sockaddr_in *)(x))->sin_addr.s_addr 506#define PORT_SA(x) ((struct sockaddr_in *)(x))->sin_port 507 PORT_SA(&dstaddr) = sp->s_port; 508 for (np = neighbors; np != NULL; np = np->n_next) 509 if (memcmp(sdl->sdl_data, np->n_name, 510 sdl->sdl_nlen) == 0 && 511 IPADDR_SA(np->n_addr) == IPADDR_SA(&dstaddr)) 512 break; 513 if (np != NULL) 514 continue; 515 len = sizeof(*np) + dstaddr.sin_len + sdl->sdl_nlen + 1; 516 np = (struct neighbor *)malloc(len); 517 if (np == NULL) 518 quit("malloc of neighbor structure"); 519 (void)memset(np, 0, len); 520 np->n_flags = flags; 521 np->n_addr = (struct sockaddr *)(np + 1); 522 np->n_addrlen = dstaddr.sin_len; 523 np->n_name = np->n_addrlen + (char *)np->n_addr; 524 np->n_next = neighbors; 525 neighbors = np; 526 (void)memcpy(np->n_addr, &dstaddr, np->n_addrlen); 527 (void)memcpy(np->n_name, sdl->sdl_data, sdl->sdl_nlen); 528 } 529 free(buf); 530 return (1); 531} 532 533#ifdef DEBUG 534static ssize_t 535Sendto(int s, const void *buf, size_t cc, int flags, const struct sockaddr *to, 536 socklen_t tolen) 537{ 538 struct whod *w = (struct whod *)buf; 539 struct whoent *we; 540 struct sockaddr_in *sasin = (struct sockaddr_in *)to; 541 542 printf("sendto %s.%d\n", inet_ntoa(sasin->sin_addr), 543 ntohs(sasin->sin_port)); 544 printf("hostname %s %s\n", w->wd_hostname, 545 interval(ntohl(w->wd_sendtime) - ntohl(w->wd_boottime), " up")); 546 printf("load %4.2f, %4.2f, %4.2f\n", 547 ntohl(w->wd_loadav[0]) / 100.0, ntohl(w->wd_loadav[1]) / 100.0, 548 ntohl(w->wd_loadav[2]) / 100.0); 549 cc -= WHDRSIZE; 550 for (we = w->wd_we, cc /= sizeof(struct whoent); cc > 0; cc--, we++) { 551 time_t t = ntohl(we->we_utmp.out_time); 552 printf("%-8.8s %s:%s %.12s", we->we_utmp.out_name, 553 w->wd_hostname, we->we_utmp.out_line, ctime(&t)+4); 554 we->we_idle = ntohl(we->we_idle) / 60; 555 if (we->we_idle) { 556 if (we->we_idle >= 100*60) 557 we->we_idle = 100*60 - 1; 558 if (we->we_idle >= 60) 559 printf(" %2d", we->we_idle / 60); 560 else 561 printf(" "); 562 printf(":%02d", we->we_idle % 60); 563 } 564 printf("\n"); 565 } 566 return (ssize_t)cc; 567} 568 569static char * 570interval(int time, const char *updown) 571{ 572 static char resbuf[32]; 573 int days, hours, minutes; 574 575 if (time < 0 || time > 3*30*24*60*60) { 576 (void)snprintf(resbuf, sizeof(resbuf), " %s ??:??", updown); 577 return (resbuf); 578 } 579 minutes = (time + 59) / 60; /* round to minutes */ 580 hours = minutes / 60; minutes %= 60; 581 days = hours / 24; hours %= 24; 582 if (days) 583 (void)snprintf(resbuf, sizeof(resbuf), "%s %2d+%02d:%02d", 584 updown, days, hours, minutes); 585 else 586 (void)snprintf(resbuf, sizeof(resbuf), "%s %2d:%02d", 587 updown, hours, minutes); 588 return resbuf; 589} 590#endif 591 592static void 593usage(void) 594{ 595 fprintf(stderr, "Usage: %s [-g <time>]", getprogname()); 596 exit(1); 597} 598