xref: /freebsd/crypto/krb5/src/lib/krad/client.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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