OpenSolaris_b135/common/net/wanboot/bootlog.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * bootlog() - error notification and progress reporting for
 *            WAN boot components
 */

#include <sys/varargs.h>
#include <sys/types.h>
#include <sys/strlog.h>
#include <sys/wanboot_impl.h>
#include <errno.h>
#include <time.h>
#include <boot_http.h>
#include <stdio.h>
#include <parseURL.h>
#include <bootlog.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <libintl.h>
#include <netboot_paths.h>
#include <wanboot_conf.h>
#include <bootinfo.h>
#ifdef	_BOOT
#include <sys/bootdebug.h>
#endif

static struct code	pri_names[] = {
	"panic",	BOOTLOG_EMERG,
	"alert",	BOOTLOG_ALERT,
	"crit",		BOOTLOG_CRIT,
	"warn",		BOOTLOG_WARNING,
	"info",		BOOTLOG_INFO,
	"debug",	BOOTLOG_DEBUG,
	"verbose",	BOOTLOG_VERBOSE,
	"progress",	BOOTLOG_PROGRESS,
	"none",		NOPRI,
	NULL,		-1
};

typedef enum {
	BL_NO_TRANSPORT,
	BL_LOCAL_FILE,
	BL_CONSOLE,
	BL_HTTP,
	BL_HTTPS
} bl_transport_t;

typedef struct list_entry {
	char message[BOOTLOG_QS_MAX];
	struct list_entry *flink;
} list;

#define	BOOTLOG_RING_NELEM 512

static struct ringbuffer_t {
	int w_ptr;
	int r_ptr;
	list entries[BOOTLOG_RING_NELEM];
} ringbuffer;

static FILE *bl_filehandle = NULL;
static http_handle_t bl_httphandle = NULL;
static url_t bl_url;
static bl_transport_t bl_transport = BL_NO_TRANSPORT;

static bl_transport_t openbootlog(void);
static boolean_t setup_con(http_handle_t, boolean_t, boolean_t);
static char *url_encode(const char *);
static boolean_t sendmessage(bl_transport_t, char *, const char *,
    bootlog_severity_t, int);
static int ptr_incr(int ptr);
static int ptr_decr(int ptr);
static void rb_init(struct ringbuffer_t *);
static void rb_write(struct ringbuffer_t *, const char *);
static int rb_read(struct ringbuffer_t *, char *);

/*
 * Return a string representing the current time; not thread-safe.
 */
static const char *
gettime(void)
{
	static char	timebuf[sizeof ("Tue Jan 19 03:14:07 2038\n")];
	time_t 		curtime;

	if (time(&curtime) == 0)
		return ("<time unavailable>");

	(void) strlcpy(timebuf, ctime(&curtime), sizeof (timebuf));
	timebuf[19] = '\0';		/* truncate before "2038" above */
	return (timebuf);
}

/*
 * bootlog_common() -  Common routine used by bootlog() and
 *	bootlog_internal() to write a message comprising a message
 *	header and a message body to the appropriate transport.
 *	The message header comprises an ident string and a message
 *	severity.
 */
