| 1 | /* |
|---|
| 2 | * remctl command-line client. |
|---|
| 3 | * |
|---|
| 4 | * This is a command-line driver for the libremctl library, which takes the |
|---|
| 5 | * command on the command line and prints out the results to standard output |
|---|
| 6 | * and standard error as appropriate. |
|---|
| 7 | * |
|---|
| 8 | * Originally written by Anton Ushakov |
|---|
| 9 | * Extensive modifications by Russ Allbery <rra@stanford.edu> |
|---|
| 10 | * Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 |
|---|
| 11 | * Board of Trustees, Leland Stanford Jr. University |
|---|
| 12 | * |
|---|
| 13 | * See LICENSE for licensing terms. |
|---|
| 14 | */ |
|---|
| 15 | |
|---|
| 16 | #include <config.h> |
|---|
| 17 | #include <portable/system.h> |
|---|
| 18 | #include <portable/getopt.h> |
|---|
| 19 | #include <portable/socket.h> |
|---|
| 20 | |
|---|
| 21 | #include <ctype.h> |
|---|
| 22 | |
|---|
| 23 | #include <client/remctl.h> |
|---|
| 24 | #include <util/util.h> |
|---|
| 25 | |
|---|
| 26 | /* Usage message. */ |
|---|
| 27 | static const char usage_message[] = "\ |
|---|
| 28 | Usage: remctl <options> <host> <command> <subcommand> <parameters>\n\ |
|---|
| 29 | \n\ |
|---|
| 30 | Options:\n\ |
|---|
| 31 | -d Debugging level of output\n\ |
|---|
| 32 | -h Display this help\n\ |
|---|
| 33 | -p <port> remctld port (default: 4373 falling back to 4444)\n\ |
|---|
| 34 | -s <service> remctld service principal (default: host/<host>)\n\ |
|---|
| 35 | -v Display the version of remctl\n"; |
|---|
| 36 | |
|---|
| 37 | |
|---|
| 38 | /* |
|---|
| 39 | * Display the usage message for remctl. |
|---|
| 40 | */ |
|---|
| 41 | static void |
|---|
| 42 | usage(int status) |
|---|
| 43 | { |
|---|
| 44 | fprintf((status == 0) ? stdout : stderr, "%s", usage_message); |
|---|
| 45 | exit(status); |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | |
|---|
| 49 | /* |
|---|
| 50 | * Get the responses back from the server, taking appropriate action on each |
|---|
| 51 | * one depending on its type. Sets the errorcode parameter to the exit status |
|---|
| 52 | * of the remote command, or to 1 if the remote command failed with an error. |
|---|
| 53 | * Returns true on success, false if some protocol-level error occurred when |
|---|
| 54 | * reading the responses. |
|---|
| 55 | */ |
|---|
| 56 | static bool |
|---|
| 57 | process_response(struct remctl *r, int *errorcode) |
|---|
| 58 | { |
|---|
| 59 | struct remctl_output *out; |
|---|
| 60 | |
|---|
| 61 | *errorcode = 0; |
|---|
| 62 | out = remctl_output(r); |
|---|
| 63 | while (out != NULL && out->type != REMCTL_OUT_DONE) { |
|---|
| 64 | switch (out->type) { |
|---|
| 65 | case REMCTL_OUT_OUTPUT: |
|---|
| 66 | if (out->stream == 1) |
|---|
| 67 | fwrite(out->data, out->length, 1, stdout); |
|---|
| 68 | else if (out->stream == 2) |
|---|
| 69 | fwrite(out->data, out->length, 1, stderr); |
|---|
| 70 | else { |
|---|
| 71 | warn("unknown output stream %d", out->stream); |
|---|
| 72 | fwrite(out->data, out->length, 1, stderr); |
|---|
| 73 | } |
|---|
| 74 | break; |
|---|
| 75 | case REMCTL_OUT_ERROR: |
|---|
| 76 | *errorcode = 255; |
|---|
| 77 | fwrite(out->data, out->length, 1, stderr); |
|---|
| 78 | fputc('\n', stderr); |
|---|
| 79 | return true; |
|---|
| 80 | case REMCTL_OUT_STATUS: |
|---|
| 81 | *errorcode = out->status; |
|---|
| 82 | return true; |
|---|
| 83 | case REMCTL_OUT_DONE: |
|---|
| 84 | break; |
|---|
| 85 | } |
|---|
| 86 | out = remctl_output(r); |
|---|
| 87 | } |
|---|
| 88 | if (out == NULL) { |
|---|
| 89 | die("error reading from server: %s", remctl_error(r)); |
|---|
| 90 | return false; |
|---|
| 91 | } else |
|---|
| 92 | return true; |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | |
|---|
| 96 | /* |
|---|
| 97 | * Main routine. Parse the arguments, open the remctl connection, send the |
|---|
| 98 | * command, and then call process_response. |
|---|
| 99 | */ |
|---|
| 100 | int |
|---|
| 101 | main(int argc, char *argv[]) |
|---|
| 102 | { |
|---|
| 103 | int option, status; |
|---|
| 104 | char *server_host; |
|---|
| 105 | struct addrinfo hints, *ai; |
|---|
| 106 | char *service_name = NULL; |
|---|
| 107 | unsigned short port = 0; |
|---|
| 108 | struct remctl *r; |
|---|
| 109 | int errorcode = 0; |
|---|
| 110 | |
|---|
| 111 | /* Set up logging and identity. */ |
|---|
| 112 | message_program_name = "remctl"; |
|---|
| 113 | if (!socket_init()) |
|---|
| 114 | die("failed to initialize socket library"); |
|---|
| 115 | |
|---|
| 116 | /* |
|---|
| 117 | * Parse options. The + tells GNU getopt to stop option parsing at the |
|---|
| 118 | * first non-argument rather than proceeding on to find options anywhere. |
|---|
| 119 | * Without this, it's hard to call remote programs that take options. |
|---|
| 120 | * Non-GNU getopt will treat the + as a supported option, which is handled |
|---|
| 121 | * below. |
|---|
| 122 | */ |
|---|
| 123 | while ((option = getopt(argc, argv, "+dhp:s:v")) != EOF) { |
|---|
| 124 | switch (option) { |
|---|
| 125 | case 'd': |
|---|
| 126 | message_handlers_debug(1, message_log_stderr); |
|---|
| 127 | break; |
|---|
| 128 | case 'h': |
|---|
| 129 | usage(0); |
|---|
| 130 | break; |
|---|
| 131 | case 'p': |
|---|
| 132 | port = atoi(optarg); |
|---|
| 133 | break; |
|---|
| 134 | case 's': |
|---|
| 135 | service_name = optarg; |
|---|
| 136 | break; |
|---|
| 137 | case 'v': |
|---|
| 138 | printf("%s\n", PACKAGE_STRING); |
|---|
| 139 | exit(0); |
|---|
| 140 | break; |
|---|
| 141 | case '+': |
|---|
| 142 | fprintf(stderr, "%s: invalid option -- +\n", argv[0]); |
|---|
| 143 | default: |
|---|
| 144 | usage(1); |
|---|
| 145 | break; |
|---|
| 146 | } |
|---|
| 147 | } |
|---|
| 148 | argc -= optind; |
|---|
| 149 | argv += optind; |
|---|
| 150 | if (argc < 3) |
|---|
| 151 | usage(1); |
|---|
| 152 | server_host = *argv++; |
|---|
| 153 | argc--; |
|---|
| 154 | |
|---|
| 155 | /* |
|---|
| 156 | * If service_name isn't set, the remctl library uses host/<server> |
|---|
| 157 | * (host@<server> in GSS-API parlance). However, if the server to which |
|---|
| 158 | * we're connecting is a DNS-load-balanced name, we have to be careful |
|---|
| 159 | * what principal name we use. |
|---|
| 160 | * |
|---|
| 161 | * Ideally, we would let the GSS-API library handle this and choose |
|---|
| 162 | * whether to canonicalize the <server> in the principal name based on the |
|---|
| 163 | * krb5.conf rdns setting and similar configuration. However, with DNS |
|---|
| 164 | * load balancing, this still may fail. At the time of network |
|---|
| 165 | * connection, we will connect to whatever the name resolves to then. |
|---|
| 166 | * After we connect, we authenticate, and the GSS-API library will then |
|---|
| 167 | * separately canonicalize the hostname. It could get a different answer |
|---|
| 168 | * than we got for our network connection, leading to an authentication |
|---|
| 169 | * failure. |
|---|
| 170 | * |
|---|
| 171 | * Therefore, if the principal isn't specified, we canonicalize the |
|---|
| 172 | * hostname to which we're connecting before we connect. Then, the |
|---|
| 173 | * additional canonicalization possibly done by the GSS-API library should |
|---|
| 174 | * return the same results and be consistent. |
|---|
| 175 | * |
|---|
| 176 | * Note that this opens the possibility of a subtle attack through DNS |
|---|
| 177 | * spoofing, since both the principal used and the host to which we're |
|---|
| 178 | * connecting can be changed by varying the DNS response. |
|---|
| 179 | * |
|---|
| 180 | * If the principal is specified explicitly, assume the user knows what |
|---|
| 181 | * they're doing and don't do any of this. |
|---|
| 182 | */ |
|---|
| 183 | if (service_name == NULL) { |
|---|
| 184 | memset(&hints, 0, sizeof(hints)); |
|---|
| 185 | hints.ai_flags = AI_CANONNAME; |
|---|
| 186 | status = getaddrinfo(server_host, NULL, &hints, &ai); |
|---|
| 187 | if (status != 0) |
|---|
| 188 | die("cannot resolve host %s: %s", server_host, |
|---|
| 189 | gai_strerror(status)); |
|---|
| 190 | server_host = xstrdup(ai->ai_canonname); |
|---|
| 191 | freeaddrinfo(ai); |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | /* Open connection. */ |
|---|
| 195 | r = remctl_new(); |
|---|
| 196 | if (r == NULL) |
|---|
| 197 | sysdie("cannot initialize remctl connection"); |
|---|
| 198 | if (!remctl_open(r, server_host, port, service_name)) |
|---|
| 199 | die("%s", remctl_error(r)); |
|---|
| 200 | |
|---|
| 201 | /* Do the work. */ |
|---|
| 202 | if (!remctl_command(r, (const char **) argv)) |
|---|
| 203 | die("%s", remctl_error(r)); |
|---|
| 204 | if (!process_response(r, &errorcode)) |
|---|
| 205 | die("%s", remctl_error(r)); |
|---|
| 206 | |
|---|
| 207 | /* Shut down cleanly. */ |
|---|
| 208 | remctl_close(r); |
|---|
| 209 | socket_shutdown(); |
|---|
| 210 | return errorcode; |
|---|
| 211 | } |
|---|