source: web/old/remctl-2.14/client/remctl.c @ f6f3e91

web
Last change on this file since f6f3e91 was f6f3e91, checked in by Jessica B. Hamrick <jhamrick@…>, 15 years ago

Preserve directory hierarchy (not sure what happened to it)

  • Property mode set to 100644
File size: 6.6 KB
Line 
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. */
27static const char usage_message[] = "\
28Usage: remctl <options> <host> <command> <subcommand> <parameters>\n\
29\n\
30Options:\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 */
41static void
42usage(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 */
56static bool
57process_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 */
100int
101main(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}
Note: See TracBrowser for help on using the repository browser.