1/* $NetBSD: rwhod.c,v 1.38 2009/04/18 13:05:54 lukem 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\ 35 The Regents of the University of California. All rights reserved."); 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.38 2009/04/18 13:05:54 lukem 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#define CHECK_INTERVAL (3 * 60) 77 78/* Time interval limit; ruptime will think that we are down > than this */ 79#define MAX_INTERVAL (11 * 60) 80 81 82static char myname[MAXHOSTNAMELEN + 1]; 83 84/* 85 * We communicate with each neighbor in a list constructed at the time we're 86 * started up. Neighbors are currently directly connected via a hardware 87 * interface. 88 */ 89struct neighbor { 90 struct neighbor *n_next; 91 char *n_name; /* interface name */ 92 struct sockaddr *n_addr; /* who to send to */ 93 int n_addrlen; /* size of address */ 94 int n_flags; /* should forward?, interface flags */ 95}; 96 97static struct neighbor *neighbors; 98static struct whod mywd; 99static struct servent *sp; 100static volatile sig_atomic_t onsighup; 101 102#define WHDRSIZE (sizeof(mywd) - sizeof(mywd.wd_we)) 103 104static int configure(int); 105static void getboottime(void); 106static void send_host_information(int); 107static void sighup(int); 108static void handleread(int); 109__dead static void quit(const char *); 110static void rt_xaddrs(void *, void *, struct rt_addrinfo *); 111static int drop_privs(char *); 112static void usage(void) __dead; 113static int verify(const char *); 114#ifdef DEBUG 115static char *interval(int, const char *); 116static ssize_t Sendto(int, const void *, size_t, int, 117 const struct sockaddr *, socklen_t); 118#else 119#define Sendto sendto 120#endif 121 122int 123main(int argc, char *argv[]) 124{ 125 int s, ch; 126 int time_interval = 180; /* Default time (180 seconds) */ 127 char *cp, *ep; 128 socklen_t on = 1; 129 struct sockaddr_in sasin; 130 struct pollfd pfd[1]; 131 struct timeval delta, next, now; 132 char *newuser = NULL; 133 134 setprogname(argv[0]); 135 136 if (getuid()) 137 errx(EXIT_FAILURE, "not super user"); 138 139 while ((ch = getopt(argc, argv, "i:u:")) != -1) { 140 switch (ch) { 141 case 'i': 142 time_interval = (int)strtol(optarg, &ep, 10); 143 144 switch (*ep) { 145 case '\0': 146 break; 147 case 'm': 148 case 'M': 149 /* Time in minutes. */ 150 time_interval *= 60; 151 if (ep[1] == '\0') 152 break; 153 /*FALLTHROUGH*/ 154 default: 155 errx(1, "Invalid argument: `%s'", optarg); 156 } 157 158 if (time_interval <= 0) 159 errx(1, "Interval must be greater than 0"); 160 161 if (time_interval > MAX_INTERVAL) 162 errx(1, "Interval cannot be greater than" 163 " %d minutes", MAX_INTERVAL / 60); 164 break; 165 166 case 'u': 167 newuser = optarg; 168 break; 169 170 default: 171 usage(); 172 } 173 } 174 175 sp = getservbyname("who", "udp"); 176 if (sp == NULL) 177 errx(EXIT_FAILURE, "udp/who: unknown service"); 178#ifndef DEBUG 179 (void)daemon(1, 0); 180 (void)pidfile(NULL); 181#endif 182 if (chdir(_PATH_RWHODIR) < 0) 183 err(EXIT_FAILURE, "%s", _PATH_RWHODIR); 184 (void)signal(SIGHUP, sighup); 185 openlog(getprogname(), LOG_PID, LOG_DAEMON); 186 /* 187 * Establish host name as returned by system. 188 */ 189 if (gethostname(myname, sizeof(myname) - 1) < 0) { 190 syslog(LOG_ERR, "gethostname: %m"); 191 exit(EXIT_FAILURE); 192 } 193 myname[sizeof(myname) - 1] = '\0'; 194 if ((cp = strchr(myname, '.')) != NULL) 195 *cp = '\0'; 196 (void)strncpy(mywd.wd_hostname, myname, sizeof(mywd.wd_hostname) - 1); 197 getboottime(); 198 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 199 syslog(LOG_ERR, "socket: %m"); 200 exit(EXIT_FAILURE); 201 } 202 if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { 203 syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m"); 204 exit(EXIT_FAILURE); 205 } 206 (void)memset(&sasin, 0, sizeof(sasin)); 207 sasin.sin_family = AF_INET; 208 sasin.sin_port = sp->s_port; 209 if (bind(s, (struct sockaddr *)&sasin, sizeof(sasin)) < 0) { 210 syslog(LOG_ERR, "bind: %m"); 211 exit(EXIT_FAILURE); 212 } 213 if (!configure(s)) 214 exit(EXIT_FAILURE); 215 216 if (newuser) 217 if (!drop_privs(newuser)) 218 exit(EXIT_FAILURE); 219 220 send_host_information(s); 221 delta.tv_sec = time_interval; 222 delta.tv_usec = 0; 223 gettimeofday(&now, NULL); 224 timeradd(&now, &delta, &next); 225 226 pfd[0].fd = s; 227 pfd[0].events = POLLIN; 228 229 for (;;) { 230 int n; 231 232 n = poll(pfd, 1, 1000); 233 234 if (onsighup) { 235 onsighup = 0; 236 getboottime(); 237 } 238 239 if (n == 1) 240 handleread(s); 241 242 (void)gettimeofday(&now, NULL); 243 if (timercmp(&now, &next, >)) { 244 send_host_information(s); 245 timeradd(&now, &delta, &next); 246 } 247 } 248 249 /* NOTREACHED */ 250 return 0; 251} 252 253static void 254sighup(int signo __unused) 255{ 256 onsighup = 1; 257} 258 259static void 260handleread(int s) 261{ 262 struct sockaddr_in from; 263 struct stat st; 264 char path[64]; 265 struct whod wd; 266 int cc, whod; 267 socklen_t len = sizeof(from); 268 269 cc = recvfrom(s, (char *)&wd, sizeof(struct whod), 0, 270 (struct sockaddr *)&from, &len); 271 if (cc <= 0) { 272 if (cc < 0 && errno != EINTR) 273 syslog(LOG_WARNING, "recv: %m"); 274 return; 275 } 276 if (from.sin_port != sp->s_port) { 277 syslog(LOG_WARNING, "%d: bad from port", 278 ntohs(from.sin_port)); 279 return; 280 } 281 if (cc < (int)WHDRSIZE) { 282 syslog(LOG_WARNING, "Short packet from %s", 283 inet_ntoa(from.sin_addr)); 284 return; 285 } 286 287 if (wd.wd_vers != WHODVERSION) 288 return; 289 if (wd.wd_type != WHODTYPE_STATUS) 290 return; 291 /* 292 * Ensure null termination of the name within the packet. 293 * Otherwise we might overflow or read past the end. 294 */ 295 wd.wd_hostname[sizeof(wd.wd_hostname)-1] = 0; 296 if (!verify(wd.wd_hostname)) { 297 syslog(LOG_WARNING, "malformed host name from %s", 298 inet_ntoa(from.sin_addr)); 299 return; 300 } 301 (void)snprintf(path, sizeof(path), "whod.%s", wd.wd_hostname); 302 /* 303 * Rather than truncating and growing the file each time, 304 * use ftruncate if size is less than previous size. 305 */ 306 whod = open(path, O_WRONLY | O_CREAT, 0644); 307 if (whod < 0) { 308 syslog(LOG_WARNING, "%s: %m", path); 309 return; 310 } 311#if ENDIAN != BIG_ENDIAN 312 { 313 int i, n = (cc - WHDRSIZE) / sizeof(struct whoent); 314 struct whoent *we; 315 316 /* undo header byte swapping before writing to file */ 317 wd.wd_sendtime = ntohl(wd.wd_sendtime); 318 for (i = 0; i < 3; i++) 319 wd.wd_loadav[i] = ntohl(wd.wd_loadav[i]); 320 wd.wd_boottime = ntohl(wd.wd_boottime); 321 we = wd.wd_we; 322 for (i = 0; i < n; i++) { 323 we->we_idle = ntohl(we->we_idle); 324 we->we_utmp.out_time = 325 ntohl(we->we_utmp.out_time); 326 we++; 327 } 328 } 329#endif 330 wd.wd_recvtime = time(NULL); 331 (void)write(whod, (char *)&wd, cc); 332 if (fstat(whod, &st) < 0 || st.st_size > cc) 333 (void)ftruncate(whod, cc); 334 (void)close(whod); 335} 336 337/* 338 * Check out host name for unprintables 339 * and other funnies before allowing a file 340 * to be created. Sorry, but blanks aren't allowed. 341 */ 342static int 343verify(const char *name) 344{ 345 int size = 0; 346 347 while (*name) { 348 if (!isascii((unsigned char)*name) || 349 !(isalnum((unsigned char)*name) || 350 ispunct((unsigned char)*name))) 351 return 0; 352 name++, size++; 353 } 354 return size > 0; 355} 356 357static void 358send_host_information(int s) 359{ 360 struct neighbor *np; 361 struct whoent *we = mywd.wd_we, *wlast; 362 int i, cc, utmpent = 0; 363 struct stat stb; 364 double avenrun[3]; 365 time_t now; 366 static struct utmpentry *ohead = NULL; 367 struct utmpentry *ep; 368 static int count = 0; 369 370 now = time(NULL); 371 if (count % 10 == 0) 372 getboottime(); 373 count++; 374 375 (void)getutentries(NULL, &ep); 376 /* XXX probably should expose utmp mtime, check that instead */ 377 if (ep != ohead) { 378 wlast = &mywd.wd_we[1024 / sizeof(struct whoent) - 1]; 379 for (; ep; ep = ep->next) { 380 (void)strncpy(we->we_utmp.out_line, ep->line, 381 sizeof(we->we_utmp.out_line)); 382 (void)strncpy(we->we_utmp.out_name, ep->name, 383 sizeof(we->we_utmp.out_name)); 384 we->we_utmp.out_time = htonl(ep->tv.tv_sec); 385 if (we >= wlast) 386 break; 387 we++; 388 } 389 utmpent = we - mywd.wd_we; 390 } 391 392 /* 393 * The test on utmpent looks silly---after all, if no one is 394 * logged on, why worry about efficiency?---but is useful on 395 * (e.g.) compute servers. 396 */ 397 if (utmpent && chdir(_PATH_DEV)) { 398 syslog(LOG_ERR, "chdir(%s): %m", _PATH_DEV); 399 exit(EXIT_FAILURE); 400 } 401 we = mywd.wd_we; 402 for (i = 0; i < utmpent; i++) { 403 if (stat(we->we_utmp.out_line, &stb) >= 0) 404 we->we_idle = htonl(now - stb.st_atime); 405 we++; 406 } 407 (void)getloadavg(avenrun, sizeof(avenrun)/sizeof(avenrun[0])); 408 for (i = 0; i < 3; i++) 409 mywd.wd_loadav[i] = htonl((u_long)(avenrun[i] * 100)); 410 cc = (char *)we - (char *)&mywd; 411 mywd.wd_sendtime = htonl(time(0)); 412 mywd.wd_vers = WHODVERSION; 413 mywd.wd_type = WHODTYPE_STATUS; 414 for (np = neighbors; np != NULL; np = np->n_next) 415 (void)Sendto(s, (char *)&mywd, cc, 0, 416 np->n_addr, np->n_addrlen); 417 if (utmpent && chdir(_PATH_RWHODIR)) { 418 syslog(LOG_ERR, "chdir(%s): %m", _PATH_RWHODIR); 419 exit(EXIT_FAILURE); 420 } 421} 422 423static void 424getboottime(void) 425{ 426 int mib[2]; 427 size_t size; 428 struct timeval tm; 429 430 mib[0] = CTL_KERN; 431 mib[1] = KERN_BOOTTIME; 432 size = sizeof(tm); 433 if (sysctl(mib, 2, &tm, &size, NULL, 0) == -1) { 434 syslog(LOG_ERR, "cannot get boottime: %m"); 435 exit(EXIT_FAILURE); 436 } 437 mywd.wd_boottime = htonl(tm.tv_sec); 438} 439 440static void 441quit(const char *msg) 442{ 443 syslog(LOG_ERR, "%s", msg); 444 exit(EXIT_FAILURE); 445} 446 447#define ROUNDUP(a) \ 448 ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) 449#define ADVANCE(x, n) ((char *)(x) + ROUNDUP((n)->sa_len)) 450 451static void 452rt_xaddrs(void *cp, void *cplim, struct rt_addrinfo *rtinfo) 453{ 454 struct sockaddr *sa; 455 int i; 456 457 (void)memset(rtinfo->rti_info, 0, sizeof(rtinfo->rti_info)); 458 for (i = 0; (i < RTAX_MAX) && (cp < cplim); i++) { 459 if ((rtinfo->rti_addrs & (1 << i)) == 0) 460 continue; 461 rtinfo->rti_info[i] = sa = (struct sockaddr *)cp; 462 cp = ADVANCE(cp, sa); 463 } 464} 465 466/* 467 * Figure out device configuration and select 468 * networks which deserve status information. 469 */ 470static int 471configure(int s) 472{ 473 struct neighbor *np; 474 struct if_msghdr *ifm; 475 struct ifa_msghdr *ifam; 476 struct sockaddr_dl *sdl; 477 size_t needed; 478 int mib[6], flags = 0, len; 479 char *buf, *lim, *next; 480 struct rt_addrinfo info; 481 struct sockaddr_in dstaddr; 482 483 mib[0] = CTL_NET; 484 mib[1] = PF_ROUTE; 485 mib[2] = 0; 486 mib[3] = AF_INET; 487 mib[4] = NET_RT_IFLIST; 488 mib[5] = 0; 489 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 490 quit("route-sysctl-estimate"); 491 if ((buf = malloc(needed)) == NULL) 492 quit("malloc"); 493 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 494 quit("actual retrieval of interface table"); 495 lim = buf + needed; 496 497 sdl = NULL; /* XXX just to keep gcc -Wall happy */ 498 for (next = buf; next < lim; next += ifm->ifm_msglen) { 499 ifm = (struct if_msghdr *)next; 500 if (ifm->ifm_type == RTM_IFINFO) { 501 sdl = (struct sockaddr_dl *)(ifm + 1); 502 flags = ifm->ifm_flags; 503 continue; 504 } 505 if ((flags & IFF_UP) == 0 || 506 (flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0) 507 continue; 508 if (ifm->ifm_type != RTM_NEWADDR) 509 quit("out of sync parsing NET_RT_IFLIST"); 510 ifam = (struct ifa_msghdr *)ifm; 511 info.rti_addrs = ifam->ifam_addrs; 512 rt_xaddrs((ifam + 1), ifam->ifam_msglen + (char *)ifam, &info); 513 /* gag, wish we could get rid of Internet dependencies */ 514 if (info.rti_info[RTAX_BRD] == NULL || 515 info.rti_info[RTAX_BRD]->sa_family != AF_INET) 516 continue; 517 (void)memcpy(&dstaddr, info.rti_info[RTAX_BRD], 518 sizeof(dstaddr)); 519#define IPADDR_SA(x) ((struct sockaddr_in *)(x))->sin_addr.s_addr 520#define PORT_SA(x) ((struct sockaddr_in *)(x))->sin_port 521 PORT_SA(&dstaddr) = sp->s_port; 522 for (np = neighbors; np != NULL; np = np->n_next) 523 if (memcmp(sdl->sdl_data, np->n_name, 524 sdl->sdl_nlen) == 0 && 525 IPADDR_SA(np->n_addr) == IPADDR_SA(&dstaddr)) 526 break; 527 if (np != NULL) 528 continue; 529 len = sizeof(*np) + dstaddr.sin_len + sdl->sdl_nlen + 1; 530 np = (struct neighbor *)malloc(len); 531 if (np == NULL) 532 quit("malloc of neighbor structure"); 533 (void)memset(np, 0, len); 534 np->n_flags = flags; 535 np->n_addr = (struct sockaddr *)(np + 1); 536 np->n_addrlen = dstaddr.sin_len; 537 np->n_name = np->n_addrlen + (char *)np->n_addr; 538 np->n_next = neighbors; 539 neighbors = np; 540 (void)memcpy(np->n_addr, &dstaddr, np->n_addrlen); 541 (void)memcpy(np->n_name, sdl->sdl_data, sdl->sdl_nlen); 542 } 543 free(buf); 544 return (1); 545} 546 547#ifdef DEBUG 548static ssize_t 549Sendto(int s, const void *buf, size_t cc, int flags, const struct sockaddr *to, 550 socklen_t tolen) 551{ 552 struct whod *w = (struct whod *)buf; 553 struct whoent *we; 554 struct sockaddr_in *sasin = (struct sockaddr_in *)to; 555 ssize_t ret; 556 557 ret = sendto(s, buf, cc, flags, to, tolen); 558 559 printf("sendto %s.%d\n", inet_ntoa(sasin->sin_addr), 560 ntohs(sasin->sin_port)); 561 printf("hostname %s %s\n", w->wd_hostname, 562 interval(ntohl(w->wd_sendtime) - ntohl(w->wd_boottime), " up")); 563 printf("load %4.2f, %4.2f, %4.2f\n", 564 ntohl(w->wd_loadav[0]) / 100.0, ntohl(w->wd_loadav[1]) / 100.0, 565 ntohl(w->wd_loadav[2]) / 100.0); 566 cc -= WHDRSIZE; 567 for (we = w->wd_we, cc /= sizeof(struct whoent); cc > 0; cc--, we++) { 568 time_t t = ntohl(we->we_utmp.out_time); 569 printf("%-8.8s %s:%s %.12s", we->we_utmp.out_name, 570 w->wd_hostname, we->we_utmp.out_line, ctime(&t)+4); 571 we->we_idle = ntohl(we->we_idle) / 60; 572 if (we->we_idle) { 573 if (we->we_idle >= 100*60) 574 we->we_idle = 100*60 - 1; 575 if (we->we_idle >= 60) 576 printf(" %2d", we->we_idle / 60); 577 else 578 printf(" "); 579 printf(":%02d", we->we_idle % 60); 580 } 581 printf("\n"); 582 } 583 return ret; 584} 585 586static char * 587interval(int time, const char *updown) 588{ 589 static char resbuf[32]; 590 int days, hours, minutes; 591 592 if (time < 0 || time > 3*30*24*60*60) { 593 (void)snprintf(resbuf, sizeof(resbuf), " %s ??:??", updown); 594 return (resbuf); 595 } 596 minutes = (time + 59) / 60; /* round to minutes */ 597 hours = minutes / 60; minutes %= 60; 598 days = hours / 24; hours %= 24; 599 if (days) 600 (void)snprintf(resbuf, sizeof(resbuf), "%s %2d+%02d:%02d", 601 updown, days, hours, minutes); 602 else 603 (void)snprintf(resbuf, sizeof(resbuf), "%s %2d:%02d", 604 updown, hours, minutes); 605 return resbuf; 606} 607#endif 608 609static int 610drop_privs(char *newuser) 611{ 612 struct passwd *pw; 613 gid_t gidset[1]; 614 615 pw = getpwnam(newuser); 616 if (pw == NULL) { 617 syslog(LOG_ERR, "no user %.100s", newuser); 618 return 0; 619 } 620 621 endpwent(); 622 623 gidset[0] = pw->pw_gid; 624 if (setgroups(1, gidset) == -1) { 625 syslog(LOG_ERR, "setgroups: %m"); 626 return 0; 627 } 628 629 if (setgid(pw->pw_gid) == -1) { 630 syslog(LOG_ERR, "setgid: %m"); 631 return 0; 632 } 633 634 if (setuid(pw->pw_uid) == -1) { 635 syslog(LOG_ERR, "setuid: %m"); 636 return 0; 637 } 638 639 return 1; 640} 641 642static void 643usage(void) 644{ 645 (void)fprintf(stderr, "Usage: %s [-i interval] [-u user]\n", getprogname()); 646 exit(EXIT_FAILURE); 647} 648