static void
bootlog_common(const char *ident, bootlog_severity_t severity, char *message)
{
	bl_transport_t	entry_transport;
	static int	blrecurs;
	static int	blretry;

	/*
	 * This function may be called recursively because the HTTP code
	 * is a bootlog consumer. The blrecurs variable is used to determine
	 * whether or not the invocation is recursive.
	 */
	blrecurs++;
	entry_transport = bl_transport;

	/*
	 * If this is the first bootlog call then setup the transport.
	 * We only do this in a non-recursive invocation as openbootlog()
	 * results in a recursive call for a HTTP or HTTPS transport.
	 */
	if (bl_transport == BL_NO_TRANSPORT && blrecurs == 1) {
		rb_init(&ringbuffer);
		bl_transport = openbootlog();
	}

	/*
	 * If we're not there already, try to move up a level.
	 * This is necessary because our consumer may have begun
	 * logging before it had enough information to initialize
	 * its HTTP or HTTPS transport. We've arbitrarily decided
	 * that we'll only check to see if we should move up, on
	 * every third (blretry) non-recursive invocation.
	 */
	if (blrecurs == 1 &&
	    !(bl_transport == BL_HTTPS || bl_transport == BL_HTTP)) {
		if (blretry > 3) {
			bl_transport = openbootlog();
			blretry = 0;
		} else
			blretry++;
	}

	if (entry_transport != bl_transport) {
		switch (bl_transport) {

		case BL_CONSOLE:
			(void) printf(
			    "%s wanboot info: WAN boot messages->console\n",
			    gettime());
			break;

		case BL_HTTP:
		case BL_HTTPS:
			(void) printf(
			    "%s wanboot info: WAN boot messages->%s:%u\n",
			    gettime(), bl_url.hport.hostname,
			    bl_url.hport.port);
			break;

		default:
			break;
		}
	}

	/*
	 * Failed attempts and recursively generated log messages are
	 * sent to the fallback transport.
	 */
	if (blrecurs > 1 || !sendmessage(bl_transport, message, ident,
	    severity, 0)) {
		/*
		 * Fallback to a log file if one exists, or the console
		 * as a last resort.  Note that bl_filehandle will always
		 * be NULL in standalone.
		 */
		(void) sendmessage(bl_filehandle != NULL ? BL_LOCAL_FILE :
		    BL_CONSOLE, message, ident, severity, 1);
	}
	blrecurs--;
}

/*
 * bootlog() - the exposed interface for logging boot messages.
 */
/* PRINTFLIKE3 */
void
bootlog(const char *ident, bootlog_severity_t severity, char *fmt, ...)
{
	char message[BOOTLOG_MSG_MAX_LEN];
	va_list adx;

	va_start(adx, fmt);
	(void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN, fmt, adx);
	va_end(adx);

	bootlog_common(ident, severity, message);
}

/*
 * libbootlog() - an internal interface for logging boot
 *		messages.
 */
/* PRINTFLIKE2 */
void
libbootlog(bootlog_severity_t severity, char *fmt, ...)
{
	char message[BOOTLOG_MSG_MAX_LEN];
	va_list adx;

	va_start(adx, fmt);
	(void) vsnprintf(message, BOOTLOG_MSG_MAX_LEN,
	    dgettext(TEXT_DOMAIN, fmt), adx);
	va_end(adx);

	bootlog_common("libwanboot", severity, message);
}

static boolean_t
send_http(void)
{
	http_respinfo_t *resp = NULL;
	char buffer[BOOTLOG_MAX_URL + (BOOTLOG_QS_MAX * 3)];
	char ringmessage[BOOTLOG_QS_MAX];
	char *lenstr;
	size_t length;
	int retries;

	while ((rb_read(&ringbuffer, ringmessage) != -1)) {
		(void) snprintf(buffer, sizeof (buffer), "%s?%s",
		    bl_url.abspath, url_encode(ringmessage));

		for (retries = 0; retries < BOOTLOG_CONN_RETRIES; retries++) {
			if (retries > 0) {
				(void) http_srv_disconnect(bl_httphandle);
				if (http_srv_connect(bl_httphandle) != 0)
					continue;
			}

			if (http_get_request(bl_httphandle, buffer) != 0 ||
			    http_process_headers(bl_httphandle, &resp) != 0)
				continue;

			if (resp->code != 200) {
				http_free_respinfo(resp);
				continue;
			}

			http_free_respinfo(resp);
			lenstr = http_get_header_value(bl_httphandle,
			    "Content-Length");
			length = strtol(lenstr, NULL, 10);
			if (http_read_body(bl_httphandle, buffer, length) > 0)
				break;
		}

		/*
		 * The attempt to log the message failed. Back the
		 * read pointer up so that we'll try to log it again
		 * later.
		 */
		if (retries == BOOTLOG_CONN_RETRIES) {
			ringbuffer.r_ptr = ptr_decr(ringbuffer.r_ptr);
			return (B_FALSE);
		}
	}

	return (B_TRUE);
}

