Initial commit
commit
1c7148f25b
@ -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>
|
@ -0,0 +1,4 @@
|
||||
NOTHING TO SEE HERE
|
||||
===================
|
||||
|
||||
*...yet*
|
@ -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>
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|