5d4fbc2546e7e7cc45824e70a2bbaaf738df4384
[citadel.git] / webcit-ng / server / tcp_sockets.c
1 // TCP sockets layer
2 //
3 // Copyright (c) 1987-2022 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or
6 // disclosure are subject to the GNU General Public License v3.
7
8 #include "webcit.h"
9
10 // lingering_close() a`la Apache. see
11 // http://httpd.apache.org/docs/2.0/misc/fin_wait_2.html for rationale
12 int lingering_close(int fd) {
13         char buf[SIZ];
14         int i;
15         fd_set set;
16         struct timeval tv, start;
17
18         gettimeofday(&start, NULL);
19         if (fd == -1)
20                 return -1;
21         shutdown(fd, 1);
22         do {
23                 do {
24                         gettimeofday(&tv, NULL);
25                         tv.tv_sec = SLEEPING - (tv.tv_sec - start.tv_sec);
26                         tv.tv_usec = start.tv_usec - tv.tv_usec;
27                         if (tv.tv_usec < 0) {
28                                 tv.tv_sec--;
29                                 tv.tv_usec += 1000000;
30                         }
31                         FD_ZERO(&set);
32                         FD_SET(fd, &set);
33                         i = select(fd + 1, &set, NULL, NULL, &tv);
34                 } while (i == -1 && errno == EINTR);
35
36                 if (i <= 0)
37                         break;
38
39                 i = read(fd, buf, sizeof buf);
40         } while (i != 0 && (i != -1 || errno == EINTR));
41
42         return close(fd);
43 }
44
45
46 // This is a generic function to set up a master socket for listening on
47 // a TCP port.  The server shuts down if the bind fails.  (IPv4/IPv6 version)
48 //
49 // ip_addr      IP address to bind
50 // port_number  port number to bind
51 // queue_len    number of incoming connections to allow in the queue
52 int webcit_tcp_server(const char *ip_addr, int port_number, int queue_len) {
53         const char *ipv4broadcast = "0.0.0.0";
54         int IsDefault = 0;
55         struct protoent *p;
56         struct sockaddr_in6 sin6;
57         struct sockaddr_in sin4;
58         int s, i, b;
59         int ip_version = 6;
60
61 retry:
62         memset(&sin6, 0, sizeof(sin6));
63         memset(&sin4, 0, sizeof(sin4));
64         sin6.sin6_family = AF_INET6;
65         sin4.sin_family = AF_INET;
66
67         if (    (ip_addr == NULL)                                       // any IPv6
68                 || (IsEmptyStr(ip_addr))
69                 || (!strcmp(ip_addr, "*"))
70         ) {
71                 IsDefault = 1;
72                 ip_version = 6;
73                 sin6.sin6_addr = in6addr_any;
74         }
75         else if (!strcmp(ip_addr, "0.0.0.0")) {                         // any IPv4
76                 ip_version = 4;
77                 sin4.sin_addr.s_addr = INADDR_ANY;
78         }
79         else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':'))) {   // specific IPv4
80                 ip_version = 4;
81                 if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
82                         syslog(LOG_WARNING, "Error binding to [%s] : %s\n", ip_addr, strerror(errno));
83                         return (-1);
84                 }
85         }
86         else {                                                          // specific IPv6
87
88                 ip_version = 6;
89                 if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
90                         syslog(LOG_WARNING, "Error binding to [%s] : %s\n", ip_addr, strerror(errno));
91                         return (-1);
92                 }
93         }
94
95         if (port_number == 0) {
96                 syslog(LOG_WARNING, "Cannot start: no port number specified.\n");
97                 return (-1);
98         }
99         sin6.sin6_port = htons((u_short) port_number);
100         sin4.sin_port = htons((u_short) port_number);
101
102         p = getprotobyname("tcp");
103
104         s = socket(((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
105         if (s < 0) {
106                 if (IsDefault && (errno == EAFNOSUPPORT)) {
107                         s = 0;
108                         ip_addr = ipv4broadcast;
109                         goto retry;
110                 }
111                 syslog(LOG_WARNING, "Can't create a listening socket: %s\n", strerror(errno));
112                 return (-1);
113         }
114
115         // Set some socket options that make sense.
116         i = 1;
117         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
118
119         if (ip_version == 6) {
120                 b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
121         }
122         else {
123                 b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
124         }
125
126         if (b < 0) {
127                 syslog(LOG_ERR, "Can't bind: %s\n", strerror(errno));
128                 close(s);
129                 return (-1);
130         }
131
132         if (listen(s, queue_len) < 0) {
133                 syslog(LOG_ERR, "Can't listen: %s\n", strerror(errno));
134                 close(s);
135                 return (-1);
136         }
137         return (s);
138 }
139
140
141 // Create a Unix domain socket and listen on it
142 // sockpath - file name of the unix domain socket
143 // queue_len - Number of incoming connections to allow in the queue
144 int webcit_uds_server(char *sockpath, int queue_len) {
145         struct sockaddr_un addr;
146         int s;
147         int i;
148         int actual_queue_len;
149
150         actual_queue_len = queue_len;
151         if (actual_queue_len < 5)
152                 actual_queue_len = 5;
153
154         i = unlink(sockpath);
155         if ((i != 0) && (errno != ENOENT)) {
156                 syslog(LOG_WARNING, "webcit: can't unlink %s: %s", sockpath, strerror(errno));
157                 return (-1);
158         }
159
160         memset(&addr, 0, sizeof(addr));
161         addr.sun_family = AF_UNIX;
162         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
163
164         s = socket(AF_UNIX, SOCK_STREAM, 0);
165         if (s < 0) {
166                 syslog(LOG_WARNING, "webcit: Can't create a unix domain socket: %s", strerror(errno));
167                 return (-1);
168         }
169
170         if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
171                 syslog(LOG_WARNING, "webcit: Can't bind: %s", strerror(errno));
172                 close(s);
173                 return (-1);
174         }
175
176         if (listen(s, actual_queue_len) < 0) {
177                 syslog(LOG_WARNING, "webcit: Can't listen: %s", strerror(errno));
178                 close(s);
179                 return (-1);
180         }
181
182         chmod(sockpath, 0777);
183         return (s);
184 }