Initial commit
This commit is contained in:
commit
1c7148f25b
48 changed files with 19200 additions and 0 deletions
20
agent/Makefile
Normal file
20
agent/Makefile
Normal 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
791
agent/azure.c
Normal 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
117
agent/cloudinit.c
Normal 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
818
agent/http.c
Normal 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
93
agent/http.h
Normal 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
218
agent/log.c
Normal 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
625
agent/main.c
Normal 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
94
agent/main.h
Normal 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
363
agent/xml.c
Normal 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
58
agent/xml.h
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue