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 *
gettime(void)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
bootlog_common(const char * ident,bootlog_severity_t severity,char * message)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
bootlog(const char * ident,bootlog_severity_t severity,char * fmt,...)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
libbootlog(bootlog_severity_t severity,char * fmt,...)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
send_http(void)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
sendmessage(bl_transport_t transport,char * message,const char * ident,bootlog_severity_t severity,int failure)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
openbootlog(void)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
setup_con(http_handle_t handle,boolean_t https,boolean_t client_auth)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 *
url_encode(const char * ibufp)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
rb_init(struct ringbuffer_t * buffer)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
ptr_incr(int ptr)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
ptr_decr(int ptr)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
rb_write(struct ringbuffer_t * buffer,const char * buff)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
rb_read(struct ringbuffer_t * buffer,char * buff)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