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