| 1 | /* |
|---|
| 2 | * Server implementation of generic protocol functions. |
|---|
| 3 | * |
|---|
| 4 | * These are the server protocol functions that can be shared between the v1 |
|---|
| 5 | * and v2 protocol. |
|---|
| 6 | * |
|---|
| 7 | * Written by Russ Allbery <rra@stanford.edu> |
|---|
| 8 | * Based on work by Anton Ushakov |
|---|
| 9 | * Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 |
|---|
| 10 | * Board of Trustees, Leland Stanford Jr. University |
|---|
| 11 | * |
|---|
| 12 | * See LICENSE for licensing terms. |
|---|
| 13 | */ |
|---|
| 14 | |
|---|
| 15 | #include <config.h> |
|---|
| 16 | #include <portable/system.h> |
|---|
| 17 | #include <portable/gssapi.h> |
|---|
| 18 | #include <portable/socket.h> |
|---|
| 19 | #include <portable/uio.h> |
|---|
| 20 | |
|---|
| 21 | #include <server/internal.h> |
|---|
| 22 | #include <util/util.h> |
|---|
| 23 | |
|---|
| 24 | |
|---|
| 25 | /* |
|---|
| 26 | * Create a new client struct from a file descriptor and establish a GSS-API |
|---|
| 27 | * context as a specified service with an incoming client and fills out the |
|---|
| 28 | * client struct. Returns a new client struct on success and NULL on failure, |
|---|
| 29 | * logging an appropriate error message. |
|---|
| 30 | */ |
|---|
| 31 | struct client * |
|---|
| 32 | server_new_client(int fd, gss_cred_id_t creds) |
|---|
| 33 | { |
|---|
| 34 | struct client *client; |
|---|
| 35 | struct sockaddr_storage ss; |
|---|
| 36 | socklen_t socklen; |
|---|
| 37 | size_t length; |
|---|
| 38 | char *buffer; |
|---|
| 39 | gss_buffer_desc send_tok, recv_tok, name_buf; |
|---|
| 40 | gss_name_t name = GSS_C_NO_NAME; |
|---|
| 41 | gss_OID doid; |
|---|
| 42 | OM_uint32 major = 0; |
|---|
| 43 | OM_uint32 minor = 0; |
|---|
| 44 | OM_uint32 acc_minor; |
|---|
| 45 | int flags, status; |
|---|
| 46 | static const OM_uint32 req_gss_flags |
|---|
| 47 | = (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG); |
|---|
| 48 | |
|---|
| 49 | /* Create and initialize a new client struct. */ |
|---|
| 50 | client = xcalloc(1, sizeof(struct client)); |
|---|
| 51 | client->fd = fd; |
|---|
| 52 | client->context = GSS_C_NO_CONTEXT; |
|---|
| 53 | client->user = NULL; |
|---|
| 54 | client->output = NULL; |
|---|
| 55 | client->hostname = NULL; |
|---|
| 56 | client->ipaddress = NULL; |
|---|
| 57 | |
|---|
| 58 | /* Fill in hostname and IP address. */ |
|---|
| 59 | socklen = sizeof(ss); |
|---|
| 60 | if (getpeername(fd, (struct sockaddr *) &ss, &socklen) != 0) { |
|---|
| 61 | syswarn("cannot get peer address"); |
|---|
| 62 | goto fail; |
|---|
| 63 | } |
|---|
| 64 | length = INET6_ADDRSTRLEN; |
|---|
| 65 | buffer = xmalloc(length); |
|---|
| 66 | client->ipaddress = buffer; |
|---|
| 67 | status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, length, |
|---|
| 68 | NULL, 0, NI_NUMERICHOST); |
|---|
| 69 | if (status != 0) { |
|---|
| 70 | syswarn("cannot translate IP address of client: %s", |
|---|
| 71 | gai_strerror(status)); |
|---|
| 72 | goto fail; |
|---|
| 73 | } |
|---|
| 74 | length = NI_MAXHOST; |
|---|
| 75 | buffer = xmalloc(length); |
|---|
| 76 | status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, length, |
|---|
| 77 | NULL, 0, NI_NAMEREQD); |
|---|
| 78 | if (status == 0) |
|---|
| 79 | client->hostname = buffer; |
|---|
| 80 | else |
|---|
| 81 | free(buffer); |
|---|
| 82 | |
|---|
| 83 | /* Accept the initial (worthless) token. */ |
|---|
| 84 | status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH); |
|---|
| 85 | if (status != TOKEN_OK) { |
|---|
| 86 | warn_token("receiving initial token", status, major, minor); |
|---|
| 87 | goto fail; |
|---|
| 88 | } |
|---|
| 89 | free(recv_tok.value); |
|---|
| 90 | if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT | TOKEN_PROTOCOL)) |
|---|
| 91 | client->protocol = 2; |
|---|
| 92 | else if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT)) |
|---|
| 93 | client->protocol = 1; |
|---|
| 94 | else { |
|---|
| 95 | warn("bad token flags %d in initial token", flags); |
|---|
| 96 | goto fail; |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | /* Now, do the real work of negotiating the context. */ |
|---|
| 100 | do { |
|---|
| 101 | status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH); |
|---|
| 102 | if (status != TOKEN_OK) { |
|---|
| 103 | warn_token("receiving context token", status, major, minor); |
|---|
| 104 | goto fail; |
|---|
| 105 | } |
|---|
| 106 | if (flags == TOKEN_CONTEXT) |
|---|
| 107 | client->protocol = 1; |
|---|
| 108 | else if (flags != (TOKEN_CONTEXT | TOKEN_PROTOCOL)) { |
|---|
| 109 | warn("bad token flags %d in context token", flags); |
|---|
| 110 | free(recv_tok.value); |
|---|
| 111 | goto fail; |
|---|
| 112 | } |
|---|
| 113 | debug("received context token (size=%lu)", |
|---|
| 114 | (unsigned long) recv_tok.length); |
|---|
| 115 | major = gss_accept_sec_context(&acc_minor, &client->context, creds, |
|---|
| 116 | &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &name, &doid, |
|---|
| 117 | &send_tok, &client->flags, NULL, NULL); |
|---|
| 118 | free(recv_tok.value); |
|---|
| 119 | |
|---|
| 120 | /* Send back a token if we need to. */ |
|---|
| 121 | if (send_tok.length != 0) { |
|---|
| 122 | debug("sending context token (size=%lu)", |
|---|
| 123 | (unsigned long) send_tok.length); |
|---|
| 124 | flags = TOKEN_CONTEXT; |
|---|
| 125 | if (client->protocol > 1) |
|---|
| 126 | flags |= TOKEN_PROTOCOL; |
|---|
| 127 | status = token_send(client->fd, flags, &send_tok); |
|---|
| 128 | if (status != TOKEN_OK) { |
|---|
| 129 | warn_token("sending context token", status, major, minor); |
|---|
| 130 | gss_release_buffer(&minor, &send_tok); |
|---|
| 131 | goto fail; |
|---|
| 132 | } |
|---|
| 133 | gss_release_buffer(&minor, &send_tok); |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | /* Bail out if we lose. */ |
|---|
| 137 | if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) { |
|---|
| 138 | warn_gssapi("while accepting context", major, acc_minor); |
|---|
| 139 | goto fail; |
|---|
| 140 | } |
|---|
| 141 | if (major == GSS_S_CONTINUE_NEEDED) |
|---|
| 142 | debug("continue needed while accepting context"); |
|---|
| 143 | } while (major == GSS_S_CONTINUE_NEEDED); |
|---|
| 144 | |
|---|
| 145 | /* Make sure that the appropriate context flags are set. */ |
|---|
| 146 | if (client->protocol > 1) { |
|---|
| 147 | if ((client->flags & req_gss_flags) != req_gss_flags) { |
|---|
| 148 | warn("client did not negotiate appropriate GSS-API flags"); |
|---|
| 149 | goto fail; |
|---|
| 150 | } |
|---|
| 151 | } |
|---|
| 152 | |
|---|
| 153 | /* Get the display version of the client name and store it. */ |
|---|
| 154 | major = gss_display_name(&minor, name, &name_buf, &doid); |
|---|
| 155 | if (major != GSS_S_COMPLETE) { |
|---|
| 156 | warn_gssapi("while displaying client name", major, minor); |
|---|
| 157 | goto fail; |
|---|
| 158 | } |
|---|
| 159 | major = gss_release_name(&minor, &name); |
|---|
| 160 | client->user = xstrndup(name_buf.value, name_buf.length); |
|---|
| 161 | gss_release_buffer(&minor, &name_buf); |
|---|
| 162 | return client; |
|---|
| 163 | |
|---|
| 164 | fail: |
|---|
| 165 | if (client->context != GSS_C_NO_CONTEXT) |
|---|
| 166 | gss_delete_sec_context(&minor, &client->context, GSS_C_NO_BUFFER); |
|---|
| 167 | if (name != GSS_C_NO_NAME) |
|---|
| 168 | gss_release_name(&minor, &name); |
|---|
| 169 | if (client->ipaddress != NULL) |
|---|
| 170 | free(client->ipaddress); |
|---|
| 171 | if (client->hostname != NULL) |
|---|
| 172 | free(client->hostname); |
|---|
| 173 | free(client); |
|---|
| 174 | return NULL; |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | |
|---|
| 178 | /* |
|---|
| 179 | * Free a client struct, including any resources that it holds. |
|---|
| 180 | */ |
|---|
| 181 | void |
|---|
| 182 | server_free_client(struct client *client) |
|---|
| 183 | { |
|---|
| 184 | OM_uint32 major, minor; |
|---|
| 185 | |
|---|
| 186 | if (client->context != GSS_C_NO_CONTEXT) { |
|---|
| 187 | major = gss_delete_sec_context(&minor, &client->context, NULL); |
|---|
| 188 | if (major != GSS_S_COMPLETE) |
|---|
| 189 | warn_gssapi("while deleting context", major, minor); |
|---|
| 190 | } |
|---|
| 191 | if (client->output != NULL) |
|---|
| 192 | free(client->output); |
|---|
| 193 | if (client->user != NULL) |
|---|
| 194 | free(client->user); |
|---|
| 195 | if (client->fd >= 0) |
|---|
| 196 | close(client->fd); |
|---|
| 197 | if (client->hostname != NULL) |
|---|
| 198 | free(client->hostname); |
|---|
| 199 | if (client->ipaddress != NULL) |
|---|
| 200 | free(client->ipaddress); |
|---|
| 201 | free(client); |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | |
|---|
| 205 | /* |
|---|
| 206 | * Receives a command token payload and builds an argv structure for it, |
|---|
| 207 | * returning that as NULL-terminated array of pointers to struct iovecs. |
|---|
| 208 | * Takes the client struct, a pointer to the beginning of the payload |
|---|
| 209 | * (starting with the argument count), and the length of the payload. If |
|---|
| 210 | * there are any problems with the request, sends an error token, logs the |
|---|
| 211 | * error, and then returns NULL. Otherwise, returns the struct iovec array. |
|---|
| 212 | */ |
|---|
| 213 | struct iovec ** |
|---|
| 214 | server_parse_command(struct client *client, const char *buffer, size_t length) |
|---|
| 215 | { |
|---|
| 216 | OM_uint32 tmp; |
|---|
| 217 | size_t argc, arglen, count; |
|---|
| 218 | struct iovec **argv = NULL; |
|---|
| 219 | const char *p = buffer; |
|---|
| 220 | |
|---|
| 221 | /* Read the argument count. */ |
|---|
| 222 | memcpy(&tmp, p, 4); |
|---|
| 223 | argc = ntohl(tmp); |
|---|
| 224 | p += 4; |
|---|
| 225 | debug("argc is %lu", (unsigned long) argc); |
|---|
| 226 | if (argc == 0) { |
|---|
| 227 | warn("command with no arguments"); |
|---|
| 228 | server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command"); |
|---|
| 229 | return NULL; |
|---|
| 230 | } |
|---|
| 231 | if (argc > MAXCMDARGS) { |
|---|
| 232 | warn("too large argc %lu in request message", (unsigned long) argc); |
|---|
| 233 | server_send_error(client, ERROR_TOOMANY_ARGS, "Too many arguments"); |
|---|
| 234 | return NULL; |
|---|
| 235 | } |
|---|
| 236 | if (length - (p - buffer) < 4 * argc) { |
|---|
| 237 | warn("command data too short"); |
|---|
| 238 | server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token"); |
|---|
| 239 | return NULL; |
|---|
| 240 | } |
|---|
| 241 | argv = xcalloc(argc + 1, sizeof(struct iovec *)); |
|---|
| 242 | |
|---|
| 243 | /* |
|---|
| 244 | * Parse out the arguments and store them into a vector. Arguments are |
|---|
| 245 | * packed: (<arglength><argument>)+. Make sure each time through the loop |
|---|
| 246 | * that they didn't send more arguments than they claimed to have. |
|---|
| 247 | */ |
|---|
| 248 | count = 0; |
|---|
| 249 | while (p <= buffer + length - 4) { |
|---|
| 250 | if (count >= argc) { |
|---|
| 251 | warn("sent more arguments than argc %lu", (unsigned long) argc); |
|---|
| 252 | server_send_error(client, ERROR_BAD_COMMAND, |
|---|
| 253 | "Invalid command token"); |
|---|
| 254 | goto fail; |
|---|
| 255 | } |
|---|
| 256 | memcpy(&tmp, p, 4); |
|---|
| 257 | arglen = ntohl(tmp); |
|---|
| 258 | p += 4; |
|---|
| 259 | if ((length - (p - buffer)) < arglen) { |
|---|
| 260 | warn("command data invalid"); |
|---|
| 261 | server_send_error(client, ERROR_BAD_COMMAND, |
|---|
| 262 | "Invalid command token"); |
|---|
| 263 | goto fail; |
|---|
| 264 | } |
|---|
| 265 | argv[count] = xmalloc(sizeof(struct iovec)); |
|---|
| 266 | argv[count]->iov_len = arglen; |
|---|
| 267 | if (arglen == 0) |
|---|
| 268 | argv[count]->iov_base = NULL; |
|---|
| 269 | else { |
|---|
| 270 | argv[count]->iov_base = xmalloc(arglen); |
|---|
| 271 | memcpy(argv[count]->iov_base, p, arglen); |
|---|
| 272 | } |
|---|
| 273 | count++; |
|---|
| 274 | p += arglen; |
|---|
| 275 | debug("arg %lu has length %lu", (unsigned long) count, |
|---|
| 276 | (unsigned long) arglen); |
|---|
| 277 | } |
|---|
| 278 | if (count != argc || p != buffer + length) { |
|---|
| 279 | warn("argument count differs from arguments seen"); |
|---|
| 280 | server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token"); |
|---|
| 281 | goto fail; |
|---|
| 282 | } |
|---|
| 283 | argv[count] = NULL; |
|---|
| 284 | return argv; |
|---|
| 285 | |
|---|
| 286 | fail: |
|---|
| 287 | if (argv != NULL) |
|---|
| 288 | server_free_command(argv); |
|---|
| 289 | return NULL; |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | |
|---|
| 293 | /* |
|---|
| 294 | * Send an error back to the client. Takes the client struct, the error code, |
|---|
| 295 | * and the message to send and dispatches to the appropriate protocol-specific |
|---|
| 296 | * function. Returns true on success, false on failure. |
|---|
| 297 | */ |
|---|
| 298 | bool |
|---|
| 299 | server_send_error(struct client *client, enum error_codes error, |
|---|
| 300 | const char *message) |
|---|
| 301 | { |
|---|
| 302 | if (client->protocol > 1) |
|---|
| 303 | return server_v2_send_error(client, error, message); |
|---|
| 304 | else { |
|---|
| 305 | if (client->output != NULL) |
|---|
| 306 | free(client->output); |
|---|
| 307 | client->output = xmalloc(strlen(message) + 1); |
|---|
| 308 | memcpy(client->output, message, strlen(message)); |
|---|
| 309 | client->output[strlen(message)] = '\n'; |
|---|
| 310 | client->outlen = strlen(message) + 1; |
|---|
| 311 | return server_v1_send_output(client, -1); |
|---|
| 312 | } |
|---|
| 313 | } |
|---|