1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * bootlog() - error notification and progress reporting for 31 * WAN boot components 32 */ 33 34 #include <sys/varargs.h> 35 #include <sys/types.h> 36 #include <sys/strlog.h> 37 #include <sys/wanboot_impl.h> 38 #include <errno.h> 39 #include <time.h> 40 #include <boot_http.h> 41 #include <stdio.h> 42 #include <parseURL.h> 43 #include <bootlog.h> 44 #include <strings.h> 45 #include <stdlib.h> 46 #include <unistd.h> 47 #include <netdb.h> 48 #include <libintl.h> 49 #include <netboot_paths.h> 50 #include <wanboot_conf.h> 51 #include <bootinfo.h> 52 #ifdef _BOOT 53 #include <sys/bootdebug.h> 54 #endif 55 56 static struct code pri_names[] = { 57 "panic", BOOTLOG_EMERG, 58 "alert", BOOTLOG_ALERT, 59 "crit", BOOTLOG_CRIT, 60 "warn", BOOTLOG_WARNING, 61 "info", BOOTLOG_INFO, 62 "debug", BOOTLOG_DEBUG, 63 "verbose", BOOTLOG_VERBOSE, 64 "progress", BOOTLOG_PROGRESS, 65 "none", NOPRI, 66 NULL, -1 67 }; 68 69 typedef enum { 70 BL_NO_TRANSPORT, 71 BL_LOCAL_FILE, 72 BL_CONSOLE, 73 BL_HTTP, 74 BL_HTTPS 75 } bl_transport_t; 76 77 typedef struct list_entry { 78 char message[BOOTLOG_QS_MAX]; 79 struct list_entry *flink; 80 } list; 81 82 #define BOOTLOG_RING_NELEM 512 83 84 static struct ringbuffer_t { 85 int w_ptr; 86 int r_ptr; 87 list entries[BOOTLOG_RING_NELEM]; 88 } ringbuffer; 89 90 static FILE *bl_filehandle = NULL; 91 static http_handle_t bl_httphandle = NULL; 92 static url_t bl_url; 93 static bl_transport_t bl_transport = BL_NO_TRANSPORT; 94 95 static bl_transport_t openbootlog(void); 96 static boolean_t setup_con(http_handle_t, boolean_t, boolean_t); 97 static char *url_encode(const char *); 98 static boolean_t sendmessage(bl_transport_t, char *, const char *, 99 bootlog_severity_t, int); 100 static int ptr_incr(int ptr); 101 static int ptr_decr(int ptr); 102 static void rb_init(struct ringbuffer_t *); 103 static void rb_write(struct ringbuffer_t *, const char *); 104 static int rb_read(struct ringbuffer_t *, char *); 105 106 /* 107 * Return a string representing the current time; not thread-safe. 108 */ 109 static const char * 110 gettime(void) 111 { 112 static char timebuf[sizeof ("Tue Jan 19 03:14:07 2038\n")]; 113 time_t curtime; 114 115 if (time(&curtime) == 0) 116 return ("<time unavailable>"); 117 118 (void) strlcpy(timebuf, ctime(&curtime), sizeof (timebuf)); 119 timebuf[19] = '\0'; /* truncate before "2038" above */ 120 return (timebuf); 121 } 122 123 /* 124 * bootlog_common() - Common routine used by bootlog() and 125 * bootlog_internal() to write a message comprising a message 126 * header and a message body to the appropriate transport. 127 * The message header comprises an ident string and a message 128 * severity. 129 */ 130 static void 131 bootlog_common(const char *ident, bootlog_severity_t severity, char *message) 132 { 133 bl_transport_t entry_transport; 134 static int blrecurs; 135 static int blretry; 136 137 /* 138 * This function may be called recursively because the HTTP code 139 * is a bootlog consumer. The blrecurs variable is used to determine 140 * whether or not the invocation is recursive. 141 */ 142 blrecurs++; 143 entry_transport = bl_transport; 144 145 /* 146 * If this is the first bootlog call then setup the transport. 147 * We only do this in a non-recursive invocation as openbootlog() 148 * results in a recursive call for a HTTP or HTTPS transport. 149 */ 150 if (bl_transport == BL_NO_TRANSPORT && blrecurs == 1) { 151 rb_init(&ringbuffer); 152 bl_transport = openbootlog(); 153 } 154 155 /* 156 * If we're not there already, try to move up a level. 157 * This is necessary because our consumer may have begun 158 * logging before it had enough information to initialize 159 * its HTTP or HTTPS transport. We've arbitrarily decided 160 * that we'll only check to see if we should move up, on 161 * every third (blretry) non-recursive invocation. 162 */ 163 if (blrecurs == 1 && 164 !(bl_transport == BL_HTTPS || bl_transport == BL_HTTP)) { 165 if (blretry > 3) { 166 bl_transport = openbootlog(); 167 blretry = 0; 168 } else 169 blretry++; 170 } 171 172 if (entry_transport != bl_transport) { 173 switch (bl_transport) { 174 175 case BL_CONSOLE: 176 (void) printf( 177 "%s wanboot info: WAN boot messages->console\n", 178 gettime()); 179 break; 180 181 case BL_HTTP: 182 case BL_HTTPS: 183 (void) printf( 184 "%s wanboot info: WAN boot messages->%s:%u\n", 185 gettime(), bl_url.hport.hostname, 186 bl_url.hport.port); 187 break; 188 189 default: 190 break; 191 } 192 } 193 194 /* 195 * Failed attempts and recursively generated log messages are 196 * sent to the fallback transport. 197 */ 198 if (blrecurs > 1 || !sendmessage(bl_transport, message, ident, 199 severity, 0)) { 200 /* 201 * Fallback to a log file if one exists, or the console 202 * as a last resort. Note that bl_filehandle will always 203 * be NULL in standalone. 204 */ 205 (void) sendmessage(bl_filehandle != NULL ? BL_LOCAL_FILE : 206 BL_CONSOLE, message, ident, severity, 1); 207 } 208 blrecurs--; 209 } 210 211 /* 212 * bootlog() - the exposed interface for logging boot messages. 213 */ 214 /* PRINTFLIKE3 */ 215 void 216 bootlog(const char *ident, bootlog_severity_t severity, char *fmt, ...) 217 { 218 char message[BOOTLOG_MSG_MAX_LEN]; 219 va_list adx; 220 221 va_start(adx, fmt); 222 (void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN, fmt, adx); 223 va_end(adx); 224 225 bootlog_common(ident, severity, message); 226 } 227 228 /* 229 * libbootlog() - an internal interface for logging boot 230 * messages. 231 */ 232 /* PRINTFLIKE2 */ 233 void 234 libbootlog(bootlog_severity_t severity, char *fmt, ...) 235 { 236 char message[BOOTLOG_MSG_MAX_LEN]; 237 va_list adx; 238 239 va_start(adx, fmt); 240 (void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN, 241 dgettext(TEXT_DOMAIN, fmt), adx); 242 va_end(adx); 243 244 bootlog_common("libwanboot", severity, message); 245 } 246 247 static boolean_t 248 send_http(void) 249 { 250 http_respinfo_t *resp = NULL; 251 char buffer[BOOTLOG_MAX_URL + (BOOTLOG_QS_MAX * 3)]; 252 char ringmessage[BOOTLOG_QS_MAX]; 253 char *lenstr; 254 size_t length; 255 int retries; 256 257 while ((rb_read(&ringbuffer, ringmessage) != -1)) { 258 (void) snprintf(buffer, sizeof (buffer), "%s?%s", 259 bl_url.abspath, url_encode(ringmessage)); 260 261 for (retries = 0; retries < BOOTLOG_CONN_RETRIES; retries++) { 262 if (retries > 0) { 263 (void) http_srv_disconnect(bl_httphandle); 264 if (http_srv_connect(bl_httphandle) != 0) 265 continue; 266 } 267 268 if (http_get_request(bl_httphandle, buffer) != 0 || 269 http_process_headers(bl_httphandle, &resp) != 0) 270 continue; 271 272 if (resp->code != 200) { 273 http_free_respinfo(resp); 274 continue; 275 } 276 277 http_free_respinfo(resp); 278 lenstr = http_get_header_value(bl_httphandle, 279 "Content-Length"); 280 length = strtol(lenstr, NULL, 10); 281 if (http_read_body(bl_httphandle, buffer, length) > 0) 282 break; 283 } 284 285 /* 286 * The attempt to log the message failed. Back the 287 * read pointer up so that we'll try to log it again 288 * later. 289 */ 290 if (retries == BOOTLOG_CONN_RETRIES) { 291 ringbuffer.r_ptr = ptr_decr(ringbuffer.r_ptr); 292 return (B_FALSE); 293 } 294 } 295 296 return (B_TRUE); 297 } 298 299 static boolean_t 300 sendmessage(bl_transport_t transport, char *message, const char *ident, 301 bootlog_severity_t severity, int failure) 302 { 303 static char *progtype = NULL; 304 char ringmessage[BOOTLOG_QS_MAX]; 305 char hostname[MAXHOSTNAMELEN]; 306 uint32_t msgid; 307 boolean_t ret; 308 int i; 309 310 /* 311 * In standalone, only log VERBOSE and DEBUG messages if the 312 * corresponding flag (-V or -d) has been passed to boot. 313 * 314 * Note that some bootlog() consumers impose additional constraints on 315 * printing these messages -- for instance, http_set_verbose() must be 316 * used before the HTTP code will call bootlog() with BOOTLOG_VERBOSE 317 * messages. 318 */ 319 #ifdef _BOOT 320 if (severity == BOOTLOG_DEBUG && !(boothowto & RB_DEBUG)) 321 return (B_TRUE); 322 if (severity == BOOTLOG_VERBOSE && !verbosemode) 323 return (B_TRUE); 324 #endif 325 326 for (i = 0; pri_names[i].c_val != NOPRI; i++) { 327 if (severity == pri_names[i].c_val) 328 break; 329 } 330 331 /* 332 * VERBOSE and DEBUG messages always go to the console 333 */ 334 if (transport != BL_CONSOLE && 335 (severity == BOOTLOG_DEBUG || severity == BOOTLOG_VERBOSE)) { 336 (void) printf("%s %s %s: %s\n", gettime(), ident, 337 pri_names[i].c_name, message); 338 } 339 340 STRLOG_MAKE_MSGID(message, msgid); 341 (void) gethostname(hostname, sizeof (hostname)); 342 343 /* 344 * Note that in this case, "<time>" is a placeholder that will be used 345 * to fill in the actual time on the remote end. 346 */ 347 (void) snprintf(ringmessage, sizeof (ringmessage), 348 "<time> %s %s: [ID %u user.%s] %s", hostname, ident, msgid, 349 pri_names[i].c_name, message); 350 351 /* 352 * Prevent duplicate messages from being inserted into 353 * the ring buffer. 354 */ 355 if (failure == 0) { 356 rb_write(&ringbuffer, ringmessage); 357 } 358 359 switch (transport) { 360 case BL_CONSOLE: 361 /* 362 * PROGRESS messages update in-place on the console, as long 363 * as they are of the same 'progress type' (see below) -- 364 * if not, reset the progress information. 365 */ 366 if (progtype != NULL && (severity != BOOTLOG_PROGRESS || 367 strncmp(progtype, message, strlen(progtype)) != 0)) { 368 (void) printf("\n"); 369 free(progtype); 370 progtype = NULL; 371 } 372 373 (void) printf("%s %s %s: %s\r", gettime(), ident, 374 pri_names[i].c_name, message); 375 376 if (severity != BOOTLOG_PROGRESS) { 377 (void) printf("\n"); 378 } else if (progtype == NULL) { 379 /* 380 * New progress message; save its "type" (the part 381 * of the message up to and including the first 382 * colon). This should be made less clumsy in the 383 * future. 384 */ 385 progtype = strdup(message); 386 if (progtype != NULL) { 387 for (i = 0; progtype[i] != '\0'; i++) { 388 if (progtype[i] == ':') { 389 progtype[++i] = '\0'; 390 break; 391 } 392 } 393 } 394 } 395 ret = B_TRUE; 396 break; 397 398 case BL_LOCAL_FILE: 399 if (bl_filehandle == NULL) 400 return (B_FALSE); 401 402 (void) fprintf(bl_filehandle, "%s %s %s: [ID %u user.%s] %s\n", 403 gettime(), hostname, ident, msgid, pri_names[i].c_name, 404 message); 405 ret = B_TRUE; 406 break; 407 408 case BL_HTTP: 409 case BL_HTTPS: 410 if (bl_httphandle == NULL) 411 return (B_FALSE); 412 ret = send_http(); 413 break; 414 415 case BL_NO_TRANSPORT: 416 default: 417 ret = B_FALSE; 418 } 419 420 return (ret); 421 } 422 423 static bl_transport_t 424 openbootlog(void) 425 { 426 static boolean_t got_boot_logger = B_FALSE; 427 static boolean_t bl_url_valid = B_FALSE; 428 static boolean_t clientauth = B_FALSE; 429 static bc_handle_t bootconf_handle; 430 bl_transport_t transport; 431 432 /* 433 * We try to use a logfile in userland since our consumer (install) 434 * needs complete control over the terminal. 435 */ 436 #ifndef _BOOT 437 if (bl_filehandle == NULL) 438 bl_filehandle = fopen("/var/log/bootlog", "a"); 439 #endif 440 transport = (bl_filehandle != NULL) ? BL_LOCAL_FILE : BL_CONSOLE; 441 442 /* 443 * If we haven't already been able to access wanboot.conf for a 444 * boot_logger URL, see if we can now. 445 */ 446 if (!got_boot_logger && 447 bootconf_init(&bootconf_handle, NULL) == BC_SUCCESS) { 448 char *urlstr; 449 char *cas; 450 451 /* 452 * If there is a boot_logger, ensure that it's is a legal URL. 453 */ 454 if ((urlstr = bootconf_get(&bootconf_handle, 455 BC_BOOT_LOGGER)) != NULL && 456 url_parse(urlstr, &bl_url) == URL_PARSE_SUCCESS) { 457 bl_url_valid = B_TRUE; 458 } 459 460 /* 461 * If the boot_logger URL uses an HTTPS scheme, see if 462 * client authentication is specified. 463 */ 464 if (bl_url.https) { 465 cas = bootconf_get(&bootconf_handle, 466 BC_CLIENT_AUTHENTICATION); 467 if (cas != NULL) { 468 clientauth = (strcmp(cas, BC_YES) == 0); 469 } 470 } 471 472 bootconf_end(&bootconf_handle); 473 474 /* 475 * Having now accessed wanboot.conf, remember not to come 476 * this way again; the value of boot_logger cannot change. 477 */ 478 got_boot_logger = B_TRUE; 479 } 480 481 /* 482 * If there is no legal boot_logger URL available, then we're done. 483 */ 484 if (!bl_url_valid) { 485 return (transport); 486 } 487 488 /* 489 * If we don't already have a bl_httphandle, try to get one. 490 * If we fail, then we're done. 491 */ 492 if (bl_httphandle == NULL) { 493 bl_httphandle = http_srv_init(&bl_url); 494 if (bl_httphandle == NULL) { 495 return (transport); 496 } 497 } 498 499 /* 500 * If we succeed in setting up the connection, 501 * then we use the connection as our transport. 502 * Otherwise, we use the transport we've already 503 * determined above. 504 */ 505 if (setup_con(bl_httphandle, bl_url.https, clientauth)) { 506 transport = bl_url.https ? BL_HTTPS : BL_HTTP; 507 } 508 509 return (transport); 510 } 511 512 static boolean_t 513 setup_con(http_handle_t handle, boolean_t https, boolean_t client_auth) 514 { 515 static boolean_t got_proxy = B_FALSE; 516 static boolean_t proxy_valid = B_FALSE; 517 static url_hport_t proxy; 518 int i; 519 520 /* 521 * If an HTTPS scheme is specified, then check that time 522 * has been initialized. 523 * If time() returns a non-zero value, then we know 524 * that the boot file system has been mounted and that 525 * we have a trusted time. 526 */ 527 if (https && time(0) == 0) 528 return (B_FALSE); 529 530 if (!got_proxy && bootinfo_init()) { 531 char hpstr[URL_MAX_STRLEN]; 532 size_t vallen = sizeof (hpstr); 533 534 /* 535 * If there is a http-proxy, ensure that it's a legal host:port. 536 */ 537 if (bootinfo_get(BI_HTTP_PROXY, hpstr, &vallen, NULL) == 538 BI_E_SUCCESS && vallen > 0) { 539 hpstr[vallen] = '\0'; 540 if (url_parse_hostport(hpstr, &proxy, 541 URL_DFLT_PROXY_PORT) == URL_PARSE_SUCCESS) { 542 proxy_valid = B_TRUE; 543 } 544 } 545 546 got_proxy = B_TRUE; 547 } 548 if (proxy_valid && http_set_proxy(handle, &proxy) != 0) 549 return (B_FALSE); 550 551 (void) http_set_keepalive(handle, 1); 552 (void) http_set_socket_read_timeout(handle, BOOTLOG_HTTP_TIMEOUT); 553 554 /* 555 * If an HTTPS scheme is specified, then setup the necessary 556 * SSL context for the connection 557 */ 558 if (https) { 559 if (http_set_random_file(handle, "/dev/urandom") == -1) 560 return (B_FALSE); 561 562 if (http_set_certificate_authority_file(NB_CA_CERT_PATH) < 0) 563 return (B_FALSE); 564 565 /* 566 * The client certificate and key will not exist unless 567 * client authentication has been configured. If it is 568 * configured then the webserver will have added these 569 * files to the wanboot file system and the HTTP library 570 * needs to be made aware of their existence. 571 */ 572 if (client_auth) { 573 if (http_set_client_certificate_file(handle, 574 NB_CLIENT_CERT_PATH) < 0) { 575 return (B_FALSE); 576 } 577 578 if (http_set_private_key_file(handle, 579 NB_CLIENT_KEY_PATH) < 0) { 580 return (B_FALSE); 581 } 582 } 583 584 if (http_set_password(handle, WANBOOT_PASSPHRASE) < 0) 585 return (B_FALSE); 586 } 587 588 for (i = 0; i < BOOTLOG_CONN_RETRIES; i++) { 589 if (http_srv_connect(handle) == 0) 590 return (B_TRUE); 591 592 (void) http_srv_disconnect(handle); 593 } 594 595 return (B_FALSE); 596 } 597 598 static char * 599 url_encode(const char *ibufp) 600 { 601 int i; 602 char c; 603 unsigned char nibble; 604 static char obuff[BOOTLOG_QS_MAX * 3]; 605 char *obufp = obuff; 606 607 /* 608 * Encode special characters as outlined in RFC2396. 609 * 610 * Special characters are encoded as a triplets beginning 611 * with '%' followed by the two hexidecimal digits representing 612 * the octet code. The space character is special. It can be encoded 613 * simply as a '+'. 614 */ 615 while ((c = *ibufp++) != '\0') { 616 /* 617 * Is the character one of the special characters 618 * that require encoding? If so append '%' to the output 619 * buffer follow that by the hexascii value. 620 */ 621 if (strchr("/?{}|^~[]`<>#%=\"\t", c) != NULL) { 622 *obufp++ = '%'; 623 /* 624 * Compute the character's hex value and 625 * convert it to ASCII. That is two nibbles 626 * per character. 627 */ 628 for (i = 1; i >= 0; i--) { 629 nibble = ((uchar_t)c >> (4 * i)) & 0x0f; 630 /* 631 * If the hex digit is 0xa - 0xf, then 632 * compute its ASCII value by adding 0x37 633 * else 0x0 - 0x9 just add 0x30. 634 */ 635 if (nibble > 0x9) 636 nibble += 0x37; 637 else 638 nibble += 0x30; 639 *obufp++ = nibble; 640 } 641 /* 642 * The space character gets a special mapping. 643 */ 644 } else if (c == ' ') { 645 *obufp++ = '+'; 646 647 /* 648 * Append the rest (sans any CR character) 649 */ 650 } else if (c != '\n') { 651 *obufp++ = c; 652 } 653 } 654 *obufp = '\0'; 655 return (obuff); 656 } 657 658 static void 659 rb_init(struct ringbuffer_t *buffer) 660 { 661 int i; 662 663 buffer->w_ptr = 0; 664 buffer->r_ptr = 0; 665 666 for (i = 0; i < BOOTLOG_RING_NELEM; i++) 667 buffer->entries[i].message[0] = '\0'; 668 } 669 670 static int 671 ptr_incr(int ptr) 672 { 673 if (++ptr < BOOTLOG_RING_NELEM) 674 return (ptr); 675 else 676 return (0); 677 } 678 679 static int 680 ptr_decr(int ptr) 681 { 682 if (ptr == 0) 683 return (BOOTLOG_RING_NELEM - 1); 684 else 685 return (--ptr); 686 } 687 688 static void 689 rb_write(struct ringbuffer_t *buffer, const char *buff) 690 { 691 (void) strlcpy(buffer->entries[buffer->w_ptr].message, buff, 692 BOOTLOG_QS_MAX); 693 buffer->w_ptr = ptr_incr(buffer->w_ptr); 694 if (buffer->r_ptr == buffer->w_ptr) 695 buffer->r_ptr = ptr_incr(buffer->r_ptr); 696 } 697 698 static int 699 rb_read(struct ringbuffer_t *buffer, char *buff) 700 { 701 if (buffer->r_ptr != buffer->w_ptr) { 702 (void) strlcpy(buff, buffer->entries[buffer->r_ptr].message, 703 BOOTLOG_QS_MAX); 704 buffer->r_ptr = ptr_incr(buffer->r_ptr); 705 return (0); 706 } 707 return (-1); 708 } 709