1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krad/client.c - Client request code for libkrad */
3 /*
4 * Copyright 2013 Red Hat, Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions 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
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include <k5-queue.h>
31 #include "internal.h"
32
33 #include <string.h>
34 #include <sys/un.h>
35 #include <unistd.h>
36 #include <stdio.h>
37 #include <limits.h>
38
39 K5_LIST_HEAD(server_head, server_st);
40
41 typedef struct remote_state_st remote_state;
42 typedef struct request_st request;
43 typedef struct server_st server;
44
45 struct remote_state_st {
46 const krad_packet *packet;
47 krad_remote *remote;
48 };
49
50 struct request_st {
51 krad_client *rc;
52
53 krad_code code;
54 krad_attrset *attrs;
55 int timeout;
56 size_t retries;
57 krad_cb cb;
58 void *data;
59
60 remote_state *remotes;
61 ssize_t current;
62 ssize_t count;
63 };
64
65 struct server_st {
66 krad_remote *serv;
67 K5_LIST_ENTRY(server_st) list;
68 };
69
70 struct krad_client_st {
71 krb5_context kctx;
72 verto_ctx *vctx;
73 struct server_head servers;
74 };
75
76 /* Return either a pre-existing server that matches the address info and the
77 * secret, or create a new one. */
78 static krb5_error_code
get_server(krad_client * rc,const struct addrinfo * ai,const char * secret,krad_remote ** out)79 get_server(krad_client *rc, const struct addrinfo *ai, const char *secret,
80 krad_remote **out)
81 {
82 krb5_error_code retval;
83 server *srv;
84
85 K5_LIST_FOREACH(srv, &rc->servers, list) {
86 if (kr_remote_equals(srv->serv, ai, secret)) {
87 *out = srv->serv;
88 return 0;
89 }
90 }
91
92 srv = calloc(1, sizeof(server));
93 if (srv == NULL)
94 return ENOMEM;
95
96 retval = kr_remote_new(rc->kctx, rc->vctx, ai, secret, &srv->serv);
97 if (retval != 0) {
98 free(srv);
99 return retval;
100 }
101
102 K5_LIST_INSERT_HEAD(&rc->servers, srv, list);
103 *out = srv->serv;
104 return 0;
105 }
106
107 /* Free a request. */
108 static void
request_free(request * req)109 request_free(request *req)
110 {
111 krad_attrset_free(req->attrs);
112 free(req->remotes);
113 free(req);
114 }
115
116 /* Create a request. */
117 static krb5_error_code
request_new(krad_client * rc,krad_code code,const krad_attrset * attrs,const struct addrinfo * ai,const char * secret,int timeout,size_t retries,krad_cb cb,void * data,request ** req)118 request_new(krad_client *rc, krad_code code, const krad_attrset *attrs,
119 const struct addrinfo *ai, const char *secret, int timeout,
120 size_t retries, krad_cb cb, void *data, request **req)
121 {
122 const struct addrinfo *tmp;
123 krb5_error_code retval;
124 request *rqst;
125 size_t i;
126
127 if (ai == NULL)
128 return EINVAL;
129
130 rqst = calloc(1, sizeof(request));
131 if (rqst == NULL)
132 return ENOMEM;
133
134 for (tmp = ai; tmp != NULL; tmp = tmp->ai_next)
135 rqst->count++;
136
137 rqst->rc = rc;
138 rqst->code = code;
139 rqst->cb = cb;
140 rqst->data = data;
141 rqst->timeout = timeout / rqst->count;
142 rqst->retries = retries;
143
144 retval = krad_attrset_copy(attrs, &rqst->attrs);
145 if (retval != 0) {
146 request_free(rqst);
147 return retval;
148 }
149
150 rqst->remotes = calloc(rqst->count + 1, sizeof(remote_state));
151 if (rqst->remotes == NULL) {
152 request_free(rqst);
153 return ENOMEM;
154 }
155
156 i = 0;
157 for (tmp = ai; tmp != NULL; tmp = tmp->ai_next) {
158 retval = get_server(rc, tmp, secret, &rqst->remotes[i++].remote);
159 if (retval != 0) {
160 request_free(rqst);
161 return retval;
162 }
163 }
164
165 *req = rqst;
166 return 0;
167 }
168
169 /* Handle a response from a server (or related errors). */
170 static void
on_response(krb5_error_code retval,const krad_packet * reqp,const krad_packet * rspp,void * data)171 on_response(krb5_error_code retval, const krad_packet *reqp,
172 const krad_packet *rspp, void *data)
173 {
174 request *req = data;
175 size_t i;
176
177 /* Do nothing if we are already completed. */
178 if (req->count < 0)
179 return;
180
181 /* If we have timed out and have more remotes to try, do so. */
182 if (retval == ETIMEDOUT && req->remotes[++req->current].remote != NULL) {
183 retval = kr_remote_send(req->remotes[req->current].remote, req->code,
184 req->attrs, on_response, req, req->timeout,
185 req->retries,
186 &req->remotes[req->current].packet);
187 if (retval == 0)
188 return;
189 }
190
191 /* Mark the request as complete. */
192 req->count = -1;
193
194 /* Inform the callback. */
195 req->cb(retval, reqp, rspp, req->data);
196
197 /* Cancel the outstanding packets. */
198 for (i = 0; req->remotes[i].remote != NULL; i++)
199 kr_remote_cancel(req->remotes[i].remote, req->remotes[i].packet);
200
201 request_free(req);
202 }
203
204 krb5_error_code
krad_client_new(krb5_context kctx,verto_ctx * vctx,krad_client ** out)205 krad_client_new(krb5_context kctx, verto_ctx *vctx, krad_client **out)
206 {
207 krad_client *tmp;
208
209 tmp = calloc(1, sizeof(krad_client));
210 if (tmp == NULL)
211 return ENOMEM;
212
213 tmp->kctx = kctx;
214 tmp->vctx = vctx;
215
216 *out = tmp;
217 return 0;
218 }
219
220 void
krad_client_free(krad_client * rc)221 krad_client_free(krad_client *rc)
222 {
223 server *srv;
224
225 if (rc == NULL)
226 return;
227
228 /* Cancel all requests before freeing any remotes, since each request's
229 * callback data may contain references to multiple remotes. */
230 K5_LIST_FOREACH(srv, &rc->servers, list)
231 kr_remote_cancel_all(srv->serv);
232
233 while (!K5_LIST_EMPTY(&rc->servers)) {
234 srv = K5_LIST_FIRST(&rc->servers);
235 K5_LIST_REMOVE(srv, list);
236 kr_remote_free(srv->serv);
237 free(srv);
238 }
239
240 free(rc);
241 }
242
243 static krb5_error_code
resolve_remote(const char * remote,struct addrinfo ** ai)244 resolve_remote(const char *remote, struct addrinfo **ai)
245 {
246 const char *svc = "radius";
247 krb5_error_code retval;
248 struct addrinfo hints;
249 char *sep, *srv;
250
251 /* Isolate the port number if it exists. */
252 srv = strdup(remote);
253 if (srv == NULL)
254 return ENOMEM;
255
256 if (srv[0] == '[') {
257 /* IPv6 */
258 sep = strrchr(srv, ']');
259 if (sep != NULL && sep[1] == ':') {
260 sep[1] = '\0';
261 svc = &sep[2];
262 }
263 } else {
264 /* IPv4 or DNS */
265 sep = strrchr(srv, ':');
266 if (sep != NULL && sep[1] != '\0') {
267 sep[0] = '\0';
268 svc = &sep[1];
269 }
270 }
271
272 /* Perform the lookup. */
273 memset(&hints, 0, sizeof(hints));
274 hints.ai_socktype = SOCK_DGRAM;
275 retval = gai_error_code(getaddrinfo(srv, svc, &hints, ai));
276 free(srv);
277 return retval;
278 }
279
280 krb5_error_code
krad_client_send(krad_client * rc,krad_code code,const krad_attrset * attrs,const char * remote,const char * secret,int timeout,size_t retries,krad_cb cb,void * data)281 krad_client_send(krad_client *rc, krad_code code, const krad_attrset *attrs,
282 const char *remote, const char *secret, int timeout,
283 size_t retries, krad_cb cb, void *data)
284 {
285 struct addrinfo usock, *ai = NULL;
286 krb5_error_code retval;
287 struct sockaddr_un ua;
288 request *req;
289
290 if (remote[0] == '/') {
291 ua.sun_family = AF_UNIX;
292 snprintf(ua.sun_path, sizeof(ua.sun_path), "%s", remote);
293 memset(&usock, 0, sizeof(usock));
294 usock.ai_family = AF_UNIX;
295 usock.ai_socktype = SOCK_STREAM;
296 usock.ai_addr = (struct sockaddr *)&ua;
297 usock.ai_addrlen = sizeof(ua);
298
299 retval = request_new(rc, code, attrs, &usock, secret, timeout, retries,
300 cb, data, &req);
301 } else {
302 retval = resolve_remote(remote, &ai);
303 if (retval == 0) {
304 retval = request_new(rc, code, attrs, ai, secret, timeout, retries,
305 cb, data, &req);
306 freeaddrinfo(ai);
307 }
308 }
309 if (retval != 0)
310 return retval;
311
312 retval = kr_remote_send(req->remotes[req->current].remote, req->code,
313 req->attrs, on_response, req, req->timeout,
314 req->retries, &req->remotes[req->current].packet);
315 if (retval != 0) {
316 request_free(req);
317 return retval;
318 }
319
320 return 0;
321 }
322