c2966b8b26b34181c50083b1ca32647a55c0c62d
[radsecproxy.git] / lib / rsp_util.c
1 /*
2  * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  */
8
9 /* Code contributions from:
10  *
11  * Stefan Winter <stefan.winter@restena.lu>
12  */
13
14 #include <sys/socket.h>
15 #include <sys/types.h>
16 #include <netinet/in.h>
17 #include <netdb.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <sys/select.h>
25 #include <stdarg.h>
26 #include "rsp_debug.h"
27 #include "rsp_util.h"
28
29 char *stringcopy(const char *s, int len) {
30     char *r;
31     if (!s)
32         return NULL;
33     if (!len)
34         len = strlen(s);
35     r = malloc(len + 1);
36     if (!r)
37         debug(DBG_ERR, "stringcopy: malloc failed");
38     memcpy(r, s, len);
39     r[len] = '\0';
40     return r;
41 }
42
43 void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) {
44     int i;
45     unsigned char *s = (unsigned char *)chars;
46     if (prefix)
47         printf(prefixfmt ? prefixfmt : "%s: ", prefix);
48     for (i = 0; i < len; i++)
49         printf(charfmt ? charfmt : "%c", s[i]);
50     printf("\n");
51 }
52
53 void port_set(struct sockaddr *sa, uint16_t port) {
54     switch (sa->sa_family) {
55     case AF_INET:
56         ((struct sockaddr_in *)sa)->sin_port = htons(port);
57         break;
58     case AF_INET6:
59         ((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
60         break;
61     }
62 }
63
64 struct sockaddr *addr_copy(struct sockaddr *in) {
65     struct sockaddr *out = NULL;
66
67     switch (in->sa_family) {
68     case AF_INET:
69         out = malloc(sizeof(struct sockaddr_in));
70         if (out) {
71             memset(out, 0, sizeof(struct sockaddr_in));
72             ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr;
73         }
74         break;
75     case AF_INET6:
76         out = malloc(sizeof(struct sockaddr_in6));
77         if (out) {
78             memset(out, 0, sizeof(struct sockaddr_in6));
79             ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr;
80         }
81         break;
82     }
83     out->sa_family = in->sa_family;
84 #ifdef SIN6_LEN
85     out->sa_len = in->sa_len;
86 #endif
87     return out;
88 }
89
90 char *addr2string(struct sockaddr *addr) {
91     union {
92         struct sockaddr *sa;
93         struct sockaddr_in *sa4;
94         struct sockaddr_in6 *sa6;
95     } u;
96     struct sockaddr_in sa4;
97     static char addr_buf[2][INET6_ADDRSTRLEN];
98     static int i = 0;
99     i = !i;
100     u.sa = addr;
101     if (u.sa->sa_family == AF_INET6) {
102         if (IN6_IS_ADDR_V4MAPPED(&u.sa6->sin6_addr)) {
103             memset(&sa4, 0, sizeof(sa4));
104             sa4.sin_family = AF_INET;
105             sa4.sin_port = u.sa6->sin6_port;
106             memcpy(&sa4.sin_addr, &u.sa6->sin6_addr.s6_addr[12], 4);
107             u.sa4 = &sa4;
108         }
109     }
110     if (getnameinfo(u.sa, SOCKADDRP_SIZE(u.sa), addr_buf[i], sizeof(addr_buf[i]),
111                     NULL, 0, NI_NUMERICHOST)) {
112         debug(DBG_WARN, "getnameinfo failed");
113         return "getnameinfo_failed";
114     }
115     return addr_buf[i];
116 }
117
118 #if 0
119 /* not in use */
120 int connectport(int type, char *host, char *port) {
121     struct addrinfo hints, *res0, *res;
122     int s = -1;
123
124     memset(&hints, 0, sizeof(hints));
125     hints.ai_socktype = type;
126     hints.ai_family = AF_UNSPEC;
127
128     if (getaddrinfo(host, port, &hints, &res0) != 0) {
129         debug(DBG_ERR, "connectport: can't resolve host %s port %s", host, port);
130         return -1;
131     }
132
133     for (res = res0; res; res = res->ai_next) {
134         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
135         if (s < 0) {
136             debug(DBG_WARN, "connectport: socket failed");
137             continue;
138         }
139         if (connect(s, res->ai_addr, res->ai_addrlen) == 0)
140             break;
141         debug(DBG_WARN, "connectport: connect failed");
142         close(s);
143         s = -1;
144     }
145     freeaddrinfo(res0);
146     return s;
147 }
148 #endif
149
150 /* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized"
151    RADIUS packet to be discarded on first attempt (due to Path MTU discovery).
152 */
153
154 void disable_DF_bit(int socket, struct addrinfo *res) {
155     if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) {
156 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
157         /*
158          * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant.
159          */
160         int r, action;
161         debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)");
162         action = IP_PMTUDISC_DONT;
163         r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action));
164         if (r == -1)
165             debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER");
166 #else
167         debug(DBG_INFO, "Non-Linux platform, unable to unset DF bit for UDP. You should check with tcpdump whether radsecproxy will send its UDP packets with DF bit set!");
168 #endif
169     }
170 }
171
172 int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) {
173     int s, on = 1;
174     struct addrinfo *res;
175
176     for (res = addrinfo; res; res = res->ai_next) {
177         if (family != AF_UNSPEC && family != res->ai_family)
178             continue;
179         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
180         if (s < 0) {
181             debug(DBG_WARN, "bindtoaddr: socket failed");
182             continue;
183         }
184
185         disable_DF_bit(s,res);
186
187         if (reuse)
188             setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
189 #ifdef IPV6_V6ONLY
190         if (v6only)
191             setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
192 #endif
193         if (!bind(s, res->ai_addr, res->ai_addrlen))
194             return s;
195         debug(DBG_WARN, "bindtoaddr: bind failed");
196         close(s);
197     }
198     return -1;
199 }
200
201 int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) {
202     int origflags, error = 0, r = -1;
203     fd_set writefds;
204     socklen_t len;
205
206     origflags = fcntl(s, F_GETFL, 0);
207     fcntl(s, F_SETFL, origflags | O_NONBLOCK);
208     if (!connect(s, addr, addrlen)) {
209         r = 0;
210         goto exit;
211     }
212     if (errno != EINPROGRESS)
213         goto exit;
214
215     FD_ZERO(&writefds);
216     FD_SET(s, &writefds);
217     if (select(s + 1, NULL, &writefds, NULL, timeout) < 1)
218         goto exit;
219
220     len = sizeof(error);
221     if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) && !error)
222         r = 0;
223
224 exit:
225     fcntl(s, F_SETFL, origflags);
226     return r;
227 }
228
229 int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) {
230     int s;
231     struct addrinfo *res;
232     struct timeval to;
233
234     s = -1;
235     if (timeout) {
236         if (addrinfo && addrinfo->ai_next && timeout > 5)
237             timeout = 5;
238         to.tv_sec = timeout;
239         to.tv_usec = 0;
240     }
241
242     for (res = addrinfo; res; res = res->ai_next) {
243         s = bindtoaddr(src, res->ai_family, 1, 1);
244         if (s < 0) {
245             debug(DBG_WARN, "connecttoserver: socket failed");
246             continue;
247         }
248         if ((timeout
249              ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, &to)
250              : connect(s, res->ai_addr, res->ai_addrlen)) == 0)
251             break;
252         debug(DBG_WARN, "connecttoserver: connect failed");
253         close(s);
254         s = -1;
255     }
256     return s;
257 }
258
259 /* Local Variables: */
260 /* c-file-style: "stroustrup" */
261 /* End: */