static boolean_t
sendmessage(bl_transport_t transport, char *message, const char *ident,
    bootlog_severity_t severity, int failure)
{
	static char *progtype = NULL;
	char ringmessage[BOOTLOG_QS_MAX];
	char hostname[MAXHOSTNAMELEN];
	uint32_t msgid;
	boolean_t ret;
	int i;

	/*
	 * In standalone, only log VERBOSE and DEBUG messages if the
	 * corresponding flag (-V or -d) has been passed to boot.
	 *
	 * Note that some bootlog() consumers impose additional constraints on
	 * printing these messages -- for instance, http_set_verbose() must be
	 * used before the HTTP code will call bootlog() with BOOTLOG_VERBOSE
	 * messages.
	 */
#ifdef	_BOOT
	if (severity == BOOTLOG_DEBUG && !(boothowto & RB_DEBUG))
		return (B_TRUE);
	if (severity == BOOTLOG_VERBOSE && !verbosemode)
		return (B_TRUE);
#endif

	for (i = 0; pri_names[i].c_val != NOPRI; i++) {
		if (severity == pri_names[i].c_val)
			break;
	}

	/*
	 * VERBOSE and DEBUG messages always go to the console
	 */
	if (transport != BL_CONSOLE &&
	    (severity == BOOTLOG_DEBUG || severity == BOOTLOG_VERBOSE)) {
		(void) printf("%s %s %s: %s\n", gettime(), ident,
		    pri_names[i].c_name, message);
	}

	STRLOG_MAKE_MSGID(message, msgid);
	(void) gethostname(hostname, sizeof (hostname));

	/*
	 * Note that in this case, "<time>" is a placeholder that will be used
	 * to fill in the actual time on the remote end.
	 */
	(void) snprintf(ringmessage, sizeof (ringmessage),
	    "<time> %s %s: [ID %u user.%s] %s", hostname, ident, msgid,
	    pri_names[i].c_name, message);

	/*
	 * Prevent duplicate messages from being inserted into
	 * the ring buffer.
	 */
	if (failure == 0) {
		rb_write(&ringbuffer, ringmessage);
	}

	switch (transport) {
	case BL_CONSOLE:
		/*
		 * PROGRESS messages update in-place on the console, as long
		 * as they are of the same 'progress type' (see below) --
		 * if not, reset the progress information.
		 */
		if (progtype != NULL && (severity != BOOTLOG_PROGRESS ||
		    strncmp(progtype, message, strlen(progtype)) != 0)) {
			(void) printf("\n");
			free(progtype);
			progtype = NULL;
		}

		(void) printf("%s %s %s: %s\r", gettime(), ident,
		    pri_names[i].c_name, message);

		if (severity != BOOTLOG_PROGRESS) {
			(void) printf("\n");
		} else if (progtype == NULL) {
			/*
			 * New progress message; save its "type" (the part
			 * of the message up to and including the first
			 * colon).  This should be made less clumsy in the
			 * future.
			 */
			progtype = strdup(message);
			if (progtype != NULL) {
				for (i = 0; progtype[i] != '\0'; i++) {
					if (progtype[i] == ':') {
						progtype[++i] = '\0';
						break;
					}
				}
			}
		}
		ret = B_TRUE;
		break;

	case BL_LOCAL_FILE:
		if (bl_filehandle == NULL)
			return (B_FALSE);

		(void) fprintf(bl_filehandle, "%s %s %s: [ID %u user.%s] %s\n",
		    gettime(), hostname, ident, msgid, pri_names[i].c_name,
		    message);
		ret = B_TRUE;
		break;

	case BL_HTTP:
	case BL_HTTPS:
		if (bl_httphandle == NULL)
			return (B_FALSE);
		ret = send_http();
		break;

	case BL_NO_TRANSPORT:
	default:
		ret = B_FALSE;
	}

	return (ret);
}

