1 /* Copyright (c) 2007-2009, UNINETT AS
2  * Copyright (c) 2016, NORDUnet A/S */
3 /* See LICENSE for licensing information. */
4 
5 #include <sys/socket.h>
6 #include <netinet/in.h>
7 #include <netinet/tcp.h>
8 #include <netdb.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 #include <poll.h>
16 #include <stdarg.h>
17 #include <assert.h>
18 #include "debug.h"
19 #include "util.h"
20 
stringcopy(const char * s,int len)21 char *stringcopy(const char *s, int len) {
22     char *r;
23     if (!s)
24 	return NULL;
25     if (!len)
26 	len = strlen(s);
27     r = malloc(len + 1);
28     if (!r)
29 	debugx(1, DBG_ERR, "stringcopy: malloc failed");
30     memcpy(r, s, len);
31     r[len] = '\0';
32     return r;
33 }
34 
printfchars(char * prefixfmt,char * prefix,char * charfmt,uint8_t * chars,int len)35 void printfchars(char *prefixfmt, char *prefix, char *charfmt, uint8_t *chars, int len) {
36     int i;
37     unsigned char *s = (unsigned char *)chars;
38     if (prefix)
39 	printf(prefixfmt ? prefixfmt : "%s: ", prefix);
40     for (i = 0; i < len; i++)
41 	printf(charfmt ? charfmt : "%c", s[i]);
42     printf("\n");
43 }
44 
port_set(struct sockaddr * sa,uint16_t port)45 void port_set(struct sockaddr *sa, uint16_t port) {
46     switch (sa->sa_family) {
47     case AF_INET:
48 	((struct sockaddr_in *)sa)->sin_port = htons(port);
49 	break;
50     case AF_INET6:
51 	((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
52 	break;
53     }
54 }
55 
addr_copy(struct sockaddr * in)56 struct sockaddr *addr_copy(struct sockaddr *in) {
57     struct sockaddr *out = NULL;
58 
59     switch (in->sa_family) {
60     case AF_INET:
61 	out = malloc(sizeof(struct sockaddr_in));
62         if (out == NULL)
63             return NULL;
64         *(struct sockaddr_in *)out = *(struct sockaddr_in *)in;
65 	break;
66     case AF_INET6:
67 	out = malloc(sizeof(struct sockaddr_in6));
68         if (out == NULL)
69             return NULL;
70         *(struct sockaddr_in6 *)out = *(struct sockaddr_in6 *)in;
71 	break;
72     }
73     assert(out);
74 #ifdef SIN6_LEN
75     out->sa_len = in->sa_len;
76 #endif
77     return out;
78 }
79 
addr2string(struct sockaddr * addr,char * buf,size_t len)80 const char *addr2string(struct sockaddr *addr, char *buf, size_t len) {
81     struct sockaddr_in6 *sa6;
82     struct sockaddr_in sa4;
83 
84     if (addr->sa_family == AF_INET6) {
85 	sa6 = (struct sockaddr_in6 *)addr;
86 	if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
87 	    memset(&sa4, 0, sizeof(sa4));
88 	    sa4.sin_family = AF_INET;
89 	    sa4.sin_port = sa6->sin6_port;
90 	    memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4);
91 	    addr = (struct sockaddr *)&sa4;
92 	}
93     }
94     if (getnameinfo(addr, SOCKADDRP_SIZE(addr), buf, len,
95                     NULL, 0, NI_NUMERICHOST)) {
96         debug(DBG_WARN, "getnameinfo failed");
97         return "getnameinfo_failed";
98     }
99     return buf;
100 }
101 
102 /* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized"
103    RADIUS packet to be discarded on first attempt (due to Path MTU discovery).
104 */
105 
disable_DF_bit(int socket,struct addrinfo * res)106 void disable_DF_bit(int socket, struct addrinfo *res) {
107     if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) {
108 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
109         /*
110          * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant.
111          */
112 	int r, action;
113         debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)");
114         action = IP_PMTUDISC_DONT;
115         r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action));
116         if (r == -1)
117 	    debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER");
118 #else
119 	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!");
120 #endif
121     }
122 }
123 
enable_keepalive(int socket)124 void enable_keepalive(int socket) {
125     int optval;
126     socklen_t optlen = sizeof(optval);
127 
128 #if !defined(TCP_KEEPCNT) || !defined(TCP_KEEPIDLE) || !defined(TCP_KEEPINTVL)
129     debug(DBG_NOTICE, "TCP Keepalive feature might be limited on this platform");
130 #else
131     optval = 3;
132     if(setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen) < 0) {
133         debug(DBG_ERR, "enable_keepalive: setsockopt TCP_KEEPCNT failed");
134     }
135     optval = 10;
136     if(setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) {
137         debug(DBG_ERR, "enable_keepalive: setsockopt TCP_KEEPIDLE %d failed", optval);
138     }
139     optval = 10;
140     if(setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) {
141         debug(DBG_ERR, "enable_keepalive: setsockopt TCP_KEEPINTVL failed");
142     }
143 #endif
144     optval = 1;
145     if(setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
146         debug(DBG_ERR, "enable_keepalive: setsockopt SO_KEEPALIVE failed");
147     }
148 }
149 
bindtoaddr(struct addrinfo * addrinfo,int family,int reuse)150 int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse) {
151     int s, on = 1;
152     struct addrinfo *res;
153 
154     for (res = addrinfo; res; res = res->ai_next) {
155 	if (family != AF_UNSPEC && family != res->ai_family)
156 	    continue;
157 	s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
158 	if (s < 0) {
159 	    debug(DBG_WARN, "bindtoaddr: socket failed");
160 	    continue;
161 	}
162 
163 	disable_DF_bit(s,res);
164 
165 	if (reuse)
166 	    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
167                 debugerrno(errno, DBG_WARN, "Failed to set SO_REUSEADDR");
168 #ifdef IPV6_V6ONLY
169 	if (family == AF_INET6)
170 	    if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1)
171                 debugerrno(errno, DBG_WARN, "Failed to set IPV6_V6ONLY");
172 #endif
173 	if (!bind(s, res->ai_addr, res->ai_addrlen))
174 	    return s;
175 	debug(DBG_WARN, "bindtoaddr: bind failed");
176 	close(s);
177     }
178     return -1;
179 }
180 
connectnonblocking(int s,const struct sockaddr * addr,socklen_t addrlen,int timeout)181 int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
182     int origflags, r = -1, sockerr = 0;
183     socklen_t errlen = sizeof(sockerr);
184     struct pollfd fds[1];
185 
186     origflags = fcntl(s, F_GETFL, 0);
187     if (origflags == -1) {
188         debugerrno(errno, DBG_WARN, "Failed to get flags");
189         return -1;
190     }
191     if (fcntl(s, F_SETFL, origflags | O_NONBLOCK) == -1) {
192         debugerrno(errno, DBG_WARN, "Failed to set O_NONBLOCK");
193         return -1;
194     }
195     if (!connect(s, addr, addrlen)) {
196 	r = 0;
197 	goto exit;
198     }
199     if (errno != EINPROGRESS)
200 	goto exit;
201 
202     fds[0].fd = s;
203     fds[0].events = POLLOUT;
204     if (poll(fds, 1, timeout * 1000) < 1)
205 	goto exit;
206 
207     if (fds[0].revents & POLLERR) {
208         if(!getsockopt(s, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &errlen))
209             debug(DBG_WARN, "Connection failed: %s", strerror(sockerr));
210         else
211             debug(DBG_WARN, "Connection failed: unknown error");
212     } else if (fds[0].revents & POLLHUP) {
213             debug(DBG_WARN, "Connect error: hang up");
214     } else if (fds[0].revents & POLLNVAL) {
215             debug(DBG_WARN, "Connect error: fd not open");
216     } else if(fds[0].revents & POLLOUT) {
217         debug(DBG_DBG, "Connection up");
218         r = 0;
219     }
220 
221 exit:
222     if (fcntl(s, F_SETFL, origflags) == -1)
223         debugerrno(errno, DBG_WARN, "Failed to set original flags back");
224     return r;
225 }
226 
connecttcp(struct addrinfo * addrinfo,struct addrinfo * src,uint16_t timeout)227 int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) {
228     int s;
229     struct addrinfo *res;
230 
231     s = -1;
232     if (timeout) {
233 	if (addrinfo && addrinfo->ai_next && timeout > 5)
234 	    timeout = 5;
235     }
236 
237     for (res = addrinfo; res; res = res->ai_next) {
238 	s = bindtoaddr(src, res->ai_family, 1);
239 	if (s < 0) {
240 	    debug(DBG_WARN, "connecttoserver: socket failed");
241 	    continue;
242 	}
243 	if ((timeout
244 	     ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, timeout)
245 	     : connect(s, res->ai_addr, res->ai_addrlen)) == 0)
246 	    break;
247 	debug(DBG_WARN, "connecttoserver: connect failed");
248 	close(s);
249 	s = -1;
250     }
251     return s;
252 }
253 
connect_wait(struct timeval attempt_start,struct timeval last_success,int firsttry)254 time_t connect_wait(struct timeval attempt_start, struct timeval last_success, int firsttry) {
255     struct timeval now;
256 
257     gettimeofday(&now, NULL);
258 
259     if (attempt_start.tv_sec < last_success.tv_sec ||
260         attempt_start.tv_sec > now.tv_sec) {
261         debug(DBG_WARN, "connect_wait: invalid timers detected!");
262         return 60;
263     }
264 
265     if (now.tv_sec - last_success.tv_sec < 30)
266         return 30 - (attempt_start.tv_sec - last_success.tv_sec);
267 
268     if (firsttry)
269         return 0;
270 
271     if (now.tv_sec - attempt_start.tv_sec < 2)
272         return 2;
273 
274     if (now.tv_sec - attempt_start.tv_sec > 60)
275         return 60;
276 
277     return now.tv_sec - attempt_start.tv_sec;
278 }
279 
280 /* Local Variables: */
281 /* c-file-style: "stroustrup" */
282 /* End: */
283