]> git.cworth.org Git - ttt/blob - src/ttt-server.c
Deleted debugging code and changed return errors to fit protocol
[ttt] / src / ttt-server.c
1 /* ttt-server.c - tic-tac-toe game server
2  *
3  * Copyright © 2005 Carl Worth
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  * Author: Carl Worth <cworth@cworth.org>
20  */
21
22 #include "ttt-server.h"
23
24 #include "ttt-args.h"
25 #include "ttt-client.h"
26 #include "ttt-error.h"
27 #include "ttt-socket.h"
28
29 struct _ttt_invite {
30     ttt_client_t    *actor;
31     ttt_client_t    *invitee;
32 };
33
34 struct _ttt_server {
35     pthread_mutex_t mutex;
36
37     const char      *host;
38     const char      *port;
39
40     ttt_client_t    **clients;
41     int             clients_size;
42     int             num_clients;
43
44     ttt_invite_t    **invites;
45     int             invites_size;
46     int             num_invites;
47 };
48
49 static void
50 ttt_server_init (ttt_server_t *server, const char *host, const char *port)
51 {
52     pthread_mutex_init (&server->mutex, NULL);
53
54     server->host = host;
55     server->port = port;
56
57     server->clients = NULL;
58     server->clients_size = 0;
59     server->num_clients = 0;
60
61     server->invites = NULL;
62     server->invites_size = 0;
63     server->num_invites = 0;
64 }
65
66 /* Exported: See ttt-server.h for documentation. */
67 ttt_error_t
68 ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
69 {
70     int i;
71     ttt_error_t error = TTT_ERROR_NONE;
72     const char *username;
73
74     pthread_mutex_lock (&server->mutex);
75
76     username = ttt_client_get_username (client);
77
78     assert (username != NULL);
79
80     if (username[0] == '\0')
81         return TTT_ERROR_INVALID_NAME;
82
83     for (i = 0; i < server->num_clients; i++) {
84         if (strcasecmp (ttt_client_get_username (server->clients[i]), username) == 0) {
85             error = TTT_ERROR_INVALID_NAME;
86             goto CLEANUP_LOCK;
87         }
88     }
89
90     fprintf (stderr, "Client %s has joined.\r\n", username);
91
92     server->num_clients++;
93
94     if (server->num_clients > server->clients_size) {
95         if (server->clients_size == 0)
96             server->clients_size = 1;
97         else
98             server->clients_size *= 2;
99
100         server->clients = xrealloc (server->clients,
101                                     server->clients_size * sizeof (ttt_client_t *));
102     }
103
104     server->clients[server->num_clients - 1] = client;
105
106  CLEANUP_LOCK:
107     pthread_mutex_unlock (&server->mutex);
108
109     return error;
110 }
111
112 /* Exported: See ttt-server.h for documentation. */
113 void
114 ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
115 {
116     ttt_invite_t *invite;
117     char         *notice;
118     ttt_bool_t   send_notice = FALSE;
119     int i;
120
121     pthread_mutex_lock (&server->mutex);
122
123     /* Auto-retract and decline pending notices */
124     /* Notices are sent after mutex unlock */
125     for (i = 0; i < server->num_invites; i++)
126     {
127         invite = server->invites[i];
128         if ((invite->actor == client) || (invite->invitee == client))
129         {
130             send_notice = TRUE;
131             if (invite->actor == client)
132                 xasprintf (&notice, "%s" "NOTICE RETRACT %s %s\r\n",
133                            notice,
134                            ttt_client_get_username(invite->actor),
135                            ttt_client_get_username(invite->invitee));
136             else
137                 xasprintf (&notice, "%s" "NOTICE DECLINE %s %s\r\n",
138                            notice,
139                            ttt_client_get_username(invite->invitee),
140                            ttt_client_get_username(invite->actor));
141
142             memmove (&server->invites[i], &server->invites[i+1],
143                      (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
144             server->num_invites--;
145             i--;
146         }
147     }
148
149     for (i = 0; i < server->num_clients; i++)
150         if (server->clients[i] == client)
151             break;
152
153     assert (i < server->num_clients);
154
155     fprintf (stderr, "Client %s has left.\r\n", ttt_client_get_username (client));
156
157     memmove (&server->clients[i], &server->clients[i+1],
158              (server->num_clients - i - 1) * sizeof (ttt_client_t *));
159
160     server->num_clients--;
161
162     pthread_mutex_unlock (&server->mutex);
163
164     if (send_notice)
165     {
166         ttt_server_broadcast(server, notice);
167         free (notice);
168     }
169 }
170
171 /* Exported: See ttt-server.h for documentation. */
172 void
173 ttt_server_broadcast (ttt_server_t *server, const char *message)
174 {
175     int i;
176
177     pthread_mutex_lock (&server->mutex);
178
179     for (i = 0; i < server->num_clients; i++)
180         ttt_client_send (server->clients[i], message);
181
182     pthread_mutex_unlock (&server->mutex);
183 }
184
185 /* Exported: See ttt-server.h for documentation. */
186 const char*
187 ttt_server_who (ttt_server_t *server)
188 {
189     int i;
190     char *response;
191
192     pthread_mutex_lock (&server->mutex);
193
194     xasprintf (&response, "WHO");
195
196     for (i = 0; i < server->num_clients; i++)
197         xasprintf (&response, "%s %s",
198                    response,
199                    ttt_client_get_username (server->clients[i]));
200
201     xasprintf (&response, "%s\r\n", response);
202
203     pthread_mutex_unlock (&server->mutex);
204
205     return response;
206 }
207
208 /* Exported: See ttt-server.h for documentation. */
209 ttt_error_t
210 ttt_server_verify_username (ttt_server_t *server,
211                             const char   *username)
212 {
213     ttt_client_t *client;
214
215     return ttt_server_get_client_from_username (server,
216                                                 username,
217                                                 &client);
218 }
219
220 /* Exported: See ttt-server.h for documentation. */
221 ttt_error_t
222 ttt_server_get_client_from_username (ttt_server_t *server,
223                                      const char   *username,
224                                      ttt_client_t **client)
225 {
226     ttt_bool_t usernamefound = FALSE;
227     const char *client_username;
228     int i;
229
230     pthread_mutex_lock (&server->mutex);
231
232     for (i = 0; i < server->num_clients; i++) {
233         client_username = ttt_client_get_username (server->clients[i]);
234         if (strcasecmp (username, client_username) == 0)
235         {
236             usernamefound = TRUE;
237             *client = server->clients[i];
238         }
239     }
240
241     pthread_mutex_unlock (&server->mutex);
242
243     if (!usernamefound)
244         return TTT_ERROR_NO_USER;
245
246     return TTT_ERROR_NONE;
247 }
248
249 /* Exported: See ttt-server.h for documentation. */
250 ttt_error_t
251 ttt_server_add_invite (ttt_server_t *server,
252                        ttt_client_t *actor,
253                        ttt_client_t *invitee)
254 {
255     ttt_invite_t *invite;
256
257     pthread_mutex_lock (&server->mutex);
258
259     invite = xmalloc (sizeof (ttt_invite_t));
260
261     invite->actor = actor;
262     invite->invitee = invitee;
263
264     server->num_invites++;
265
266     if (server->num_invites > server->invites_size) {
267         if (server->invites_size == 0)
268             server->invites_size = 1;
269         else
270             server->invites_size *= 2;
271
272         server->invites = xrealloc (server->invites,
273                                     server->invites_size * sizeof (ttt_invite_t *));
274     }
275
276     server->invites[server->num_invites - 1] = invite;
277
278     pthread_mutex_unlock (&server->mutex);
279
280     return TTT_ERROR_NONE;
281 }
282
283 /* Exported: See ttt-server.h for documentation. */
284 ttt_error_t
285 ttt_server_remove_invite (ttt_server_t *server,
286                           ttt_client_t *actor,
287                           ttt_client_t *invitee)
288 {
289     ttt_invite_t *invite;
290     ttt_error_t  error;
291     int i;
292
293     pthread_mutex_lock (&server->mutex);
294
295     error = TTT_ERROR_NO_INVITE;
296     for (i = 0; i < server->num_invites; i++)
297     {
298         invite = server->invites[i];
299         if ((invite->actor == actor) && (invite->invitee == invitee))
300         {
301             error = TTT_ERROR_NONE;
302             break;
303         }
304     }
305
306     if (error)
307         goto CLEANUP_LOCK;
308
309     assert (i < server->num_invites);
310
311     memmove (&server->invites[i], &server->invites[i+1],
312              (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
313
314     server->num_invites--;
315
316  CLEANUP_LOCK:
317     pthread_mutex_unlock (&server->mutex);
318
319     return error;
320 }
321
322 /* Exported: See ttt-server.h for documentation. */
323 const char*
324 ttt_server_get_host (ttt_server_t *server)
325 {
326     return server->host;
327 }
328
329 /* Exported: See ttt-server.h for documentation. */
330 const char*
331 ttt_server_get_port (ttt_server_t *server)
332 {
333     return server->port;
334 }
335
336 static const char *WELCOME_MESSAGE = 
337 "Welcome to ttt-server. The server is currently listening on:\r\n"
338 "\r\n"
339 "       %s:%s\r\n"
340 "\r\n"
341 "To test this, simply connect one or more clients to that host and port.\r\n"
342 "For example:\r\n"
343 "\r\n"
344 "       telnet %s %s\r\n"
345 "\r\n"
346 "The TTTP (tic-tac-toe protocol) has been partially implemented.\r\n"
347 "The following commands should work: HELO, HELP, INVITE, ACCEPT, RETRACT, DECLINE, MESSAGE, STATISTICS, QUIT, VERSION, WHO.\r\n"
348 "\r\n";
349
350 static void
351 _ttt_server_accept (void *closure, int client_socket)
352 {
353     ttt_server_t *server = closure;
354
355     ttt_client_new (server, client_socket);
356 }
357
358 static void
359 _detach_and_write_child_pid_to (const char *filename)
360 {
361     pid_t pid;
362
363     /* Use the Unix double-fork trick to detach completely. See
364      * setsid(2) for some details as to why two forks are
365      * needed. */
366     pid = xfork ();
367     if (pid) {
368         /* First parent just exits */
369         exit (0);
370     }
371     
372     chdir ("/");
373     setsid ();
374     
375     pid = xfork ();
376     if (pid) {
377         /* Second parent exits after writing pid */
378         FILE *file = xfopen (filename, "w");
379         fprintf (file, "%d\n", pid);
380         fclose (file);
381         exit (0);
382     }
383     
384     /* Final, detached child returns. */
385 }
386
387 int 
388 main (int argc, char **argv)
389 {
390     ttt_args_t args;
391     ttt_server_t server;
392     int socket;
393
394     ttt_args_parse (&args, argc, argv);
395
396     if (args.log_file || args.detach) {
397         FILE *log_file;
398         /* In the detach case, we force redirection to a log file. */
399         if (args.log_file == NULL)
400             args.log_file = "/var/log/ttt-server.log";
401         log_file = fopen (args.log_file, "a");
402         if (log_file == NULL) {
403             printf ("Warning: Failed to open log file %s: %s.\n",
404                     args.log_file, strerror (errno));
405             printf ("Logging will be disabled.\n");
406             xdup2 (1, 2);
407         } else {
408             xdup2 (fileno (log_file), 2);
409         }
410     }
411
412     if (args.detach)
413         _detach_and_write_child_pid_to (args.pid_file);
414
415     /* Now that we've setup logging and the pid file, drop any special
416      * permissions we might have if we were asked to do that. */
417     if (args.user) {
418         int ret;
419         struct passwd *pwd;
420         errno = 0;
421         pwd = getpwnam (args.user);
422         if (pwd == NULL) {
423             fprintf (stderr, "Error: Failed to lookup uid for %s: %s. Aborting.\n",
424                      args.user,
425                      errno == 0 ? "User not found" : strerror (errno));
426             exit (1);
427         }
428         ret = setuid (pwd->pw_uid);
429         if (ret == -1) {
430             fprintf (stderr, "Error: Failed to setuid to %d (%s): %s. Aborting.\n",
431                      pwd->pw_uid, args.user, strerror (errno));
432             exit (1);
433         }
434     }
435
436     socket = ttt_socket_create_server (args.host, args.port);
437
438     if (args.detach) {
439         printf ("Server started listening on %s:%s\n", args.host, args.port);
440         fprintf (stderr, "Server started listening on %s:%s\n", args.host, args.port);
441     } else {
442         printf (WELCOME_MESSAGE, args.host, args.port, args.host, args.port);
443     }
444
445     fclose (stdout);
446     fclose (stdin);
447
448     ttt_server_init (&server, args.host, args.port);
449
450     while (1)
451         ttt_socket_accept (socket, _ttt_server_accept, &server);
452
453     /* We only reach here if something bad happened. */
454     return 1;
455 }