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