Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krad / client.c
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     time_t last;
68     K5_LIST_ENTRY(server_st) list;
69 };
70
71 struct krad_client_st {
72     krb5_context kctx;
73     verto_ctx *vctx;
74     struct server_head servers;
75 };
76
77 /* Return either a pre-existing server that matches the address info and the
78  * secret, or create a new one. */
79 static krb5_error_code
80 get_server(krad_client *rc, const struct addrinfo *ai, const char *secret,
81            krad_remote **out)
82 {
83     krb5_error_code retval;
84     time_t currtime;
85     server *srv;
86
87     if (time(&currtime) == (time_t)-1)
88         return errno;
89
90     K5_LIST_FOREACH(srv, &rc->servers, list) {
91         if (kr_remote_equals(srv->serv, ai, secret)) {
92             srv->last = currtime;
93             *out = srv->serv;
94             return 0;
95         }
96     }
97
98     srv = calloc(1, sizeof(server));
99     if (srv == NULL)
100         return ENOMEM;
101     srv->last = currtime;
102
103     retval = kr_remote_new(rc->kctx, rc->vctx, ai, secret, &srv->serv);
104     if (retval != 0) {
105         free(srv);
106         return retval;
107     }
108
109     K5_LIST_INSERT_HEAD(&rc->servers, srv, list);
110     *out = srv->serv;
111     return 0;
112 }
113
114 /* Free a request. */
115 static void
116 request_free(request *req)
117 {
118     krad_attrset_free(req->attrs);
119     free(req->remotes);
120     free(req);
121 }
122
123 /* Create a request. */
124 static krb5_error_code
125 request_new(krad_client *rc, krad_code code, const krad_attrset *attrs,
126             const struct addrinfo *ai, const char *secret, int timeout,
127             size_t retries, krad_cb cb, void *data, request **req)
128 {
129     const struct addrinfo *tmp;
130     krb5_error_code retval;
131     request *rqst;
132     size_t i;
133
134     if (ai == NULL)
135         return EINVAL;
136
137     rqst = calloc(1, sizeof(request));
138     if (rqst == NULL)
139         return ENOMEM;
140
141     for (tmp = ai; tmp != NULL; tmp = tmp->ai_next)
142         rqst->count++;
143
144     rqst->rc = rc;
145     rqst->code = code;
146     rqst->cb = cb;
147     rqst->data = data;
148     rqst->timeout = timeout / rqst->count;
149     rqst->retries = retries;
150
151     retval = krad_attrset_copy(attrs, &rqst->attrs);
152     if (retval != 0) {
153         request_free(rqst);
154         return retval;
155     }
156
157     rqst->remotes = calloc(rqst->count + 1, sizeof(remote_state));
158     if (rqst->remotes == NULL) {
159         request_free(rqst);
160         return ENOMEM;
161     }
162
163     i = 0;
164     for (tmp = ai; tmp != NULL; tmp = tmp->ai_next) {
165         retval = get_server(rc, tmp, secret, &rqst->remotes[i++].remote);
166         if (retval != 0) {
167             request_free(rqst);
168             return retval;
169         }
170     }
171
172     *req = rqst;
173     return 0;
174 }
175
176 /* Close remotes that haven't been used in a while. */
177 static void
178 age(struct server_head *head, time_t currtime)
179 {
180     server *srv, *tmp;
181
182     K5_LIST_FOREACH_SAFE(srv, head, list, tmp) {
183         if (currtime == (time_t)-1 || currtime - srv->last > 60 * 60) {
184             K5_LIST_REMOVE(srv, list);
185             kr_remote_free(srv->serv);
186             free(srv);
187         }
188     }
189 }
190
191 /* Handle a response from a server (or related errors). */
192 static void
193 on_response(krb5_error_code retval, const krad_packet *reqp,
194             const krad_packet *rspp, void *data)
195 {
196     request *req = data;
197     time_t currtime;
198     size_t i;
199
200     /* Do nothing if we are already completed. */
201     if (req->count < 0)
202         return;
203
204     /* If we have timed out and have more remotes to try, do so. */
205     if (retval == ETIMEDOUT && req->remotes[++req->current].remote != NULL) {
206         retval = kr_remote_send(req->remotes[req->current].remote, req->code,
207                                 req->attrs, on_response, req, req->timeout,
208                                 req->retries,
209                                 &req->remotes[req->current].packet);
210         if (retval == 0)
211             return;
212     }
213
214     /* Mark the request as complete. */
215     req->count = -1;
216
217     /* Inform the callback. */
218     req->cb(retval, reqp, rspp, req->data);
219
220     /* Cancel the outstanding packets. */
221     for (i = 0; req->remotes[i].remote != NULL; i++)
222         kr_remote_cancel(req->remotes[i].remote, req->remotes[i].packet);
223
224     /* Age out servers that haven't been used in a while. */
225     if (time(&currtime) != (time_t)-1)
226         age(&req->rc->servers, currtime);
227
228     request_free(req);
229 }
230
231 krb5_error_code
232 krad_client_new(krb5_context kctx, verto_ctx *vctx, krad_client **out)
233 {
234     krad_client *tmp;
235
236     tmp = calloc(1, sizeof(krad_client));
237     if (tmp == NULL)
238         return ENOMEM;
239
240     tmp->kctx = kctx;
241     tmp->vctx = vctx;
242
243     *out = tmp;
244     return 0;
245 }
246
247 void
248 krad_client_free(krad_client *rc)
249 {
250     if (rc == NULL)
251         return;
252
253     age(&rc->servers, -1);
254     free(rc);
255 }
256
257 static krb5_error_code
258 resolve_remote(const char *remote, struct addrinfo **ai)
259 {
260     const char *svc = "radius";
261     krb5_error_code retval;
262     struct addrinfo hints;
263     char *sep, *srv;
264
265     /* Isolate the port number if it exists. */
266     srv = strdup(remote);
267     if (srv == NULL)
268         return ENOMEM;
269
270     if (srv[0] == '[') {
271         /* IPv6 */
272         sep = strrchr(srv, ']');
273         if (sep != NULL && sep[1] == ':') {
274             sep[1] = '\0';
275             svc = &sep[2];
276         }
277     } else {
278         /* IPv4 or DNS */
279         sep = strrchr(srv, ':');
280         if (sep != NULL && sep[1] != '\0') {
281             sep[0] = '\0';
282             svc = &sep[1];
283         }
284     }
285
286     /* Perform the lookup. */
287     memset(&hints, 0, sizeof(hints));
288     hints.ai_socktype = SOCK_DGRAM;
289     retval = gai_error_code(getaddrinfo(srv, svc, &hints, ai));
290     free(srv);
291     return retval;
292 }
293
294 krb5_error_code
295 krad_client_send(krad_client *rc, krad_code code, const krad_attrset *attrs,
296                  const char *remote, const char *secret, int timeout,
297                  size_t retries, krad_cb cb, void *data)
298 {
299     struct addrinfo usock, *ai = NULL;
300     krb5_error_code retval;
301     struct sockaddr_un ua;
302     request *req;
303
304     if (remote[0] == '/') {
305         ua.sun_family = AF_UNIX;
306         snprintf(ua.sun_path, sizeof(ua.sun_path), "%s", remote);
307         memset(&usock, 0, sizeof(usock));
308         usock.ai_family = AF_UNIX;
309         usock.ai_socktype = SOCK_STREAM;
310         usock.ai_addr = (struct sockaddr *)&ua;
311         usock.ai_addrlen = sizeof(ua);
312
313         retval = request_new(rc, code, attrs, &usock, secret, timeout, retries,
314                              cb, data, &req);
315     } else {
316         retval = resolve_remote(remote, &ai);
317         if (retval == 0) {
318             retval = request_new(rc, code, attrs, ai, secret, timeout, retries,
319                                  cb, data, &req);
320             freeaddrinfo(ai);
321         }
322     }
323     if (retval != 0)
324         return retval;
325
326     retval = kr_remote_send(req->remotes[req->current].remote, req->code,
327                             req->attrs, on_response, req, req->timeout,
328                             req->retries, &req->remotes[req->current].packet);
329     if (retval != 0) {
330         request_free(req);
331         return retval;
332     }
333
334     return 0;
335 }