1 /* ttt-server.c - tic-tac-toe game server
3 * Copyright © 2005 Carl Worth
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)
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.
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.
19 * Author: Carl Worth <cworth@cworth.org>
22 #include "ttt-server.h"
25 #include "ttt-client.h"
26 #include "ttt-error.h"
27 #include "ttt-socket.h"
31 ttt_client_t *invitee;
35 pthread_mutex_t mutex;
40 ttt_client_t **clients;
44 ttt_invite_t **invites;
50 ttt_server_init (ttt_server_t *server, const char *host, const char *port)
52 pthread_mutex_init (&server->mutex, NULL);
57 server->clients = NULL;
58 server->clients_size = 0;
59 server->num_clients = 0;
61 server->invites = NULL;
62 server->invites_size = 0;
63 server->num_invites = 0;
66 /* Exported: See ttt-server.h for documentation. */
68 ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
71 ttt_error_t error = TTT_ERROR_NONE;
74 pthread_mutex_lock (&server->mutex);
76 username = ttt_client_get_username (client);
78 assert (username != NULL);
80 if (username[0] == '\0')
81 return TTT_ERROR_INVALID_NAME;
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;
90 fprintf (stderr, "Client %s has joined.\r\n", username);
92 server->num_clients++;
94 if (server->num_clients > server->clients_size) {
95 if (server->clients_size == 0)
96 server->clients_size = 1;
98 server->clients_size *= 2;
100 server->clients = xrealloc (server->clients,
101 server->clients_size * sizeof (ttt_client_t *));
104 server->clients[server->num_clients - 1] = client;
107 pthread_mutex_unlock (&server->mutex);
112 /* Exported: See ttt-server.h for documentation. */
114 ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
116 ttt_invite_t *invite;
118 ttt_bool_t send_notice = FALSE;
121 pthread_mutex_lock (&server->mutex);
123 /* Auto-retract and decline pending notices */
124 /* Notices are sent after mutex unlock */
125 for (i = 0; i < server->num_invites; i++)
127 invite = server->invites[i];
128 if ((invite->actor == client) || (invite->invitee == client))
131 if (invite->actor == client)
132 xasprintf (¬ice, "%s" "NOTICE RETRACT %s %s\r\n",
134 ttt_client_get_username(invite->actor),
135 ttt_client_get_username(invite->invitee));
137 xasprintf (¬ice, "%s" "NOTICE DECLINE %s %s\r\n",
139 ttt_client_get_username(invite->invitee),
140 ttt_client_get_username(invite->actor));
142 memmove (&server->invites[i], &server->invites[i+1],
143 (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
144 server->num_invites--;
149 for (i = 0; i < server->num_clients; i++)
150 if (server->clients[i] == client)
153 assert (i < server->num_clients);
155 fprintf (stderr, "Client %s has left.\r\n", ttt_client_get_username (client));
157 memmove (&server->clients[i], &server->clients[i+1],
158 (server->num_clients - i - 1) * sizeof (ttt_client_t *));
160 server->num_clients--;
162 pthread_mutex_unlock (&server->mutex);
166 ttt_server_broadcast(server, notice);
171 /* Exported: See ttt-server.h for documentation. */
173 ttt_server_broadcast (ttt_server_t *server, const char *message)
177 pthread_mutex_lock (&server->mutex);
179 for (i = 0; i < server->num_clients; i++)
180 ttt_client_send (server->clients[i], message);
182 pthread_mutex_unlock (&server->mutex);
185 /* Exported: See ttt-server.h for documentation. */
187 ttt_server_who (ttt_server_t *server)
192 pthread_mutex_lock (&server->mutex);
194 xasprintf (&response, "WHO");
196 for (i = 0; i < server->num_clients; i++)
197 xasprintf (&response, "%s %s",
199 ttt_client_get_username (server->clients[i]));
201 xasprintf (&response, "%s\r\n", response);
203 pthread_mutex_unlock (&server->mutex);
208 /* Exported: See ttt-server.h for documentation. */
210 ttt_server_verify_username (ttt_server_t *server,
211 const char *username)
213 ttt_client_t *client;
215 return ttt_server_get_client_from_username (server,
220 /* Exported: See ttt-server.h for documentation. */
222 ttt_server_get_client_from_username (ttt_server_t *server,
223 const char *username,
224 ttt_client_t **client)
226 ttt_bool_t usernamefound = FALSE;
227 const char *client_username;
230 pthread_mutex_lock (&server->mutex);
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)
236 usernamefound = TRUE;
237 *client = server->clients[i];
241 pthread_mutex_unlock (&server->mutex);
244 return TTT_ERROR_NO_USER;
246 return TTT_ERROR_NONE;
249 /* Exported: See ttt-server.h for documentation. */
251 ttt_server_add_invite (ttt_server_t *server,
253 ttt_client_t *invitee)
255 ttt_invite_t *invite;
257 pthread_mutex_lock (&server->mutex);
259 invite = xmalloc (sizeof (ttt_invite_t));
261 invite->actor = actor;
262 invite->invitee = invitee;
264 server->num_invites++;
266 if (server->num_invites > server->invites_size) {
267 if (server->invites_size == 0)
268 server->invites_size = 1;
270 server->invites_size *= 2;
272 server->invites = xrealloc (server->invites,
273 server->invites_size * sizeof (ttt_invite_t *));
276 server->invites[server->num_invites - 1] = invite;
278 pthread_mutex_unlock (&server->mutex);
280 return TTT_ERROR_NONE;
283 /* Exported: See ttt-server.h for documentation. */
285 ttt_server_remove_invite (ttt_server_t *server,
287 ttt_client_t *invitee)
289 ttt_invite_t *invite;
293 pthread_mutex_lock (&server->mutex);
295 error = TTT_ERROR_NO_INVITE;
296 for (i = 0; i < server->num_invites; i++)
298 invite = server->invites[i];
299 if ((invite->actor == actor) && (invite->invitee == invitee))
301 error = TTT_ERROR_NONE;
309 assert (i < server->num_invites);
311 memmove (&server->invites[i], &server->invites[i+1],
312 (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
314 server->num_invites--;
317 pthread_mutex_unlock (&server->mutex);
322 /* Exported: See ttt-server.h for documentation. */
324 ttt_server_get_host (ttt_server_t *server)
329 /* Exported: See ttt-server.h for documentation. */
331 ttt_server_get_port (ttt_server_t *server)
336 static const char *WELCOME_MESSAGE =
337 "Welcome to ttt-server. The server is currently listening on:\r\n"
341 "To test this, simply connect one or more clients to that host and port.\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"
351 _ttt_server_accept (void *closure, int client_socket)
353 ttt_server_t *server = closure;
355 ttt_client_new (server, client_socket);
359 _detach_and_write_child_pid_to (const char *filename)
363 /* Use the Unix double-fork trick to detach completely. See
364 * setsid(2) for some details as to why two forks are
368 /* First parent just exits */
377 /* Second parent exits after writing pid */
378 FILE *file = xfopen (filename, "w");
379 fprintf (file, "%d\n", pid);
384 /* Final, detached child returns. */
388 main (int argc, char **argv)
394 ttt_args_parse (&args, argc, argv);
396 if (args.log_file || args.detach) {
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");
408 xdup2 (fileno (log_file), 2);
413 _detach_and_write_child_pid_to (args.pid_file);
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. */
421 pwd = getpwnam (args.user);
423 fprintf (stderr, "Error: Failed to lookup uid for %s: %s. Aborting.\n",
425 errno == 0 ? "User not found" : strerror (errno));
428 ret = setuid (pwd->pw_uid);
430 fprintf (stderr, "Error: Failed to setuid to %d (%s): %s. Aborting.\n",
431 pwd->pw_uid, args.user, strerror (errno));
436 socket = ttt_socket_create_server (args.host, args.port);
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);
442 printf (WELCOME_MESSAGE, args.host, args.port, args.host, args.port);
448 ttt_server_init (&server, args.host, args.port);
451 ttt_socket_accept (socket, _ttt_server_accept, &server);
453 /* We only reach here if something bad happened. */