1 /*
2  * metaclient.c
3  * Meta client implementation for TomeNET
4  */
5 
6 /* added this for consistency in some (unrelated) header-inclusion,
7    it IS a server file, isn't it? */
8 #define SERVER
9 
10 #include "angband.h"
11 #include <sys/types.h>
12 #ifndef WINDOWS
13 #include <netinet/in.h>
14 #include <sys/socket.h>
15 #include <netdb.h>
16 #endif
17 
18 typedef enum {
19 	META_NONE = 0,
20 	META_CONNECTING,
21 	META_CONNECTED,
22 	META_DISABLED
23 } meta_state_t;
24 
25 /* Has the code been initialized? */
26 static bool meta_initialized = FALSE;
27 
28 /* Metaserver socket */
29 static int meta_fd = -1;
30 
31 /* State of the metaserver connection */
32 static meta_state_t meta_state = META_DISABLED;
33 
34 /* Local hostname */
35 static char meta_local_name[1024];
36 
37 /* Sockaddr structure containing the address of the metaserver */
38 static struct sockaddr_in meta_sockaddr;
39 
40 /* Has the output handler been registered with the scheduler? */
41 static bool meta_output_installed = FALSE;
42 
43 /* Do we need to send updated information to the metaserver? */
44 static bool meta_needs_update = FALSE;
45 
46 static void meta_init(void);
47 static void meta_connect(int blocking);
48 static int meta_write(int flag);
49 static void meta_close(void);
50 static void meta_sched_output_callback(int fd, int arg);
51 
52 /*
53  * Initialize the meta client code
54  */
meta_init(void)55 static void meta_init(void) {
56 	struct hostent *hp;
57 
58 	if (!meta_initialized) {
59 		/* Find out the hostname of the server */
60 		if (cfg.bind_name) {
61 			strncpy(meta_local_name, cfg.bind_name, 1024);
62 		} else {
63 			GetLocalHostName(meta_local_name, 1024);
64 		}
65 
66 		/* Initialize the metaserver address structure */
67 		memset(&meta_sockaddr, 0, sizeof(meta_sockaddr));
68 		meta_sockaddr.sin_family = AF_INET;
69 		meta_sockaddr.sin_port = htons(cfg.meta_port);
70 
71 		/* Look up the metaserver hostname */
72 		hp = gethostbyname(cfg.meta_address);
73 		if (hp == NULL) {
74 			s_printf("Failed to resolve metaserver hostname!\n");
75 			meta_sockaddr.sin_addr.s_addr = 0;
76 		} else {
77 			meta_sockaddr.sin_addr.s_addr = ((struct in_addr*)(hp->h_addr))->s_addr;
78 		}
79 
80 		/* Set the flag */
81 		meta_initialized = TRUE;
82 	}
83 }
84 
85 /*
86  * Called on every server tick from dungeon()
87  */
meta_tick(void)88 void meta_tick(void) {
89 	switch (meta_state) {
90 		case META_NONE:
91 			if (meta_needs_update) {
92 				meta_connect(FALSE);
93 			}
94 			break;
95 		case META_CONNECTING:
96 			break;
97 		case META_CONNECTED:
98 			meta_write(META_UPDATE);
99 			meta_close();
100 			break;
101 		case META_DISABLED:
102 			break;
103 	}
104 }
105 
106 /*
107  * Connect to the metaserver
108  */
meta_connect(int blocking)109 static void meta_connect(int blocking) {
110 	int sock;
111 
112 	/* Create a socket */
113 	sock = socket(AF_INET, SOCK_STREAM, 0);
114 	if (sock == -1) {
115 #ifdef WINDOWS
116 		int errval = WSAGetLastError();
117 #else
118 		int errval = errno;
119 #endif
120 		s_printf("Failed to create meta socket! (errno = %d)\n", errval);
121 		return;
122 	}
123 
124 	if (!blocking) {
125 		/* Make it non-blocking */
126 		if (SetSocketNonBlocking(sock, 1) == -1) {
127 			s_printf("Failed to make meta socket non-blocking!\n");
128 		}
129 	}
130 
131 	if (connect(sock, (struct sockaddr *)&meta_sockaddr, sizeof(meta_sockaddr)) == -1) {
132 #ifdef WINDOWS
133 		int errval = WSAGetLastError();
134 		if (errval != WSAEINPROGRESS && errval != WSAEWOULDBLOCK) {
135 #else
136 		int errval = errno;
137 		if (errval != EINPROGRESS && errval != EWOULDBLOCK) {
138 #endif
139 			s_printf("Failed to connect to metaserver! (errno = %d)\n", errval);
140 			close(sock);
141 			return;
142 		}
143 	}
144 
145 	install_output(meta_sched_output_callback, sock, 0);
146 	meta_output_installed = TRUE;
147 	meta_fd = sock;
148 	meta_state = META_CONNECTING;
149 }
150 
151 /*
152  * Send data to the metaserver
153  */
154 static int meta_write(int flag) {
155 	char buf_meta[16384];
156 	char temp[160];
157 	char *url, *notes;
158 	int num_dungeon_masters = 0;
159 	int i;
160 
161 	memset(buf_meta, 0, sizeof(buf_meta));
162 
163 	url = html_escape(meta_local_name);
164 	sprintf(buf_meta, "<server url=\"%s\" port=\"%d\" protocol=\"2\"", url, (int) cfg.game_port);
165 	free(url);
166 
167 	if (flag & META_DIE) {
168 		strcat(buf_meta, " death=\"true\"></server>");
169 	} else {
170 		strcat(buf_meta, ">");
171 
172 		strcat(buf_meta, "<notes>");
173 		notes = html_escape(cfg.server_notes);
174 		strcat(buf_meta, notes);
175 		free(notes);
176 		strcat(buf_meta, "</notes>");
177 
178 		for (i = 1; i <= NumPlayers; i++) {
179 			if (Players[i]->admin_dm && cfg.secret_dungeon_master) num_dungeon_masters++;
180 		}
181 
182 		/* if someone other than a dungeon master is playing */
183 		if (NumPlayers - num_dungeon_masters) {
184 			char *name;
185 			for (i = 1; i <= NumPlayers; i++) {
186 				/* handle the cfg_secret_dungeon_master option */
187 				if (Players[i]->admin_dm && cfg.secret_dungeon_master) continue;
188 
189 				strcat(buf_meta, "<player>");
190 				name = html_escape(Players[i]->name);
191 				strcat(buf_meta, name);
192 				free(name);
193 				strcat(buf_meta, "</player>");
194 			}
195 		}
196 
197 #ifdef TEST_SERVER
198 		strcat(buf_meta, "<game>TomeNET TEST-ONLY</game>");
199 #else
200  #ifdef ARCADE_SERVER /* made this one higher priority since using RPG_SERVER as an "ARCADE-addon" might be desired :) */
201 		//removed "Smash." -Moltor
202 		strcat(buf_meta, "<game>TomeNET Arcade</game>");
203  #else
204   #ifdef RPG_SERVER
205 //		strcat(buf_meta, "<game>TomeNET Ironman (not for beginners)</game>");
206 		strcat(buf_meta, "<game>TomeNET Ironman</game>");
207   #else
208    #ifdef FUN_SERVER
209 		strcat(buf_meta, "<game>TomeNET Fun</game>");
210    #else
211                 strcat(buf_meta, "<game>TomeNET</game>");
212    #endif
213   #endif
214  #endif
215 #endif
216                 /* Append the version number */
217                 snprintf(temp, sizeof(temp), "<version>%d.%d.%d%s</version></server>", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, SERVER_VERSION_TAG);
218                 strcat(buf_meta, temp);
219 	}
220 
221 	/* Send data to metaserver */
222 	if (send(meta_fd, buf_meta, strlen(buf_meta), 0) == -1) {
223 #ifdef WINDOWS
224 		int errval = WSAGetLastError();
225 		if (errval != WSAEWOULDBLOCK) {
226 #else
227 		int errval = errno;
228 		if (errval != EINPROGRESS && errval != EWOULDBLOCK) {
229 #endif
230 			s_printf("Writing to meta socket failed! (errno = %d)\n", errval);
231 		} else {
232 			s_printf("Writing to meta socket would block!\n");
233 		}
234 
235 		/* Failed to connect or send data */
236 		meta_needs_update = FALSE;
237 		return FALSE;
238 	}
239 
240 	/* Toggle the update needed flag */
241 	meta_needs_update = FALSE;
242 	return TRUE;
243 }
244 
245 /*
246  * Close the metaserver connection
247  */
248 static void meta_close(void) {
249 	if (meta_fd != -1) {
250 		if (meta_output_installed) {
251 			remove_output(meta_fd);
252 			meta_output_installed = FALSE;
253 		}
254 		close(meta_fd);
255 		meta_fd = -1;
256 	}
257 
258 	meta_state = META_NONE;
259 }
260 
261 /*
262  * Called from sched() to inform us that the metaserver connection has been
263  * established
264  */
265 static void meta_sched_output_callback(int fd, int arg) {
266 	if (meta_fd == fd) {
267 		meta_state = META_CONNECTED;
268 		remove_output(fd);
269 	}
270 }
271 
272 /*
273  * Called from Report_to_meta() in nserver.c
274  */
275 void meta_report(int flag) {
276 	if (flag & META_START) {
277 		meta_init();
278 		if (meta_state == META_DISABLED) {
279 			meta_state = META_NONE;
280 		}
281 		meta_needs_update = TRUE;
282 	} else if (flag & META_DIE) {
283 		meta_close();
284 
285 		/* Block the timer so that connect() succeeds */
286 		block_timer();
287 		meta_connect(TRUE);
288 		meta_write(flag);
289 		allow_timer();
290 		meta_close();
291 		meta_state = META_DISABLED;
292 	} else if (flag & META_UPDATE) {
293 		meta_needs_update = TRUE;
294 	}
295 }
296