1 /* $NetBSD: statd.c,v 1.31 2017/06/03 14:44:12 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1995 5 * A.R. Gordon (andrew.gordon@net-tel.co.uk). 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. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed for the FreeBSD project 18 * This product includes software developed by Christos Zoulas. 19 * 4. Neither the name of the author nor the names of any co-contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 */ 36 37 #include <sys/cdefs.h> 38 #ifndef lint 39 __RCSID("$NetBSD: statd.c,v 1.31 2017/06/03 14:44:12 christos Exp $"); 40 #endif 41 42 /* main() function for status monitor daemon. Some of the code in this */ 43 /* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */ 44 /* The actual program logic is in the file procs.c */ 45 46 #include <sys/param.h> 47 #include <sys/wait.h> 48 49 #include <err.h> 50 #include <ctype.h> 51 #include <errno.h> 52 #include <fcntl.h> 53 #include <signal.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <syslog.h> 58 #include <unistd.h> 59 #include <util.h> 60 #include <db.h> 61 #include <netconfig.h> 62 63 #include <rpc/rpc.h> 64 65 #include "statd.h" 66 67 struct sigaction sa; 68 int debug = 0; /* Controls syslog() for debug msgs */ 69 int _rpcsvcdirty = 0; /* XXX ??? */ 70 static DB *db; /* Database file */ 71 72 Header status_info; 73 74 static char undefdata[] = "\0\1\2\3\4\5\6\7"; 75 static DBT undefkey = { 76 undefdata, 77 sizeof(undefdata) 78 }; 79 80 81 /* statd.c */ 82 static int walk_one __P((int (*fun )__P ((DBT *, HostInfo *, void *)), DBT *, DBT *, void *)); 83 static int walk_db __P((int (*fun )__P ((DBT *, HostInfo *, void *)), void *)); 84 static int reset_host __P((DBT *, HostInfo *, void *)); 85 static int check_work __P((DBT *, HostInfo *, void *)); 86 static int unmon_host __P((DBT *, HostInfo *, void *)); 87 static int notify_one __P((DBT *, HostInfo *, void *)); 88 static void init_file __P((const char *)); 89 static int notify_one_host __P((const char *)); 90 static void die __P((int)) __dead; 91 92 int main __P((int, char **)); 93 94 int 95 main(argc, argv) 96 int argc; 97 char **argv; 98 { 99 int ch; 100 struct sigaction nsa; 101 int maxrec = RPC_MAXDATASIZE; 102 103 while ((ch = getopt(argc, argv, "d")) != (-1)) { 104 switch (ch) { 105 case 'd': 106 debug = 1; 107 break; 108 default: 109 case '?': 110 (void)fprintf(stderr, "usage: %s [-d]\n", 111 getprogname()); 112 exit(1); 113 /* NOTREACHED */ 114 } 115 } 116 (void)rpcb_unset(SM_PROG, SM_VERS, NULL); 117 118 rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); 119 120 if (!svc_create(sm_prog_1, SM_PROG, SM_VERS, "udp")) { 121 errx(EXIT_FAILURE, "cannot create udp service."); 122 /* NOTREACHED */ 123 } 124 if (!svc_create(sm_prog_1, SM_PROG, SM_VERS, "tcp")) { 125 errx(EXIT_FAILURE, "cannot create udp service."); 126 /* NOTREACHED */ 127 } 128 129 init_file("/var/db/statd.status"); 130 131 /* 132 * Note that it is NOT sensible to run this program from inetd - the 133 * protocol assumes that it will run immediately at boot time. 134 */ 135 if (!debug) 136 daemon(0, 0); 137 138 sigemptyset(&nsa.sa_mask); 139 nsa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT; 140 nsa.sa_handler = SIG_IGN; 141 (void)sigaction(SIGCHLD, &nsa, NULL); 142 143 pidfile(NULL); 144 openlog("rpc.statd", 0, LOG_DAEMON); 145 if (debug) 146 syslog(LOG_INFO, "Starting - debug enabled"); 147 else 148 syslog(LOG_INFO, "Starting"); 149 150 sa.sa_handler = die; 151 sa.sa_flags = 0; 152 sigemptyset(&sa.sa_mask); 153 (void)sigaction(SIGTERM, &sa, NULL); 154 (void)sigaction(SIGQUIT, &sa, NULL); 155 (void)sigaction(SIGHUP, &sa, NULL); 156 (void)sigaction(SIGINT, &sa, NULL); 157 158 sa.sa_handler = SIG_IGN; 159 sa.sa_flags = SA_RESTART; 160 sigemptyset(&sa.sa_mask); 161 sigaddset(&sa.sa_mask, SIGALRM); 162 163 /* Initialisation now complete - start operating */ 164 165 /* Notify hosts that need it */ 166 notify_handler(0); 167 168 while (1) 169 svc_run(); /* Should never return */ 170 die(0); 171 } 172 173 /* notify_handler ---------------------------------------------------------- */ 174 /* 175 * Purpose: Catch SIGALRM and collect process status 176 * Returns: Nothing. 177 * Notes: No special action required, other than to collect the 178 * process status and hence allow the child to die: 179 * we only use child processes for asynchronous transmission 180 * of SM_NOTIFY to other systems, so it is normal for the 181 * children to exit when they have done their work. 182 */ 183 void 184 notify_handler(sig) 185 int sig; 186 { 187 time_t now; 188 189 NO_ALARM; 190 sa.sa_handler = SIG_IGN; 191 (void)sigaction(SIGALRM, &sa, NULL); 192 193 now = time(NULL); 194 195 (void) walk_db(notify_one, &now); 196 197 if (walk_db(check_work, &now) == 0) { 198 /* 199 * No more work to be done. 200 */ 201 CLR_ALARM; 202 return; 203 } 204 sync_file(); 205 ALARM; 206 alarm(5); 207 } 208 209 /* sync_file --------------------------------------------------------------- */ 210 /* 211 * Purpose: Packaged call of msync() to flush changes to mmap()ed file 212 * Returns: Nothing. Errors to syslog. 213 */ 214 void 215 sync_file() 216 { 217 DBT data; 218 219 data.data = &status_info; 220 data.size = sizeof(status_info); 221 switch ((*db->put)(db, &undefkey, &data, 0)) { 222 case 0: 223 return; 224 case -1: 225 goto bad; 226 default: 227 abort(); 228 } 229 if ((*db->sync)(db, 0) == -1) { 230 bad: 231 syslog(LOG_ERR, "database corrupted %m"); 232 die(1); 233 } 234 } 235 236 /* change_host -------------------------------------------------------------- */ 237 /* 238 * Purpose: Update/Create an entry for host 239 * Returns: Nothing 240 * Notes: 241 * 242 */ 243 void 244 change_host(hostnamep, hp) 245 char *hostnamep; 246 HostInfo *hp; 247 { 248 DBT key, data; 249 char *ptr; 250 char hostname[MAXHOSTNAMELEN + 1]; 251 HostInfo h; 252 253 strncpy(hostname, hostnamep, MAXHOSTNAMELEN + 1); 254 h = *hp; 255 256 for (ptr = hostname; *ptr; ptr++) 257 if (isupper((unsigned char) *ptr)) 258 *ptr = tolower((unsigned char) *ptr); 259 260 key.data = hostname; 261 key.size = ptr - hostname + 1; 262 data.data = &h; 263 data.size = sizeof(h); 264 265 switch ((*db->put)(db, &key, &data, 0)) { 266 case -1: 267 syslog(LOG_ERR, "database corrupted %m"); 268 die(1); 269 case 0: 270 return; 271 default: 272 abort(); 273 } 274 } 275 276 277 /* find_host -------------------------------------------------------------- */ 278 /* 279 * Purpose: Find the entry in the status file for a given host 280 * Returns: Copy of entry in hd, or NULL 281 * Notes: 282 * 283 */ 284 HostInfo * 285 find_host(hostname, hp) 286 char *hostname; 287 HostInfo *hp; 288 { 289 DBT key, data; 290 char *ptr; 291 292 for (ptr = hostname; *ptr; ptr++) 293 if (isupper((unsigned char) *ptr)) 294 *ptr = tolower((unsigned char) *ptr); 295 296 key.data = hostname; 297 key.size = ptr - hostname + 1; 298 switch ((*db->get)(db, &key, &data, 0)) { 299 case 0: 300 if (data.size != sizeof(*hp)) 301 goto bad; 302 return memcpy(hp, data.data, sizeof(*hp)); 303 case 1: 304 return NULL; 305 case -1: 306 goto bad; 307 default: 308 abort(); 309 } 310 311 bad: 312 syslog(LOG_ERR, "Database corrupted %m"); 313 return NULL; 314 } 315 316 /* walk_one ------------------------------------------------------------- */ 317 /* 318 * Purpose: Call the given function if the element is valid 319 * Returns: Nothing - exits on error 320 * Notes: 321 */ 322 static int 323 walk_one(fun, key, data, ptr) 324 int (*fun) __P((DBT *, HostInfo *, void *)); 325 DBT *key, *data; 326 void *ptr; 327 { 328 HostInfo h; 329 if (key->size == undefkey.size && 330 memcmp(key->data, undefkey.data, key->size) == 0) 331 return 0; 332 if (data->size != sizeof(HostInfo)) { 333 syslog(LOG_ERR, "Bad data in database"); 334 die(1); 335 } 336 memcpy(&h, data->data, sizeof(h)); 337 return (*fun)(key, &h, ptr); 338 } 339 340 /* walk_db -------------------------------------------------------------- */ 341 /* 342 * Purpose: Iterate over all elements calling the given function 343 * Returns: -1 if function failed, 0 on success 344 * Notes: 345 */ 346 static int 347 walk_db(fun, ptr) 348 int (*fun) __P((DBT *, HostInfo *, void *)); 349 void *ptr; 350 { 351 DBT key, data; 352 353 switch ((*db->seq)(db, &key, &data, R_FIRST)) { 354 case -1: 355 goto bad; 356 case 1: 357 /* We should have at least the magic entry at this point */ 358 abort(); 359 case 0: 360 if (walk_one(fun, &key, &data, ptr) == -1) 361 return -1; 362 break; 363 default: 364 abort(); 365 } 366 367 368 for (;;) 369 switch ((*db->seq)(db, &key, &data, R_NEXT)) { 370 case -1: 371 goto bad; 372 case 0: 373 if (walk_one(fun, &key, &data, ptr) == -1) 374 return -1; 375 break; 376 case 1: 377 return 0; 378 default: 379 abort(); 380 } 381 bad: 382 syslog(LOG_ERR, "Corrupted database %m"); 383 die(1); 384 } 385 386 /* reset_host ------------------------------------------------------------ */ 387 /* 388 * Purpose: Clean up existing hosts in file. 389 * Returns: Always success 0. 390 * Notes: Clean-up of existing file - monitored hosts will have a 391 * pointer to a list of clients, which refers to memory in 392 * the previous incarnation of the program and so are 393 * meaningless now. These pointers are zeroed and the fact 394 * that the host was previously monitored is recorded by 395 * setting the notifyReqd flag, which will in due course 396 * cause a SM_NOTIFY to be sent. 397 * 398 * Note that if we crash twice in quick succession, some hosts 399 * may already have notifyReqd set, where we didn't manage to 400 * notify them before the second crash occurred. 401 */ 402 static int 403 reset_host(key, hi, ptr) 404 DBT *key; 405 HostInfo *hi; 406 void *ptr; 407 { 408 409 if (hi->monList) { 410 hi->notifyReqd = *(time_t *) ptr; 411 hi->attempts = 0; 412 hi->monList = NULL; 413 change_host((char *)key->data, hi); 414 } 415 return 0; 416 } 417 418 /* check_work ------------------------------------------------------------ */ 419 /* 420 * Purpose: Check if there is work to be done. 421 * Returns: 0 if there is no work to be done -1 if there is. 422 * Notes: 423 */ 424 static int 425 check_work(key, hi, ptr) 426 DBT *key; 427 HostInfo *hi; 428 void *ptr; 429 { 430 return hi->notifyReqd ? -1 : 0; 431 } 432 433 /* unmon_host ------------------------------------------------------------ */ 434 /* 435 * Purpose: Unmonitor a host 436 * Returns: 0 437 * Notes: 438 */ 439 static int 440 unmon_host(key, hi, ptr) 441 DBT *key; 442 HostInfo *hi; 443 void *ptr; 444 { 445 char *name = key->data; 446 447 if (do_unmon(name, hi, ptr)) 448 change_host(name, hi); 449 return 0; 450 } 451 452 /* notify_one ------------------------------------------------------------ */ 453 /* 454 * Purpose: Notify one host. 455 * Returns: 0 if success -1 on failure 456 * Notes: 457 */ 458 static int 459 notify_one(key, hi, ptr) 460 DBT *key; 461 HostInfo *hi; 462 void *ptr; 463 { 464 time_t now = *(time_t *) ptr; 465 char *name = key->data; 466 int error; 467 468 if (hi->notifyReqd == 0 || hi->notifyReqd > now) 469 return 0; 470 471 /* 472 * If one of the initial attempts fails, we wait 473 * for a while and have another go. This is necessary 474 * because when we have crashed, (eg. a power outage) 475 * it is quite possible that we won't be able to 476 * contact all monitored hosts immediately on restart, 477 * either because they crashed too and take longer 478 * to come up (in which case the notification isn't 479 * really required), or more importantly if some 480 * router etc. needed to reach the monitored host 481 * has not come back up yet. In this case, we will 482 * be a bit late in re-establishing locks (after the 483 * grace period) but that is the best we can do. We 484 * try 10 times at 5 sec intervals, 10 more times at 485 * 1 minute intervals, then 24 more times at hourly 486 * intervals, finally giving up altogether if the 487 * host hasn't come back to life after 24 hours. 488 */ 489 if (notify_one_host(name) || hi->attempts++ >= 44) { 490 error = 0; 491 hi->notifyReqd = 0; 492 hi->attempts = 0; 493 } else { 494 error = -1; 495 if (hi->attempts < 10) 496 hi->notifyReqd += 5; 497 else if (hi->attempts < 20) 498 hi->notifyReqd += 60; 499 else 500 hi->notifyReqd += 60 * 60; 501 } 502 change_host(name, hi); 503 return error; 504 } 505 506 /* init_file -------------------------------------------------------------- */ 507 /* 508 * Purpose: Open file, create if necessary, initialise it. 509 * Returns: Nothing - exits on error 510 * Notes: Called before process becomes daemon, hence logs to 511 * stderr rather than syslog. 512 * Opens the file, then mmap()s it for ease of access. 513 * Also performs initial clean-up of the file, zeroing 514 * monitor list pointers, setting the notifyReqd flag in 515 * all hosts that had a monitor list, and incrementing 516 * the state number to the next even value. 517 */ 518 static void 519 init_file(filename) 520 const char *filename; 521 { 522 DBT data; 523 524 db = dbopen(filename, O_RDWR|O_CREAT|O_NDELAY|O_EXLOCK, 0644, DB_HASH, 525 NULL); 526 if (db == NULL) 527 err(EXIT_FAILURE, "Cannot open `%s'", filename); 528 529 switch ((*db->get)(db, &undefkey, &data, 0)) { 530 case 1: 531 /* New database */ 532 (void)memset(&status_info, 0, sizeof(status_info)); 533 sync_file(); 534 return; 535 536 case -1: 537 err(EXIT_FAILURE, "error accessing database (%s)", strerror(errno)); 538 case 0: 539 /* Existing database */ 540 if (data.size != sizeof(status_info)) 541 errx(EXIT_FAILURE, "database corrupted %lu != %lu", 542 (u_long)data.size, (u_long)sizeof(status_info)); 543 memcpy(&status_info, data.data, data.size); 544 break; 545 default: 546 abort(); 547 } 548 549 reset_database(); 550 return; 551 } 552 553 /* reset_database --------------------------------------------------------- */ 554 /* 555 * Purpose: Clears the statd database 556 * Returns: Nothing 557 * Notes: If this is not called on reset, it will leak memory. 558 */ 559 void 560 reset_database() 561 { 562 time_t now = time(NULL); 563 walk_db(reset_host, &now); 564 565 /* Select the next higher even number for the state counter */ 566 status_info.ourState = 567 (status_info.ourState + 2) & 0xfffffffe; 568 status_info.ourState++; /* XXX - ??? */ 569 sync_file(); 570 } 571 572 /* unmon_hosts --------------------------------------------------------- */ 573 /* 574 * Purpose: Unmonitor all the hosts 575 * Returns: Nothing 576 * Notes: 577 */ 578 void 579 unmon_hosts() 580 { 581 time_t now = time(NULL); 582 walk_db(unmon_host, &now); 583 sync_file(); 584 } 585 586 static int 587 notify_one_host(hostname) 588 const char *hostname; 589 { 590 struct timeval timeout = {20, 0}; /* 20 secs timeout */ 591 CLIENT *cli; 592 char dummy; 593 stat_chge arg; 594 char our_hostname[MAXHOSTNAMELEN + 1]; 595 596 gethostname(our_hostname, sizeof(our_hostname)); 597 our_hostname[sizeof(our_hostname) - 1] = '\0'; 598 arg.mon_name = our_hostname; 599 arg.state = status_info.ourState; 600 601 if (debug) 602 syslog(LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", 603 hostname, our_hostname); 604 605 cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp"); 606 if (!cli) { 607 syslog(LOG_ERR, "Failed to contact host %s%s", hostname, 608 clnt_spcreateerror("")); 609 return (FALSE); 610 } 611 if (clnt_call(cli, SM_NOTIFY, xdr_stat_chge, &arg, xdr_void, 612 &dummy, timeout) != RPC_SUCCESS) { 613 syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", 614 hostname); 615 clnt_destroy(cli); 616 return (FALSE); 617 } 618 clnt_destroy(cli); 619 return (TRUE); 620 } 621 622 623 static void 624 die(n) 625 int n; 626 { 627 (*db->close)(db); 628 exit(n); 629 } 630