Add copyright information to LICENSE.
[libradsec.git] / util.c
1 /* Copyright (c) 2006-2009, Stig Venaas, UNINETT AS.
2  * Copyright (c) 2010, UNINETT AS, NORDUnet A/S.
3  * Copyright (c) 2010-2012, NORDUnet A/S. */
4 /* See LICENSE for licensing information. */
5
6 /* Code contributions from:
7  *
8  * Stefan Winter <stefan.winter@restena.lu>
9  */
10
11 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <netdb.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <sys/select.h>
21 #include <stdarg.h>
22 #include "debug.h"
23 #include "util.h"
24
25 char *stringcopy(const char *s, int len) {
26     char *r;
27     if (!s)
28         return NULL;
29     if (!len)
30         len = strlen(s);
31     r = malloc(len + 1);
32     if (!r)
33         debug(DBG_ERR, "stringcopy: malloc failed");
34     memcpy(r, s, len);
35     r[len] = '\0';
36     return r;
37 }
38
39 void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) {
40     int i;
41     unsigned char *s = (unsigned char *)chars;
42     if (prefix)
43         printf(prefixfmt ? prefixfmt : "%s: ", prefix);
44     for (i = 0; i < len; i++)
45         printf(charfmt ? charfmt : "%c", s[i]);
46     printf("\n");
47 }
48
49 void port_set(struct sockaddr *sa, uint16_t port) {
50     switch (sa->sa_family) {
51     case AF_INET:
52         ((struct sockaddr_in *)sa)->sin_port = htons(port);
53         break;
54     case AF_INET6:
55         ((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
56         break;
57     }
58 }
59
60 struct sockaddr *addr_copy(struct sockaddr *in) {
61     struct sockaddr *out = NULL;
62
63     switch (in->sa_family) {
64     case AF_INET:
65         out = malloc(sizeof(struct sockaddr_in));
66         if (out) {
67             memset(out, 0, sizeof(struct sockaddr_in));
68             ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr;
69         }
70         break;
71     case AF_INET6:
72         out = malloc(sizeof(struct sockaddr_in6));
73         if (out) {
74             memset(out, 0, sizeof(struct sockaddr_in6));
75             ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr;
76         }
77         break;
78     }
79     out->sa_family = in->sa_family;
80 #ifdef SIN6_LEN
81     out->sa_len = in->sa_len;
82 #endif
83     return out;
84 }
85
86 char *addr2string(struct sockaddr *addr) {
87     struct sockaddr_in6 *sa6;
88     struct sockaddr_in sa4;
89     static char addr_buf[2][INET6_ADDRSTRLEN];
90     static int i = 0;
91     i = !i;
92     if (addr->sa_family == AF_INET6) {
93         sa6 = (struct sockaddr_in6 *)addr;
94         if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
95             memset(&sa4, 0, sizeof(sa4));
96             sa4.sin_family = AF_INET;
97             sa4.sin_port = sa6->sin6_port;
98             memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4);
99             addr = (struct sockaddr *)&sa4;
100         }
101     }
102     if (getnameinfo(addr, SOCKADDRP_SIZE(addr), addr_buf[i], sizeof(addr_buf[i]),
103                     NULL, 0, NI_NUMERICHOST)) {
104         debug(DBG_WARN, "getnameinfo failed");
105         return "getnameinfo_failed";
106     }
107     return addr_buf[i];
108 }
109
110 /* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized"
111    RADIUS packet to be discarded on first attempt (due to Path MTU discovery).
112 */
113
114 void disable_DF_bit(int socket, struct addrinfo *res) {
115     if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) {
116 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
117         /*
118          * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant.
119          */
120         int r, action;
121         debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)");
122         action = IP_PMTUDISC_DONT;
123         r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action));
124         if (r == -1)
125             debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER");
126 #else
127         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!");
128 #endif
129     }
130 }
131
132 int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) {
133     int s, on = 1;
134     struct addrinfo *res;
135
136     for (res = addrinfo; res; res = res->ai_next) {
137         if (family != AF_UNSPEC && family != res->ai_family)
138             continue;
139         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
140         if (s < 0) {
141             debug(DBG_WARN, "bindtoaddr: socket failed");
142             continue;
143         }
144
145         disable_DF_bit(s,res);
146
147         if (reuse)
148             setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
149 #ifdef IPV6_V6ONLY
150         if (v6only)
151             setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
152 #endif
153         if (!bind(s, res->ai_addr, res->ai_addrlen))
154             return s;
155         debug(DBG_WARN, "bindtoaddr: bind failed");
156         close(s);
157     }
158     return -1;
159 }
160
161 int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) {
162     int origflags, error = 0, r = -1;
163     fd_set writefds;
164     socklen_t len;
165
166     origflags = fcntl(s, F_GETFL, 0);
167     fcntl(s, F_SETFL, origflags | O_NONBLOCK);
168     if (!connect(s, addr, addrlen)) {
169         r = 0;
170         goto exit;
171     }
172     if (errno != EINPROGRESS)
173         goto exit;
174
175     FD_ZERO(&writefds);
176     FD_SET(s, &writefds);
177     if (select(s + 1, NULL, &writefds, NULL, timeout) < 1)
178         goto exit;
179
180     len = sizeof(error);
181     if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) && !error)
182         r = 0;
183
184 exit:
185     fcntl(s, F_SETFL, origflags);
186     return r;
187 }
188
189 int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) {
190     int s;
191     struct addrinfo *res;
192     struct timeval to;
193
194     s = -1;
195     if (timeout) {
196         if (addrinfo && addrinfo->ai_next && timeout > 5)
197             timeout = 5;
198         to.tv_sec = timeout;
199         to.tv_usec = 0;
200     }
201
202     for (res = addrinfo; res; res = res->ai_next) {
203         s = bindtoaddr(src, res->ai_family, 1, 1);
204         if (s < 0) {
205             debug(DBG_WARN, "connecttoserver: socket failed");
206             continue;
207         }
208         if ((timeout
209              ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, &to)
210              : connect(s, res->ai_addr, res->ai_addrlen)) == 0)
211             break;
212         debug(DBG_WARN, "connecttoserver: connect failed");
213         close(s);
214         s = -1;
215     }
216     return s;
217 }
218
219 /* Local Variables: */
220 /* c-file-style: "stroustrup" */
221 /* End: */