/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-2000 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, 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.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: amservice.c 11167 2008-05-06 11:53:54Z martineau $
*
* Take the REQ packet in stdin and output the REP packet in stdout
*/
#include "amanda.h"
#include "amutil.h"
#include "conffile.h"
#include "packet.h"
#include "protocol.h"
#include "amfeatures.h"
#include "event.h"
#include "getopt.h"
static struct option long_options[] = {
{"version" , 0, NULL, 1},
{"features" , 1, NULL, 2},
{"stream" , 1, NULL, 3},
{"config" , 1, NULL, 4},
{NULL, 0, NULL, 0}
};
static int copy_stream = 0;
static time_t conf_ctimeout;
static am_feature_t *our_features = NULL;
static char *our_feature_string = NULL;
static int remote_errors = 0;
static event_handle_t *event_in;
static security_stream_t *fd;
static gboolean event_paused = FALSE;
/* local functions */
void usage(void);
void client_protocol(char *hostname, char *auth, char *service, char *config,
FILE *input_file);
static void connect_streams(security_handle_t *sech);
static void client_first_stream(security_handle_t *sech, int port_num);
int main(int argc, char **argv);
static void read_stream_in(void *cookie);
static void read_stream_server(void *cookie, void *buf, ssize_t size);
static void read_in(void *cookie);
static void read_server(void *cookie, void *buf, ssize_t size);
void
usage(void)
{
fprintf(stderr, _("Usage: amservice [--version] [-o configoption]* [-f input_file [-s]]\n"
" [--config CONFIG] [--features FEATURES-STRING]\n"
" [--stream NAME,IN,OUT]* host auth service\n"));
exit(1);
/*NOTREACHED*/
}
typedef struct lstream_t {
char *name;
int fd_in;
int fd_out;
event_handle_t *event;
gboolean event_paused;
struct rstream_t *rstream;
} lstream_t;
typedef struct rstream_t {
char *name;
int port;
security_stream_t *fd;
lstream_t *lstream;
} rstream_t;
static int nb_lstream = 0;
static lstream_t lstreams[DATA_FD_COUNT];
static int nb_rstream = 0;
static rstream_t rstreams[DATA_FD_COUNT];
int
main(
int argc,
char ** argv)
{
config_overrides_t *cfg_ovr;
char *hostname;
char *auth;
char *service;
char *config = NULL;
int opt;
extern int optind;
extern char *optarg;
FILE *input_file;
int use_connect = 0;
int got_input_file = 0;
int i;
unsigned char gfd[32768];
glib_init();
/*
* Configure program for internationalization:
* 1) Only set the message locale for now.
* 2) Set textdomain for all amanda related programs to "amanda"
* We don't want to be forced to support dozens of message catalogs.
*/
setlocale(LC_MESSAGES, "C");
textdomain("amanda");
// safe_fd(-1, 0);
safe_cd();
set_pname("amservice");
if (geteuid() != getuid()) {
error(_("amservice must not be setuid root"));
}
/* drop root privileges */
set_root_privs(-1);
/* Don't die when child closes pipe */
signal(SIGPIPE, SIG_IGN);
dbopen(DBG_SUBDIR_SERVER);
add_amanda_log_handler(amanda_log_stderr);
our_features = am_init_feature_set();
our_feature_string = am_feature_to_string(our_features);
/* process arguments */
for (i=0; i<argc; i++) {
g_debug("argv[%d] = %s", i, argv[i]);
}
for (i = 0;i < 32768; i++) {
gfd[i] = 0;
}
cfg_ovr = new_config_overrides(argc/2);
input_file = stdin;
while((opt = getopt_long(argc, argv, "o:f:s", long_options, NULL)) != EOF) {
switch(opt) {
case 1: printf("amservice-%s\n", VERSION);
return(0);
break;
case 2: g_free(our_feature_string);
g_free(our_features);
our_feature_string = g_strdup(optarg);
our_features = am_string_to_feature(our_feature_string);
break;
case 3: { gchar *copy_optarg = g_strdup(optarg);
gchar *coma = strchr(copy_optarg, ',');
gchar *stream_in;
if (nb_lstream == DATA_FD_COUNT) {
g_critical("Too many --stream, maximum is %d",
DATA_FD_COUNT);
exit(1);
} else if (coma) {
*coma++ = '\0';
stream_in = coma;
coma = strchr(coma, ',');
if (coma) {
*coma++ = '\0';
lstreams[nb_lstream].name = g_strdup(copy_optarg);
lstreams[nb_lstream].fd_in = atoi(stream_in);
lstreams[nb_lstream].fd_out = atoi(coma);
gfd[lstreams[nb_lstream].fd_in] = 1;
gfd[lstreams[nb_lstream].fd_out] = 1;
nb_lstream++;
}
}
if (!coma) {
g_critical("Invalid --stream option (%s)", optarg);
exit(1);
}
g_free(copy_optarg);
break;
}
case 4: g_free(config);
config = g_strdup(optarg);
break;
case 'o': add_config_override_opt(cfg_ovr, optarg);
break;
case 'f': if (got_input_file == 1) {
g_critical("Invalid two -f argument");
exit(1);
}
got_input_file = 1;
if (*optarg == '/') {
input_file = fopen(optarg, "r");
} else {
char *name = g_strjoin(NULL, get_original_cwd(), "/",
optarg, NULL);
input_file = fopen(name, "r");
amfree(name);
}
if (!input_file) {
g_critical("Cannot open input file '%s': %s",
optarg, strerror(errno));
exit(1);
}
break;
case 's': use_connect = 1;
break;
}
}
if (use_connect && !got_input_file) {
g_critical("The -s option require -f");
exit(1);
}
/* close all unused fd */
for (i = 3;i < 32768; i++) {
if (gfd[i] == 0 && i != dbfd() &&
(!got_input_file || i != fileno(input_file))) {
close(i);
}
}
argc -= optind, argv += optind;
if(argc < 3) usage();
/* set a default config */
set_config_overrides(cfg_ovr);
config_init(CONFIG_INIT_CLIENT|CONFIG_INIT_GLOBAL, NULL);
if (config) {
config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY, config);
}
dbrename(get_config_name(), DBG_SUBDIR_SERVER);
if (config_errors(NULL) >= CFGERR_WARNINGS) {
config_print_errors();
if (config_errors(NULL) >= CFGERR_ERRORS) {
g_critical(_("errors processing config file"));
}
}
conf_ctimeout = (time_t)getconf_int(CNF_CTIMEOUT);
hostname = argv[0];
auth = argv[1];
if (g_str_equal(auth,"NULL")) {
auth = getconf_str(CNF_AUTH);
}
service = argv[2];
/* start client side checks */
copy_stream = use_connect && got_input_file;
client_protocol(hostname, auth, service, config, input_file);
amfree(our_feature_string);
am_release_feature_set(our_features);
our_features = NULL;
if (got_input_file)
fclose(input_file);
dbclose();
return(remote_errors != 0);
}
/* --------------------------------------------------- */
static void handle_result(void *, pkt_t *, security_handle_t *);
void start_host(char *hostname, char *auth, char *req);
void
start_host(
char *hostname,
char *auth,
char *req)
{
const security_driver_t *secdrv;
secdrv = security_getdriver(auth);
if (secdrv == NULL) {
fprintf(stderr, _("Could not find security driver \"%s\".\n"), auth);
} else {
protocol_sendreq(hostname, secdrv, generic_client_get_security_conf,
req, conf_ctimeout, handle_result, NULL);
}
}
void
client_protocol(
char *hostname,
char *auth,
char *service,
char *config,
FILE *input_file)
{
GString *strbuf = g_string_new(NULL);
char *req, *req1;
g_string_append_printf(strbuf, "SERVICE %s\nOPTIONS ", service);
g_string_append_printf(strbuf, "features=%s;", our_feature_string);
g_string_append_printf(strbuf, "hostname=%s;", hostname);
if (config) {
g_string_append_printf(strbuf, "config=%s;", config);
}
g_string_append_printf(strbuf, "\n");
req1 = g_malloc(1024);
while(fgets(req1, 1024, input_file) != NULL)
g_string_append(strbuf, req1);
g_free(req1);
protocol_init();
req = g_string_free(strbuf, FALSE);
start_host(hostname, auth, req);
protocol_run();
fflush(stdout);
amfree(our_feature_string);
return;
}
static void
handle_result(
void *datap G_GNUC_UNUSED,
pkt_t *pkt,
security_handle_t *sech)
{
char *line;
char *s;
int ch;
security_close_connection(sech, "AA");
if (pkt == NULL) {
g_fprintf(stdout,
_("Request failed: %s\n"), security_geterror(sech));
remote_errors++;
return;
}
s = pkt->body;
ch = *s++;
while(ch) {
line = s - 1;
skip_quoted_line(s, ch);
if (s[-2] == '\n') {
s[-2] = '\0';
}
if (copy_stream) {
g_debug("REP: %s\n", line);
} else {
fprintf(stdout, "%s\n", line);
}
if (g_str_has_prefix(line, "CONNECT ")) {
gchar *copy_line = g_strdup(line);
gchar *name;
gchar *port;
copy_line = strchr(copy_line, ' ');
if (copy_line) {
copy_line++;
name = copy_line;
while (copy_line && (copy_line = strchr(copy_line, ' '))) {
*copy_line++ = '\0';
port = copy_line;
copy_line = strchr(copy_line, ' ');
if (copy_line) {
*copy_line++ = '\0';
}
rstreams[nb_rstream].name = g_strdup(name);
rstreams[nb_rstream].port = atoi(port);
nb_rstream++;
name = copy_line;
}
} else {
remote_errors++;
}
} else if (g_str_has_prefix(line, "ERROR ")) {
if (copy_stream) {
fprintf(stdout, "%s\n", line);
}
remote_errors++;
}
}
if (remote_errors)
return;
if (copy_stream) {
client_first_stream(sech, rstreams[0].port);
} else if (nb_rstream > 0) {
fprintf(stdout, "\n");
fclose(stdout);
close(1);
connect_streams(sech);
} else {
fprintf(stdout, "\n");
fclose(stdout);
}
}
static void
connect_streams(
security_handle_t *sech)
{
int r, l;
for (r = 0; r < nb_rstream; r++) {
for (l = 0; l < nb_lstream; l++) {
if (g_str_equal(rstreams[r].name, lstreams[l].name)) {
security_stream_t *fd;
rstreams[r].lstream = &lstreams[l];
lstreams[l].rstream = &rstreams[r];
fd = security_stream_client(sech, rstreams[r].port);
if (!fd) {
g_critical("Could not connect to stream: security_stream_client failed\n");
exit(1);
}
if (security_stream_auth(fd) < 0) {
g_critical("could not authenticate stream: %s\n", security_stream_geterror(fd));
exit(1);
}
rstreams[r].fd = fd;
/* read from server */
lstreams[l].event = event_create((event_id_t)lstreams[l].fd_in, EV_READFD, read_stream_in, &lstreams[l]);
event_activate(lstreams[l].event);
/* read from connected stream */
security_stream_read(fd, read_stream_server, &rstreams[r]);
}
}
}
for (r = 0; r < nb_rstream; r++) {
if (!rstreams[r].lstream) {
g_critical("Remote stream '%s' is not redirected", rstreams[r].name);
exit(1);
}
}
for (l = 0; l < nb_lstream; l++) {
if (!lstreams[l].rstream) {
g_critical("Stream '%s' is not connected", lstreams[l].name);
exit(1);
}
}
}
#define STACK_SIZE 1048576
static void read_stream_in_callback(void *, ssize_t, void *, ssize_t);
static void
read_stream_in_callback(
void *cookie G_GNUC_UNUSED,
ssize_t stack_size,
void *buf,
ssize_t size G_GNUC_UNUSED)
{
int l;
g_free(buf);
/* re-enable events */
if (stack_size < STACK_SIZE && event_paused) {
for (l = 0; l < nb_lstream; l++) {
if (lstreams[l].event_paused) {
lstreams[l].event_paused = FALSE;
lstreams[l].event = event_create((event_id_t)lstreams[l].fd_in, EV_READFD, read_stream_in, &lstreams[l]);
event_activate(lstreams[l].event);
}
}
event_paused = FALSE;
}
}
static void
read_stream_in(
void *cookie)
{
lstream_t *lstream = cookie;
ssize_t nread;
char *buf = g_malloc(65536);
int stack_size;
int l;
nread = read(lstream->fd_in, buf, 65536);
if (nread <= 0) {
if (lstream->event) {
event_release(lstream->event);
lstream->event = NULL;
}
g_free(buf);
security_stream_close_async(lstream->rstream->fd, read_stream_in_callback, NULL);
return;
}
stack_size = security_stream_write_async(lstream->rstream->fd, buf, nread, read_stream_in_callback, NULL);
/* pause events */
if (stack_size > STACK_SIZE) {
for (l = 0; l < nb_lstream; l++) {
if (lstreams[l].event) {
event_release(lstreams[l].event);
lstreams[l].event_paused = TRUE;
} else {
lstreams[l].event_paused = FALSE;
}
}
event_paused = TRUE;
}
}
static void
read_stream_server(
void * cookie,
void * buf,
ssize_t size)
{
rstream_t *rstream = cookie;
ssize_t result;
switch (size) {
case -1:
case 0:
security_stream_read_cancel(rstream->fd);
close(rstream->lstream->fd_out);
rstream->lstream->fd_out = -1;
break;
default:
result = full_write(rstream->lstream->fd_out, buf, size);
if (result != size) {
g_debug("failed to write to fd %d: %s", rstream->lstream->fd_out, strerror(errno));
}
break;
}
}
static void
client_first_stream(
security_handle_t *sech,
int port_num)
{
if (port_num == 0) {
g_critical("The service did not ask to open stream, do not use '-s' with that service");
}
fd = security_stream_client(sech, port_num);
if (!fd) {
g_critical("Could not connect to stream: %s\n", security_stream_geterror(fd));
}
if (security_stream_auth(fd) < 0) {
g_critical("could not authenticate stream: %s\n", security_stream_geterror(fd));
}
printf("Connected\n");
/* read from stdin */
event_in = event_create((event_id_t)0, EV_READFD, read_in, NULL);
event_activate(event_in);
/* read from connected stream */
security_stream_read(fd, read_server, NULL);
}
static void
read_in(
void *cookie G_GNUC_UNUSED)
{
ssize_t nread;
char buf[1025];
nread = read(0, buf, 1024);
if (nread <= 0) {
event_release(event_in);
security_stream_close(fd);
return;
}
buf[nread] = '\0';
security_stream_write(fd, buf, nread);
}
static void
read_server(
void * cookie G_GNUC_UNUSED,
void * buf,
ssize_t size)
{
ssize_t result;
switch (size) {
case -1:
case 0: security_stream_close(fd);
event_release(event_in);
break;
default:
result = full_write(1, buf, size);
if (result != size) {
g_debug("failed to write to stdout: %s", strerror(errno));
}
break;
}
}