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