README: Simplify strength info, add link to pachi-tr
[pachi.git] / network.c
blobf3cb321d94f57f93e9ac825456e25686832df14d
1 /* Utility functions to redirect stdin, stdout, stderr to sockets. */
3 #define DEBUG
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <stdbool.h>
8 #include <assert.h>
9 #include <unistd.h>
10 #include <errno.h>
11 #include <pthread.h>
12 #include <sys/types.h>
14 #ifdef _WIN32
15 #include <winsock2.h>
16 #include <ws2tcpip.h>
17 #else
18 #include <sys/socket.h>
19 #include <netdb.h>
20 #include <netinet/in.h>
21 #endif
23 #include "debug.h"
24 #include "util.h"
26 #define STDIN 0
27 #define STDOUT 1
28 #define STDERR 2
30 #define BSIZE 4096
32 static inline void
33 die(char *msg)
35 perror(msg);
36 exit(42);
39 /* Create a socket, bind to it on the given port and listen.
40 * This function is restricted to server mode (port has
41 * no hostname). Returns the socket. */
42 int
43 port_listen(char *port, int max_connections)
45 int sock = socket(AF_INET, SOCK_STREAM, 0);
46 if (sock == -1)
47 die("socket");
49 struct sockaddr_in server_addr;
50 memset(&server_addr, 0, sizeof(server_addr));
51 server_addr.sin_family = AF_INET;
52 server_addr.sin_port = htons(atoi(port));
53 server_addr.sin_addr.s_addr = INADDR_ANY;
55 const int val = 1;
56 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)))
57 die("setsockopt");
58 if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
59 die("bind");
60 if (listen(sock, max_connections) == -1)
61 die("listen");
62 return sock;
65 /* Returns true if in private address range: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 */
66 static bool
67 is_private(struct in_addr *in)
69 return (ntohl(in->s_addr) & 0xff000000) >> 24 == 10
70 || (ntohl(in->s_addr) & 0xfff00000) >> 16 == 172 * 256 + 16
71 || (ntohl(in->s_addr) & 0xffff0000) >> 16 == 192 * 256 + 168;
74 /* Waits for a connection on the given socket, and returns the file descriptor.
75 * Updates the client address if it is not null.
76 * WARNING: the connection is not authenticated. As a weak security measure,
77 * the connections are limited to a private network. */
78 int
79 open_server_connection(int socket, struct in_addr *client)
81 assert(socket >= 0);
82 for (;;) {
83 struct sockaddr_in client_addr;
84 int sin_size = sizeof(struct sockaddr_in);
85 int fd = accept(socket, (struct sockaddr *)&client_addr, (socklen_t *)&sin_size);
86 if (fd == -1) {
87 die("accept");
89 if (is_private(&client_addr.sin_addr)) {
90 if (client)
91 *client = client_addr.sin_addr;
92 return fd;
94 close(fd);
98 /* Opens a new connection to the given port name, which must
99 * contain a host name. Returns the open file descriptor,
100 * or -1 if the open fails. */
101 static int
102 open_client_connection(char *port_name)
104 char hostname[BSIZE];
105 strncpy(hostname, port_name, sizeof(hostname));
106 char *port = strchr(hostname, ':');
107 assert(port);
108 *port++ = '\0';
110 struct hostent *host = gethostbyname(hostname);
111 if (!host)
112 return -1;
113 int sock = socket(AF_INET, SOCK_STREAM, 0);
114 if (sock == -1)
115 die("socket");
116 struct sockaddr_in sin;
117 memcpy(&sin.sin_addr.s_addr, host->h_addr, host->h_length);
118 sin.sin_family = AF_INET;
119 sin.sin_port = htons(atoi(port));
121 if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
122 close(sock);
123 return -1;
125 return sock;
128 /* Allow connexion queue > 1 to avoid race conditions. */
129 #define MAX_CONNEXIONS 5
131 struct port_info {
132 int socket;
133 char *port;
136 /* Wait at most 30s between connection attempts. */
137 #define MAX_WAIT 30
139 /* Open a connection on the given socket/port.
140 * Act as server if the port doesn't contain a hostname,
141 * as a client otherwise. If socket < 0 or in client mode,
142 * create the socket from the given port and update socket.
143 * Block until the connection succeeds.
144 * Return a file descriptor for the new connection. */
145 static int
146 open_connection(struct port_info *info)
148 int conn;
149 char *p = strchr(info->port, ':');
150 if (p) {
151 for (int try = 1;; ) {
152 conn = open_client_connection(info->port);
153 if (conn >= 0) break;
154 sleep(try);
155 if (try < MAX_WAIT) try++;
157 info->socket = conn;
158 } else {
159 if (info->socket < 0)
160 info->socket = port_listen(info->port, MAX_CONNEXIONS);
161 conn = open_server_connection(info->socket, NULL);
163 return conn;
166 /* Open the log connection on the given port, redirect stderr to it. */
167 static void
168 open_log_connection(struct port_info *info)
170 int log_conn = open_connection(info);
171 if (dup2(log_conn, STDERR) < 0)
172 die("dup2");
173 if (DEBUGL(0))
174 fprintf(stderr, "log connection opened\n");
177 /* Thread keeping the log connection open and redirecting stderr to it.
178 * It also echoes its input, which can be used to check if the
179 * program is alive. As a weak identity check, in server mode the input
180 * must start with "Pachi" (without the quotes). */
181 static void *
182 log_thread(void *arg)
184 struct port_info *info = arg;
185 assert(info && info->port);
186 for (;;) {
187 char buf[BSIZE];
188 int size;
189 bool check = !strchr(info->port, ':');
190 if (!check)
191 write(STDERR, "Pachi\n", 6);
192 while ((size = read(STDERR, buf, BSIZE)) > 0) {
193 if (check && strncasecmp(buf, "Pachi", 5)) break;
194 check = false;
195 write(STDERR, buf, size);
197 fflush(stderr);
198 open_log_connection(info);
202 /* Open the log connection on the given port, redirect stderr to it,
203 * and keep reopening it if the connection is closed. */
204 void
205 open_log_port(char *port)
207 pthread_t thread;
208 static struct port_info log_info = { .socket = -1 };
209 log_info.port = port;
210 open_log_connection(&log_info);
212 /* From now on, log_info may only be modified by the single
213 * log_thread so static allocation is ok and there is no race. */
214 pthread_create(&thread, NULL, log_thread, (void *)&log_info);
217 /* Open the gtp connection on the given port, redirect stdin & stdout to it. */
218 void
219 open_gtp_connection(int *socket, char *port)
221 static struct port_info gtp_info = { .socket = -1 };
222 gtp_info.port = port;
223 int gtp_conn = open_connection(&gtp_info);
224 for (int d = STDIN; d <= STDOUT; d++) {
225 if (dup2(gtp_conn, d) < 0)
226 die("dup2");
228 if (DEBUGL(0))
229 fprintf(stderr, "gtp connection opened\n");