int main(void) { struct kerberos_config *config; const char *err; struct remctl *r; struct remctl_output *output; const char *command[] = { "test", "env", "REMOTE_ADDR", NULL }; /* Set up Kerberos and remctld. */ config = kerberos_setup(TAP_KRB_NEEDS_KEYTAB); remctld_start(config, "data/conf-simple", (char *) 0); plan(10); /* Successful connection to 127.0.0.1. */ r = remctl_new(); ok(r != NULL, "remctl_new"); ok(remctl_set_source_ip(r, "127.0.0.1"), "remctl_set_source_ip"); ok(remctl_open(r, "127.0.0.1", 14373, config->principal), "remctl_open to 127.0.0.1"); ok(remctl_command(r, command), "remctl_command"); output = remctl_output(r); ok(output != NULL, "remctl_output"); if (output == NULL) ok_block(3, 0, "remctl_output failed"); else { is_int(REMCTL_OUT_OUTPUT, output->type, "output token"); ok(memcmp("127.0.0.1\n", output->data, strlen("127.0.0.1\n")) == 0, "correct IP address"); is_int(strlen("127.0.0.1\n"), output->length, "correct length"); } /* Failed connection to ::1. */ ok(!remctl_open(r, "::1", 14373, config->principal), "remctl_open to ::1"); err = remctl_error(r); diag("error: %s", err); ok(((strncmp("cannot connect to ::1 (port 14373): ", err, strlen("cannot connect to ::1 (port 14373): ")) == 0) || (strncmp("unknown host ::1: ", err, strlen("unknown host ::1: ")) == 0)), "failed with correct error"); remctl_close(r); return 0; }
/* * Get the responses back from the server, taking appropriate action on each * one depending on its type. Sets the errorcode parameter to the exit status * of the remote command, or to 1 if the remote command failed with an error. * Returns true on success, false if some protocol-level error occurred when * reading the responses. */ static bool process_response(struct remctl *r, int *errorcode) { struct remctl_output *out; *errorcode = 0; out = remctl_output(r); while (out != NULL && out->type != REMCTL_OUT_DONE) { switch (out->type) { case REMCTL_OUT_OUTPUT: if (out->stream == 1) fwrite_checked(out->data, out->length, 1, stdout); else if (out->stream == 2) fwrite_checked(out->data, out->length, 1, stderr); else { warn("unknown output stream %d", out->stream); fwrite_checked(out->data, out->length, 1, stderr); } break; case REMCTL_OUT_ERROR: *errorcode = 255; fwrite_checked(out->data, out->length, 1, stderr); fputc('\n', stderr); return true; case REMCTL_OUT_STATUS: *errorcode = out->status; return true; case REMCTL_OUT_DONE: break; } out = remctl_output(r); } if (out == NULL) { die("error reading from server: %s", remctl_error(r)); return false; } else return true; }
int main(void) { struct kerberos_config *config; const char *cache; struct remctl *r; struct remctl_output *output; int status; const char *command[] = { "test", "test", NULL }; /* Set up Kerberos and remctld. */ config = kerberos_setup(TAP_KRB_NEEDS_KEYTAB); remctld_start(config, "data/conf-simple", (char *) 0); plan(12); /* Get the current ticket cache and then change KRB5CCNAME. */ cache = getenv("KRB5CCNAME"); if (cache == NULL) bail("failed to set KRB5CCNAME"); putenv((char *) "KRB5CCNAME=./nonexistent-file"); /* Connecting without setting the ticket cache should fail. */ r = remctl_new(); ok(r != NULL, "remctl_new"); ok(!remctl_open(r, "127.0.0.1", 14373, config->principal), "remctl_open to 127.0.0.1"); /* Set the ticket cache and connect to 127.0.0.1 and run a command. */ status = remctl_set_ccache(r, cache); if (!status) { is_string("setting credential cache not supported", remctl_error(r), "remctl_set_ccache failed with correct error"); skip_block(9, "credential cache setting not supported"); } else { ok(remctl_set_ccache(r, cache), "remctl_set_ccache"); ok(remctl_open(r, "127.0.0.1", 14373, config->principal), "remctl_open to 127.0.0.1"); ok(remctl_command(r, command), "remctl_command"); output = remctl_output(r); ok(output != NULL, "remctl_output #1"); if (output == NULL) ok_block(3, 0, "remctl_output failed"); else { is_int(REMCTL_OUT_OUTPUT, output->type, "output token"); ok(memcmp("hello world\n", output->data, strlen("hello world\n")) == 0, "correct output"); is_int(strlen("hello world\n"), output->length, "correct length"); } output = remctl_output(r); ok(output != NULL, "remctl_output #2"); if (output == NULL) ok_block(2, 0, "remctl_output failed"); else { is_int(REMCTL_OUT_STATUS, output->type, "status token"); is_int(0, output->status, "status is correct"); } } remctl_close(r); return 0; }
/* * Main routine. Parse the arguments, open the remctl connection, send the * command, and then call process_response. */ int main(int argc, char *argv[]) { int option, status; char *server_host; struct addrinfo hints, *ai; const char *source = NULL; const char *service_name = NULL; unsigned short port = 0; struct remctl *r; int errorcode = 0; /* Set up logging and identity. */ message_program_name = "remctl"; if (!socket_init()) die("failed to initialize socket library"); /* * Parse options. The + tells GNU getopt to stop option parsing at the * first non-argument rather than proceeding on to find options anywhere. * Without this, it's hard to call remote programs that take options. * Non-GNU getopt will treat the + as a supported option, which is handled * below. */ while ((option = getopt(argc, argv, "+b:dhp:s:v")) != EOF) { switch (option) { case 'b': source = optarg; break; case 'd': message_handlers_debug(1, message_log_stderr); break; case 'h': usage(0); break; case 'p': port = atoi(optarg); break; case 's': service_name = optarg; break; case 'v': printf("%s\n", PACKAGE_STRING); exit(0); break; case '+': fprintf(stderr, "%s: invalid option -- +\n", argv[0]); default: usage(1); break; } } argc -= optind; argv += optind; if (argc < 2) usage(1); server_host = *argv++; argc--; /* * If service_name isn't set, the remctl library uses host/<server> * (host@<server> in GSS-API parlance). However, if the server to which * we're connecting is a DNS-load-balanced name, we have to be careful * what principal name we use. * * Ideally, we would let the GSS-API library handle this and choose * whether to canonicalize the <server> in the principal name based on the * krb5.conf rdns setting and similar configuration. However, with DNS * load balancing, this still may fail. At the time of network * connection, we will connect to whatever the name resolves to then. * After we connect, we authenticate, and the GSS-API library will then * separately canonicalize the hostname. It could get a different answer * than we got for our network connection, leading to an authentication * failure. * * Therefore, if the principal isn't specified, we canonicalize the * hostname to which we're connecting before we connect. Then, the * additional canonicalization possibly done by the GSS-API library should * return the same results and be consistent. * * Note that this opens the possibility of a subtle attack through DNS * spoofing, since both the principal used and the host to which we're * connecting can be changed by varying the DNS response. * * If the principal is specified explicitly, assume the user knows what * they're doing and don't do any of this. */ if (service_name == NULL) { memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; status = getaddrinfo(server_host, NULL, &hints, &ai); if (status != 0) die("cannot resolve host %s: %s", server_host, gai_strerror(status)); server_host = xstrdup(ai->ai_canonname); freeaddrinfo(ai); } /* Open connection. */ r = remctl_new(); if (r == NULL) sysdie("cannot initialize remctl connection"); if (source != NULL) if (!remctl_set_source_ip(r, source)) die("%s", remctl_error(r)); if (!remctl_open(r, server_host, port, service_name)) die("%s", remctl_error(r)); /* Do the work. */ if (!remctl_command(r, (const char **) argv)) die("%s", remctl_error(r)); if (!process_response(r, &errorcode)) die("%s", remctl_error(r)); /* Shut down cleanly. */ remctl_close(r); socket_shutdown(); return errorcode; }