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