mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-01-13 16:30:33 +00:00
445 lines
12 KiB
C
445 lines
12 KiB
C
|
/*
|
||
|
* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MIT
|
||
|
*
|
||
|
* Portions created by Alan Antonuk are Copyright (c) 2012-2013
|
||
|
* Alan Antonuk. All Rights Reserved.
|
||
|
*
|
||
|
* Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc.
|
||
|
* All Rights Reserved.
|
||
|
*
|
||
|
* Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
|
||
|
* VMware, Inc. and Tony Garnock-Jones. All Rights Reserved.
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person
|
||
|
* obtaining a copy of this software and associated documentation
|
||
|
* files (the "Software"), to deal in the Software without
|
||
|
* restriction, including without limitation the rights to use, copy,
|
||
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||
|
* of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be
|
||
|
* included in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
* SOFTWARE.
|
||
|
* ***** END LICENSE BLOCK *****
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include "config.h"
|
||
|
#endif
|
||
|
|
||
|
#include "common.h"
|
||
|
#ifdef WITH_SSL
|
||
|
#include <amqp_ssl_socket.h>
|
||
|
#endif
|
||
|
#include <amqp_tcp_socket.h>
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#ifdef WINDOWS
|
||
|
#include "compat.h"
|
||
|
#endif
|
||
|
|
||
|
void die(const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
vfprintf(stderr, fmt, ap);
|
||
|
va_end(ap);
|
||
|
fprintf(stderr, "\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
void die_errno(int err, const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
|
||
|
if (err == 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
vfprintf(stderr, fmt, ap);
|
||
|
va_end(ap);
|
||
|
fprintf(stderr, ": %s\n", strerror(err));
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
void die_amqp_error(int err, const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
|
||
|
if (err >= 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
vfprintf(stderr, fmt, ap);
|
||
|
va_end(ap);
|
||
|
fprintf(stderr, ": %s\n", amqp_error_string2(err));
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
const char *amqp_server_exception_string(amqp_rpc_reply_t r) {
|
||
|
int res;
|
||
|
static char s[512];
|
||
|
|
||
|
switch (r.reply.id) {
|
||
|
case AMQP_CONNECTION_CLOSE_METHOD: {
|
||
|
amqp_connection_close_t *m = (amqp_connection_close_t *)r.reply.decoded;
|
||
|
res = snprintf(s, sizeof(s), "server connection error %d, message: %.*s",
|
||
|
m->reply_code, (int)m->reply_text.len,
|
||
|
(char *)m->reply_text.bytes);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case AMQP_CHANNEL_CLOSE_METHOD: {
|
||
|
amqp_channel_close_t *m = (amqp_channel_close_t *)r.reply.decoded;
|
||
|
res = snprintf(s, sizeof(s), "server channel error %d, message: %.*s",
|
||
|
m->reply_code, (int)m->reply_text.len,
|
||
|
(char *)m->reply_text.bytes);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
res = snprintf(s, sizeof(s), "unknown server error, method id 0x%08X",
|
||
|
r.reply.id);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return res >= 0 ? s : NULL;
|
||
|
}
|
||
|
|
||
|
const char *amqp_rpc_reply_string(amqp_rpc_reply_t r) {
|
||
|
switch (r.reply_type) {
|
||
|
case AMQP_RESPONSE_NORMAL:
|
||
|
return "normal response";
|
||
|
|
||
|
case AMQP_RESPONSE_NONE:
|
||
|
return "missing RPC reply type";
|
||
|
|
||
|
case AMQP_RESPONSE_LIBRARY_EXCEPTION:
|
||
|
return amqp_error_string2(r.library_error);
|
||
|
|
||
|
case AMQP_RESPONSE_SERVER_EXCEPTION:
|
||
|
return amqp_server_exception_string(r);
|
||
|
|
||
|
default:
|
||
|
abort();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void die_rpc(amqp_rpc_reply_t r, const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
|
||
|
if (r.reply_type == AMQP_RESPONSE_NORMAL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
vfprintf(stderr, fmt, ap);
|
||
|
va_end(ap);
|
||
|
fprintf(stderr, ": %s\n", amqp_rpc_reply_string(r));
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
static char *amqp_url;
|
||
|
static char *amqp_server;
|
||
|
static int amqp_port = -1;
|
||
|
static char *amqp_vhost;
|
||
|
static char *amqp_username;
|
||
|
static char *amqp_password;
|
||
|
static int amqp_heartbeat = 0;
|
||
|
#ifdef WITH_SSL
|
||
|
static int amqp_ssl = 0;
|
||
|
static char *amqp_cacert = "/etc/ssl/certs/cacert.pem";
|
||
|
static char *amqp_key = NULL;
|
||
|
static char *amqp_cert = NULL;
|
||
|
#endif /* WITH_SSL */
|
||
|
|
||
|
const char *connect_options_title = "Connection options";
|
||
|
struct poptOption connect_options[] = {
|
||
|
{"url", 'u', POPT_ARG_STRING, &amqp_url, 0, "the AMQP URL to connect to",
|
||
|
"amqp://..."},
|
||
|
{"server", 's', POPT_ARG_STRING, &amqp_server, 0,
|
||
|
"the AMQP server to connect to", "hostname"},
|
||
|
{"port", 0, POPT_ARG_INT, &amqp_port, 0, "the port to connect on", "port"},
|
||
|
{"vhost", 0, POPT_ARG_STRING, &amqp_vhost, 0,
|
||
|
"the vhost to use when connecting", "vhost"},
|
||
|
{"username", 0, POPT_ARG_STRING, &amqp_username, 0,
|
||
|
"the username to login with", "username"},
|
||
|
{"password", 0, POPT_ARG_STRING, &amqp_password, 0,
|
||
|
"the password to login with", "password"},
|
||
|
{"heartbeat", 0, POPT_ARG_INT, &amqp_heartbeat, 0,
|
||
|
"heartbeat interval, set to 0 to disable", "heartbeat"},
|
||
|
#ifdef WITH_SSL
|
||
|
{"ssl", 0, POPT_ARG_NONE, &amqp_ssl, 0, "connect over SSL/TLS", NULL},
|
||
|
{"cacert", 0, POPT_ARG_STRING, &amqp_cacert, 0,
|
||
|
"path to the CA certificate file", "cacert.pem"},
|
||
|
{"key", 0, POPT_ARG_STRING, &amqp_key, 0,
|
||
|
"path to the client private key file", "key.pem"},
|
||
|
{"cert", 0, POPT_ARG_STRING, &amqp_cert, 0,
|
||
|
"path to the client certificate file", "cert.pem"},
|
||
|
#endif /* WITH_SSL */
|
||
|
{NULL, '\0', 0, NULL, 0, NULL, NULL}};
|
||
|
|
||
|
static void init_connection_info(struct amqp_connection_info *ci) {
|
||
|
ci->user = NULL;
|
||
|
ci->password = NULL;
|
||
|
ci->host = NULL;
|
||
|
ci->port = -1;
|
||
|
ci->vhost = NULL;
|
||
|
ci->user = NULL;
|
||
|
|
||
|
amqp_default_connection_info(ci);
|
||
|
|
||
|
if (amqp_url)
|
||
|
die_amqp_error(amqp_parse_url(strdup(amqp_url), ci), "Parsing URL '%s'",
|
||
|
amqp_url);
|
||
|
|
||
|
if (amqp_server) {
|
||
|
char *colon;
|
||
|
if (amqp_url) {
|
||
|
die("--server and --url options cannot be used at the same time");
|
||
|
}
|
||
|
|
||
|
/* parse the server string into a hostname and a port */
|
||
|
colon = strchr(amqp_server, ':');
|
||
|
if (colon) {
|
||
|
char *port_end;
|
||
|
size_t host_len;
|
||
|
|
||
|
/* Deprecate specifying the port number with the
|
||
|
--server option, because it is not ipv6 friendly.
|
||
|
--url now allows connection options to be
|
||
|
specified concisely. */
|
||
|
fprintf(stderr,
|
||
|
"Specifying the port number with --server is deprecated\n");
|
||
|
|
||
|
host_len = colon - amqp_server;
|
||
|
ci->host = malloc(host_len + 1);
|
||
|
memcpy(ci->host, amqp_server, host_len);
|
||
|
ci->host[host_len] = 0;
|
||
|
|
||
|
if (amqp_port >= 0) {
|
||
|
die("both --server and --port options specify server port");
|
||
|
}
|
||
|
|
||
|
ci->port = strtol(colon + 1, &port_end, 10);
|
||
|
if (ci->port < 0 || ci->port > 65535 || port_end == colon + 1 ||
|
||
|
*port_end != 0)
|
||
|
die("bad server port number in '%s'", amqp_server);
|
||
|
}
|
||
|
|
||
|
#if WITH_SSL
|
||
|
if (amqp_ssl && !ci->ssl) {
|
||
|
die("the --ssl option specifies an SSL connection"
|
||
|
" but the --url option does not");
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (amqp_port >= 0) {
|
||
|
if (amqp_url) {
|
||
|
die("--port and --url options cannot be used at the same time");
|
||
|
}
|
||
|
|
||
|
ci->port = amqp_port;
|
||
|
}
|
||
|
|
||
|
if (amqp_username) {
|
||
|
if (amqp_url) {
|
||
|
die("--username and --url options cannot be used at the same time");
|
||
|
}
|
||
|
|
||
|
ci->user = amqp_username;
|
||
|
}
|
||
|
|
||
|
if (amqp_password) {
|
||
|
if (amqp_url) {
|
||
|
die("--password and --url options cannot be used at the same time");
|
||
|
}
|
||
|
|
||
|
ci->password = amqp_password;
|
||
|
}
|
||
|
|
||
|
if (amqp_vhost) {
|
||
|
if (amqp_url) {
|
||
|
die("--vhost and --url options cannot be used at the same time");
|
||
|
}
|
||
|
|
||
|
ci->vhost = amqp_vhost;
|
||
|
}
|
||
|
|
||
|
if (amqp_heartbeat < 0) {
|
||
|
die("--heartbeat must be a positive value");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
amqp_connection_state_t make_connection(void) {
|
||
|
int status;
|
||
|
amqp_socket_t *socket = NULL;
|
||
|
struct amqp_connection_info ci;
|
||
|
amqp_connection_state_t conn;
|
||
|
|
||
|
init_connection_info(&ci);
|
||
|
conn = amqp_new_connection();
|
||
|
if (ci.ssl) {
|
||
|
#ifdef WITH_SSL
|
||
|
socket = amqp_ssl_socket_new(conn);
|
||
|
if (!socket) {
|
||
|
die("creating SSL/TLS socket");
|
||
|
}
|
||
|
if (amqp_cacert) {
|
||
|
amqp_ssl_socket_set_cacert(socket, amqp_cacert);
|
||
|
}
|
||
|
if (amqp_key) {
|
||
|
amqp_ssl_socket_set_key(socket, amqp_cert, amqp_key);
|
||
|
}
|
||
|
#else
|
||
|
die("librabbitmq was not built with SSL/TLS support");
|
||
|
#endif
|
||
|
} else {
|
||
|
socket = amqp_tcp_socket_new(conn);
|
||
|
if (!socket) {
|
||
|
die("creating TCP socket (out of memory)");
|
||
|
}
|
||
|
}
|
||
|
status = amqp_socket_open(socket, ci.host, ci.port);
|
||
|
if (status) {
|
||
|
die("opening socket to %s:%d", ci.host, ci.port);
|
||
|
}
|
||
|
die_rpc(amqp_login(conn, ci.vhost, 0, 131072, amqp_heartbeat,
|
||
|
AMQP_SASL_METHOD_PLAIN, ci.user, ci.password),
|
||
|
"logging in to AMQP server");
|
||
|
if (!amqp_channel_open(conn, 1)) {
|
||
|
die_rpc(amqp_get_rpc_reply(conn), "opening channel");
|
||
|
}
|
||
|
return conn;
|
||
|
}
|
||
|
|
||
|
void close_connection(amqp_connection_state_t conn) {
|
||
|
int res;
|
||
|
die_rpc(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), "closing channel");
|
||
|
die_rpc(amqp_connection_close(conn, AMQP_REPLY_SUCCESS),
|
||
|
"closing connection");
|
||
|
|
||
|
res = amqp_destroy_connection(conn);
|
||
|
die_amqp_error(res, "closing connection");
|
||
|
}
|
||
|
|
||
|
amqp_bytes_t read_all(int fd) {
|
||
|
size_t space = 4096;
|
||
|
amqp_bytes_t bytes;
|
||
|
|
||
|
bytes.bytes = malloc(space);
|
||
|
bytes.len = 0;
|
||
|
|
||
|
for (;;) {
|
||
|
ssize_t res = read(fd, (char *)bytes.bytes + bytes.len, space - bytes.len);
|
||
|
if (res == 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (res < 0) {
|
||
|
if (errno == EINTR) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
die_errno(errno, "reading");
|
||
|
}
|
||
|
|
||
|
bytes.len += res;
|
||
|
if (bytes.len == space) {
|
||
|
space *= 2;
|
||
|
bytes.bytes = realloc(bytes.bytes, space);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bytes;
|
||
|
}
|
||
|
|
||
|
void write_all(int fd, amqp_bytes_t data) {
|
||
|
while (data.len > 0) {
|
||
|
ssize_t res = write(fd, data.bytes, data.len);
|
||
|
if (res < 0) {
|
||
|
die_errno(errno, "write");
|
||
|
}
|
||
|
|
||
|
data.len -= res;
|
||
|
data.bytes = (char *)data.bytes + res;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void copy_body(amqp_connection_state_t conn, int fd) {
|
||
|
size_t body_remaining;
|
||
|
amqp_frame_t frame;
|
||
|
|
||
|
int res = amqp_simple_wait_frame(conn, &frame);
|
||
|
die_amqp_error(res, "waiting for header frame");
|
||
|
if (frame.frame_type != AMQP_FRAME_HEADER) {
|
||
|
die("expected header, got frame type 0x%X", frame.frame_type);
|
||
|
}
|
||
|
|
||
|
body_remaining = frame.payload.properties.body_size;
|
||
|
while (body_remaining) {
|
||
|
res = amqp_simple_wait_frame(conn, &frame);
|
||
|
die_amqp_error(res, "waiting for body frame");
|
||
|
if (frame.frame_type != AMQP_FRAME_BODY) {
|
||
|
die("expected body, got frame type 0x%X", frame.frame_type);
|
||
|
}
|
||
|
|
||
|
write_all(fd, frame.payload.body_fragment);
|
||
|
body_remaining -= frame.payload.body_fragment.len;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
poptContext process_options(int argc, const char **argv,
|
||
|
struct poptOption *options, const char *help) {
|
||
|
int c;
|
||
|
poptContext opts = poptGetContext(NULL, argc, argv, options, 0);
|
||
|
poptSetOtherOptionHelp(opts, help);
|
||
|
|
||
|
while ((c = poptGetNextOpt(opts)) >= 0) {
|
||
|
/* no options require explicit handling */
|
||
|
}
|
||
|
|
||
|
if (c < -1) {
|
||
|
fprintf(stderr, "%s: %s\n", poptBadOption(opts, POPT_BADOPTION_NOALIAS),
|
||
|
poptStrerror(c));
|
||
|
poptPrintUsage(opts, stderr, 0);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
return opts;
|
||
|
}
|
||
|
|
||
|
void process_all_options(int argc, const char **argv,
|
||
|
struct poptOption *options) {
|
||
|
poptContext opts = process_options(argc, argv, options, "[OPTIONS]...");
|
||
|
const char *opt = poptPeekArg(opts);
|
||
|
|
||
|
if (opt) {
|
||
|
fprintf(stderr, "unexpected operand: %s\n", opt);
|
||
|
poptPrintUsage(opts, stderr, 0);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
poptFreeContext(opts);
|
||
|
}
|
||
|
|
||
|
amqp_bytes_t cstring_bytes(const char *str) {
|
||
|
return str ? amqp_cstring_bytes(str) : amqp_empty_bytes;
|
||
|
}
|