Fixed typo in previous commit
[rlserver.git] / menu.c
blob9aa32ee9ff321fa1f0b9ecc66747962766fd2724
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 session_info *sessionlist;
18 extern pthread_mutex_t sessionlist_mutex;
20 #define EDITOR_COMMAND "easyedit-1.4.6/ee"
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 (const 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, NULL);
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 (const session_info *sess, const char *msg)
93 char buf[1];
96 if (send(sess->socket, msg, strlen(msg) + 1, 0) == -1) return -1;
98 // wait for a keypress
99 int n = recv(sess->socket, buf, 1, 0);
100 if (n == -1) return -1;
102 return 0;
106 #define REG_INPUTS 3
108 static int register_menu (session_info *sess)
110 char data[REG_INPUTS][BUF_SIZE];
111 int id, r = 0;
112 const char *const prompts[REG_INPUTS] =
114 "\r\nUser name (3-16 chars, alphabet and digits only, start with a letter): ",
115 "\r\nPassword (4-16 chars, it is INSECURE, so do not use the same as email): ",
116 "\r\nEmail (optional): "
118 int lengths[REG_INPUTS][2] = { {MIN_USER_NAME_LEN, USER_NAME_LEN}, {MIN_USER_PASS_LEN, USER_PASS_LEN}, {0, USER_EMAIL_LEN} };
119 char buf[BUF_SIZE];
120 user usr;
123 // ask user for data
124 while (r < REG_INPUTS)
126 int len;
128 if (send(sess->socket, prompts[r], strlen(prompts[r]), 0) == -1) return -1;
130 len = recv_input(sess, data[r], lengths[r][1]);
131 if (len < 0) return -1;
133 if (len < lengths[r][0])
135 int l = sprintf(buf, "\r\nminimum %d symbols, try again: ", lengths[r][0]);
136 if (send(sess->socket, buf, l, 0) == -1) return -1;
137 continue;
140 r++;
144 // try to register using the data
145 id = add_user(&usr, data[0], data[1], data[2]);
146 if (id > 0)
148 int l;
150 sess->user_id = usr.id;
151 strcpy (sess->user_name, usr.name);
153 write_log (LOG_DEBUG, "registered user %s", usr.name);
155 l = sprintf(buf, "\r\nRegistration succeded\r\n");
157 if (send(sess->socket, buf, l, 0) == -1) return -1;
159 else
161 int l;
162 if (id == -1)
164 l = sprintf(buf, "\r\nInvalid name\r\n");
166 else if (id == -4)
168 l = sprintf(buf, "\r\nUser with such name already exists\r\n");
170 else
172 write_log (LOG_ERROR, "user registration failed, error %d", id);
174 l = sprintf(buf, "\r\nRegistration failed\r\n");
177 if (send(sess->socket, buf, l, 0) == -1) return -1;
180 // wait for a keypress
181 int n = recv(sess->socket, buf, BUF_SIZE, 0);
182 if (n == -1) return -1;
184 return 0;
189 static int login_menu (session_info *sess)
191 const char login_prompt[] = "\r\nUser name: ";
192 const char pass_prompt[] = "\r\nPassword: ";
193 char name[USER_NAME_LEN], pass[USER_PASS_LEN];
194 int id = 0;
195 user usr;
198 if (send(sess->socket, login_prompt, sizeof(login_prompt) - 1, 0) == -1) return -1;
199 if (recv_input(sess, name, USER_NAME_LEN) < 0) return -1;
201 if (send(sess->socket, pass_prompt, sizeof(pass_prompt) - 1, 0) == -1) return -1;
202 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
204 // check user name and password
205 if (check_user_login(&usr, name, pass) < 0)
207 if (print_and_wait(sess, "\r\nLogin failed\r\n") == -1) return -1;
208 return -2;
211 // disallow simultaneous connections
212 if (user_is_connected(usr.id))
214 if (print_and_wait(sess, "\r\nYou are already logged in\r\n") == -1) return -1;
215 return -3;
218 sess->user_id = usr.id;
219 strcpy (sess->user_name, usr.name);
220 write_log (LOG_DEBUG, "user %s logs in", usr.name);
222 return id;
227 static int passwd_menu (const session_info *sess)
229 const char old_prompt[] = "\r\nOld Password: ";
230 const char new_prompt[] = "\r\nNew Password: ";
231 char pass[USER_PASS_LEN];
232 user usr;
233 int len;
236 // ask for old password first
237 if (send(sess->socket, old_prompt, sizeof(old_prompt) - 1, 0) == -1) return -1;
238 if (recv_input(sess, pass, USER_PASS_LEN) < 0) return -1;
240 // check old password
241 if (check_user_login(&usr, sess->user_name, pass) < 0)
243 if (print_and_wait(sess, "\r\nWrong password\r\n") == -1) return -1;
244 return -2;
247 // ask for new password
248 if (send(sess->socket, new_prompt, sizeof(new_prompt) - 1, 0) == -1) return -1;
249 len = recv_input(sess, pass, USER_PASS_LEN);
250 if (len < 0) return -1;
252 if (len < MIN_USER_PASS_LEN)
254 if (print_and_wait(sess, "\r\nNew password is too short\r\n") == -1) return -1;
255 return -3;
258 if (change_password(sess->user_id, pass) == 0)
260 if (print_and_wait(sess, "\r\nPassword has been changed\r\n") == -1) return -1;
263 return 0;
268 static int observe (session_info *sess)
270 uint8_t buf[BUF_SIZE];
273 while (1)
275 int n = recv(sess->socket, buf, BUF_SIZE, 0);
276 if (n == -1) return -1;
278 if (buf[0] == 'q') break;
281 return 0;
286 static int observer_menu (session_info *sess)
288 char header[] = TELNET_CLS "\tGame\t\t\t\tUser\t\t Idle\t\tTerminal\r\n";
289 char buf[BUF_SIZE];
290 session_info *s;
291 int n, l, gn, count = 0;
292 time_t now;
294 char inp[20];
297 if (send(sess->socket, header, sizeof(header) - 1, 0) == -1) return -1;
299 pthread_mutex_lock (&sessionlist_mutex);
300 now = time(NULL);
301 for (s = sessionlist; s != NULL; s = s->next)
303 int idle;
305 if (s->game[0] == 0 || s->observer_count == -1) continue;
307 idle = now - s->last_activity;
309 l = sprintf(buf, "%3d\t%s", count + 1, s->game);
310 for (; l < 4 * 8; l++) buf[l] = ' ';
312 l += sprintf(&buf[l], "\t%s", s->user_name);
313 for (; l < 6 * 8; l++) buf[l] = ' ';
315 l += sprintf(&buf[l], "\t%3dm %02ds\t%dx%d\r\n", idle / 60, idle % 60, s->term_wid, s->term_hgt);
316 if (send(sess->socket, buf, l, 0) == -1) return -1;
317 count++;
319 pthread_mutex_unlock (&sessionlist_mutex);
320 l = sprintf(buf, "\r\n%d games total\r\n\r\n"
321 "Input number to observe or anything else to return to the main menu\r\n"
322 "You can press 'q' anytime to stop observing\r\n", count);
323 if (send(sess->socket, buf, l, 0) == -1) return -1;
326 // get number
327 n = recv_input(sess, inp, 20);
328 if (n == -1) return -1;
330 gn = atoi(inp);
331 if (gn > 0 && gn <= count)
333 // try to observe the specified game
334 if (add_observer(gn - 1, sess->socket))
336 int r = observe(sess);
337 stop_observing (sess);
338 if (r == -1) return -1;
342 return 0;
347 static int play_menu (session_info *sess)
349 const char msg[] = TELNET_CLS "Select a game to play by entering its id\r\n";
350 game_info *gamelist, *g;
351 char buf[BUF_SIZE];
352 int n;
353 char inp[20];
356 gamelist = load_gamelist();
358 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
360 free_gamelist (gamelist);
361 return -1;
364 g = gamelist;
365 while (g != NULL)
367 int l = sprintf(buf, "%8s: %s\r\n", g->id, g->name);
369 if (send(sess->socket, buf, l, 0) == -1)
371 free_gamelist (gamelist);
372 return -1;
375 g = g->next;
379 // get id
380 n = recv_input(sess, inp, 20);
381 if (n == -1) return -1;
383 // find and run game with such id
384 g = gamelist;
385 while (g != NULL)
387 if (strcmp(inp, g->id) == 0)
389 run_game (sess, g);
390 free_gamelist (gamelist);
392 if (print_and_wait(sess, "\r\nYou have left the game\r\n") == -1) return -1;
394 return 1;
397 g = g->next;
400 free_gamelist (gamelist);
402 return 0;
407 static void run_editor (session_info *sess, const game_info *g, int i)
409 game_info ed;
410 char name[BUF_SIZE];
411 int dirln;
414 // prefix with game directory name
415 if (g->dir[0]) dirln = sprintf(name, "%s/", g->dir);
416 else dirln = 0;
418 // copy file name to bufer, substituting user name
419 str_replace (&name[dirln], g->files[i].copy, "$USER$", sess->user_name);
421 // set up a a game struct for editor
422 memset (&ed, sizeof(game_info), 0);
423 strcpy (ed.id, "editor");
424 strcpy (ed.name, "editor");
425 snprintf (ed.command, GAME_COMMAND_LEN, EDITOR_COMMAND " %s", name);
427 // disable observing
428 sess->observer_count = -1;
430 run_game (sess, &ed);
432 // enable observing again
433 sess->observer_count = 0;
438 static int edit_menu (session_info *sess)
440 const char msg[] = TELNET_CLS "Select a file to edit by entering its number\r\n";
441 game_info *gamelist, *g;
442 char buf[BUF_SIZE], name[BUF_SIZE];
443 int n;
444 char inp[20];
447 gamelist = load_gamelist();
449 if (send(sess->socket, msg, sizeof(msg), 0) == -1)
451 free_gamelist (gamelist);
452 return -1;
455 g = gamelist;
456 while (g != NULL)
458 struct stat st;
459 int l, i, idx = 1, dirln;
462 for (i = 0; i < GAME_FILES; i++)
464 if (!g->files[i].file[0]) break;
465 if (!g->files[i].allow_edit) continue;
467 // prefix with game directory name
468 if (g->dir[0]) dirln = sprintf(name, "%s/", g->dir);
469 else dirln = 0;
471 // copy file name to bufer, substituting user name
472 str_replace (&name[dirln], g->files[i].copy, "$USER$", sess->user_name);
474 // file does not exist
475 if (stat(name, &st) != 0) continue;
477 l = sprintf(buf, "%d: [%s] %s\r\n", idx, g->name, name);
479 if (send(sess->socket, buf, l, 0) == -1)
481 free_gamelist (gamelist);
482 return -1;
485 // we (ab)use the allow_edit to store index
486 // to find the file when the user enters a number
487 g->files[i].allow_edit = 0x80000000 | idx;
489 idx++;
492 g = g->next;
496 // get id
497 n = recv_input(sess, inp, 20);
498 if (n == -1) return -1;
500 n = 0x80000000 | atoi(inp);
502 // find and run game with such id
503 g = gamelist;
504 while (g != NULL)
506 int i;
508 for (i = 0; i < GAME_FILES; i++)
510 if (!g->files[i].file[0]) break;
511 if (g->files[i].allow_edit != n) continue;
513 run_editor (sess, g, i);
515 free_gamelist (gamelist);
517 return 0;
520 g = g->next;
523 free_gamelist (gamelist);
525 return 0;
530 static int print_main_menu (session_info *sess)
532 char msg[BUF_SIZE];
533 const char *reg, *name, *play;
536 // XXX this is ugly, clean it up someday
538 if (sess->user_id < 0)
540 reg = "r) Register as a user (needed in order to play)";
541 name = "player of roguelikes";
542 play = "l) Log in to play";
544 else
546 reg = "c) Change password";
547 name = sess->user_name;
548 play = "p) Play\r\n\te) edit";
551 int l = snprintf(msg, BUF_SIZE, TELNET_CLS "Welcome to the roguelike server, %s!\r\n"
552 "\t%s\r\n"
553 "\to) Observe running games\r\n"
554 "\t%s\r\n"
555 "\tq) Leave\r\n", name, play, reg);
557 if (send(sess->socket, msg, l, 0) == -1) return -1;
558 return 0;
563 int main_menu (session_info *sess)
565 if (print_main_menu(sess) == -1) return -1;
567 while (1)
569 char buf[BUF_SIZE];
570 int n = recv(sess->socket, buf, BUF_SIZE, 0);
571 if (n == -1) return -1;
573 n = translate_telnet_input(buf, n, sess);
574 if (n > 0)
576 if (buf[0] == 'p' && sess->user_id >= 0)
578 if (play_menu(sess) == -1) return -1;
579 if (print_main_menu(sess) == -1) return -1;
582 if (buf[0] == 'e' && sess->user_id >= 0)
584 if (edit_menu(sess) == -1) return -1;
585 if (print_main_menu(sess) == -1) return -1;
588 if (buf[0] == 'l' && sess->user_id < 0)
590 if (login_menu(sess) == -1) return -1;
591 if (print_main_menu(sess) == -1) return -1;
594 if (buf[0] == 'r' && sess->user_id < 0)
596 if (register_menu(sess) == -1) return -1;
597 if (print_main_menu(sess) == -1) return -1;
600 if (buf[0] == 'c' && sess->user_id >= 0)
602 if (passwd_menu(sess) == -1) return -1;
603 if (print_main_menu(sess) == -1) return -1;
606 if (buf[0] == 'o')
608 if (observer_menu(sess) == -1) return -1;
609 if (print_main_menu(sess) == -1) return -1;
612 if (buf[0] == 'q') return 0;