static bl_transport_t
openbootlog(void)
{
	static boolean_t	got_boot_logger = B_FALSE;
	static boolean_t	bl_url_valid = B_FALSE;
	static boolean_t	clientauth = B_FALSE;
	static bc_handle_t	bootconf_handle;
	bl_transport_t		transport;

	/*
	 * We try to use a logfile in userland since our consumer (install)
	 * needs complete control over the terminal.
	 */
#ifndef	_BOOT
	if (bl_filehandle == NULL)
		bl_filehandle = fopen("/var/log/bootlog", "a");
#endif
	transport = (bl_filehandle != NULL) ? BL_LOCAL_FILE : BL_CONSOLE;

	/*
	 * If we haven't already been able to access wanboot.conf for a
	 * boot_logger URL, see if we can now.
	 */
	if (!got_boot_logger &&
	    bootconf_init(&bootconf_handle, NULL) == BC_SUCCESS) {
		char	*urlstr;
		char	*cas;

		/*
		 * If there is a boot_logger, ensure that it's is a legal URL.
		 */
		if ((urlstr = bootconf_get(&bootconf_handle,
		    BC_BOOT_LOGGER)) != NULL &&
		    url_parse(urlstr, &bl_url) == URL_PARSE_SUCCESS) {
			bl_url_valid = B_TRUE;
		}

		/*
		 * If the boot_logger URL uses an HTTPS scheme, see if
		 * client authentication is specified.
		 */
		if (bl_url.https) {
			cas = bootconf_get(&bootconf_handle,
			    BC_CLIENT_AUTHENTICATION);
			if (cas != NULL) {
				clientauth = (strcmp(cas, BC_YES) == 0);
			}
		}

		bootconf_end(&bootconf_handle);

		/*
		 * Having now accessed wanboot.conf, remember not to come
		 * this way again; the value of boot_logger cannot change.
		 */
		got_boot_logger = B_TRUE;
	}

	/*
	 * If there is no legal boot_logger URL available, then we're done.
	 */
	if (!bl_url_valid) {
		return (transport);
	}

	/*
	 * If we don't already have a bl_httphandle, try to get one.
	 * If we fail, then we're done.
	 */
	if (bl_httphandle == NULL) {
		bl_httphandle = http_srv_init(&bl_url);
		if (bl_httphandle == NULL) {
			return (transport);
		}
	}

	/*
	 * If we succeed in setting up the connection,
	 * then we use the connection as our transport.
	 * Otherwise, we use the transport we've already
	 * determined above.
	 */
	if (setup_con(bl_httphandle, bl_url.https, clientauth)) {
		transport = bl_url.https ? BL_HTTPS : BL_HTTP;
	}

	return (transport);
}

static boolean_t
setup_con(http_handle_t handle, boolean_t https, boolean_t client_auth)
{
	static boolean_t	got_proxy = B_FALSE;
	static boolean_t	proxy_valid = B_FALSE;
	static url_hport_t	proxy;
	int			i;

	/*
	 * If an HTTPS scheme is specified, then check that time
	 * has been initialized.
	 * If time() returns a non-zero value, then we know
	 * that the boot file system has been mounted and that
	 * we have a trusted time.
	 */
	if (https && time(0) == 0)
		return (B_FALSE);

	if (!got_proxy && bootinfo_init()) {
		char	hpstr[URL_MAX_STRLEN];
		size_t	vallen = sizeof (hpstr);

		/*
		 * If there is a http-proxy, ensure that it's a legal host:port.
		 */
		if (bootinfo_get(BI_HTTP_PROXY, hpstr, &vallen, NULL) ==
		    BI_E_SUCCESS && vallen > 0) {
			hpstr[vallen] = '\0';
			if (url_parse_hostport(hpstr, &proxy,
			    URL_DFLT_PROXY_PORT) == URL_PARSE_SUCCESS) {
				proxy_valid = B_TRUE;
			}
		}

		got_proxy = B_TRUE;
	}
	if (proxy_valid && http_set_proxy(handle, &proxy) != 0)
		return (B_FALSE);

	(void) http_set_keepalive(handle, 1);
	(void) http_set_socket_read_timeout(handle, BOOTLOG_HTTP_TIMEOUT);

	/*
	 * If an HTTPS scheme is specified, then setup the necessary
	 * SSL context for the connection
	 */
	if (https) {
		if (http_set_random_file(handle, "/dev/urandom") == -1)
			return (B_FALSE);

		if (http_set_certificate_authority_file(NB_CA_CERT_PATH) < 0)
			return (B_FALSE);

		/*
		 * The client certificate and key will not exist unless
		 * client authentication has been configured. If it is
		 * configured then the webserver will have added these
		 * files to the wanboot file system and the HTTP library
		 * needs to be made aware of their existence.
		 */
		if (client_auth) {
			if (http_set_client_certificate_file(handle,
			    NB_CLIENT_CERT_PATH) < 0) {
				return (B_FALSE);
			}

			if (http_set_private_key_file(handle,
			    NB_CLIENT_KEY_PATH) < 0) {
				return (B_FALSE);
			}
		}

		if (http_set_password(handle, WANBOOT_PASSPHRASE) < 0)
			return (B_FALSE);
	}

	for (i = 0; i < BOOTLOG_CONN_RETRIES; i++) {
		if (http_srv_connect(handle) == 0)
			return (B_TRUE);

		(void) http_srv_disconnect(handle);
	}

	return (B_FALSE);
}

