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 | } |
---|