]> git.cworth.org Git - ttt/blob - src/ttt-server.c
2005-12-09 Carl Worth <cworth@cworth.org>
[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_server {
30     pthread_mutex_t mutex;
31
32     const char *host;
33     const char *port;
34
35     ttt_client_t **clients;
36     int clients_size;
37     int num_clients;
38 };
39
40 static void
41 ttt_server_init (ttt_server_t *server, const char *host, const char *port)
42 {
43     pthread_mutex_init (&server->mutex, NULL);
44
45     server->host = host;
46     server->port = port;
47
48     server->clients = NULL;
49     server->clients_size = 0;
50     server->num_clients = 0;
51 }
52
53 /* Exported: See ttt-server.h for documentation. */
54 ttt_error_t
55 ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
56 {
57     int i;
58     ttt_error_t error = TTT_ERROR_NONE;
59     const char *username;
60
61     pthread_mutex_lock (&server->mutex);
62
63     username = ttt_client_get_username (client);
64
65     assert (username != NULL);
66
67     if (username[0] == '\0')
68         return TTT_ERROR_INVALID_NAME;
69
70     for (i = 0; i < server->num_clients; i++) {
71         if (strcmp (ttt_client_get_username (server->clients[i]), username) == 0) {
72             error = TTT_ERROR_INVALID_NAME;
73             goto CLEANUP_LOCK;
74         }
75     }
76
77     fprintf (stderr, "Client %s has joined.\r\n", username);
78
79     server->num_clients++;
80
81     if (server->num_clients > server->clients_size) {
82         if (server->clients_size == 0)
83             server->clients_size = 1;
84         else
85             server->clients_size *= 2;
86
87         server->clients = xrealloc (server->clients,
88                                     server->clients_size * sizeof (ttt_client_t *));
89     }
90
91     server->clients [server->num_clients - 1] = client;
92
93  CLEANUP_LOCK:
94     pthread_mutex_unlock (&server->mutex);
95
96     return error;
97 }
98
99 /* Exported: See ttt-server.h for documentation. */
100 void
101 ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
102 {
103     int i;
104
105     pthread_mutex_lock (&server->mutex);
106
107     for (i = 0; i < server->num_clients; i++)
108         if (server->clients[i] == client)
109             break;
110
111     assert (i < server->num_clients);
112
113     fprintf (stderr, "Client %s has left.\r\n", ttt_client_get_username (client));
114
115     memmove (&server->clients[i], &server->clients[i+1],
116              (server->num_clients - i - 1) * sizeof (ttt_client_t *));
117
118     server->num_clients--;
119
120     pthread_mutex_unlock (&server->mutex);
121 }
122
123 /* Exported: See ttt-server.h for documentation. */
124 void
125 ttt_server_broadcast (ttt_server_t *server, const char *message)
126 {
127     int i;
128
129     pthread_mutex_lock (&server->mutex);
130
131     for (i = 0; i < server->num_clients; i++)
132         ttt_client_send (server->clients[i], message);
133
134     pthread_mutex_unlock (&server->mutex);
135 }
136
137 /* Exported: See ttt-server.h for documentation. */
138 const char*
139 ttt_server_who (ttt_server_t *server)
140 {
141     int i;
142     char *response;
143
144     pthread_mutex_lock (&server->mutex);
145
146     xasprintf (&response, "WHO");
147
148     for (i = 0; i < server->num_clients; i++)
149         xasprintf (&response, "%s %s",
150                    response,
151                    ttt_client_get_username (server->clients[i]));
152
153     xasprintf (&response, "%s\r\n", response);
154
155     pthread_mutex_unlock (&server->mutex);
156
157     return response;
158 }
159
160 /* Exported: See ttt-server.h for documentation. */
161 ttt_error_t
162 ttt_server_statistics (ttt_server_t *server, const char *username, char **response)
163 {
164     ttt_bool_t usernamefound = FALSE;
165     const char *client_username;
166     int client_num_wins;
167     int i;
168
169     pthread_mutex_lock (&server->mutex);
170
171     for (i = 0; i < server->num_clients; i++) {
172         client_username = ttt_client_get_username (server->clients[i]);
173         if (strcasecmp (username, client_username) == 0) {
174             usernamefound = TRUE;
175             client_num_wins = ttt_client_get_num_wins (server->clients[i]);
176             xasprintf (response, "STATISTICS %s \"\r\n"
177                        "TICTACTOE WINS %d\r\n\"\r\n",
178                        client_username,
179                        client_num_wins);
180         }
181     }
182
183     pthread_mutex_unlock (&server->mutex);
184
185     if (!usernamefound)
186         return TTT_ERROR_NO_USER;
187
188     return TTT_ERROR_NONE;
189 }
190
191 /* Exported: See ttt-server.h for documentation. */
192 ttt_error_t
193 ttt_server_verify_username (ttt_server_t *server, const char *username)
194 {
195     ttt_bool_t usernamefound = FALSE;
196     const char *client_username;
197     int i;
198
199     pthread_mutex_lock (&server->mutex);
200
201     for (i = 0; i < server->num_clients; i++) {
202         client_username = ttt_client_get_username (server->clients[i]);
203         if (strcasecmp (username, client_username) == 0)
204             usernamefound = TRUE;
205     }
206
207     pthread_mutex_unlock (&server->mutex);
208
209     if (!usernamefound)
210         return TTT_ERROR_NO_USER;
211
212     return TTT_ERROR_NONE;
213 }
214
215 /* Exported: See ttt-server.h for documentation. */
216 const char*
217 ttt_server_get_host (ttt_server_t *server)
218 {
219     return server->host;
220 }
221
222 /* Exported: See ttt-server.h for documentation. */
223 const char*
224 ttt_server_get_port (ttt_server_t *server)
225 {
226     return server->port;
227 }
228
229 static const char *WELCOME_MESSAGE = 
230 "Welcome to ttt-server. The server is currently listening on:\r\n"
231 "\r\n"
232 "       %s:%s\r\n"
233 "\r\n"
234 "To test this, simply connect one or more clients to that host and port.\r\n"
235 "For example:\r\n"
236 "\r\n"
237 "       telnet %s %s\r\n"
238 "\r\n"
239 "The TTTP (tic-tac-toe protocol) has been partially implemented.\r\n"
240 "The following commands should work: HELO, HELP, MESSAGE, VERSION, QUIT, WHO.\r\n"
241 "\r\n";
242
243 static void
244 _ttt_server_accept (void *closure, int client_socket)
245 {
246     ttt_server_t *server = closure;
247
248     ttt_client_new (server, client_socket);
249 }
250
251 static void
252 _detach_and_write_child_pid_to (const char *filename)
253 {
254     pid_t pid;
255
256     /* Use the Unix double-fork trick to detach completely. See
257      * setsid(2) for some details as to why two forks are
258      * needed. */
259     pid = xfork ();
260     if (pid) {
261         /* First parent just exits */
262         exit (0);
263     }
264     
265     chdir ("/");
266     setsid ();
267     
268     pid = xfork ();
269     if (pid) {
270         /* Second parent exits after writing pid */
271         FILE *file = xfopen (filename, "w");
272         fprintf (file, "%d\n", pid);
273         fclose (file);
274         exit (0);
275     }
276     
277     /* Final, detached child returns. */
278 }
279
280 int 
281 main (int argc, char **argv)
282 {
283     ttt_args_t args;
284     ttt_server_t server;
285     int socket;
286
287     ttt_args_parse (&args, argc, argv);
288
289     if (args.log_file || args.detach) {
290         FILE *log_file;
291         /* In the detach case, we force redirection to a log file. */
292         if (args.log_file == NULL)
293             args.log_file = "/var/log/ttt-server.log";
294         log_file = fopen (args.log_file, "a");
295         if (log_file == NULL) {
296             printf ("Warning: Failed to open log file %s: %s.\n",
297                     args.log_file, strerror (errno));
298             printf ("Logging will be disabled.\n");
299             xdup2 (1, 2);
300         } else {
301             xdup2 (fileno (log_file), 2);
302         }
303     }
304
305     if (args.detach)
306         _detach_and_write_child_pid_to (args.pid_file);
307
308     /* Now that we've setup logging and the pid file, drop any special
309      * permissions we might have if we were asked to do that. */
310     if (args.user) {
311         int ret;
312         struct passwd *pwd;
313         errno = 0;
314         pwd = getpwnam (args.user);
315         if (pwd == NULL) {
316             fprintf (stderr, "Error: Failed to lookup uid for %s: %s. Aborting.\n",
317                      args.user,
318                      errno == 0 ? "User not found" : strerror (errno));
319             exit (1);
320         }
321         ret = setuid (pwd->pw_uid);
322         if (ret == -1) {
323             fprintf (stderr, "Error: Failed to setuid to %d (%s): %s. Aborting.\n",
324                      pwd->pw_uid, args.user, strerror (errno));
325             exit (1);
326         }
327     }
328
329     socket = ttt_socket_create_server (args.host, args.port);
330
331     if (args.detach) {
332         printf ("Server started listening on %s:%s\n", args.host, args.port);
333         fprintf (stderr, "Server started listening on %s:%s\n", args.host, args.port);
334     } else {
335         printf (WELCOME_MESSAGE, args.host, args.port, args.host, args.port);
336     }
337
338     fclose (stdout);
339     fclose (stdin);
340
341     ttt_server_init (&server, args.host, args.port);
342
343     while (1)
344         ttt_socket_accept (socket, _ttt_server_accept, &server);
345
346     /* We only reach here if something bad happened. */
347     return 1;
348 }