/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
* 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 2012 Milan Jurik. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <libgen.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <libnvpair.h>
#include <sys/sysmacros.h>
#include <sys/wanboot_impl.h>
#include <p12aux.h>
#include <parseURL.h>
/*
* These can be replaced with wanbootutil.h once the openssl interfaces
* are moved to libwanboot.
*/
#include <wanboot/key_util.h>
#include <hmac_sha1.h>
#include <netboot_paths.h>
#include <wanboot_conf.h>
/*
* Exit status:
*/
#define WBCGI_STATUS_OK 0
/*
* Possible return values from netboot_ftw():
*/
/*
* getsubopt() is used to map one of the contents[] keywords
* to one of these types
*/
#define WBCGI_CONTENT_BOOTFILE 0
static char *contents[] =
/*
* getsubopt() is used to parse the query string for
* the keywords defined by queryopts[]
*/
#define WBCGI_QUERYOPT_CONTENT 0
static char *queryopts[] =
static char *
{
char *msg;
switch (status) {
case 400:
msg = "Bad Request";
break;
case 403:
msg = "Forbidden";
break;
case 500:
msg = "Internal Server Error";
break;
default:
msg = "Unknown status";
break;
}
return (msg);
}
static void
{
spec_msg = "";
}
}
static char *
{
int chars;
}
return (ptr);
}
static void
{
}
}
static char *
{
int chars;
}
return (ptr);
}
/*
* File I/O stuff:
*/
static boolean_t
{
if (nbytes <= 0) {
return (B_FALSE);
}
}
return (B_TRUE);
}
static boolean_t
{
int ifd;
return (B_FALSE);
}
break;
}
}
return (ret);
}
static boolean_t
{
int chars;
goto cleanup;
}
if (buf == MAP_FAILED) {
goto cleanup;
}
goto cleanup;
}
}
}
} else {
}
}
if (rfd != -1) {
}
if (wfd != -1) {
}
return (ret);
}
static boolean_t
{
int fd;
}
if (fd != -1) {
}
return (ret);
}
static boolean_t
{
int fd;
}
if (fd != -1) {
}
return (ret);
}
static boolean_t
{
int fd;
}
if (fd != -1) {
}
return (ret);
}
static boolean_t
{
int fd;
}
if (fd != -1) {
}
return (ret);
}
static char *
determine_doc_root(void)
{
char *doc_root;
/*
* If DOCUMENT_ROOT is valid, use that.
*/
/*
* No DOCUMENT_ROOT - try PATH_TRANSLATED.
*/
/*
* Can't determine the document root.
*/
return (NULL);
}
}
return (doc_root);
}
static boolean_t
char **docrootp)
{
char *method;
char *query_string;
char *value;
char *junk;
int i;
return (B_FALSE);
}
return (B_FALSE);
}
for (i = 0; i < strlen(query_string); i++) {
if (query_string[i] == '&') {
query_string[i] = ',';
}
}
return (B_FALSE);
}
while (*query_string != '\0') {
case WBCGI_QUERYOPT_CONTENT:
break;
case WBCGI_QUERYOPT_NET:
break;
case WBCGI_QUERYOPT_CID:
break;
case WBCGI_QUERYOPT_NONCE:
break;
default:
return (B_FALSE);
}
}
switch (*contentp) {
default:
return (B_FALSE);
case WBCGI_CONTENT_BOOTFS:
print_status(400,
"(CONTENT, IP, CID and NONCE required)");
return (B_FALSE);
}
break;
case WBCGI_CONTENT_BOOTFILE:
case WBCGI_CONTENT_ROOTFS:
print_status(400,
"(CONTENT, IP, CID and DOCUMENT_ROOT required)");
return (B_FALSE);
}
break;
}
return (B_TRUE);
}
static boolean_t
const char *keyfile, const char *encryption_type)
{
int chars;
int status;
return (B_FALSE);
}
"%s -o type=%s -k %s < %s > %s", WBCGI_ENCR_PATH,
return (B_FALSE);
}
return (B_FALSE);
}
"(encrypt_payload: failed, status=%d)", status);
return (B_FALSE);
}
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
const char *keyfile)
{
int chars;
int status;
return (B_FALSE);
}
return (B_FALSE);
}
return (B_FALSE);
}
"(hash_payload: failed, status=%d)", status);
return (B_FALSE);
}
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
{
int chars;
int status;
return (B_FALSE);
}
"%s -x -f %s -s %s -o type=rsa",
return (B_FALSE);
}
return (B_FALSE);
}
"(extract_keystore: failed, status=%d)", status);
return (B_FALSE);
}
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
{
int chars;
int status;
return (B_FALSE);
}
return (B_FALSE);
}
return (B_FALSE);
}
"(mkisofs: failed, status=%d)", status);
return (B_FALSE);
}
return (B_FALSE);
}
return (B_TRUE);
}
/*
* This function, when invoked with a file name, optional network and
* client ID strings, and callback function will search for the file
* in the following locations:
*
* NB_NETBOOT_ROOT/<network>/<client id>/<file>
* NB_NETBOOT_ROOT/<client id>/<file>
* NB_NETBOOT_ROOT/<network>/<file>
* NB_NETBOOT_ROOT/<file>
*
* The callback function is invoked each time the file is found until
* we have searched all of the above locations or the callback function
* returns a value other than WBCGI_FTW_CBCONT.
*
* Arguments:
* filename - Name of file to search for.
* net - Optional network number to include in search hierarchy.
* cid - Optional client ID to include in search hierarchy.
* cb - Callback function to be called when file is found.
* arg - Argument to be supplied to the callback funtion.
*
* Returns:
* WBCGI_FTW_DONE, WBCGI_FTW_CBOK or WBCGI_FTW_CBERR.
*/
static int
{
int ret;
int i = 0;
>= MAXPATHLEN)
return (WBCGI_FTW_CBERR);
return (WBCGI_FTW_CBERR);
return (WBCGI_FTW_CBERR);
return (WBCGI_FTW_CBERR);
}
/*
* Loop through hierarchy and check for file existence.
*/
while (i > 0) {
--i;
return (ret);
}
}
return (WBCGI_FTW_DONE);
}
/*ARGSUSED*/
static int
{
return (WBCGI_FTW_CBOK);
}
static int
{
return (WBCGI_FTW_CBOK);
}
static int
{
return (WBCGI_FTW_CBERR);
}
return (WBCGI_FTW_CBOK);
}
static int
{
return (WBCGI_FTW_CBERR);
}
return (WBCGI_FTW_CBOK);
}
/*
* Add the certs found in the trustfile found in path (a trust store) to
* the file found at bootfs_dir/truststore. If necessary, create the
* output file.
*/
static int
{
int errtype = 0;
int chars;
int i;
goto cleanup;
}
/*
* If we are inadvertantly writing to the input file.
* return success.
* XXX Pete: how can this happen, and why success?
*/
goto cleanup;
}
goto cleanup;
}
/*
* Read what's already there, so that new information
* can be added.
*/
errtype = 1;
goto cleanup;
}
if (i <= 0) {
errtype = 1;
goto cleanup;
}
} else {
"(error accessing file %s, error %s)",
else
return (WBCGI_FTW_CBERR);
}
/*
* Note: We could copy the file to the new trustfile, but
* we can't verify the password that way. Therefore, copy
* it by reading it.
*/
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
}
goto cleanup;
}
errtype = 1;
goto cleanup;
}
if (i <= 0) {
errtype = 1;
goto cleanup;
}
/*
* Merge the two stacks of pkcs12 certs.
*/
for (i = 0; i < sk_X509_num(i_anchors); i++) {
/* LINTED */
x = sk_X509_delete(i_anchors, i);
(void) sk_X509_push(o_anchors, x);
}
/*
* Create the pkcs12 structure from the modified input stack and
* then write out that structure.
*/
goto cleanup;
}
goto cleanup;
}
if (ret == WBCGI_FTW_CBERR) {
if (errtype == 1) {
"(internal PKCS12 error while copying %s to %s)",
path, (char *)truststorepath);
} else {
"(error copying %s to %s)",
path, (char *)truststorepath);
}
} else {
}
}
}
/* Will also close wfd */
}
}
}
}
return (ret);
}
static boolean_t
{
/*
* Map keytype into the ka structure
*/
goto cleanup;
}
/*
* Open the key file for reading.
*/
goto cleanup;
}
/*
* Find the valid client key, if it exists.
*/
goto cleanup;
}
}
return (ret);
}
static boolean_t
{
/*
* Initialize cached nodename
*/
"(unable to retrieve uname, errno %d)", errno);
return (B_FALSE);
}
}
/*
* If hostname is local node name, return the address this
* request came in on, which is supplied as SERVER_ADDR in the
* cgi environment. This ensures we don't send back a possible
* alternate address that may be unreachable from the client's
* network. Otherwise, just resolve with nameservice.
*/
if (!may_be_crap) {
}
return (may_be_crap);
}
}
return (B_FALSE);
}
return (B_TRUE);
}
/*
* one_name() is called for each certificate found and is passed the string
* that X509_NAME_oneline() returns. Its job is to find the common name and
* determine whether it is a host name; if it is then a line suitable for
*/
static boolean_t
{
char *p;
char *q;
char c;
p += WBCGI_CNSTR_LEN;
c = *q;
*q = '\0';
*q = c;
} else {
}
}
return (ret);
}
/*
* Loop through the certificates in a file
*/
static int
{
int errtype = 0;
int chars;
int i;
goto cleanup;
}
errtype = 1;
goto cleanup;
}
if (i <= 0) {
errtype = 1;
goto cleanup;
}
for (i = 0; i < sk_X509_num(certs); i++) {
/* LINTED */
x = sk_X509_value(certs, i);
nvl)) {
goto cleanup;
}
}
if (ret == WBCGI_FTW_CBERR) {
if (errtype == 1) {
"(internal PKCS12 error reading %s)", path);
} else {
"error reading %s", path);
}
} else {
}
}
}
}
}
return (ret);
}
/*
* Create a hosts file by extracting hosts from client and truststore
* files. Use the CN. Then we should copy that file to the inet dir.
*/
static boolean_t
{
int i;
char *hostslist;
/*
*/
goto cleanup;
}
/*
* Extract and resolve hostnames from CNs.
*/
goto cleanup;
}
/*
* Extract and resolve hostnames from any URLs in bootconf.
*/
char *urlstr;
goto cleanup;
}
}
}
/*
* If there is a resolve-hosts list in bootconf, resolve those
* hostnames too.
*/
char *hostname;
goto cleanup;
}
}
}
/*
*/
goto cleanup;
}
char *hostname;
char *ipstr;
goto cleanup;
}
goto cleanup;
}
}
}
/*
* hostfd is automatically closed as well.
*/
}
return (ret);
}
static boolean_t
{
char *boot_file;
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
return (ret);
}
/*
* Create the wanboot file system whose contents are determined by the
* security configuration specified in bootconf.
*/
static boolean_t
const char *bootconf, char **wanbootfs_imagep)
{
char *server_authentication;
char *client_authentication;
char *scf;
/*
* Initialize SSL stuff.
*/
/*
* Get the security strategy values.
*/
/*
* Make a temporary directory structure for the wanboot file system.
*/
goto cleanup;
}
goto cleanup;
}
if (authenticate_client) {
/*
* Add the client private key.
*/
NB_CLIENT_KEY)) == NULL ||
goto cleanup;
}
/*
* Add the client certificate.
*/
NB_CLIENT_CERT)) == NULL ||
goto cleanup;
}
}
if (authenticate_client || authenticate_server) {
/*
* Add the trustfile; at least one truststore must exist.
*/
NB_CA_CERT)) == NULL) {
goto cleanup;
}
}
goto cleanup;
}
/*
*/
"urandom")) == NULL ||
goto cleanup;
}
}
/*
* Add the wanboot.conf(4) file.
*/
goto cleanup;
}
/*
* Add the system_conf file if present.
*/
goto cleanup;
}
NB_SYSTEM_CONF)) == NULL ||
goto cleanup;
}
}
/*
* Create the /nonce file.
*/
goto cleanup;
}
/*
* URLs in bootconf and resolve-hosts in bootconf.
*/
goto cleanup;
}
/*
* but unfortunately the HSFS support in the standalone doesn't handle
* symlinks.
*/
goto cleanup;
}
/*
* Create the /timestamp file.
*/
goto cleanup;
}
/*
* Create an HSFS file system for the directory.
*/
goto cleanup;
}
/*
* Clean up temporary files and directories.
*/
if (keystorepath != NULL &&
(void) unlink(keystorepath);
}
if (certstorepath != NULL &&
(void) unlink(certstorepath);
}
if (truststorepath != NULL &&
(void) unlink(truststorepath);
}
if (bootconfpath != NULL &&
(void) unlink(bootconfpath);
}
if (systemconfpath != NULL &&
(void) unlink(systemconfpath);
}
if (urandompath != NULL &&
(void) unlink(urandompath);
}
}
}
if (etc_hostspath != NULL &&
(void) unlink(etc_hostspath);
}
if (timestamppath != NULL &&
(void) unlink(timestamppath);
}
if (bootfs_etc_inet_dir != NULL &&
(void) rmdir(bootfs_etc_inet_dir);
}
if (bootfs_etc_dir != NULL &&
(void) rmdir(bootfs_etc_dir);
}
if (bootfs_dev_dir != NULL &&
(void) rmdir(bootfs_dev_dir);
}
if (bootfs_dir != NULL &&
(void) rmdir(bootfs_dir);
}
/*
* Free allocated memory.
*/
return (ret);
}
static boolean_t
{
char *root_server;
char *root_file;
int chars;
goto cleanup;
}
}
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
if (fd != -1) {
}
return (ret);
}
static boolean_t
{
int chars;
return (B_FALSE);
}
/*
* Multi-part header.
*/
"%s--%s%s%sapplication/octet-stream%s%s", WBCGI_CRNL,
return (B_FALSE);
}
/*
* Multi-part header for part one.
*/
return (B_FALSE);
}
/*
* Multi-part header for part two.
*/
return (B_FALSE);
}
/*
* End-of-parts Trailer.
*/
return (B_FALSE);
}
/*
* Message header.
*/
"%s%u%s%smultipart/mixed; boundary=%s%s%s", WBCGI_CONTENT_LENGTH,
return (B_FALSE);
}
/*
* Write the message out. If things fall apart during this then
* there's no way to report the error back to the client.
*/
return (B_FALSE);
}
return (B_TRUE);
}
/*ARGSUSED*/
int
{
int content;
char *net;
char *cid;
char *nonce;
char *docroot;
char *payload;
char *signature_type;
char *encryption_type;
/*
* Process the query string.
*/
goto cleanup;
}
/*
* Sanity check that the netboot directory exists.
*/
goto cleanup;
}
/*
* Get absolute bootconf pathname.
*/
goto cleanup;
}
/*
* Initialize bc_handle from the given wanboot.conf file.
*/
int chars;
else
goto cleanup;
}
/*
* Get and check signature and encryption types,
* presence of helper utilities, keystore, etc.
*/
BC_SIGNATURE_TYPE)) != NULL) {
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
}
BC_ENCRYPTION_TYPE)) != NULL) {
if (signature_type == NULL) {
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
goto cleanup;
}
}
/*
*/
switch (content) {
case WBCGI_CONTENT_BOOTFILE:
goto cleanup;
}
break;
case WBCGI_CONTENT_BOOTFS:
bootconf, &wanbootfs_image)) {
goto cleanup;
}
break;
case WBCGI_CONTENT_ROOTFS:
goto cleanup;
}
break;
}
/*
* Encrypt the payload if necessary.
*/
if (content != WBCGI_CONTENT_BOOTFILE &&
content != WBCGI_CONTENT_ROOTFS &&
encryption_type != NULL) {
goto cleanup;
}
encryption_type)) {
goto cleanup;
}
}
/*
* Compute the hash (actual or null).
*/
goto cleanup;
}
if (signature_type != NULL &&
goto cleanup;
}
} else {
if (!create_null_hash(payload_hash)) {
goto cleanup;
}
}
/*
* For the rootfs the actual payload transmitted is the file
* containing the size of the rootfs (as a string of ascii digits);
* point payload at this instead.
*/
if (content == WBCGI_CONTENT_ROOTFS) {
}
/*
* Finally, deliver the payload and hash as a multipart message.
*/
goto cleanup;
}
/*
* Clean up temporary files.
*/
if (wanbootfs_image != NULL &&
(void) unlink(wanbootfs_image);
}
if (miniroot_info != NULL &&
(void) unlink(miniroot_info);
}
if (encr_payload != NULL &&
(void) unlink(encr_payload);
}
if (payload_hash != NULL &&
(void) unlink(payload_hash);
}
/*
* Free up any allocated strings.
*/
return (ret);
}