xref: /titanic_50/usr/src/common/net/wanboot/bootlog.c (revision ba7866cd2cbdf574f47d4e38a1301b90744dd677)
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