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