Treat TID req as error if a response is not sent
[trust_router.git] / common / tr_gss.c
1 /*
2  * Copyright (c) 2018, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34
35 #include <talloc.h>
36 #include <gssapi.h>
37 #include <string.h>
38
39 #include <tr_msg.h>
40 #include <tr_debug.h>
41 #include <gsscon.h>
42 #include <tr_gss.h>
43
44 /**
45  * tr_gss.c - GSS connection handler
46  *
47  * The chief entry point to this module is tr_gss_handle_connection(). This
48  * function accepts an incoming socket connection, runs the GSS authorization
49  * and authentication process, accepts a request, processes it, then sends
50  * the reply and returns without closing the connection.
51  *
52  * Callers need to provide two callbacks, each with a cookie for passing
53  * custom data to the callback.
54  *
55  *   * TR_GSS_AUTH_FN auth_cb: Authorization callback
56  *     - This callback is used during the GSS auth process to determine whether
57  *       a credential should be authorized to connect.
58  *
59  *   * TR_GSS_HANDLE_REQ_FN req_cb: Request handler callback
60  *     - After auth, this callback is passed the string form of the incoming request.
61  *       It should process the request and return a string form of the outgoing
62  *       response, if any.
63  */
64
65 typedef struct tr_gss_cookie {
66   TR_GSS_AUTH_FN *auth_cb;
67   void *auth_cookie;
68 } TR_GSS_COOKIE;
69
70 static int tr_gss_auth_cb(gss_name_t clientName, gss_buffer_t displayName, void *data)
71 {
72   TR_GSS_COOKIE *cookie = talloc_get_type_abort(data, TR_GSS_COOKIE);
73   TR_NAME name ={(char *) displayName->value, (int) displayName->length};
74   int result=0;
75
76   tr_debug("tr_gss_auth_cb: Calling auth handler for %.*s.", name.len, name.buf);
77   if (cookie->auth_cb(clientName, &name, cookie->auth_cookie)) {
78     tr_debug("tr_gss_auth_cb: client '%.*s' denied authorization.", name.len, name.buf);
79     result=EACCES; /* denied */
80   }
81
82   return result;
83 }
84
85
86 /**
87  * Handle GSS authentication and authorization
88  *
89  * @param conn connection file descriptor
90  * @param acceptor_service name of acceptor to present to initiator
91  * @param acceptor_hostname hostname of acceptor to present to initiator
92  * @param gssctx GSS context
93  * @param auth_cb authorization callback
94  * @param auth_cookie generic data to pass to the authorization callback
95  * @return 0 on successful auth, 1 on disallowed auth, -1 on error
96  */
97 static int tr_gss_auth_connection(int conn,
98                                   const char *acceptor_service,
99                                   const char *acceptor_hostname,
100                                   gss_ctx_id_t *gssctx,
101                                   TR_GSS_AUTH_FN auth_cb,
102                                   void *auth_cookie)
103 {
104   int rc = 0;
105   int auth, autherr = 0;
106   gss_buffer_desc nameBuffer = {0, NULL};
107   TR_GSS_COOKIE *cookie = NULL;
108
109   nameBuffer.value = talloc_asprintf(NULL, "%s@%s", acceptor_service, acceptor_hostname);
110   if (nameBuffer.value == NULL) {
111     tr_err("tr_gss_auth_connection: Error allocating acceptor name.");
112     return -1;
113   }
114   nameBuffer.length = strlen(nameBuffer.value);
115
116   /* Set up for the auth callback. There are two layers of callbacks here: we
117    * use our own, which handles gsscon interfacing and calls the auth_cb parameter
118    * to do the actual auth. Store the auth_cb information in a metacookie. */
119   cookie = talloc(NULL, TR_GSS_COOKIE);
120   cookie->auth_cb=auth_cb;
121   cookie->auth_cookie=auth_cookie;
122
123   /* Now call gsscon with *our* auth callback and cookie */
124   tr_debug("tr_gss_auth_connection: Beginning passive authentication as %.*s",
125            nameBuffer.length, nameBuffer.value);
126   rc = gsscon_passive_authenticate(conn, nameBuffer, gssctx, tr_gss_auth_cb, cookie);
127   talloc_free(cookie);
128   talloc_free(nameBuffer.value);
129   if (rc) {
130     tr_debug("tr_gss_auth_connection: Error from gsscon_passive_authenticate(), rc = %d.", rc);
131     return -1;
132   }
133
134   tr_debug("tr_gss_auth_connection: Authentication succeeded, now authorizing.");
135   rc = gsscon_authorize(*gssctx, &auth, &autherr);
136   if (rc) {
137     tr_debug("tr_gss_auth_connection: Error from gsscon_authorize, rc = %d, autherr = %d.",
138              rc, autherr);
139     return -1;
140   }
141
142   if (auth)
143     tr_debug("tr_gss_auth_connection: Connection authenticated, conn = %d.", conn);
144   else
145     tr_debug("tr_gss_auth_connection: Authentication failed, conn %d.", conn);
146
147   return !auth;
148 }
149
150 /**
151  * Read a request from the GSS connection
152  *
153  * @param mem_ctx talloc context for the result
154  * @param conn file descriptor for the connection
155  * @param gssctx GSS context
156  * @return talloc'ed string containing the request, or null on error
157  */
158 static char *tr_gss_read_req(TALLOC_CTX *mem_ctx, int conn, gss_ctx_id_t gssctx)
159 {
160   int err;
161   char *retval = NULL;
162   char *buf = NULL;
163   size_t buflen = 0;
164
165   err = gsscon_read_encrypted_token(conn, gssctx, &buf, &buflen);
166   if (err || (buf == NULL)) {
167     if (buf)
168       free(buf);
169     tr_debug("tr_gss_read_req: Error reading from connection, rc=%d", err);
170     return NULL;
171   }
172
173   tr_debug("tr_gss_read_req: Read %u bytes.", (unsigned) buflen);
174
175   // get a talloc'ed version, guaranteed to have a null termination
176   retval = talloc_asprintf(mem_ctx, "%.*s", (int) buflen, buf);
177   free(buf);
178
179   return retval;
180 }
181
182 /**
183  * Write a response to the GSS connection
184  *
185  * @param conn file descriptor for the connection
186  * @param gssctx GSS context
187  * @param resp encoded response string to send
188  * @return 0 on success, -1 on error
189  */
190 static int tr_gss_write_resp(int conn, gss_ctx_id_t gssctx, const char *resp)
191 {
192   int err = 0;
193
194   /* Send the response over the connection */
195   err = gsscon_write_encrypted_token (conn, gssctx, resp, strlen(resp) + 1);
196   if (err) {
197     tr_debug("tr_gss_send_response: Error sending response over connection, rc=%d.", err);
198     return -1;
199   }
200   return 0;
201 }
202
203 /**
204  * Handle a request/response connection
205  *
206  * Authorizes/authenticates the connection, then reads a response, passes that to a
207  * callback to get a response, sends that, then returns.
208  *
209  * @param conn connection file descriptor
210  * @param acceptor_service acceptor name to present
211  * @param acceptor_hostname acceptor hostname to present
212  * @param auth_cb callback for authorization
213  * @param auth_cookie cookie for the auth_cb
214  * @param req_cb callback to handle the request and produce the response
215  * @param req_cookie cookie for the req_cb
216  */
217 TR_GSS_RC tr_gss_handle_connection(int conn,
218                                    const char *acceptor_service,
219                                    const char *acceptor_hostname,
220                                    TR_GSS_AUTH_FN auth_cb,
221                                    void *auth_cookie,
222                                    TR_GSS_HANDLE_REQ_FN req_cb,
223                                    void *req_cookie)
224 {
225   TALLOC_CTX *tmp_ctx = talloc_new(NULL);
226   gss_ctx_id_t gssctx = GSS_C_NO_CONTEXT;
227   char *req_str = NULL;
228   size_t req_len = 0;
229   TR_MSG *req_msg = NULL;
230   TR_MSG *resp_msg = NULL;
231   char *resp_str = NULL;
232   TR_GSS_RC rc = TR_GSS_ERROR;
233
234   tr_debug("tr_gss_handle_connection: Attempting to accept %s connection on fd %d.",
235            acceptor_service, conn);
236
237   if (tr_gss_auth_connection(conn,
238                              acceptor_service,
239                              acceptor_hostname,
240                              &gssctx,
241                              auth_cb,
242                              auth_cookie)) {
243     tr_notice("tr_gss_handle_connection: Error authorizing connection.");
244     goto cleanup;
245   }
246
247   tr_debug("tr_gss_handle_connection: Connection authorized");
248
249   // TODO: should there be a timeout on this?
250   do {
251     /* continue until an error breaks us out */
252     // try to read a request
253     req_str = tr_gss_read_req(tmp_ctx, conn, gssctx);
254
255     if (req_str == NULL) {
256       // an error occurred, give up
257       tr_notice("tr_gss_handle_connection: Error reading request");
258       goto cleanup;
259     }
260
261     req_len = strlen(req_str);
262
263     /* If we got no characters, we will loop again. Free the empty response for the next loop. */
264     if (req_len == 0)
265       talloc_free(req_str);
266
267   } while (req_len == 0);
268
269   /* Decode the request */
270   req_msg = tr_msg_decode(tmp_ctx, req_str, req_len);
271   if (req_msg == NULL) {
272     tr_notice("tr_gss_handle_connection: Error decoding response");
273     goto cleanup;
274   }
275
276   /* Hand off the request for processing and get the response */
277   resp_msg = req_cb(tmp_ctx, req_msg, req_cookie);
278
279   if (resp_msg == NULL) {
280     // no response, clean up
281     goto cleanup;
282   }
283
284   /* Encode the response */
285   resp_str = tr_msg_encode(tmp_ctx, resp_msg);
286   if (resp_str == NULL) {
287     /* We apparently can't encode a response, so just return */
288     tr_err("tr_gss_handle_connection: Error encoding response");
289     goto cleanup;
290   }
291
292   // send the response
293   if (tr_gss_write_resp(conn, gssctx, resp_str)) {
294     tr_err("tr_gss_handle_connection: Error writing response");
295     goto cleanup;
296   }
297
298   /* we successfully sent a response */
299   rc = TR_GSS_SUCCESS;
300
301 cleanup:
302   talloc_free(tmp_ctx);
303   return rc;
304 }