1 /*
2  *     gtkatlantic - the gtk+ monopd client, enjoy network monopoly games
3  *
4  *
5  *  Copyright © 2002-2015 Sylvain Rochet
6  *
7  *  gtkatlantic is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; see the file COPYING. If not, see
19  *  <http://www.gnu.org/licenses/>.
20  */
21 
22 /* ** functions for connect/disconnect server, receive/send data to/from server */
23 
24 #include "config.h"
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31 
32 #ifndef WIN32
33 
34 #include <netdb.h>
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
37 #include <netinet/in.h>
38 #include <netinet/tcp.h>
39 #include <fcntl.h>
40 #define closesocket close
41 #define my_io_channel_socket_new g_io_channel_unix_new
42 
43 #else /* WIN32 */
44 
45 #undef _WIN32_WINNT
46 #define _WIN32_WINNT _WIN32_WINNT_WINXP  /* getaddrinfo() is only supported since WinXP */
47 #include <winsock2.h>
48 #include <ws2tcpip.h>
49 #include <mstcpip.h>
50 #define my_io_channel_socket_new g_io_channel_win32_new_socket
51 #ifndef SHUT_RDWR
52 #define SHUT_RDWR SD_BOTH
53 #endif /* SHUT_RDWR */
54 
55 #endif /* WIN32 */
56 
57 #include <sys/time.h>
58 #include <sys/types.h>
59 #include <time.h>
60 
61 #include "xmlparse.h"
62 #include "global.h"
63 #include "client.h"
64 #include "game.h"
65 #include "interface.h"
66 
67 static void* client_connect_async(connection *c);
68 static gboolean client_connect_finish(connection *c);
69 static gboolean client_connect_failed(gchar *msg);
70 static void client_connect_free(connection *c);
71 
72 /* -- connect to server
73      >
74      > return false if:
75      >   - max number of connection reached
76      >   - can't resolve host
77      >   - can't open port or socket
78 */
79 #if DEBUG
80 static guint32 nextid;
81 #endif /* DEBUG */
client_connect(guint8 type,gchar * host,gint32 port,gchar * cmd)82 void client_connect(guint8 type, gchar* host, gint32 port, gchar *cmd) {
83 	connection *c;
84 	GThread *t;
85 	gchar *msg;
86 
87 	switch (type) {
88 		case CONNECT_TYPE_MONOPD_GETGAME:
89 			client_disconnect(global->customserver_connect);
90 			break;
91 		case CONNECT_TYPE_MONOPD_GAME:
92 			client_disconnect(global->game_connect);
93 			break;
94 		case CONNECT_TYPE_METASERVER:
95 			client_disconnect(global->metaserver_connect);
96 			break;
97 	}
98 
99 	c = g_malloc0(sizeof(connection));
100 #if DEBUG
101 	c->id = nextid++;
102 #endif /* DEBUG */
103 	c->type = type;
104 	c->host = g_strdup(host);
105 	c->port = port;
106 	c->buffer_out = cmd;
107 
108 	msg = g_strdup_printf("Connecting to %s:%d…", c->host, c->port);
109 	interface_set_infolabel(msg, "008000", true);
110 	g_free(msg);
111 
112 	t = g_thread_new("connect", (GThreadFunc)client_connect_async, c);
113 	g_thread_unref(t);
114 }
115 
client_connect_async(connection * c)116 static void* client_connect_async(connection *c) {
117 	struct addrinfo hints;
118 	struct addrinfo *result, *addrinfo;
119 	char port_str[6];
120 	int r;
121 	char ip_str[INET6_ADDRSTRLEN];
122 	int sock;
123 
124 	memset(&hints, 0, sizeof(struct addrinfo));
125 	hints.ai_family = AF_UNSPEC;
126 	hints.ai_socktype = SOCK_STREAM;
127 	snprintf(port_str, sizeof(port_str), "%d", c->port);
128 
129 	r = getaddrinfo(c->host, port_str, &hints, &result);
130 	if (r != 0) {
131 		gchar *msg = g_strdup_printf("Can't resolve host %s:%d: %s", c->host, c->port, gai_strerror(r));
132 		g_idle_add((GSourceFunc)client_connect_failed, msg);
133 		goto failed;
134 	}
135 
136 	for (addrinfo = result; addrinfo != NULL; addrinfo = addrinfo->ai_next) {
137 		sock = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
138 		if (sock < 0) {
139 			gchar *msg = g_strdup_printf("Socket failed %s:%d: %s", c->host, c->port, strerror(errno));
140 			g_idle_add((GSourceFunc)client_connect_failed, msg);
141 			continue;
142 		}
143 
144 		ip_str[0] = '\0';
145 #ifndef WIN32
146 		if (addrinfo->ai_family == AF_INET) {
147 			inet_ntop(addrinfo->ai_family, &(((struct sockaddr_in *)addrinfo->ai_addr)->sin_addr), ip_str, INET6_ADDRSTRLEN);
148 		} else if (addrinfo->ai_family == AF_INET6) {
149 			inet_ntop(addrinfo->ai_family, &(((struct sockaddr_in6 *)addrinfo->ai_addr)->sin6_addr), ip_str, INET6_ADDRSTRLEN);
150 		}
151 #else /* WIN32 */
152 		if (addrinfo->ai_family == AF_INET) {
153 			DWORD len = INET6_ADDRSTRLEN;
154 			WSAAddressToString(addrinfo->ai_addr, (DWORD)sizeof(struct sockaddr_in), NULL, ip_str, &len);
155 		} else if (addrinfo->ai_family == AF_INET6) {
156 			DWORD len = INET6_ADDRSTRLEN;
157 			WSAAddressToString(addrinfo->ai_addr, (DWORD)sizeof(struct sockaddr_in6), NULL, ip_str, &len);
158 		}
159 #endif /* WIN32 */
160 
161 		r = connect(sock, addrinfo->ai_addr, addrinfo->ai_addrlen);
162 		if (r < 0) {
163 			gchar *msg = g_strdup_printf("Connect failed to %s:%d, %s: %s", c->host, c->port, ip_str, strerror(errno));
164 			g_idle_add((GSourceFunc)client_connect_failed, msg);
165 			close(sock);
166 			continue;
167 		}
168 		break;
169 	}
170 
171 	freeaddrinfo(result);
172 
173 	if (addrinfo == NULL) {
174 		goto failed;
175 	}
176 
177 #ifndef WIN32
178 	/* set socket to non-blocking */
179 	int flags = fcntl(sock, F_GETFL);
180 	if (flags >= 0) {
181 		flags |= O_NONBLOCK;
182 		fcntl(sock, F_SETFL, flags);
183 	}
184 #else /* WIN32 */
185 	u_long on = 1;
186 	ioctlsocket(sock, FIONBIO, &on);
187 #endif /* WIN32 */
188 
189 	/* Disable Nagle's algorithm */
190 	int opt = 1;
191 	setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt));
192 
193 	/*
194 	 * Player can stay for a while in the get game window or game lobby without receiving or sending
195 	 * any packet. Some end user broken network equipment forget about idle TCP sessions more sooner
196 	 * than acceptable. Enable TCP keepalive to prevent that.
197 	 */
198 #ifdef WIN32 /* keepalive */
199 	DWORD bytesret = 0;
200 	struct tcp_keepalive keepalive;
201 	keepalive.onoff = TRUE;
202 	keepalive.keepalivetime = 10000; /* ms */
203 	keepalive.keepaliveinterval = 10000; /* ms */
204 
205 	if (WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &bytesret, NULL, NULL)) {
206 		fprintf(stderr, "WSAIotcl(SIO_KEEPALIVE_VALS) failed on socket %d: %d\n", sock, WSAGetLastError());
207 	}
208 #elif defined(SO_KEEPALIVE)
209 	opt = 1;
210 	if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&opt, sizeof(opt))) {
211 		fprintf(stderr, "setsockopt() SO_KEEPALIVE failed on socket %d: %s\n", sock, strerror(errno));
212 	}
213 #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
214 	/* 10s idle before keepalive probe */
215 	opt = 10;
216 	if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&opt, sizeof(opt)))  {
217 		fprintf(stderr, "setsockopt() TCP_KEEPIDLE failed on socket %d: %s\n", sock, strerror(errno));
218 	}
219 	/* 10s probe interval */
220 	opt = 10;
221 	if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&opt, sizeof(opt)))  {
222 		fprintf(stderr, "setsockopt() TCP_KEEPINTVL failed on socket %d: %s\n", sock, strerror(errno));
223 	}
224 	/* consider session down after 10 missed probes */
225 	opt = 10;
226 	if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&opt, sizeof(opt)))  {
227 		fprintf(stderr, "setsockopt() TCP_KEEPCNT failed on socket %d: %s\n", sock, strerror(errno));
228 	}
229 #endif /* TCP_KEEPIDLE && TCP_KEEPINTVL && TCP_KEEPCNT */
230 #endif /* keepalive */
231 
232 	c->ip = g_strdup(ip_str);
233 	c->socket = sock;
234 	g_idle_add((GSourceFunc)client_connect_finish, c);
235 	return NULL;
236 
237 failed:
238 	g_free(c->host);
239 	if (c->buffer_out) {
240 		g_free(c->buffer_out);
241 	}
242 	g_free(c);
243 	return NULL;
244 }
245 
client_connect_finish(connection * c)246 static gboolean client_connect_finish(connection *c) {
247 	GIOChannel *channel;
248 	gchar *msg;
249 
250 	/* we are a bit too late */
251 	if ( (c->type == CONNECT_TYPE_MONOPD_GETGAME && global->customserver_connect)
252 	  || (c->type == CONNECT_TYPE_MONOPD_GETGAME && global->phase != PHASE_GETGAMES)
253 	  || (c->type == CONNECT_TYPE_METASERVER && global->metaserver_connect)
254 	  || (c->type == CONNECT_TYPE_METASERVER && global->phase != PHASE_GETGAMES)
255 	  || (c->type == CONNECT_TYPE_MONOPD_GAME && global->game_connect)
256 	  ) {
257 		client_connect_free(c);
258 		return G_SOURCE_REMOVE;
259 	}
260 
261 	channel = my_io_channel_socket_new(c->socket);
262 	g_io_channel_set_encoding(channel, NULL, NULL);
263 	c->channel = channel;
264 	c->event_source_id = g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, (GIOFunc)client_event, c);
265 
266 	msg = g_strdup_printf("Connected to %s:%d, using %s", c->host, c->port, c->ip);
267 	interface_set_infolabel(msg, "008000", false);
268 	g_free(msg);
269 
270 	if (c->buffer_out) {
271 		client_send(c, c->buffer_out);
272 		g_free(c->buffer_out);
273 		c->buffer_out = NULL;
274 	}
275 
276 	switch (c->type) {
277 		case CONNECT_TYPE_MONOPD_GETGAME:
278 			global->customserver_connect = c;
279 			break;
280 		case CONNECT_TYPE_MONOPD_GAME:
281 			global->game_connect = c;
282 			break;
283 		case CONNECT_TYPE_METASERVER:
284 			global->metaserver_connect = c;
285 			break;
286 	}
287 
288 	return G_SOURCE_REMOVE;
289 }
290 
client_connect_failed(gchar * msg)291 static gboolean client_connect_failed(gchar *msg) {
292 	interface_set_infolabel(msg, "b00000", true);
293 	g_free(msg);
294 	return G_SOURCE_REMOVE;
295 }
296 
297 /* -- close the connection */
client_disconnect(connection * c)298 void client_disconnect(connection *c) {
299 	if (!c) return;
300 
301 	switch (c->type) {
302 		case CONNECT_TYPE_MONOPD_GETGAME:
303 			global->customserver_connect = NULL;
304 			break;
305 		case CONNECT_TYPE_MONOPD_GAME:
306 			global->game_connect = NULL;
307 			break;
308 		case CONNECT_TYPE_METASERVER:
309 			global->metaserver_connect = NULL;
310 			break;
311 	}
312 
313 	client_connect_free(c);
314 }
315 
client_connect_free(connection * c)316 static void client_connect_free(connection *c) {
317 	if (c->event_source_id) g_source_remove(c->event_source_id);
318 	if (c->channel) g_io_channel_unref(c->channel);
319 	shutdown(c->socket, SHUT_RDWR);
320 	closesocket(c->socket);
321 	if (c->buffer_in) {
322 		g_free(c->buffer_in);
323 	}
324 	g_free(c->host);
325 	g_free(c->ip);
326 	g_free(c->server_version);
327 	g_free(c);
328 }
329 
client_event(GIOChannel * channel,GIOCondition condition,connection * c)330 gboolean client_event(GIOChannel *channel, GIOCondition condition, connection *c) {
331 
332 	if (condition == G_IO_IN) {
333 		GIOStatus status;
334 		gchar chunk[1024 +1];
335 		gsize bytes_read;
336 		GError *err = NULL;
337 		gchar *buffer, *beg, *cur;
338 
339 		status = g_io_channel_read_chars(channel, chunk, 1024, &bytes_read, &err);
340 		switch (status) {
341 			case G_IO_STATUS_NORMAL:
342 				break;
343 
344 			case G_IO_STATUS_AGAIN:
345 				return TRUE;
346 
347 			case G_IO_STATUS_ERROR:
348 			case G_IO_STATUS_EOF:
349 				client_disconnect(c);
350 				interface_set_infolabel("Connection to server lost.", "b00000", true);
351 				return TRUE;
352 		}
353 
354 		chunk[bytes_read] = '\0';
355 
356 		if(c->buffer_in == NULL) {
357 			buffer = g_strdup(chunk);
358 		} else {
359 			buffer = g_strconcat(c->buffer_in, chunk, NULL);
360 			g_free(c->buffer_in);
361 			c->buffer_in = NULL;
362 		}
363 
364 		/* parse data */
365 		for(beg = cur = buffer; *cur; cur++)  {
366 			if( *cur == '\n') {
367 				*cur = '\0';
368 #if DEBUG
369 				fprintf(stdout, "\033[32mID(%.2d): RECV(%.4d): [%s]\033[m\n", c->id, (int)strlen(beg), beg);
370 #endif /* DEBUG */
371 				switch(c->type)  {
372 
373 					case CONNECT_TYPE_MONOPD_GETGAME:
374 						xmlparse_getgamelist_plugger(c, beg);
375 						break;
376 					case CONNECT_TYPE_MONOPD_GAME:
377 						xmlparse_game_plugger(c, beg);
378 						break;
379 
380 					case CONNECT_TYPE_METASERVER:
381 						xmlparse_metaserver(c, beg);
382 						break;
383 				}
384 
385 				beg = cur+1;
386 			}
387 		}
388 
389 		/* bufferise remaining data */
390 		if (beg != cur) {
391 			c->buffer_in = g_strdup(beg);
392 		}
393 
394 		g_free(buffer);
395 	}
396 
397 	else /* if (condition == G_IO_ERR || condition == G_IO_HUP) */ {
398 		client_disconnect(c);
399 		interface_set_infolabel("Connection to server lost.", "b00000", true);
400 		return TRUE;
401 	}
402 
403 	return TRUE;
404 }
405 
406 
407 /* -- send data to server
408       >> get from send_tmp buffer
409       >> flush send_tmp buffer */
client_send(connection * c,gchar * data)410 void client_send(connection *c, gchar *data)  {
411 	size_t len;
412 	ssize_t s;
413 
414 	if (!c) return;
415 
416 	len = strlen(data);
417 	s = send(c->socket, data, len, 0);
418 	if (s < 0 || (size_t)s != len) {
419 		return;
420 	}
421 
422 #if DEBUG
423 	fprintf(stdout, "\033[31mID(%.2d): SEND(%.4d): [%s]\033[m\n", c->id, (int)len, data);
424 #endif /* DEBUG */
425 }
426