fixed telnet commands not being parsed in some places in menu
[rlserver.git] / menu.c
blob39bd14f3d3d271b5d572f1aeffe14a937b3878c4
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <sys/socket.h>
6 #include <sys/stat.h>
7 #include <pthread.h>
8 #include <time.h>
9 #include "telnet.h"
10 #include "users.h"
11 #include "games.h"
12 #include "server.h"
13 #include "run.h"
14 #include "log.h"
17 extern server_config config;
19 extern session_info *sessionlist;
20 extern pthread_mutex_t sessionlist_mutex;
22 #define BUF_SIZE 1 * 1024
25 // return 1 if enter is pressed
26 static int input (char c, char *str, int *pos, int len, char *out, int *out_pos)
28 // enter
29 if (c == '\n')
31 str[*pos] = 0;
32 out[*out_pos] = 0;
33 return 1;
36 // backspace
37 if (c == 8 || c == 127)
39 if (*pos > 0) *pos -= 1;
41 out[*out_pos] = 8;
42 if (*out_pos < len) *out_pos += 1;
44 else if (*pos < len - 1)
46 str[*pos] = c;
47 if (*pos < len) *pos += 1;
49 out[*out_pos] = c;
50 if (*out_pos < len) *out_pos += 1;
53 return 0;
58 static int recv_input (session_info *sess, char *inp, int len)
60 char buf[BUF_SIZE], out[BUF_SIZE];
61 int pos = 0;
64 while (1)
66 int opos = 0;
68 int n = recv(sess->socket, buf, BUF_SIZE, 0);
69 if (n == -1) return -1;
71 n = translate_telnet_input(buf, n, sess);
72 if (n > 0)
74 int i;
75 for (i = 0; i < n; i++)
77 if (input(buf[i], inp, &pos, len, out, &opos))
79 return pos;
84 // echo
85 if (send(sess->socket, out, opos, 0) == -1) return -1;
91 static int print_and_wait (session_info *sess, const char *msg)
93 char buf[BUF_SIZE];
96 if (send(sess->socket, msg, strlen(msg) + 1, 0) == -1) return -1;
98 while (1)
100 // wait for a keypress
101 int n = recv(sess->socket, buf, BUF_SIZE, 0);
102 if (n == -1) return -1;
104 if (translate_telnet_input(buf, n, sess) > 0) break;
107 return 0;
111 #define REG_INPUTS 3
113 static int register_menu (session_info *sess)
115 char data[REG_INPUTS][BUF_SIZE];
116 int id, r = 0;
117 const char *const prompts[REG_INPUTS] =
119 "\r\nUser name (3-16 chars, alphabet and digits only, start with a letter): ",
120 "\r\nPassword (4-16 chars, it is INSECURE, so do not use the same as email): ",
121 "\r\nEmail (optional): "
123 int lengths[REG_INPUTS][2] = { {MIN_USER_NAME_LEN, USER_NAME_LEN}, {MIN_USER_PASS_LEN, USER_PASS_LEN}, {0, USER_EMAIL_LEN} };
124 char buf[BUF_SIZE];
125 user usr;
128 // ask user for data
129 while (r < REG_INPUTS)
131 int len;
133 if (send(sess->socket, prompts[r], strlen(prompts[r]), 0) == -1) return -1;
135 len = recv_input(sess, data[r], lengths[r][1]);
136 if (len < 0) return -1;
138 if (len < lengths[r][0])
140 int l = sprintf(buf, "\r\nminimum %d symbols, try again: ", lengths[r][0]);
141 if (send(sess->socket, buf, l, 0) == -1) return -1;
142 continue;
145 r++;
149 // try to register using the data
150 id = add_user(&usr, data[0], data[1], data[2]);
151 if (id > 0)
153 int l;
155 sess->user_id = usr.id;
156 strcpy (sess->user_name, usr.name);
158 write_log (LOG_DEBUG, "registered user %s", usr.name);
160 l = sprintf(buf, "\r\nRegistration succeded\r\n");
162 if (send(sess->socket, buf, l, 0) == -1) return -1;
164 else
166 int l;
167 if (id == -1)
169 l = sprintf(buf, "\r\nInvalid name\r\n");
171 else if (id == -4)
173 l = sprintf(buf, "\r\nUser with such name already exists\r\n");
175 else
177 write_log (LOG_ERROR, "user registration failed, error %d", id);
179 l = sprintf(buf, "\r\nRegistration failed\r\n");
182 if (send(sess->socket, buf, l, 0) == -1) return -1;
185 // wait for a keypress
186 int n = recv(sess->socket, buf, BUF_SIZE, 0);
187 if (n == -1) return -1;
189 return 0;
194 static int login_menu (session_info *sess)
196 const char login_prompt[] = "\r\nUser name: ";
197 const char pass_prompt[] = "\r\nPassword: ";
198 char name[USER_NAME_LEN], pass[USER_PASS_LEN];
199 int id = 0;
200 user usr;
203 if (send(sess->socket, login_prompt, sizeof(login_prompt) - 1, 0) == -1) return -1;
204 if (recv_input(sess, name, USER_NAME_LEN) < 0) return -1;
206 if (send(sess->socket, pass_prompt, sizeof(pass_prompt) - 1, 0) == -1) return -1;
207 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
209 // check user name and password
210 if (check_user_login(&usr, name, pass) < 0)
212 if (print_and_wait(sess, "\r\nLogin failed\r\n") == -1) return -1;
213 return -2;
216 // disallow simultaneous connections
217 if (user_is_connected(usr.id))
219 if (print_and_wait(sess, "\r\nYou are already logged in\r\n") == -1) return -1;
220 return -3;
223 sess->user_id = usr.id;
224 strcpy (sess->user_name, usr.name);
225 write_log (LOG_DEBUG, "user %s logs in", usr.name);
227 return id;
232 static int passwd_menu (session_info *sess)
234 const char old_prompt[] = "\r\nOld Password: ";
235 const char new_prompt[] = "\r\nNew Password: ";
236 char pass[USER_PASS_LEN];
237 user usr;
238 int len;
241 // ask for old password first
242 if (send(sess->socket, old_prompt, sizeof(old_prompt) - 1, 0) == -1) return -1;
243 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
245 // check old password
246 if (check_user_login(&usr, sess->user_name, pass) < 0)
248 if (print_and_wait(sess, "\r\nWrong password\r\n") == -1) return -1;
249 return -2;
252 // ask for new password
253 if (send(sess->socket, new_prompt, sizeof(new_prompt) - 1, 0) == -1) return -1;
254 len = recv_input(sess, pass, USER_PASS_LEN);
255 if (len < 0) return -1;
257 if (len < MIN_USER_PASS_LEN)
259 if (print_and_wait(sess, "\r\nNew password is too short\r\n") == -1) return -1;
260 return -3;
263 if (change_password(sess->user_id, pass) == 0)
265 if (print_and_wait(sess, "\r\nPassword has been changed\r\n") == -1) return -1;
268 return 0;
273 static int observe (session_info *sess)
275 uint8_t buf[BUF_SIZE];
278 while (1)
280 int n = recv(sess->socket, buf, BUF_SIZE, 0);
281 if (n == -1) return -1;
283 if (buf[0] == 'q') break;
286 return 0;
291 static int observer_menu (session_info *sess)
293 char header[] = TELNET_CLS "\tGame\t\t\t\tUser\t\t Idle\t\tTerminal\r\n";
294 char buf[BUF_SIZE];
295 session_info *s;
296 int n, l, gn, count = 0;
297 time_t now;
299 char inp[20];
302 if (send(sess->socket, header, sizeof(header) - 1, 0) == -1) return -1;
304 pthread_mutex_lock (&sessionlist_mutex);
305 now = time(NULL);
306 for (s = sessionlist; s != NULL; s = s->next)
308 int idle;
310 if (s->game[0] == 0 || s->observer_count == -1) continue;
312 idle = now - s->last_activity;
314 l = sprintf(buf, "%3d\t%s", count + 1, s->game);
315 for (; l < 4 * 8; l++) buf[l] = ' ';
317 l += sprintf(&buf[l], "\t%s", s->user_name);
318 for (; l < 6 * 8; l++) buf[l] = ' ';
320 l += sprintf(&buf[l], "\t%3dm %02ds\t%dx%d\r\n", idle / 60, idle % 60, s->term_wid, s->term_hgt);
321 if (send(sess->socket, buf, l, 0) == -1) return -1;
322 count++;
324 pthread_mutex_unlock (&sessionlist_mutex);
325 l = sprintf(buf, "\r\n%d games total\r\n\r\n"
326 "Input number to observe or anything else to return to the main menu\r\n"
327 "You can press 'q' anytime to stop observing\r\n", count);
328 if (send(sess->socket, buf, l, 0) == -1) return -1;
331 // get number
332 n = recv_input(sess, inp, 20);
333 if (n == -1) return -1;
335 gn = atoi(inp);
336 if (gn > 0 && gn <= count)
338 // try to observe the specified game
339 if (add_observer(gn - 1, sess->socket))
341 int r = observe(sess);
342 stop_observing (sess);
343 if (r == -1) return -1;
347 return 0;
352 // if score == 1, display highscore list and exit rather than playing
353 static int play_menu (session_info *sess, int score)
355 const char msg[] = TELNET_CLS "Select a game by entering its id\r\n";
356 game_info *gamelist, *g;
357 char buf[BUF_SIZE];
358 int n;
359 char inp[20];
362 gamelist = load_gamelist();
364 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
366 free_gamelist (gamelist);
367 return -1;
370 for (g = gamelist; g != NULL; g = g->next)
372 if (score && !g->cmd_score[0]) continue;
374 int l = sprintf(buf, "%8s: %s\r\n", g->id, g->name);
376 if (send(sess->socket, buf, l, 0) == -1)
378 free_gamelist (gamelist);
379 return -1;
384 // get id
385 n = recv_input(sess, inp, 20);
386 if (n == -1) return -1;
388 // find and run game with such id
389 for (g = gamelist; g != NULL; g = g->next)
391 if (score && !g->cmd_score[0]) continue;
393 if (strcmp(inp, g->id) == 0)
395 // hack for displaying score
396 if (score)
398 sess->observer_count = -1;
399 strcpy (g->cmd, g->cmd_score);
402 run_game (sess, g);
403 free_gamelist (gamelist);
405 if (print_and_wait(sess, "\r\nYou have left the game\r\n") == -1) return -1;
407 if (sess->observer_count == -1) sess->observer_count = 0;
409 return 1;
413 free_gamelist (gamelist);
415 return 0;
420 static void run_editor (session_info *sess, const game_info *g, int i)
422 game_info ed;
423 char name[BUF_SIZE];
424 int dirln;
427 // prefix with game directory name
428 if (g->dir[0]) dirln = sprintf(name, "%s/", g->dir);
429 else dirln = 0;
431 // copy file name to bufer
432 strcat (&name[dirln], g->files[i].copy);
434 // set up a a game struct for editor
435 memset (&ed, 0, sizeof(game_info));
436 strcpy (ed.id, "editor");
437 strcpy (ed.name, "editor");
438 ed.enter = '\n';
439 snprintf (ed.cmd, GAME_COMMAND_LEN, "%s %s", config.editor, name);
441 // disable observing
442 sess->observer_count = -1;
444 run_game (sess, &ed);
446 // enable observing again
447 sess->observer_count = 0;
452 static int edit_menu (session_info *sess)
454 const char msg[] = TELNET_CLS "Select a file to edit by entering its number\r\n";
455 game_info *gamelist, *g;
456 char buf[BUF_SIZE], name[BUF_SIZE];
457 int n, idx = 1;
458 char inp[20];
461 gamelist = load_gamelist();
463 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
465 free_gamelist (gamelist);
466 return -1;
469 g = gamelist;
470 while (g != NULL)
472 struct stat st;
473 int l, i, dirln = 0;
475 for (i = 0; i < GAME_FILES; i++)
477 if (!g->files[i].file[0]) break;
478 if (!g->files[i].allow_edit) continue;
480 // prefix with game directory name, expanding user name
481 if (g->dir[0])
483 str_replace (name, g->dir, "$USER$", sess->user_name);
484 strcat (name, "/");
485 dirln = strlen(name);
488 // copy file name to bufer, expanding user name
489 str_replace (&name[dirln], g->files[i].copy, "$USER$", sess->user_name);
490 // file does not exist
491 if (stat(name, &st) != 0) continue;
493 l = sprintf(buf, "%3d: [%s] %s\r\n", idx, g->name, name);
495 if (send(sess->socket, buf, l, 0) == -1)
497 free_gamelist (gamelist);
498 return -1;
501 // we (ab)use the allow_edit to store index
502 // to find the file when the user enters a number
503 g->files[i].allow_edit = 0x80000000 | idx;
505 idx++;
508 g = g->next;
512 // get id
513 n = recv_input(sess, inp, 20);
514 if (n == -1) return -1;
516 n = 0x80000000 | atoi(inp);
518 // find and run game with such id
519 g = gamelist;
520 while (g != NULL)
522 int i;
524 for (i = 0; i < GAME_FILES; i++)
526 if (!g->files[i].file[0]) break;
527 if (g->files[i].allow_edit != n) continue;
529 run_editor (sess, g, i);
531 free_gamelist (gamelist);
533 return 0;
536 g = g->next;
539 free_gamelist (gamelist);
541 return 0;
546 static int print_main_menu (session_info *sess)
548 char msg[BUF_SIZE];
549 const char *reg, *name, *play;
552 // XXX this is ugly, clean it up someday
554 if (sess->user_id < 0)
556 reg = "r) Register as a user (needed in order to play)";
557 name = "player of roguelikes";
558 play = "l) Log in to play";
560 else
562 reg = "c) Change password";
563 name = sess->user_name;
564 play = "p) Play\r\n\te) Edit game options files";
567 int l = snprintf(msg, BUF_SIZE, TELNET_CLS "Welcome to the roguelike server, %s!\r\n"
568 "\t%s\r\n"
569 "\to) Observe running games\r\n"
570 "\ts) View high-score lists\r\n"
571 "\t%s\r\n"
572 "\tq) Leave\r\n", name, play, reg);
574 if (send(sess->socket, msg, l, 0) == -1) return -1;
575 return 0;
580 int main_menu (session_info *sess)
582 if (print_main_menu(sess) == -1) return -1;
584 while (1)
586 char buf[BUF_SIZE];
587 int n = recv(sess->socket, buf, BUF_SIZE, 0);
588 if (n == -1) return -1;
590 n = translate_telnet_input(buf, n, sess);
591 if (n > 0)
593 if (buf[0] == 'p' && sess->user_id >= 0)
595 if (play_menu(sess, 0) == -1) return -1;
596 if (print_main_menu(sess) == -1) return -1;
599 if (buf[0] == 's')
601 if (play_menu(sess, 1) == -1) return -1;
602 if (print_main_menu(sess) == -1) return -1;
605 if (buf[0] == 'e' && sess->user_id >= 0)
607 if (edit_menu(sess) == -1) return -1;
608 if (print_main_menu(sess) == -1) return -1;
611 if (buf[0] == 'l' && sess->user_id < 0)
613 if (login_menu(sess) == -1) return -1;
614 if (print_main_menu(sess) == -1) return -1;
617 if (buf[0] == 'r' && sess->user_id < 0)
619 if (register_menu(sess) == -1) return -1;
620 if (print_main_menu(sess) == -1) return -1;
623 if (buf[0] == 'c' && sess->user_id >= 0)
625 if (passwd_menu(sess) == -1) return -1;
626 if (print_main_menu(sess) == -1) return -1;
629 if (buf[0] == 'o')
631 if (observer_menu(sess) == -1) return -1;
632 if (print_main_menu(sess) == -1) return -1;
635 if (buf[0] == 'q') return 0;