static char *
url_encode(const char *ibufp)
{
	int i;
	char c;
	unsigned char nibble;
	static char obuff[BOOTLOG_QS_MAX * 3];
	char *obufp = obuff;

	/*
	 * Encode special characters as outlined in RFC2396.
	 *
	 * Special characters are encoded as a triplets beginning
	 * with '%' followed by the two hexidecimal digits representing
	 * the octet code. The space character is special. It can be encoded
	 * simply as a '+'.
	 */
	while ((c = *ibufp++) != '\0') {
		/*
		 * Is the character one of the special characters
		 * that require encoding? If so append '%' to the output
		 * buffer follow that by the hexascii value.
		 */
		if (strchr("/?{}|^~[]`<>#%=\"\t", c) != NULL) {
			*obufp++ = '%';
			/*
			 * Compute the character's hex value and
			 * convert it to ASCII. That is two nibbles
			 * per character.
			 */
			for (i = 1; i >= 0; i--) {
				nibble = ((uchar_t)c >> (4 * i)) & 0x0f;
				/*
				 * If the hex digit is 0xa - 0xf, then
				 * compute its ASCII value by adding 0x37
				 * else 0x0 - 0x9 just add 0x30.
				 */
				if (nibble > 0x9)
					nibble += 0x37;
				else
					nibble += 0x30;
				*obufp++ = nibble;
			}
		/*
		 * The space character gets a special mapping.
		 */
		} else if (c == ' ') {
			*obufp++ = '+';

		/*
		 * Append the rest (sans any CR character)
		 */
		} else if (c != '\n') {
			*obufp++ = c;
		}
	}
	*obufp = '\0';
	return (obuff);
}

static void
rb_init(struct ringbuffer_t *buffer)
{
	int i;

	buffer->w_ptr = 0;
	buffer->r_ptr = 0;

	for (i = 0; i < BOOTLOG_RING_NELEM; i++)
		buffer->entries[i].message[0] = '\0';
}

static int
ptr_incr(int ptr)
{
	if (++ptr < BOOTLOG_RING_NELEM)
		return (ptr);
	else
		return (0);
}

static int
ptr_decr(int ptr)
{
	if (ptr == 0)
		return (BOOTLOG_RING_NELEM - 1);
	else
		return (--ptr);
}

static void
rb_write(struct ringbuffer_t *buffer, const char *buff)
{
	(void) strlcpy(buffer->entries[buffer->w_ptr].message, buff,
	    BOOTLOG_QS_MAX);
	buffer->w_ptr = ptr_incr(buffer->w_ptr);
	if (buffer->r_ptr == buffer->w_ptr)
		buffer->r_ptr = ptr_incr(buffer->r_ptr);
}

static int
rb_read(struct ringbuffer_t *buffer, char *buff)
{
	if (buffer->r_ptr != buffer->w_ptr) {
		(void) strlcpy(buff, buffer->entries[buffer->r_ptr].message,
		    BOOTLOG_QS_MAX);
		buffer->r_ptr = ptr_incr(buffer->r_ptr);
		return (0);
	}
	return (-1);
}