Initial commit

This commit is contained in:
Reyk Floeter 2017-06-29 11:40:48 +02:00
commit 1c7148f25b
48 changed files with 19200 additions and 0 deletions

20
agent/Makefile Normal file
View file

@ -0,0 +1,20 @@
PROG= cloud-agent
SRCS= main.c xml.c azure.c cloudinit.c http.c log.c
BINDIR= /usr/local/libexec
.ifdef USE_OPENSSL
CFLAGS+= -DUSE_OPENSSL=1
.endif
CFLAGS+= -Wall
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations
CFLAGS+= -Wshadow -Wpointer-arith
CFLAGS+= -Wsign-compare -Wcast-qual
LDADD+= -lexpat -ltls -lssl -lcrypto
DPADD+= ${LIBEXPAT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
NOMAN= yes
.include <bsd.prog.mk>

791
agent/azure.c Normal file
View file

@ -0,0 +1,791 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/queue.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include "main.h"
#include "http.h"
#include "xml.h"
static struct azure_config {
const char *az_apiversion;
unsigned int az_incarnation;
const char *az_privkey;
const char *az_pubkey;
const char *az_certs;
char *az_pubkeyval;
char *az_container;
} az_config = {
.az_apiversion = "2015-04-05",
.az_incarnation = 1,
.az_privkey = "/var/db/azure-transport.key",
.az_pubkey = "/var/db/azure-transport.pub",
.az_certs = "/var/db/azure-certificates.pem"
};
static struct httpget
*azure_request(struct system_config *, struct xml *,
const char *, const void *, size_t, struct httphead **);
static int azure_keys(struct system_config *);
static int azure_getpubkeys(struct system_config *);
static int azure_getendpoint(struct system_config *);
static int azure_getovfenv(struct system_config *);
static int azure_versions(struct system_config *);
static int azure_goalstate(struct system_config *);
static int azure_certificates(struct system_config *);
static int azure_reporthealth(struct system_config *, const char *);
int
azure(struct system_config *sc)
{
int ret = -1;
/* Apply defaults */
if ((sc->sc_username = strdup("azure-user")) == NULL) {
log_warnx("failed to set default user");
goto done;
}
sc->sc_cdrom = "/dev/cd0c";
sc->sc_ovfenv = "/var/db/azure-ovf-env.xml";
sc->sc_priv = &az_config;
if (azure_getendpoint(sc) != 0) {
log_warnx("failed to get endpoint");
goto done;
}
if (azure_getovfenv(sc) != 0) {
log_warnx("failed to get ovf-env.xml");
goto done;
}
if (azure_versions(sc) != 0) {
log_warnx("failed to get endpoint versions");
goto done;
}
if (azure_goalstate(sc) != 0) {
log_warnx("failed to get goalstate");
goto done;
}
if (azure_keys(sc) != 0) {
log_warnx("failed to get transport keys");
goto done;
}
if (azure_certificates(sc) != 0) {
log_warnx("failed to get certificates");
goto done;
}
if (azure_reporthealth(sc, "Ready") != 0) {
log_warnx("failed to report health");
goto done;
}
ret = 0;
done:
free(az_config.az_container);
free(az_config.az_pubkeyval);
return (ret);
}
int
azure_keys(struct system_config *sc)
{
struct azure_config *az = sc->sc_priv;
int fd, i;
const char *k[4];
FILE *fp = NULL, *keyfp = NULL;
char buf[BUFSIZ];
char *keybuf = NULL;
size_t keybufsz;
k[0] = az->az_privkey;
k[1] = az->az_pubkey;
k[2] = az->az_certs;
k[3] = NULL;
if (access(az->az_privkey, R_OK) != 0 ||
access(az->az_pubkey, R_OK) != 0) {
/* Ugh, we must generate the files before writing the keys */
for (i = 0; k[i] != NULL; i++) {
if ((fd = open(k[i],
O_WRONLY|O_CREAT|O_TRUNC, 0600)) == -1)
return (-1);
close(fd);
}
fd = disable_output(sc, STDERR_FILENO);
/* Now generate the actual transport keys */
if (shell("openssl", "req",
"-x509", "-nodes", "-subj", "/CN=LinuxTransport",
"-days", "32768", "-newkey", "rsa:2048",
"-keyout", az->az_privkey,
"-out", az->az_pubkey,
NULL) != 0) {
log_debug("%s: failed to generate keys", __func__);
return (-1);
}
enable_output(sc, STDERR_FILENO, fd);
}
if ((fp = fopen(az->az_pubkey, "r")) == NULL) {
log_debug("%s: failed to read public key", __func__);
goto done;
}
if ((keyfp = open_memstream(&keybuf, &keybufsz)) == NULL) {
log_debug("%s: failed to open public key stream", __func__);
goto done;
}
/* We have to read the public key into a single base64 line */
while (fgets(buf, sizeof(buf), fp) != NULL) {
buf[strcspn(buf, "\r\n")] = '\0';
if (strcmp("-----BEGIN CERTIFICATE-----", buf) == 0 ||
strcmp("-----END CERTIFICATE-----", buf) == 0 ||
strlen(buf) < 1)
continue;
if (fputs(buf, keyfp) < 0) {
log_debug("%s: failed to write public key",
__func__);
goto done;
}
}
fclose(keyfp);
keyfp = NULL;
az->az_pubkeyval = keybuf;
done:
if (fp != NULL)
fclose(fp);
if (keyfp != NULL) {
fclose(keyfp);
free(keybuf);
}
return (0);
}
struct httpget *
azure_request(struct system_config *sc, struct xml *xml, const char *path,
const void *post, size_t postsz, struct httphead **head)
{
struct azure_config *az = sc->sc_priv;
struct httpget *g = NULL;
struct httphead **reqhead = NULL;
int i;
if (xml != NULL && xml_init(xml) != 0)
return (NULL);
for (i = 0; head != NULL && head[i] != NULL; i++)
;
if ((reqhead = calloc(i + 3, sizeof(struct httphead *))) == NULL) {
log_debug("%s: head", __func__);
goto fail;
}
for (i = 0; head != NULL && head[i] != NULL; i++)
reqhead[i] = head[i];
reqhead[i++] = &(struct httphead){ "x-ms-agent-name", "cloud-agent" };
reqhead[i++] = &(struct httphead){ "x-ms-version", az->az_apiversion };
reqhead[i++] = NULL;
g = http_get(&sc->sc_addr, 1,
sc->sc_endpoint, 80, path, post, postsz, reqhead);
if (g == NULL || g->code != 200) {
log_debug("%s: invalid response", __func__);
goto fail;
}
free(reqhead);
if (xml == NULL) {
if (log_getverbose() > 2)
fwrite(g->bodypart, g->bodypartsz, 1, stderr);
return (g);
}
if (g->bodypartsz < 1 ||
xml_parse_buffer(xml, g->bodypart, g->bodypartsz) != 0) {
log_debug("%s: xml", __func__);
goto fail;
}
if (log_getverbose() > 2)
xml_print(xml, TAILQ_FIRST(&xml->ox_root), 0, stderr);
return (g);
fail:
xml_free(xml);
if (reqhead != NULL)
free(reqhead);
if (g != NULL)
http_get_free(g);
return (NULL);
}
static int
azure_versions(struct system_config *sc)
{
struct azure_config *az = sc->sc_priv;
struct httpget *g;
struct xmlelem *xe, *xv;
int ret = -1;
struct xml xml;
if ((g = azure_request(sc, &xml, "/?comp=versions",
NULL, 0, NULL)) == NULL)
goto done;
if ((xe = xml_findl(&xml.ox_root,
"Versions", "Supported", NULL)) == NULL) {
log_debug("%s: unexpected xml document", __func__);
goto done;
}
TAILQ_FOREACH(xv, &xe->xe_head, xe_entry) {
if (strcmp("Version", xv->xe_tag) == 0 &&
strcmp(xv->xe_data, az->az_apiversion) == 0) {
/* success! */
log_debug("%s: API version %s", __func__, xv->xe_data);
ret = 0;
break;
}
}
done:
xml_free(&xml);
http_get_free(g);
return (ret);
}
static int
azure_goalstate(struct system_config *sc)
{
struct azure_config *az = sc->sc_priv;
struct httpget *g;
struct xmlelem *xe;
int ret = -1;
struct xml xml;
const char *errstr = NULL;
if ((g = azure_request(sc, &xml, "/machine/?comp=goalstate",
NULL, 0, NULL)) == NULL)
goto done;
if ((xe = xml_findl(&xml.ox_root,
"GoalState", "Version", NULL)) == NULL ||
strcmp(xe->xe_data, az->az_apiversion) != 0) {
log_debug("%s: unexpected API version", __func__);
goto done;
}
if ((xe = xml_findl(&xml.ox_root,
"GoalState", "Incarnation", NULL)) == NULL) {
log_debug("%s: unexpected incarnation", __func__);
goto done;
}
az->az_incarnation = strtonum(xe->xe_data, 1, INT_MAX, &errstr);
if (errstr != NULL) {
log_debug("%s: unexpected incarnation: %s", __func__, errstr);
goto done;
}
if ((xe = xml_findl(&xml.ox_root,
"GoalState", "Container", "ContainerId", NULL)) == NULL ||
(az->az_container = strdup(xe->xe_data)) == NULL) {
log_debug("%s: unexpected container id", __func__);
goto done;
}
if ((xe = xml_findl(&xml.ox_root,
"GoalState", "Container", "RoleInstanceList",
"RoleInstance", "InstanceId", NULL)) == NULL ||
(sc->sc_instance = strdup(xe->xe_data)) == NULL) {
log_debug("%s: unexpected instance id", __func__);
goto done;
}
log_debug("%s: container %s instance %s incarnation %d", __func__,
az->az_container, sc->sc_instance, az->az_incarnation);
ret = 0;
done:
xml_free(&xml);
http_get_free(g);
return (ret);
}
static int
azure_certificates(struct system_config *sc)
{
struct azure_config *az = sc->sc_priv;
struct httpget *g;
struct httphead *reqhead[3];
int ret = -1;
char *req = NULL;
char tmp1[32], tmp2[32];
struct xml xml;
struct xmlelem *xe, *data;
int fd;
memset(tmp1, 0, sizeof(tmp1));
memset(tmp2, 0, sizeof(tmp2));
reqhead[0] = &(struct httphead){ "x-ms-cipher-name", "DES_EDE3_CBC" };
reqhead[1] = &(struct httphead){
"x-ms-guest-agent-public-x509-cert", az->az_pubkeyval
};
reqhead[2] = NULL;
if (asprintf(&req, "/machine/%s/%s?comp=certificates&incarnation=%d",
az->az_container, sc->sc_instance, az->az_incarnation) == -1)
return (-1);
g = azure_request(sc, &xml, req, NULL, 0, reqhead);
http_get_free(g);
free(req);
req = NULL;
/* certificates are optional and only needed w/o password auth */
if (g == NULL)
return (0);
if ((xe = xml_findl(&xml.ox_root,
"CertificateFile", "Version", NULL)) == NULL ||
strcmp(xe->xe_data, az->az_apiversion) != 0) {
log_debug("%s: unexpected API version", __func__);
goto done;
}
if ((xe = xml_findl(&xml.ox_root,
"CertificateFile", "Format", NULL)) == NULL ||
strcmp(xe->xe_data, "Pkcs7BlobWithPfxContents") != 0) {
log_debug("%s: unexpected format", __func__);
goto done;
}
if ((data = xml_findl(&xml.ox_root,
"CertificateFile", "Data", NULL)) == NULL) {
log_debug("%s: no data", __func__);
goto done;
}
/* Write CMS blob to temporary file */
strlcpy(tmp1, "/tmp/azure-cms.XXXXXXXX", sizeof(tmp1));
if ((fd = mkstemp(tmp1)) == -1) {
log_debug("%s: failed to write data", __func__);
goto done;
}
dprintf(fd, "MIME-Version: 1.0\n"
"Content-Disposition: attachment; filename=\"smime.p7m\"\n"
"Content-Type: application/pkcs7-mime;"
" smime-type=enveloped-data; name=\"smime.p7m\"\n"
"Content-Transfer-Encoding: base64\n"
"\n%s",
data->xe_data);
close(fd);
strlcpy(tmp2, "/tmp/azure-pkcs12.XXXXXXXX", sizeof(tmp2));
if ((fd = mkstemp(tmp2)) == -1) {
log_debug("%s: failed to write data", __func__);
goto done;
}
close(fd);
fd = disable_output(sc, STDERR_FILENO);
#ifdef USE_OPENSSL
/*
* XXX Now comes the part that needs CMS which is only
* XXX present in OpenSSL but got removed from LibreSSL.
*/
log_debug("%s: running openssl cms", __func__);
if (shell("/usr/local/bin/eopenssl", "cms", /* )) */
#else
if (shell("/usr/local/bin/cms",
#endif
"-decrypt", "-inkey", az->az_privkey, "-des3",
"-in", tmp1, "-out", tmp2, NULL) != 0) {
enable_output(sc, STDERR_FILENO, fd);
log_debug("%s: failed to decrypt CMS blob", __func__);
goto done;
}
unlink(tmp1);
/* Decrypt PKCS12 blob (now with LibreSSL) */
if (shell("openssl", "pkcs12",
"-nodes", "-password", "pass:",
"-in", tmp2, "-out", az->az_certs, NULL) != 0) {
enable_output(sc, STDERR_FILENO, fd);
log_debug("%s: failed to decrypt PKCS12 blob", __func__);
goto done;
}
unlink(tmp2);
enable_output(sc, STDERR_FILENO, fd);
/*
* XXX the following could be done using libcrypto directly
*/
ret = azure_getpubkeys(sc);
done:
unlink(tmp1);
unlink(tmp2);
xml_free(&xml);
return (ret);
}
int
azure_getpubkeys(struct system_config *sc)
{
struct azure_config *az = sc->sc_priv;
char buf[BUFSIZ];
char *in = NULL, *out = NULL, *p, *v;
FILE *fp;
int ret = -1;
FILE *infp = NULL;
char *inbuf;
size_t inbufsz;
if ((fp = fopen(az->az_certs, "r")) == NULL) {
log_debug("%s: failed to read certificates", __func__);
goto done;
}
/* Read all certificates */
while (fgets(buf, sizeof(buf), fp) != NULL) {
buf[strcspn(buf, "\r\n")] = '\0';
if (strcmp("-----BEGIN CERTIFICATE-----", buf) == 0) {
if ((infp = open_memstream(&inbuf, &inbufsz)) == NULL) {
log_debug("%s: failed to write cert", __func__);
goto done;
}
} else if (infp == NULL)
continue;
fprintf(infp, "%s\n", buf);
if (strcmp("-----END CERTIFICATE-----", buf) == 0) {
fclose(infp);
infp = NULL;
/* Convert certificate into public key */
if (shellout(inbuf, &in,
"openssl", "x509", "-fingerprint", "-pubkey",
"-noout", NULL) != 0) {
log_debug("%s: could not get public key",
__func__);
goto done;
}
free(inbuf);
inbuf = NULL;
/* Convert public key into SSH key */
if (shellout(in, &out,
"ssh-keygen", "-i", "-m", "PKCS8",
"-f", "/dev/stdin", NULL) == -1) {
log_debug("%s: could not get ssh key",
__func__);
goto done;
}
/* Get public key fingerprint */
if ((p = strstr(in, "Fingerprint=")) == NULL) {
log_debug("%s: could not get fingerprint",
__func__);
goto done;
}
p[strcspn(p, "\r\n")] = '\0';
p += strlen("Fingerprint=");
/* Strip colons */
for (v = p + strlen(p); v != p; v--)
if (*v == ':')
memmove(v, v + 1, strlen(v));
if (agent_setpubkey(sc, out, p) > 0)
log_debug("%s: public key %s", __func__, p);
free(in);
in = NULL;
free(out);
out = NULL;
}
}
ret = 0;
done:
free(inbuf);
free(in);
free(out);
return (ret);
}
static int
azure_reporthealth(struct system_config *sc, const char *message)
{
struct azure_config *az = sc->sc_priv;
struct httpget *g = NULL;
struct httphead *httph, *reqhead[2];
const char *errstr = NULL;
size_t httphsz, i;
int ret = -1;
char *req;
int reqsz;
const char *state;
reqhead[0] = &(struct httphead){
"Content-Type", "text/xml; charset=utf-8"
};
reqhead[1] = NULL;
if (strcmp("Ready", message) == 0) {
state = "<State>Ready</State>";
} else {
state =
"<State>NotReady</State>\n"
"<Details>\n"
"<SubStatus>Provisioning</SubStatus>\n"
"<Description>Starting</Description>\n"
"</Details>";
}
reqsz = asprintf(&req,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n"
"<GoalStateIncarnation>%u</GoalStateIncarnation>\n"
"<Container>\n"
"<ContainerId>%s</ContainerId>\n"
"<RoleInstanceList>\n"
"<Role>\n"
"<InstanceId>%s</InstanceId>\n"
"<Health>%s</Health>\n"
"</Role>\n"
"</RoleInstanceList>\n"
"</Container>\n"
"</Health>\n",
az->az_incarnation,
az->az_container,
sc->sc_instance,
state);
if (reqsz == -1)
goto done;
if ((g = azure_request(sc, NULL, "/machine/?comp=health",
req, reqsz, reqhead)) == NULL)
goto done;
httph = http_head_parse(g->http, g->xfer, &httphsz);
for (i = 0; i < httphsz; i++) {
if (strcmp(httph[i].key,
"x-ms-latest-goal-state-incarnation-number") == 0) {
az->az_incarnation =
strtonum(httph[i].val, 1, INT_MAX, &errstr);
if (errstr != NULL) {
log_debug("%s: unexpected incarnation: %s",
__func__, errstr);
goto done;
}
ret = 0;
break;
}
}
if (ret != 0)
goto done;
log_debug("%s: %s, incarnation %u", __func__,
message, az->az_incarnation);
done:
http_get_free(g);
return (ret);
}
static int
azure_getovfenv(struct system_config *sc)
{
struct xml xml;
struct xmlelem *xp, *xe, *xk, *xv;
const char *sshfp, *sshval;
int mount = 0, ret = -1, fd = -1;
FILE *fp;
/* try to mount the cdrom */
if (shell("mount", "-r", sc->sc_cdrom, "/mnt", NULL) == 0) {
log_debug("%s: mounted %s", __func__, sc->sc_cdrom);
mount = 1;
}
if (xml_init(&xml) != 0) {
log_debug("%s: xml", __func__);
goto done;
}
xml_parse(&xml, "/mnt/ovf-env.xml");
/* unmount if we mounted the cdrom before */
if (mount && shell("umount", "/mnt", NULL) == 0) {
log_debug("%s: unmounted %s", __func__, sc->sc_cdrom);
}
if ((xp = xml_findl(&xml.ox_root,
"Environment", "wa:ProvisioningSection",
"LinuxProvisioningConfigurationSet", NULL)) == NULL) {
log_debug("%s: could not find OVF structure", __func__);
goto done;
}
if ((xe = xml_findl(&xp->xe_head,
"SSH", "PublicKeys", NULL)) != NULL) {
/* Find all (optional) SSH keys */
TAILQ_FOREACH(xk, &xe->xe_head, xe_entry) {
if (strcasecmp(xk->xe_tag, "PublicKey") != 0)
continue;
sshfp = sshval = NULL;
if ((xv = xml_findl(&xk->xe_head,
"Fingerprint", NULL)) != NULL)
sshfp = xv->xe_data;
if ((xv = xml_findl(&xk->xe_head,
"Value", NULL)) != NULL)
sshval = xv->xe_data;
if (agent_addpubkey(sc, sshval, sshfp) != 0)
log_warnx("failed to add ssh pubkey");
}
}
if ((xe = xml_findl(&xp->xe_head, "HostName", NULL)) != NULL) {
if ((sc->sc_hostname = strdup(xe->xe_data)) == NULL) {
log_debug("%s: hostname failed", __func__);
goto done;
}
}
if ((xe = xml_findl(&xp->xe_head, "UserName", NULL)) != NULL) {
free(sc->sc_username);
if ((sc->sc_username = strdup(xe->xe_data)) == NULL) {
log_debug("%s: username failed", __func__);
goto done;
}
}
if ((xe = xml_findl(&xp->xe_head, "UserPassword", NULL)) != NULL) {
if ((sc->sc_password = calloc(1, 128)) == NULL ||
crypt_newhash(xe->xe_data, "bcrypt,a",
sc->sc_password, 128) != 0) {
log_debug("%s: password failed", __func__);
goto done;
}
}
if ((fd = open(sc->sc_ovfenv, O_WRONLY|O_CREAT|O_TRUNC, 0600)) == -1 ||
(fp = fdopen(fd, "w")) == NULL) {
log_debug("%s: failed to open %s", __func__, sc->sc_ovfenv);
goto done;
}
xml_print(&xml, TAILQ_FIRST(&xml.ox_root), 0, fp);
fclose(fp);
log_debug("%s: wrote %s", __func__, sc->sc_ovfenv);
ret = 0;
done:
if (fd != -1)
close(fd);
xml_free(&xml);
return (ret);
}
static int
azure_getendpoint(struct system_config *sc)
{
char path[PATH_MAX], buf[BUFSIZ], *ep = NULL;
int a[4];
FILE *fp;
if ((size_t)snprintf(path, sizeof(path), "/var/db/dhclient.leases.%s",
sc->sc_interface) >= sizeof(path)) {
log_debug("%s: invalid path", __func__);
return (-1);
}
if ((fp = fopen(path, "r")) == NULL) {
log_debug("%s: failed to open %s", __func__, path);
return (-1);
}
while (fgets(buf, sizeof(buf), fp) != NULL) {
buf[strcspn(buf, ";\n")] = '\0';
/* Find last occurence of option-245 */
if (sscanf(buf, " option option-245 %x:%x:%x:%x",
&a[0], &a[1], &a[2], &a[3]) == 4) {
free(ep);
if (asprintf(&ep, "%d.%d.%d.%d",
a[0], a[1], a[2], a[3]) == -1) {
log_debug("%s: asprintf", __func__);
return (-1);
}
}
}
fclose(fp);
if (ep == NULL) {
log_debug("%s: endpoint not found", __func__);
return (-1);
}
sc->sc_endpoint = ep;
sc->sc_addr.ip = sc->sc_endpoint;
sc->sc_addr.family = 4;
log_debug("%s: %s", __func__, ep);
return (0);
}

117
agent/cloudinit.c Normal file
View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/queue.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include "main.h"
#include "http.h"
#include "xml.h"
static int cloudinit_fetch(struct system_config *);
static char *cloudinit_get(struct system_config *, const char *, size_t *);
int
ec2(struct system_config *sc)
{
if ((sc->sc_username = strdup("ec2-user")) == NULL ||
(sc->sc_endpoint = strdup("169.254.169.254")) == NULL) {
log_warnx("failed to set defaults");
return (-1);
}
return (cloudinit_fetch(sc));
}
int
cloudinit(struct system_config *sc)
{
/* XXX get endpoint from DHCP lease file */
if ((sc->sc_username = strdup("puffy")) == NULL ||
(sc->sc_endpoint = strdup("169.254.169.254")) == NULL) {
log_warnx("failed to set defaults");
return (-1);
}
return (cloudinit_fetch(sc));
}
static char *
cloudinit_get(struct system_config *sc, const char *path, size_t *strsz)
{
struct httpget *g = NULL;
char *str = NULL;
log_debug("%s: %s", __func__, path);
g = http_get(&sc->sc_addr, 1,
sc->sc_endpoint, 80, path, NULL, 0, NULL);
if (g != NULL && g->code == 200 && g->bodypartsz > 0) {
if ((str = calloc(1, g->bodypartsz + 1)) != NULL) {
memcpy(str, g->bodypart, g->bodypartsz);
if (strsz != NULL)
*strsz = g->bodypartsz;
}
}
http_get_free(g);
return (str);
}
static int
cloudinit_fetch(struct system_config *sc)
{
int ret = 0;
char *str = NULL;
sc->sc_addr.ip = sc->sc_endpoint;
sc->sc_addr.family = 4;
/* hostname */
if ((sc->sc_instance = cloudinit_get(sc,
"/latest/meta-data/instance-id", NULL)) == NULL)
goto fail;
/* hostname */
if ((sc->sc_hostname = cloudinit_get(sc,
"/latest/meta-data/local-hostname", NULL)) == NULL)
goto fail;
/* pubkey */
if ((str = cloudinit_get(sc,
"/latest/meta-data/public-keys/0/openssh-key", NULL)) == NULL)
goto fail;
if (agent_addpubkey(sc, str, NULL) != 0)
goto fail;
/* userdata */
if ((sc->sc_userdata = cloudinit_get(sc,
"/latest/user-data", &sc->sc_userdatalen)) == NULL)
goto fail;
ret = 0;
fail:
free(str);
return (ret);
}

818
agent/http.c Normal file
View file

@ -0,0 +1,818 @@
/* $Id: http.c,v 1.20 2017/03/26 18:41:02 deraadt Exp $ */
/*
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include <err.h>
#include <limits.h>
#include <netdb.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <tls.h>
#include <unistd.h>
#include "http.h"
#define DEFAULT_CA_FILE "/etc/ssl/cert.pem"
/*
* A buffer for transferring HTTP/S data.
*/
struct httpxfer {
char *hbuf; /* header transfer buffer */
size_t hbufsz; /* header buffer size */
int headok; /* header has been parsed */
char *bbuf; /* body transfer buffer */
size_t bbufsz; /* body buffer size */
int bodyok; /* body has been parsed */
char *headbuf; /* lookaside buffer for headers */
struct httphead *head; /* parsed headers */
size_t headsz; /* number of headers */
};
/*
* An HTTP/S connection object.
*/
struct http {
int fd; /* connected socket */
short port; /* port number */
struct source src; /* endpoint (raw) host */
char *path; /* path to request */
char *host; /* name of endpoint host */
struct tls *ctx; /* if TLS */
writefp writer; /* write function */
readfp reader; /* read function */
};
struct tls_config *tlscfg;
static ssize_t
dosysread(char *buf, size_t sz, const struct http *http)
{
ssize_t rc;
rc = read(http->fd, buf, sz);
if (rc < 0)
warn("%s: read", http->src.ip);
return rc;
}
static ssize_t
dosyswrite(const void *buf, size_t sz, const struct http *http)
{
ssize_t rc;
rc = write(http->fd, buf, sz);
if (rc < 0)
warn("%s: write", http->src.ip);
return rc;
}
static ssize_t
dotlsread(char *buf, size_t sz, const struct http *http)
{
ssize_t rc;
do {
rc = tls_read(http->ctx, buf, sz);
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
if (rc < 0)
warnx("%s: tls_read: %s", http->src.ip,
tls_error(http->ctx));
return rc;
}
static ssize_t
dotlswrite(const void *buf, size_t sz, const struct http *http)
{
ssize_t rc;
do {
rc = tls_write(http->ctx, buf, sz);
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
if (rc < 0)
warnx("%s: tls_write: %s", http->src.ip,
tls_error(http->ctx));
return rc;
}
int
http_init()
{
if (tlscfg != NULL)
return 0;
if (tls_init() == -1) {
warn("tls_init");
goto err;
}
tlscfg = tls_config_new();
if (tlscfg == NULL) {
warn("tls_config_new");
goto err;
}
if (tls_config_set_ca_file(tlscfg, DEFAULT_CA_FILE) == -1) {
warn("tls_config_set_ca_file: %s", tls_config_error(tlscfg));
goto err;
}
return 0;
err:
tls_config_free(tlscfg);
tlscfg = NULL;
return -1;
}
static ssize_t
http_read(char *buf, size_t sz, const struct http *http)
{
ssize_t ssz, xfer;
xfer = 0;
do {
if ((ssz = http->reader(buf, sz, http)) < 0)
return -1;
if (ssz == 0)
break;
xfer += ssz;
sz -= ssz;
buf += ssz;
} while (ssz > 0 && sz > 0);
return xfer;
}
static int
http_write(const char *buf, size_t sz, const struct http *http)
{
ssize_t ssz, xfer;
xfer = sz;
while (sz > 0) {
if ((ssz = http->writer(buf, sz, http)) < 0)
return -1;
sz -= ssz;
buf += (size_t)ssz;
}
return xfer;
}
void
http_disconnect(struct http *http)
{
int rc;
if (http->ctx != NULL) {
/* TLS connection. */
do {
rc = tls_close(http->ctx);
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
if (rc < 0)
warnx("%s: tls_close: %s", http->src.ip,
tls_error(http->ctx));
tls_free(http->ctx);
}
if (http->fd != -1) {
if (close(http->fd) == -1)
warn("%s: close", http->src.ip);
}
http->fd = -1;
http->ctx = NULL;
}
void
http_free(struct http *http)
{
if (http == NULL)
return;
http_disconnect(http);
free(http->host);
free(http->path);
free(http->src.ip);
free(http);
}
struct http *
http_alloc(const struct source *addrs, size_t addrsz,
const char *host, short port, const char *path)
{
struct sockaddr_storage ss;
int family, fd, c;
socklen_t len;
size_t cur, i = 0;
struct http *http;
/* Do this while we still have addresses to connect. */
again:
if (i == addrsz)
return NULL;
cur = i++;
/* Convert to PF_INET or PF_INET6 address from string. */
memset(&ss, 0, sizeof(struct sockaddr_storage));
if (addrs[cur].family == 4) {
family = PF_INET;
((struct sockaddr_in *)&ss)->sin_family = AF_INET;
((struct sockaddr_in *)&ss)->sin_port = htons(port);
c = inet_pton(AF_INET, addrs[cur].ip,
&((struct sockaddr_in *)&ss)->sin_addr);
len = sizeof(struct sockaddr_in);
} else if (addrs[cur].family == 6) {
family = PF_INET6;
((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
c = inet_pton(AF_INET6, addrs[cur].ip,
&((struct sockaddr_in6 *)&ss)->sin6_addr);
len = sizeof(struct sockaddr_in6);
} else {
warnx("%s: unknown family", addrs[cur].ip);
goto again;
}
if (c < 0) {
warn("%s: inet_ntop", addrs[cur].ip);
goto again;
} else if (c == 0) {
warnx("%s: inet_ntop", addrs[cur].ip);
goto again;
}
/* Create socket and connect. */
fd = socket(family, SOCK_STREAM, 0);
if (fd == -1) {
warn("%s: socket", addrs[cur].ip);
goto again;
} else if (connect(fd, (struct sockaddr *)&ss, len) == -1) {
warn("%s: connect", addrs[cur].ip);
close(fd);
goto again;
}
/* Allocate the communicator. */
http = calloc(1, sizeof(struct http));
if (http == NULL) {
warn("calloc");
close(fd);
return NULL;
}
http->fd = fd;
http->port = port;
http->src.family = addrs[cur].family;
http->src.ip = strdup(addrs[cur].ip);
http->host = strdup(host);
http->path = strdup(path);
if (http->src.ip == NULL || http->host == NULL || http->path == NULL) {
warn("strdup");
goto err;
}
/* If necessary, do our TLS setup. */
if (port != 443) {
http->writer = dosyswrite;
http->reader = dosysread;
return http;
}
http->writer = dotlswrite;
http->reader = dotlsread;
if ((http->ctx = tls_client()) == NULL) {
warn("tls_client");
goto err;
} else if (tls_configure(http->ctx, tlscfg) == -1) {
warnx("%s: tls_configure: %s",
http->src.ip, tls_error(http->ctx));
goto err;
}
if (tls_connect_socket(http->ctx, http->fd, http->host) != 0) {
warnx("%s: tls_connect_socket: %s, %s", http->src.ip,
http->host, tls_error(http->ctx));
goto err;
}
return http;
err:
http_free(http);
return NULL;
}
static int
http_head_write(struct httphead **head, const struct http *http)
{
char *req = NULL;
int i, c;
if (head == NULL)
return (0);
for (i = 0; head[i] != NULL && head[i]->key != NULL; i++) {
/* Append terminating \r\n after last header line */
c = asprintf(&req, "%s: %s\r\n%s", head[i]->key, head[i]->val,
head[i + 1] == NULL ? "\r\n" : "");
if (!http_write(req, c, http)) {
free(req);
return (-1);
}
free(req);
}
return (0);
}
struct httpxfer *
http_open(const struct http *http, const void *p, size_t psz,
struct httphead **reqhead)
{
char *req;
int c;
struct httpxfer *trans;
if (p == NULL) {
c = asprintf(&req,
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n%s",
http->path, http->host,
reqhead != NULL ? "" : "\r\n");
} else {
c = asprintf(&req,
"POST %s HTTP/1.0\r\n"
"Host: %s\r\n"
"Content-Length: %zu\r\n%s",
http->path, http->host, psz,
reqhead != NULL ? "" : "\r\n");
}
if (c == -1) {
warn("asprintf");
return NULL;
} else if (!http_write(req, c, http)) {
free(req);
return NULL;
} else if (http_head_write(reqhead, http) != 0) {
free(req);
return NULL;
} else if (p != NULL && !http_write(p, psz, http)) {
free(req);
return NULL;
}
free(req);
trans = calloc(1, sizeof(struct httpxfer));
if (trans == NULL)
warn("calloc");
return trans;
}
void
http_close(struct httpxfer *x)
{
if (x == NULL)
return;
free(x->hbuf);
free(x->bbuf);
free(x->headbuf);
free(x->head);
free(x);
}
/*
* Read the HTTP body from the wire.
* If invoked multiple times, this will return the same pointer with the
* same data (or NULL, if the original invocation returned NULL).
* Returns NULL if read or allocation errors occur.
* You must not free the returned pointer.
*/
char *
http_body_read(const struct http *http, struct httpxfer *trans, size_t *sz)
{
char buf[BUFSIZ];
ssize_t ssz;
void *pp;
size_t szp;
if (sz == NULL)
sz = &szp;
/* Have we already parsed this? */
if (trans->bodyok > 0) {
*sz = trans->bbufsz;
return trans->bbuf;
} else if (trans->bodyok < 0)
return NULL;
*sz = 0;
trans->bodyok = -1;
do {
/* If less than sizeof(buf), at EOF. */
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
return NULL;
else if (ssz == 0)
break;
pp = recallocarray(trans->bbuf,
trans->bbufsz, trans->bbufsz + ssz, 1);
if (pp == NULL) {
warn("recallocarray");
return NULL;
}
trans->bbuf = pp;
memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
trans->bbufsz += ssz;
} while (ssz == sizeof(buf));
trans->bodyok = 1;
*sz = trans->bbufsz;
return trans->bbuf;
}
struct httphead *
http_head_get(const char *v, struct httphead *h, size_t hsz)
{
size_t i;
for (i = 0; i < hsz; i++) {
if (strcmp(h[i].key, v))
continue;
return &h[i];
}
return NULL;
}
/*
* Look through the headers and determine our HTTP code.
* This will return -1 on failure, otherwise the code.
*/
int
http_head_status(const struct http *http, struct httphead *h, size_t sz)
{
int rc;
unsigned int code;
struct httphead *st;
if ((st = http_head_get("Status", h, sz)) == NULL) {
warnx("%s: no status header", http->src.ip);
return -1;
}
rc = sscanf(st->val, "%*s %u %*s", &code);
if (rc < 0) {
warn("sscanf");
return -1;
} else if (rc != 1) {
warnx("%s: cannot convert status header", http->src.ip);
return -1;
}
return code;
}
/*
* Parse headers from the transfer.
* Malformed headers are skipped.
* A special "Status" header is added for the HTTP status line.
* This can only happen once http_head_read has been called with
* success.
* This can be invoked multiple times: it will only parse the headers
* once and after that it will just return the cache.
* You must not free the returned pointer.
* If the original header parse failed, or if memory allocation fails
* internally, this returns NULL.
*/
struct httphead *
http_head_parse(const struct http *http, struct httpxfer *trans, size_t *sz)
{
size_t hsz, szp;
struct httphead *h;
char *cp, *ep, *ccp, *buf;
if (sz == NULL)
sz = &szp;
/*
* If we've already parsed the headers, return the
* previously-parsed buffer now.
* If we have errors on the stream, return NULL now.
*/
if (trans->head != NULL) {
*sz = trans->headsz;
return trans->head;
} else if (trans->headok <= 0)
return NULL;
if ((buf = strdup(trans->hbuf)) == NULL) {
warn("strdup");
return NULL;
}
hsz = 0;
cp = buf;
do {
if ((cp = strstr(cp, "\r\n")) != NULL)
cp += 2;
hsz++;
} while (cp != NULL);
/*
* Allocate headers, then step through the data buffer, parsing
* out headers as we have them.
* We know at this point that the buffer is NUL-terminated in
* the usual way.
*/
h = calloc(hsz, sizeof(struct httphead));
if (h == NULL) {
warn("calloc");
free(buf);
return NULL;
}
*sz = hsz;
hsz = 0;
cp = buf;
do {
if ((ep = strstr(cp, "\r\n")) != NULL) {
*ep = '\0';
ep += 2;
}
if (hsz == 0) {
h[hsz].key = "Status";
h[hsz++].val = cp;
continue;
}
/* Skip bad headers. */
if ((ccp = strchr(cp, ':')) == NULL) {
warnx("%s: header without separator", http->src.ip);
continue;
}
*ccp++ = '\0';
while (isspace((int)*ccp))
ccp++;
h[hsz].key = cp;
h[hsz++].val = ccp;
} while ((cp = ep) != NULL);
trans->headbuf = buf;
trans->head = h;
trans->headsz = hsz;
return h;
}
/*
* Read the HTTP headers from the wire.
* If invoked multiple times, this will return the same pointer with the
* same data (or NULL, if the original invocation returned NULL).
* Returns NULL if read or allocation errors occur.
* You must not free the returned pointer.
*/
char *
http_head_read(const struct http *http, struct httpxfer *trans, size_t *sz)
{
char buf[BUFSIZ];
ssize_t ssz;
char *ep;
void *pp;
size_t szp;
if (sz == NULL)
sz = &szp;
/* Have we already parsed this? */
if (trans->headok > 0) {
*sz = trans->hbufsz;
return trans->hbuf;
} else if (trans->headok < 0)
return NULL;
*sz = 0;
ep = NULL;
trans->headok = -1;
/*
* Begin by reading by BUFSIZ blocks until we reach the header
* termination marker (two CRLFs).
* We might read into our body, but that's ok: we'll copy out
* the body parts into our body buffer afterward.
*/
do {
/* If less than sizeof(buf), at EOF. */
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
return NULL;
else if (ssz == 0)
break;
pp = recallocarray(trans->hbuf,
trans->hbufsz, trans->hbufsz + ssz, 1);
if (pp == NULL) {
warn("recallocarray");
return NULL;
}
trans->hbuf = pp;
memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
trans->hbufsz += ssz;
/* Search for end of headers marker. */
ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
} while (ep == NULL && ssz == sizeof(buf));
if (ep == NULL) {
warnx("%s: partial transfer", http->src.ip);
return NULL;
}
*ep = '\0';
/*
* The header data is invalid if it has any binary characters in
* it: check that now.
* This is important because we want to guarantee that all
* header keys and pairs are properly NUL-terminated.
*/
if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
warnx("%s: binary data in header", http->src.ip);
return NULL;
}
/*
* Copy remaining buffer into body buffer.
*/
ep += 4;
trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
trans->bbuf = malloc(trans->bbufsz);
if (trans->bbuf == NULL) {
warn("malloc");
return NULL;
}
memcpy(trans->bbuf, ep, trans->bbufsz);
trans->headok = 1;
*sz = trans->hbufsz;
return trans->hbuf;
}
void
http_get_free(struct httpget *g)
{
if (g == NULL)
return;
http_close(g->xfer);
http_free(g->http);
free(g);
}
struct httpget *
http_get(const struct source *addrs, size_t addrsz, const char *domain,
short port, const char *path, const void *post, size_t postsz,
struct httphead **reqhead)
{
struct http *h;
struct httpxfer *x;
struct httpget *g;
struct httphead *head;
size_t headsz, bodsz, headrsz;
int code;
char *bod, *headr;
h = http_alloc(addrs, addrsz, domain, port, path);
if (h == NULL)
return NULL;
if ((x = http_open(h, post, postsz, reqhead)) == NULL) {
http_free(h);
return NULL;
} else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {
http_close(x);
http_free(h);
return NULL;
} else if ((bod = http_body_read(h, x, &bodsz)) == NULL) {
http_close(x);
http_free(h);
return NULL;
}
http_disconnect(h);
if ((head = http_head_parse(h, x, &headsz)) == NULL) {
http_close(x);
http_free(h);
return NULL;
} else if ((code = http_head_status(h, head, headsz)) < 0) {
http_close(x);
http_free(h);
return NULL;
}
if ((g = calloc(1, sizeof(struct httpget))) == NULL) {
warn("calloc");
http_close(x);
http_free(h);
return NULL;
}
g->headpart = headr;
g->headpartsz = headrsz;
g->bodypart = bod;
g->bodypartsz = bodsz;
g->head = head;
g->headsz = headsz;
g->code = code;
g->xfer = x;
g->http = h;
return g;
}
#if 0
int
main(void)
{
struct httpget *g;
struct httphead *httph;
size_t i, httphsz;
struct source addrs[2];
size_t addrsz;
struct httphead *reqhead[3];
/* This could be allocated ;) */
reqhead[0] = &(struct httphead){ "X-Hello", "World" };
reqhead[1] = &(struct httphead){ "X-Test", "123" };
reqhead[2] = NULL;
#if 0
addrs[0].ip = "127.0.0.1";
addrs[0].family = 4;
addrsz = 1;
#else
addrs[0].ip = "2a00:1450:400a:806::2004";
addrs[0].family = 6;
addrs[1].ip = "193.135.3.123";
addrs[1].family = 4;
addrsz = 2;
#endif
if (http_init() == -1)
errx(EXIT_FAILURE, "http_init");
#if 0
g = http_get(addrs, addrsz, "localhost", 80, "/index.html",
NULL, 0, reqhead);
#else
g = http_get(addrs, addrsz, "www.google.ch", 80, "/index.html",
NULL, 0, reqhead);
#endif
if (g == NULL)
errx(EXIT_FAILURE, "http_get");
httph = http_head_parse(g->http, g->xfer, &httphsz);
warnx("code: %d", g->code);
for (i = 0; i < httphsz; i++)
warnx("head: [%s]=[%s]", httph[i].key, httph[i].val);
http_get_free(g);
return (EXIT_SUCCESS);
}
#endif

93
agent/http.h Normal file
View file

@ -0,0 +1,93 @@
/* $Id: http.h,v 1.5 2017/01/25 13:52:53 inoguchi Exp $ */
/*
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef HTTP_H
#define HTTP_H
struct source {
int family; /* 4 (PF_INET) or 6 (PF_INET6) */
char *ip; /* IPV4 or IPV6 address */
};
struct http;
/*
* Write and read callbacks to allow HTTP and HTTPS.
* Both of these return the number of bytes read (or written) or -1 on
* failure.
* 0 bytes read means that the connection has closed.
*/
typedef ssize_t (*writefp)(const void *, size_t, const struct http *);
typedef ssize_t (*readfp)(char *, size_t, const struct http *);
/*
* HTTP/S header pair.
* There's also a cooked-up pair, "Status", with the status code.
* Both strings are NUL-terminated.
*/
struct httphead {
const char *key;
const char *val;
};
/*
* Grab all information from a transfer.
* DO NOT free any parts of this, and editing the parts (e.g., changing
* the underlying strings) will persist; so in short, don't.
* All of these values will be set upon http_get() success.
*/
struct httpget {
struct httpxfer *xfer; /* underlying transfer */
struct http *http; /* underlying connection */
int code; /* return code */
struct httphead *head; /* headers */
size_t headsz; /* number of headers */
char *headpart; /* header buffer */
size_t headpartsz; /* size of headpart */
char *bodypart; /* body buffer */
size_t bodypartsz; /* size of bodypart */
};
int http_init(void);
/* Convenience functions. */
struct httpget *http_get(const struct source *, size_t,
const char *, short, const char *,
const void *, size_t, struct httphead **);
void http_get_free(struct httpget *);
/* Allocation and release. */
struct http *http_alloc(const struct source *, size_t,
const char *, short, const char *);
void http_free(struct http *);
struct httpxfer *http_open(const struct http *, const void *, size_t,
struct httphead **);
void http_close(struct httpxfer *);
void http_disconnect(struct http *);
/* Access. */
char *http_head_read(const struct http *,
struct httpxfer *, size_t *);
struct httphead *http_head_parse(const struct http *,
struct httpxfer *, size_t *);
char *http_body_read(const struct http *,
struct httpxfer *, size_t *);
int http_head_status(const struct http *,
struct httphead *, size_t);
struct httphead *http_head_get(const char *,
struct httphead *, size_t);
#endif /* HTTP_H */

218
agent/log.c Normal file
View file

@ -0,0 +1,218 @@
/* $OpenBSD: log.c,v 1.35 2017/03/21 12:06:56 bluhm Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <time.h>
static int debug;
static int verbose;
const char *log_procname;
void log_init(int, int);
void log_procinit(const char *);
void log_setverbose(int);
int log_getverbose(void);
void log_warn(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_warnx(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_info(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_debug(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void logit(int, const char *, ...)
__attribute__((__format__ (printf, 2, 3)));
void vlog(int, const char *, va_list)
__attribute__((__format__ (printf, 2, 0)));
__dead void fatal(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
__dead void fatalx(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void
log_init(int n_debug, int facility)
{
extern char *__progname;
debug = n_debug;
verbose = n_debug;
log_procinit(__progname);
if (!debug)
openlog(__progname, LOG_PID | LOG_NDELAY, facility);
tzset();
}
void
log_procinit(const char *procname)
{
if (procname != NULL)
log_procname = procname;
}
void
log_setverbose(int v)
{
verbose = v;
}
int
log_getverbose(void)
{
return (verbose);
}
void
logit(int pri, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vlog(pri, fmt, ap);
va_end(ap);
}
void
vlog(int pri, const char *fmt, va_list ap)
{
char *nfmt;
int saved_errno = errno;
if (debug) {
/* best effort in out of mem situations */
if (asprintf(&nfmt, "%s\n", fmt) == -1) {
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
} else {
vfprintf(stderr, nfmt, ap);
free(nfmt);
}
fflush(stderr);
} else
vsyslog(pri, fmt, ap);
errno = saved_errno;
}
void
log_warn(const char *emsg, ...)
{
char *nfmt;
va_list ap;
int saved_errno = errno;
/* best effort to even work in out of memory situations */
if (emsg == NULL)
logit(LOG_ERR, "%s", strerror(saved_errno));
else {
va_start(ap, emsg);
if (asprintf(&nfmt, "%s: %s", emsg,
strerror(saved_errno)) == -1) {
/* we tried it... */
vlog(LOG_ERR, emsg, ap);
logit(LOG_ERR, "%s", strerror(saved_errno));
} else {
vlog(LOG_ERR, nfmt, ap);
free(nfmt);
}
va_end(ap);
}
errno = saved_errno;
}
void
log_warnx(const char *emsg, ...)
{
va_list ap;
va_start(ap, emsg);
vlog(LOG_ERR, emsg, ap);
va_end(ap);
}
void
log_info(const char *emsg, ...)
{
va_list ap;
va_start(ap, emsg);
vlog(LOG_INFO, emsg, ap);
va_end(ap);
}
void
log_debug(const char *emsg, ...)
{
va_list ap;
if (verbose > 1) {
va_start(ap, emsg);
vlog(LOG_DEBUG, emsg, ap);
va_end(ap);
}
}
static void
vfatalc(int code, const char *emsg, va_list ap)
{
static char s[BUFSIZ];
const char *sep;
if (emsg != NULL) {
(void)vsnprintf(s, sizeof(s), emsg, ap);
sep = ": ";
} else {
s[0] = '\0';
sep = "";
}
if (code)
logit(LOG_CRIT, "%s: %s%s%s",
log_procname, s, sep, strerror(code));
else
logit(LOG_CRIT, "%s%s%s", log_procname, sep, s);
}
void
fatal(const char *emsg, ...)
{
va_list ap;
va_start(ap, emsg);
vfatalc(errno, emsg, ap);
va_end(ap);
exit(1);
}
void
fatalx(const char *emsg, ...)
{
va_list ap;
va_start(ap, emsg);
vfatalc(0, emsg, ap);
va_end(ap);
exit(1);
}

625
agent/main.c Normal file
View file

@ -0,0 +1,625 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/wait.h>
#include <sys/socket.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "main.h"
#include "xml.h"
__dead void usage(void);
static struct system_config *agent_init(void);
static void agent_free(struct system_config *);
static int agent_pf(struct system_config *, int);
static void agent_unconfigure(void);
int
shell(const char *arg, ...)
{
const char **argv, *a;
int argc, i = 0, status;
va_list ap;
pid_t pid, child_pid;
struct sigaction sigint, sigquit;
sigset_t mask, omask;
/* create arguments */
va_start(ap, arg);
for (argc = 2; va_arg(ap, const char *) != NULL; argc++)
;
va_end(ap);
if ((argv = calloc(argc, sizeof(const char *))) == NULL)
fatal("%s: calloc", __func__);
argv[i++] = arg;
va_start(ap, arg);
while ((a = va_arg(ap, char *)) != NULL)
argv[i++] = a;
va_end(ap);
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &omask);
/* run command in forked process */
switch (child_pid = fork()) {
case -1:
sigprocmask(SIG_SETMASK, &omask, NULL);
free(argv);
return (-1);
case 0:
sigprocmask(SIG_SETMASK, &omask, NULL);
execvp(argv[0], (char *const *)(caddr_t)argv);
_exit(127);
}
free(argv);
sigaction(SIGINT, NULL, &sigint);
sigaction(SIGQUIT, NULL, &sigquit);
do {
pid = waitpid(child_pid, &status, 0);
} while (pid == -1 && errno == EINTR);
sigprocmask(SIG_SETMASK, &omask, NULL);
sigaction(SIGINT, &sigint, NULL);
sigaction(SIGQUIT, &sigquit, NULL);
/* Simplified return value: returns 0 on success and -1 on error */
if (pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0)
return (0);
return (-1);
}
int
shellout(const char *in, char **out, const char *arg, ...)
{
const char **argv = NULL, *a;
int argc, i = 0, status;
va_list ap;
pid_t pid, child_pid;
struct sigaction sigint, sigquit;
sigset_t mask, omask;
FILE *outfp = NULL, *fp = NULL;
char *outbuf;
size_t outbufsz;
char buf[BUFSIZ];
int fdi[2], fdo[2];
if (out)
*out = NULL;
/* create arguments */
va_start(ap, arg);
for (argc = 2; va_arg(ap, const char *) != NULL; argc++)
;
va_end(ap);
if ((argv = calloc(argc, sizeof(const char *))) == NULL)
fatal("%s: calloc", __func__);
argv[i++] = arg;
va_start(ap, arg);
while ((a = va_arg(ap, char *)) != NULL)
argv[i++] = a;
va_end(ap);
if (in && socketpair(AF_UNIX,
SOCK_STREAM|SOCK_CLOEXEC, AF_UNSPEC, fdi) == -1)
goto fail;
if (out && socketpair(AF_UNIX,
SOCK_STREAM|SOCK_CLOEXEC, AF_UNSPEC, fdo) == -1)
goto fail;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &omask);
/* run command in forked process */
switch (child_pid = fork()) {
case -1:
sigprocmask(SIG_SETMASK, &omask, NULL);
goto fail;
case 0:
if (in) {
close(fdi[1]);
if (dup2(fdi[0], STDIN_FILENO) == -1)
_exit(127);
}
if (out) {
close(fdo[1]);
if (dup2(fdo[0], STDOUT_FILENO) == -1)
_exit(127);
}
sigprocmask(SIG_SETMASK, &omask, NULL);
execvp(argv[0], (char *const *)(caddr_t)argv);
_exit(127);
}
free(argv);
sigaction(SIGINT, NULL, &sigint);
sigaction(SIGQUIT, NULL, &sigquit);
if (in) {
close(fdi[0]);
if ((fp = fdopen(fdi[1], "w")) != NULL) {
fputs(in, fp);
fflush(fp);
fclose(fp);
}
close(fdi[1]);
}
if (out) {
close(fdo[0]);
if ((fp = fdopen(fdo[1], "r")) != NULL &&
(outfp = open_memstream(&outbuf, &outbufsz)) != NULL) {
while (fgets(buf, sizeof(buf), fp) != NULL) {
fputs(buf, outfp);
}
fclose(outfp);
*out = outbuf;
}
fclose(fp);
close(fdo[1]);
}
do {
pid = waitpid(child_pid, &status, 0);
} while (pid == -1 && errno == EINTR);
sigprocmask(SIG_SETMASK, &omask, NULL);
sigaction(SIGINT, &sigint, NULL);
sigaction(SIGQUIT, &sigquit, NULL);
/* Simplified return value: returns 0 on success and -1 on error */
if (pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0)
return (0);
fail:
free(argv);
if (out) {
free(*out);
*out = NULL;
}
return (-1);
}
int
disable_output(struct system_config *sc, int fd)
{
int oldfd;
if (log_getverbose() > 2)
return (-1);
if ((oldfd = dup(fd)) == -1 ||
dup2(sc->sc_nullfd, fd) == -1)
return (-1);
return (oldfd);
}
int
enable_output(struct system_config *sc, int fd, int oldfd)
{
if (oldfd == -1)
return (0);
close(fd);
if (dup2(oldfd, fd) == -1)
return (-1);
return (0);
}
static struct system_config *
agent_init(void)
{
struct system_config *sc;
if ((sc = calloc(1, sizeof(*sc))) == NULL)
return (NULL);
TAILQ_INIT(&sc->sc_pubkeys);
if ((sc->sc_nullfd = open("/dev/null", O_RDWR)) == -1) {
free(sc);
return (NULL);
}
return (sc);
}
static void
agent_free(struct system_config *sc)
{
struct ssh_pubkey *ssh;
free(sc->sc_hostname);
free(sc->sc_username);
free(sc->sc_password);
free(sc->sc_userdata);
free(sc->sc_endpoint);
free(sc->sc_instance);
close(sc->sc_nullfd);
while ((ssh = TAILQ_FIRST(&sc->sc_pubkeys))) {
free(ssh->ssh_keyval);
free(ssh->ssh_keyfp);
TAILQ_REMOVE(&sc->sc_pubkeys, ssh, ssh_entry);
free(ssh);
}
}
int
agent_addpubkey(struct system_config *sc, const char *sshval, const char *sshfp)
{
struct ssh_pubkey *ssh;
/* Ignore if neither key nor fingerprint is available */
if (sshval == NULL && sshfp == NULL)
return (0);
if ((ssh = calloc(1, sizeof(*ssh))) == NULL)
return (-1);
if (sshfp != NULL && (ssh->ssh_keyfp = strdup(sshfp)) == NULL) {
free(ssh);
return (-1);
}
if (sshval != NULL && (ssh->ssh_keyval = strdup(sshval)) == NULL) {
free(ssh->ssh_keyfp);
free(ssh);
return (-1);
}
TAILQ_INSERT_TAIL(&sc->sc_pubkeys, ssh, ssh_entry);
return (0);
}
int
agent_setpubkey(struct system_config *sc, const char *sshval, const char *sshfp)
{
struct ssh_pubkey *ssh;
int ret = 0;
char *v = NULL;
TAILQ_FOREACH(ssh, &sc->sc_pubkeys, ssh_entry) {
if (sshfp && ssh->ssh_keyfp &&
strcasecmp(ssh->ssh_keyfp, sshfp) == 0) {
if ((sshval == NULL) ||
(sshval && (v = strdup(sshval)) == NULL))
break;
v[strcspn(v, "\r\n")] = '\0';
free(ssh->ssh_keyval);
ssh->ssh_keyval = v;
ret++;
}
}
return (ret);
}
static int
fileout(const char *str, const char *mode, const char *fmt, ...)
{
FILE *fp;
va_list ap;
char *path;
int ret;
va_start(ap, fmt);
ret = vasprintf(&path, fmt, ap);
va_end(ap);
if (ret == -1)
return (-1);
if ((fp = fopen(path, mode)) == NULL) {
free(path);
return (-1);
}
if (str != NULL) {
fputs(str, fp);
if (strpbrk(str, "\r\n") == NULL)
fputs("\n", fp);
}
fclose(fp);
free(path);
return (0);
}
static char *
filein(const char *mode, const char *fmt, ...)
{
FILE *fp;
va_list ap;
char *path;
int ret;
char buf[BUFSIZ];
FILE *infp;
char *inbuf;
size_t inbufsz;
va_start(ap, fmt);
ret = vasprintf(&path, fmt, ap);
va_end(ap);
if (ret == -1)
return (NULL);
if ((fp = fopen(path, mode)) == NULL) {
free(path);
return (NULL);
}
free(path);
if ((infp = open_memstream(&inbuf, &inbufsz)) == NULL)
fclose(fp);
while (fgets(buf, sizeof(buf), fp) != NULL) {
fputs(buf, infp);
}
fclose(fp);
fclose(infp);
return (inbuf);
}
static int
agent_pf(struct system_config *sc, int open)
{
int ret;
if (shell("rcctl", "get", "pf", "status", NULL) != 0)
return (0);
if (open)
ret = shellout("pass out proto tcp from egress to port www\n",
NULL, "pfctl", "-f", "-", NULL);
else
ret = shellout("\n", NULL, "pfctl", "-f", "-", NULL);
return (ret);
}
int
agent_configure(struct system_config *sc, int noaction)
{
struct ssh_pubkey *ssh;
char *str1, *str2;
/* Skip configuration on the same instance */
if ((str1 = filein("r", "/var/db/cloud-instance")) != NULL &&
strcmp(sc->sc_instance, str1) == 0) {
free(str1);
return (0);
}
free(str1);
if (!noaction &&
fileout(sc->sc_instance, "w", "/var/db/cloud-instance") != 0)
log_warnx("instance failed");
/* hostname */
log_debug("%s: hostname %s", __func__, sc->sc_hostname);
if (!noaction &&
fileout(sc->sc_hostname, "w", "/etc/myname") != 0)
log_warnx("hostname failed");
else
(void)shell("hostname", sc->sc_hostname, NULL);
/* username */
log_debug("%s: username %s", __func__, sc->sc_username);
if (!noaction &&
shell("useradd", "-L", "staff", "-G", "wheel",
"-m", sc->sc_username, NULL) != 0)
log_warnx("username failed");
/* password */
if (sc->sc_password == NULL) {
str1 = "/PasswordAuthentication/"
"s/.*/PasswordAuthentication no/";
str2 = "permit keepenv nopass :wheel as root\n"
"permit keepenv nopass root\n";
} else {
if (!noaction &&
shell("usermod", "-p", sc->sc_password,
sc->sc_username, NULL) != 0)
log_warnx("password failed");
str1 = "/PasswordAuthentication/"
"s/.*/PasswordAuthentication yes/";
str2 = "permit keepenv persist :wheel as root\n"
"permit keepenv nopass root\n";
}
/* doas */
if (fileout(str2, "w", "/etc/doas.conf") != 0)
log_warnx("doas failed");
/* ssh configuration */
if (sc->sc_password == NULL && !TAILQ_EMPTY(&sc->sc_pubkeys))
str1 = "/PasswordAuthentication/"
"s/.*/PasswordAuthentication no/";
else
str1 = "/PasswordAuthentication/"
"s/.*/PasswordAuthentication yes/";
shell("sed", "-i", "-e", str1,
"-e", "/ClientAliveInterval/s/.*/ClientAliveInterval 180/",
"/etc/ssh/sshd_config",
NULL);
/* ssh public keys */
TAILQ_FOREACH(ssh, &sc->sc_pubkeys, ssh_entry) {
if (ssh->ssh_keyval == NULL)
continue;
log_debug("%s: key %s", __func__, ssh->ssh_keyval);
if (!noaction &&
fileout(ssh->ssh_keyval, "a",
"/home/%s/.ssh/authorized_keys",
sc->sc_username) != 0)
log_warnx("public key failed");
}
if (sc->sc_userdata) {
/* XXX */
}
log_debug("%s: %s", __func__, "/etc/rc.firsttime");
if (!noaction && fileout("logger -s -t cloud-agent <<EOF\n"
"#############################################################\n"
"-----BEGIN SSH HOST KEY FINGERPRINTS-----\n"
"$(for _f in /etc/ssh/ssh_host_*_key.pub;"
" do ssh-keygen -lf ${_f}; done)\n"
"-----END SSH HOST KEY FINGERPRINTS-----\n"
"#############################################################\n"
"EOF\n",
"a", "/etc/rc.firsttime") != 0)
log_warnx("userdata failed");
return (0);
}
void
agent_unconfigure(void)
{
/* Disable root pasword */
(void)shell("chpass", "-a",
"root:*:0:0:daemon:0:0:Charlie &:/root:/bin/ksh", NULL);
/* Delete keys */
(void)shell("sh", "-c",
"rm -rf /etc/{iked,isakmpd}/{local.pub,private/local.key}"
" /etc/ssh/ssh_host_*"
" /etc/dhclient.conf /var/db/dhclient.leases.*"
" /tmp/{.[!.],}*", NULL);
/* Delete old seed files */
(void)fileout(NULL, "w", "/etc/random.seed");
(void)fileout(NULL, "w", "/var/db/host.random");
/* Clear logfiles */
(void)shell("sh", "-c",
"for _l in $(find /var/log -type f ! -name '*.gz' -size +0); do"
" >${_l}; "
"done", NULL);
(void)fileout("permit keepenv persist :wheel as root\n"
"permit keepenv nopass root\n", "w", "/etc/doas.conf");
}
__dead void
usage(void)
{
extern char *__progname;
fprintf(stderr, "usage: %s [-nuv] interface\n",
__progname);
exit(1);
}
int
main(int argc, char *const *argv)
{
struct system_config *sc;
int verbose = 0, noaction = 0, unconfigure = 0;
int ch, ret;
while ((ch = getopt(argc, argv, "nvu")) != -1) {
switch (ch) {
case 'n':
noaction = 1;
break;
case 'v':
verbose += 2;
break;
case 'u':
unconfigure = 1;
break;
default:
usage();
}
}
argv += optind;
argc -= optind;
/* log to stderr */
log_init(1, LOG_DAEMON);
log_setverbose(verbose);
if (unconfigure) {
agent_unconfigure();
exit(0);
}
if (argc != 1)
usage();
if (pledge("stdio cpath rpath wpath exec proc dns inet", NULL) == -1)
fatal("pledge");
if ((sc = agent_init()) == NULL)
fatalx("agent");
sc->sc_interface = argv[0];
if (agent_pf(sc, 1) != 0)
fatalx("pf");
if (http_init() == -1)
fatalx("http_init");
/*
* XXX Detect cloud with help from hostctl and sysctl
* XXX in addition to the interface name.
*/
if (strcmp("hvn0", sc->sc_interface) == 0)
ret = azure(sc);
else if (strcmp("xnf0", sc->sc_interface) == 0)
ret = ec2(sc);
else if (strcmp("vio0", sc->sc_interface) == 0)
ret = cloudinit(sc);
else
fatal("unsupported cloud interface %s", sc->sc_interface);
if (agent_pf(sc, 0) != 0)
fatalx("pf");
if (pledge("stdio cpath rpath wpath exec proc", NULL) == -1)
fatal("pledge");
if (ret == 0 && agent_configure(sc, noaction) != 0)
fatal("provisioning failed");
agent_free(sc);
return (0);
}

94
agent/main.h Normal file
View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MAIN_H
#define MAIN_H
#include <sys/queue.h>
#include <sys/cdefs.h>
#include <stdarg.h>
#include <stddef.h>
#include "http.h"
struct ssh_pubkey {
char *ssh_keyval;
char *ssh_keyfp;
TAILQ_ENTRY(ssh_pubkey) ssh_entry;
};
TAILQ_HEAD(ssh_pubkeys, ssh_pubkey);
struct system_config {
char *sc_hostname;
char *sc_username;
char *sc_password;
char *sc_pubkey;
unsigned char *sc_userdata;
size_t sc_userdatalen;
char *sc_endpoint;
char *sc_instance;
const char *sc_ovfenv;
const char *sc_interface;
const char *sc_cdrom;
struct source sc_addr;
struct ssh_pubkeys sc_pubkeys;
int sc_nullfd;
void *sc_priv;
};
/* azure.c */
int azure(struct system_config *);
/* cloudinit.c */
int ec2(struct system_config *);
int cloudinit(struct system_config *);
/* main.c */
int shell(const char *, ...);
int shellout(const char *, char **, const char *, ...);
int disable_output(struct system_config *, int);
int enable_output(struct system_config *, int, int);
int agent_addpubkey(struct system_config *, const char *, const char *);
int agent_setpubkey(struct system_config *, const char *, const char *);
int agent_configure(struct system_config *, int);
/* log.c */
void log_init(int, int);
void log_procinit(const char *);
void log_setverbose(int);
int log_getverbose(void);
void log_warn(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_warnx(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_info(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void log_debug(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
void logit(int, const char *, ...)
__attribute__((__format__ (printf, 2, 3)));
void vlog(int, const char *, va_list)
__attribute__((__format__ (printf, 2, 0)));
__dead void fatal(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
__dead void fatalx(const char *, ...)
__attribute__((__format__ (printf, 1, 2)));
#endif /* MAIN_H */

363
agent/xml.c Normal file
View file

@ -0,0 +1,363 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <ctype.h>
#include <err.h>
#include <expat.h>
#include "main.h"
#include "xml.h"
static void xml_start_element(void *, const char *, const char **);
static void xml_end_element(void *, const char *);
static void xml_char_data(void *, const char *, int);
static void xml_inc(struct xmlelem *, int);
static void
xml_inc(struct xmlelem *b, int depth)
{
struct xmlelem *xe;
b->xe_depth += depth;
TAILQ_FOREACH(xe, &b->xe_head, xe_entry)
xml_inc(xe, depth);
}
void
xml_add(struct xmlelem *a, struct xmlelem *b)
{
xml_inc(b, a->xe_depth);
TAILQ_INSERT_TAIL(&a->xe_head, b, xe_entry);
}
void
xml_delete(struct xmlhead *xh)
{
struct xmlelem *xe, *tmp;
int i;
TAILQ_FOREACH_SAFE(xe, xh, xe_entry, tmp) {
xml_delete(&xe->xe_head);
TAILQ_REMOVE(xh, xe, xe_entry);
if (xe->xe_attr != NULL) {
for (i = 0; xe->xe_attr[i] != NULL; i++)
free(xe->xe_attr[i]);
free(xe->xe_attr);
}
free(xe->xe_data);
free(xe->xe_tag);
free(xe);
}
}
struct xmlelem *
xml_get(struct xmlhead *root, const char *arg)
{
struct xmlelem *xe;
TAILQ_FOREACH(xe, root, xe_entry) {
/* search case-insensitive */
if (strcasecmp(xe->xe_tag, arg) == 0)
return (xe);
}
return (NULL);
}
struct xmlelem *
xml_findv(struct xmlhead *root, const char **argv, int argc)
{
struct xmlelem *xe = NULL;
struct xmlhead *head = root;
int i;
for (i = 0; i < argc; i++) {
if ((xe = xml_get(head, argv[i])) == NULL)
break;
head = &xe->xe_head;
}
return (xe);
}
struct xmlelem *
xml_findl(struct xmlhead *root, const char *arg, ...)
{
struct xmlelem *xe = NULL;
const char **argv = NULL;
int argc, i;
const char *tag;
va_list ap;
va_start(ap, arg);
for (argc = 1; va_arg(ap, const char *) != NULL; argc++)
;
va_end(ap);
if ((argv = calloc(argc, sizeof(const char *))) == NULL)
fatal("calloc");
i = 0;
argv[i++] = arg;
va_start(ap, arg);
while ((tag = va_arg(ap, const char *)) != NULL)
argv[i++] = tag;
va_end(ap);
xe = xml_findv(root, argv, argc);
free(argv);
return (xe);
}
/*
* Print XML tree suitable for OVF
*
* This parser and printer does not support CDATA with embedded
* elements which is not required for OVF - it is more or less a simple
* key/value store without HTML-like markup.
*/
void
xml_print(struct xml *env, struct xmlelem *xe, int data_only, FILE *fp)
{
struct xmlelem *xelm;
int i;
if (xe == NULL)
return;
if (data_only) {
if (xe->xe_datalen)
fprintf(fp, "%*s\n",
(int)xe->xe_datalen, xe->xe_data);
TAILQ_FOREACH(xelm, &xe->xe_head, xe_entry)
xml_print(env, xelm, data_only, fp);
return;
}
/* Print XML header for the root node */
if (xe->xe_parent == NULL)
fprintf(fp, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
fprintf(fp, "%*s<%s", xe->xe_depth * 2, "", xe->xe_tag);
for (i = 0; xe->xe_attr[i] != NULL; i += 2) {
fprintf(fp, " %s=\"%s\"",
xe->xe_attr[i], xe->xe_attr[i + 1]);
}
fprintf(fp, ">");
if (xe->xe_datalen)
fprintf(fp, "%*s",
(int)xe->xe_datalen, xe->xe_data);
if (!TAILQ_EMPTY(&xe->xe_head))
fprintf(fp, "\n");
TAILQ_FOREACH(xelm, &xe->xe_head, xe_entry)
xml_print(env, xelm, data_only, fp);
if (TAILQ_EMPTY(&xe->xe_head))
fprintf(fp, "</%s>\n", xe->xe_tag);
else
fprintf(fp, "%*s</%s>\n", xe->xe_depth * 2, "", xe->xe_tag);
}
/*
* Simple XML parser
*/
static void
xml_start_element(void *data, const char *el, const char **attr)
{
struct xml *env = data;
struct xmlelem *xe;
struct xmlhead *xh;
int i;
if ((xe = calloc(1, sizeof(*xe))) == NULL)
fatal("callac");
TAILQ_INIT(&xe->xe_head);
if (env->ox_cur == NULL)
xh = &env->ox_root;
else
xh = &env->ox_cur->xe_head;
xe->xe_parent = env->ox_cur;
xe->xe_depth = env->ox_depth;
if ((xe->xe_tag = strdup(el)) == NULL)
fatal("strdup");
TAILQ_INSERT_TAIL(xh, xe, xe_entry);
env->ox_cur = xe;
/* Copy attributes */
for (i = 0; attr[i] != NULL; i += 2)
;
xe->xe_nattr = i / 2;
if ((xe->xe_attr = calloc(i + 1, sizeof(char *))) == NULL)
fatal("calloc");
for (i = 0; attr[i] != NULL; i++) {
if ((xe->xe_attr[i] = strdup(attr[i])) == NULL)
fatal("strdup");
}
env->ox_depth++;
}
static void
xml_end_element(void *data, const char *el)
{
struct xml *env = data;
struct xmlelem *xe = env->ox_cur;
if (xe == NULL || strcmp(xe->xe_tag, el) != 0)
fatal("unexpected closing tag: %s <> %s", el, xe->xe_tag);
if (xe->xe_data == NULL)
xe->xe_data = strdup("");
env->ox_cur = xe->xe_parent;
env->ox_depth--;
}
static void
xml_char_data(void *data, const char *s, int len)
{
struct xml *env = data;
struct xmlelem *xe = env->ox_cur;
char *p;
int i;
int ok = 0;
off_t off = 0;
for (i = 0; i < len && s[i] != '\0'; i++) {
if (!isspace(s[i])) {
ok = 1;
break;
}
}
if (!ok)
return;
/* XXX there might be a better way to handle libexpat cdata */
if ((p = realloc(xe->xe_data, xe->xe_datalen + len + 2)) == NULL)
fatal("realloc");
if (xe->xe_datalen) {
p[xe->xe_datalen] = '\n';
off = 1;
}
memcpy(p + xe->xe_datalen + off, s, len);
p[xe->xe_datalen + off + len] = '\0';
xe->xe_data = p;
xe->xe_datalen += len + off;
env->ox_data = 1;
}
int
xml_init(struct xml *env)
{
XML_Parser parser;
memset(env, 0, sizeof(*env));
TAILQ_INIT(&env->ox_root);
if ((parser = XML_ParserCreate(NULL)) == NULL)
return (-1);
env->ox_parser = parser;
XML_SetUserData(parser, env);
XML_SetElementHandler(parser,
xml_start_element, xml_end_element);
XML_SetCharacterDataHandler(parser, xml_char_data);
return (0);
}
void
xml_free(struct xml *env)
{
if (env == NULL)
return;
if (env->ox_parser != NULL)
XML_ParserFree(env->ox_parser);
xml_delete(&env->ox_root);
memset(env, 0, sizeof(*env));
}
int
xml_parse_buffer(struct xml *env, char *xml, size_t xmllen)
{
XML_Parser parser = env->ox_parser;
if (XML_Parse(parser, xml, xmllen,
XML_TRUE) == XML_STATUS_ERROR)
return (-1);
XML_ParserFree(parser);
env->ox_parser = NULL;
if (TAILQ_EMPTY(&env->ox_root))
return (-1);
return (0);
}
int
xml_parse(struct xml *env, const char *file)
{
XML_Parser parser = env->ox_parser;
int fd;
void *xml;
ssize_t len;
if ((fd = open(file, O_RDONLY)) == -1)
fatal("open %s", file);
do {
if ((xml = XML_GetBuffer(parser, BUFSIZ)) == NULL)
fatalx("XML_GetBuffer");
if ((len = read(fd, xml, BUFSIZ)) <= 0)
break;
if (XML_ParseBuffer(parser, len, XML_FALSE) == XML_STATUS_ERROR)
fatalx("XML_ParseBuffer");
} while (len == BUFSIZ);
close(fd);
if (XML_Parse(parser, NULL, 0, XML_TRUE) == XML_STATUS_ERROR)
fatalx("XML_Parse");
XML_ParserFree(parser);
env->ox_parser = NULL;
return (0);
}

58
agent/xml.h Normal file
View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef OVFXML_H
#define OVFXML_H
#include <sys/queue.h>
#include <stddef.h>
TAILQ_HEAD(xmlhead, xmlelem);
struct xmlelem {
char *xe_tag;
char **xe_attr;
unsigned int xe_nattr;
unsigned int xe_depth;
char *xe_data;
size_t xe_datalen;
struct xmlelem *xe_parent;
struct xmlhead xe_head;
TAILQ_ENTRY(xmlelem) xe_entry;
};
struct xml {
int ox_depth;
int ox_data;
struct xmlhead ox_root;
struct xmlelem *ox_cur;
struct xmlelem *ox_prev;
void *ox_parser;
};
int xml_init(struct xml *);
void xml_free(struct xml *);
void xml_add(struct xmlelem *, struct xmlelem *);
void xml_delete(struct xmlhead *);
struct xmlelem *xml_get(struct xmlhead *, const char *);
struct xmlelem *xml_findv(struct xmlhead *, const char **, int);
struct xmlelem *xml_findl(struct xmlhead *, const char *, ...);
void xml_print(struct xml *, struct xmlelem *, int, FILE *);
int xml_parse_buffer(struct xml *, char *, size_t);
int xml_parse(struct xml *, const char *);
#endif /* OVFXML_H */