Browse Source

Initial commit

fix-cms
Reyk Floeter 6 years ago
commit
1c7148f25b
  1. 14
      Makefile
  2. 4
      README.md
  3. 20
      agent/Makefile
  4. 791
      agent/azure.c
  5. 117
      agent/cloudinit.c
  6. 818
      agent/http.c
  7. 93
      agent/http.h
  8. 218
      agent/log.c
  9. 625
      agent/main.c
  10. 94
      agent/main.h
  11. 363
      agent/xml.c
  12. 58
      agent/xml.h
  13. 3
      cms/Makefile
  14. 20
      cms/Makefile.inc
  15. 9
      cms/bin/Makefile
  16. 203
      cms/bin/app_rand.c
  17. 3251
      cms/bin/apps.c
  18. 385
      cms/bin/apps.h
  19. 1354
      cms/bin/cms.c
  20. 19
      cms/bin/libressl_apps.h
  21. 19
      cms/lib/Makefile
  22. 155
      cms/lib/asn1_locl.h
  23. 579
      cms/lib/asn1_mac.h
  24. 973
      cms/lib/asn1t.h
  25. 553
      cms/lib/cms.h
  26. 459
      cms/lib/cms_asn1.c
  27. 197
      cms/lib/cms_att.c
  28. 134
      cms/lib/cms_cd.c
  29. 145
      cms/lib/cms_dd.c
  30. 264
      cms/lib/cms_enc.c
  31. 977
      cms/lib/cms_env.c
  32. 309
      cms/lib/cms_err.c
  33. 394
      cms/lib/cms_ess.c
  34. 133
      cms/lib/cms_io.c
  35. 465
      cms/lib/cms_kari.c
  36. 471
      cms/lib/cms_lcl.h
  37. 656
      cms/lib/cms_lib.c
  38. 435
      cms/lib/cms_pwri.c
  39. 960
      cms/lib/cms_sd.c
  40. 837
      cms/lib/cms_smime.c
  41. 109
      cms/lib/cryptlib.h
  42. 370
      cms/lib/evp_locl.h
  43. 169
      cms/lib/libressl_cms.h
  44. 90
      cms/lib/libressl_evp.h
  45. 698
      cms/lib/libressl_pkey.c
  46. 147
      cms/lib/libressl_stubs.c
  47. BIN
      extras/ovf-env.iso
  48. 43
      extras/ovf-env.xml

14
Makefile

@ -0,0 +1,14 @@
#
# The Azure agents needs CMS to obtain the SSH public keys.
# LibreSSL has removed CMS, so either use OpenSSL to decrypt CMS
# messages or compile the old CMS code for LibreSSL.
#
.ifdef USE_OPENSSL
MAKE_FLAGS+= USE_OPENSSL=1
.else
SUBDIR= cms
.endif
SUBDIR+= agent
.include <bsd.subdir.mk>

4
README.md

@ -0,0 +1,4 @@
NOTHING TO SEE HERE
===================
*...yet*

20
agent/Makefile

@ -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

@ -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

@ -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

@ -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).