updated docs
[rlserver.git] / server.c
blob34b9b1fff987908905a579c39e095752b711ec20
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <arpa/inet.h>
11 #include <sys/wait.h>
12 #include <signal.h>
13 #include <pthread.h>
14 #include <pwd.h>
15 #include <grp.h>
16 #include "telnet.h"
17 #include "sessions.h"
18 #include "server.h"
19 #include "log.h"
21 #define CONFIG_FILE "config"
23 // socket used to listen for new connections
24 int server_socket = -1;
26 server_config config = {"ee", 3456, 3, 5, -1, -1, "", ""};
31 * scan system users for the specified name
32 * returns uid or -1
34 static int find_uid (const char *name)
36 struct passwd *pwd;
39 // rewind to the first entry
40 setpwent ();
42 // scan entries
43 while ((pwd = getpwent()) != NULL)
45 if (strcmp(name, pwd->pw_name) == 0) return pwd->pw_uid;
48 // not found
49 return -1;
55 * scan system groups for the specified name
56 * returns uid or -1
58 static int find_gid (const char *name)
60 struct group *grp;
63 // rewind to the first entry
64 setgrent ();
66 // scan entries
67 while ((grp = getgrent()) != NULL)
69 if (strcmp(name, grp->gr_name) == 0) return grp->gr_gid;
72 // not found
73 return -1;
78 static int parse_config_line (char *buf, int ln)
80 char *val;
81 int len;
84 // find value after ':'
85 val = strchr(buf, ':');
86 if (val == NULL)
88 write_log (LOG_ERROR, CONFIG_FILE ":%d: parse error", ln);
89 return -1;
92 // skip leading spaces
93 do val++; while (isspace(val[0]));
95 len = strlen(val);
96 if (val[len - 1] == '\n') val[--len] = 0; // subtract 1 for \n at the end
97 if (len >= CONFIG_LEN)
99 write_log (LOG_ERROR, CONFIG_FILE ":%d: value is too long (max %d)", ln, CONFIG_LEN);
100 return -1;
103 if (strncmp(buf, "port:", 5) == 0)
105 config.port = atoi(val);
106 return 0;
109 if (strncmp(buf, "backlog:", 8) == 0)
111 config.backlog = atoi(val);
112 return 0;
115 if (strncmp(buf, "editor:", 7) == 0)
117 memcpy (config.editor, val, len);
118 return 0;
121 if (strncmp(buf, "kill_delay:", 11) == 0)
123 config.delay_before_kill = atoi(val);
124 return 0;
127 if (strncmp(buf, "user:", 5) == 0)
129 if ((config.uid = find_uid(val)) == -1)
131 write_log (LOG_ERROR, CONFIG_FILE ":%d: invalid user %s", ln, val);
132 return -1;
134 return 0;
137 if (strncmp(buf, "group:", 6) == 0)
139 if ((config.gid = find_gid(val)) == -1)
141 write_log (LOG_ERROR, CONFIG_FILE ":%d: invalid group %s", ln, val);
142 return -1;
144 return 0;
147 if (strncmp(buf, "stats:", 6) == 0)
149 memcpy (config.game_stat_file, val, len);
150 return 0;
153 if (strncmp(buf, "log:", 4) == 0)
155 memcpy (config.game_log_file, val, len);
156 return 0;
159 write_log (LOG_ERROR, CONFIG_FILE ":%d: unknown parameter", ln);
160 return -1;
165 #define CFG_BUF_SIZE (CONFIG_LEN + 16)
166 static int load_config (void)
168 char buf[CFG_BUF_SIZE];
169 FILE *f;
170 int ln = 1;
173 if ((f = fopen(CONFIG_FILE, "r")) == NULL)
175 write_log (LOG_ERROR, "can't open config file: " CONFIG_FILE);
176 return -1;
179 while (1)
181 // end of file
182 if (fgets(buf, CFG_BUF_SIZE, f) == NULL) break;
184 // skip comments and empty strings
185 if (buf[0] == '#' || buf[0] == '\n' || buf[0] == 0)
187 ln++;
188 continue;
191 if (parse_config_line(buf, ln) == -1)
193 fclose (f);
194 return -1;
197 ln++;
200 fclose (f);
201 return 0;
206 static void sigchld_handler (int s)
208 while (waitpid(-1, NULL, WNOHANG) > 0);
213 static void sigterm_handler (int s)
215 write_log (LOG_INFO, "initiating shutdown");
217 // do not accept new connections
218 close (server_socket);
220 // terminate all running games
221 terminate_games ();
223 /* we do not really care if spawned threads have been finished
224 because all threads will be forced to close anyway when the main process exits
225 it is a little bit dirty, but is simple and works */
227 extern pthread_mutex_t sessionlist_mutex;
228 pthread_mutex_destroy (&sessionlist_mutex);
230 // close and release all connections
231 close_sessions ();
232 release_sessions ();
234 write_log (LOG_INFO, "shutdown complete");
235 exit (0);
240 static void init_signals (void)
242 struct sigaction sa;
244 // SIGCHLD
245 // prevent terminated child processes from becoming zombies
246 sa.sa_handler = sigchld_handler;
247 sigemptyset (&sa.sa_mask);
248 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
249 if (sigaction(SIGCHLD, &sa, NULL) == -1)
251 write_log (LOG_FATAL, "error setting SIGCHLD handler");
252 exit (1);
255 // SIGTERM
256 // gracefully close all runing games and exit upon request
257 sa.sa_handler = sigterm_handler;
258 sigemptyset (&sa.sa_mask);
259 sa.sa_flags = SA_RESTART;
260 if (sigaction(SIGTERM, &sa, NULL) == -1)
262 write_log (LOG_FATAL, "error setting SIGTERM handler");
263 exit (1);
265 // use the same handler for SIGINT
266 if (sigaction(SIGINT, &sa, NULL) == -1)
268 write_log (LOG_FATAL, "error setting SIGINT handler");
269 exit (1);
272 // SIGPIPE - ignore it (check return codes for errors instead)
273 sa.sa_handler = SIG_IGN;
274 sigemptyset (&sa.sa_mask);
275 if (sigaction(SIGPIPE, &sa, NULL) == -1)
277 write_log (LOG_FATAL, "error setting SIGPIPE to ignore");
278 exit (1);
284 static int open_socket (int port)
286 int sockfd; // listen on sock_fd
287 struct sockaddr_in my_addr; // my address information
288 int yes = 1;
290 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
292 write_log (LOG_FATAL, "unable to create socket");
293 return -1;
296 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
298 write_log (LOG_FATAL, "unable to set socket options");
299 close (sockfd);
300 return -1;
303 my_addr.sin_family = AF_INET; // host byte order
304 my_addr.sin_port = htons(port); // short, network byte order
305 my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
306 memset (&(my_addr.sin_zero), 0, 8); // zero the rest of the struct
308 if (bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1)
310 write_log (LOG_FATAL, "unable to bind socket to port %d", port);
311 close (sockfd);
312 return -1;
315 if (listen(sockfd, config.backlog) == -1)
317 write_log (LOG_FATAL, "unable to listen on socket");
318 close (sockfd);
319 return -1;
322 return sockfd;
327 typedef struct
329 int sock;
330 } thread_data;
335 * Handle newly connected client in a thread
337 static void* connection_thread (void *data)
339 session_info *sess;
340 int r;
341 int sock = ((thread_data*)data)->sock;
342 free (data);
344 send_telnet_init (sock);
346 sess = add_session(sock);
348 r = main_menu(sess);
350 if (sess->user_name[0]) write_log (LOG_DEBUG, "user %s disconnected", sess->user_name);
351 else write_log (LOG_DEBUG, "anonymous user disconnected");
353 remove_session (sess);
354 close (sock);
355 return NULL;
360 static void handle_connect (int new_fd)
362 pthread_attr_t thread_attr;
363 thread_data *data;
364 pthread_t th;
367 if ((data = malloc(sizeof(thread_data))) == NULL)
369 close (new_fd);
370 write_log (LOG_ERROR, "can not handle connection: not enough memory");
371 return;
374 data->sock = new_fd;
376 // init thread data
377 pthread_attr_init (&thread_attr);
378 pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_DETACHED);
379 pthread_attr_setscope (&thread_attr, PTHREAD_SCOPE_PROCESS);
381 // handle new connection in a thread
382 if (pthread_create(&th, &thread_attr, connection_thread, data) != 0)
384 write_log (LOG_ERROR, "can not create thread to handle connection");
385 free (data);
386 close (new_fd);
392 int main (void)
394 // init things
396 init_log ();
397 write_log (LOG_INFO, "starting");
399 if (load_config() == -1) return 2;
401 init_signals ();
403 server_socket = open_socket(config.port);
404 if (server_socket == -1) return 1;
407 // drop privilegies
409 if (config.gid != -1)
411 if (setgid(config.gid) != 0)
413 write_log (LOG_FATAL, "can not set group id to %d\n", config.gid);
414 return 3;
418 if (config.uid != -1)
420 if (setuid(config.uid) != 0)
422 write_log (LOG_FATAL, "can not set user id to %d\n", config.uid);
423 return 3;
427 // had the user lost his mind?
428 if (getuid() == 0)
430 write_log (LOG_FATAL, "running this software as root is strictly forbidden");
431 return 3;
434 // detach from the terminal in a daemon style init
435 if (fork()) return 0;
436 close (0);
437 close (1);
438 close (2);
440 write_log (LOG_INFO, "init complete, ready to serve");
442 // the main loop
443 while (1)
445 struct sockaddr_in their_addr; // connector's address information
446 socklen_t sin_size = sizeof(struct sockaddr_in);
447 int new_fd;
449 // got a new connection
450 if ((new_fd = accept(server_socket, (struct sockaddr*)&their_addr, &sin_size)) == -1)
452 write_log (LOG_ERROR, "unable to accept connection");
453 continue;
456 write_log (LOG_DEBUG, "got connection from %s\n", inet_ntoa(their_addr.sin_addr));
458 handle_connect (new_fd);
461 // code here is never supposed to execute
462 // the program terminates via SIGTERM handler
464 return 0;