Annotation of dgamelaunch-openbsd/dgamelaunch.c, Revision 1.3
1.1 rubenllo 1: /* dgamelaunch.c
2: *
1.3 ! rubenllo 3: * (c)2021 Rubén Llorente <porting@use.startmail.com>
1.1 rubenllo 4: * (c)2001-4 M. Drew Streib <dtype@dtype.org>
5: * also parts (c) 2003-4 Joshua Kwan <joshk@triplehelix.org>,
6: * Brett Carrington <brettcar@segvio.org>,
7: * Jilles Tjoelker <jilles@stack.nl>
8: *
9: * This program is free software; you can redistribute it and/or modify
10: * it under the terms of the GNU General Public License as published by
11: * the Free Software Foundation; either version 2 of the License, or
12: * (at your option) any later version.
13: *
14: * This program is distributed in the hope that it will be useful,
15: * but WITHOUT ANY WARRANTY; without even the implied warranty of
16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: * GNU General Public License for more details.
18: *
19: * You should have received a copy of the GNU General Public License
20: * along with this program; if not, write to the Free Software
21: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22: */
23:
24: /*
25: * See this program in action at http://alt.org/nethack/
26: *
27: * This is a little wrapper for nethack (and soon other programs) that
28: * will allow them to be run from a telnetd session, chroot, shed privs,
29: * make a simple login, then play the game.
30: */
31:
32: #define _GNU_SOURCE
33:
34: #include "dgamelaunch.h"
35: #include "config.h"
36: #include "ttyplay.h"
37: #include "ttyrec.h"
38:
39: /* a request from the author: please leave some remnance of
40: * 'based on dgamelaunch version xxx' in any derivative works, or
41: * even keep the line the same altogether. I'm probably happy
42: * to make any changes you need. */
43:
44: /* ************************************************************* */
45:
46: /* program stuff */
47: #include <sys/types.h>
48: #include <sys/time.h>
49: #include <sys/wait.h>
50: #include <sys/ioctl.h> /* ttyrec */
51: #include <sys/stat.h>
52: #ifdef USE_RLIMIT
53: #include <sys/resource.h>
54: #endif
55:
56: #ifdef USE_SHMEM
57: #include <sys/ipc.h>
58: #include <sys/shm.h>
59: #endif
60:
61: #include <libgen.h>
62: #include <stdlib.h>
63: #include <curses.h>
64: #include <locale.h>
65:
66: #ifdef USE_SQLITE3
67: # include <sqlite3.h>
68: #endif
69:
70: #if defined(__FreeBSD__)
71: # include <libutil.h>
72: #elif defined(__NetBSD__)
73: # include <util.h>
1.2 rubenllo 74: #elif defined(__OpenBSD__)
75: # include <unistd.h>
1.1 rubenllo 76: #elif defined(__APPLE__)
77: # include <unistd.h>
78: #else
79: # include <crypt.h>
80: #endif
81:
82: #ifdef __linux__
83: # include <pty.h>
84: #endif
85:
86: #include <fcntl.h>
87: #include <pwd.h>
88: #include <grp.h>
89: #include <time.h>
90: #include <errno.h>
91: #include <dirent.h>
92: #include <string.h>
93: #include <signal.h>
94: #include <assert.h>
95: #include <ctype.h>
96: #include <unistd.h>
97: #include <termios.h>
98:
99: extern FILE* yyin;
100: extern int yyparse ();
101:
102: /* global variables */
103:
104: char * __progname;
105: int g_idle_alarm_enabled = 0;
106: int showplayers = 0;
107: int initplayer = 0;
108: void (*g_chain_winch)(int);
109:
110: #ifndef USE_SQLITE3
111: int f_num = 0;
112: struct dg_user **users = NULL;
113: #endif
114: struct dg_user *me = NULL;
115: struct dg_banner banner;
116:
117: static struct dg_watchcols default_watchcols[] = {
118: {SORTMODE_NONE, SORTMODE_NONE, 1, "", "%s)"},
119: {SORTMODE_USERNAME, SORTMODE_USERNAME, 4, "Username", "%-15s"},
120: {SORTMODE_GAMENUM, SORTMODE_GAMENUM, 21, "Game", "%-5s"},
121: {SORTMODE_WINDOWSIZE, SORTMODE_WINDOWSIZE, 28, " Size", "%s"},
122: {SORTMODE_STARTTIME, SORTMODE_STARTTIME, 37, "Start date & time", "%s"},
123: {SORTMODE_IDLETIME, SORTMODE_IDLETIME, 58, "Idle time", "%-10s"},
124: #ifdef USE_SHMEM
125: {SORTMODE_WATCHERS, SORTMODE_WATCHERS, 70, "Watchers", "%s"},
126: #endif
127: };
128:
129: int color_remap[16] = {
130: COLOR_PAIR(9) | A_NORMAL,
131: COLOR_PAIR(COLOR_BLUE) | A_NORMAL,
132: COLOR_PAIR(COLOR_GREEN) | A_NORMAL,
133: COLOR_PAIR(COLOR_CYAN) | A_NORMAL,
134: COLOR_PAIR(COLOR_RED) | A_NORMAL,
135: COLOR_PAIR(COLOR_MAGENTA) | A_NORMAL,
136: COLOR_PAIR(COLOR_YELLOW) | A_NORMAL,
137: COLOR_PAIR(COLOR_BLACK) | A_NORMAL,
138: COLOR_PAIR(10) | A_BOLD,
139: COLOR_PAIR(COLOR_BLUE) | A_BOLD,
140: COLOR_PAIR(COLOR_GREEN) | A_BOLD,
141: COLOR_PAIR(COLOR_CYAN) | A_BOLD,
142: COLOR_PAIR(COLOR_RED) | A_BOLD,
143: COLOR_PAIR(COLOR_MAGENTA) | A_BOLD,
144: COLOR_PAIR(COLOR_YELLOW) | A_BOLD,
145: COLOR_PAIR(COLOR_WHITE) | A_BOLD,
146: };
147:
148: static struct dg_watchcols *default_watchcols_list[DGL_MAXWATCHCOLS + 1];
149:
150: struct dg_user *
151: cpy_me(struct dg_user *me)
152: {
153: struct dg_user *tmp = malloc(sizeof(struct dg_user));
154:
155: if (tmp && me) {
156: #ifdef USE_SQLITE3
157: tmp->id = me->id;
158: #endif
159: if (me->username) tmp->username = strdup(me->username);
160: if (me->email) tmp->email = strdup(me->email);
161: if (me->env) tmp->env = strdup(me->env);
162: if (me->password) tmp->password = strdup(me->password);
163: tmp->flags = me->flags;
164: }
165: return tmp;
166: }
167:
168: #ifndef HAVE_SETENV
169: int
170: mysetenv (const char* name, const char* value, int overwrite)
171: {
172: int retval;
173: char *buf = NULL;
174:
175: if (getenv(name) == NULL || overwrite)
176: {
177: size_t len = strlen(name) + 1 + strlen(value) + 1; /* NAME=VALUE\0 */
178: buf = malloc(len);
179: snprintf(buf, len, "%s=%s", name, value);
180: retval = putenv(buf);
181: }
182: else
183: retval = -1;
184:
185: return retval;
186: }
187: #else /* use native setenv */
188: # define mysetenv setenv
189: #endif
190:
191: /* ************************************************************* */
192: /* for ttyrec */
193:
194: void
195: ttyrec_getpty ()
196: {
197: #ifdef HAVE_OPENPTY
198: if (openpty (&master, &slave, NULL, NULL, NULL) == -1) {
199: debug_write("cannot openpty");
200: graceful_exit (61);
201: }
202: #else
203: if ((master = open ("/dev/ptmx", O_RDWR)) < 0) {
204: debug_write("cannot open /dev/ptmx");
205: graceful_exit (62);
206: }
207: grantpt (master);
208: unlockpt (master);
209: if ((slave = open ((const char *) ptsname (master), O_RDWR)) < 0)
210: {
211: debug_write("cannot open master ptsname");
212: graceful_exit (65);
213: }
214: #endif
215: ioctl (slave, TIOCSWINSZ, (char *) &win);
216: tcsetattr(slave, TCSANOW, &tt);
217: }
218:
219: /* ************************************************************* */
220:
221: static int dgl_signal_blocked = 0;
222: static sigset_t dgl_signal_blockmask;
223: static sigset_t dgl_signal_oldmask;
224:
225: void
226: signals_block()
227: {
228: if (!dgl_signal_blocked) {
229: sigemptyset(&dgl_signal_blockmask);
230: sigaddset(&dgl_signal_blockmask, SIGHUP);
231: sigaddset(&dgl_signal_blockmask, SIGINT);
232: sigaddset(&dgl_signal_blockmask, SIGQUIT);
233: sigaddset(&dgl_signal_blockmask, SIGTERM);
234: sigprocmask(SIG_BLOCK, &dgl_signal_blockmask, &dgl_signal_oldmask);
235: dgl_signal_blocked = 1;
236: }
237: }
238:
239: void
240: signals_release()
241: {
242: if (dgl_signal_blocked) {
243: sigprocmask(SIG_SETMASK, &dgl_signal_oldmask, NULL);
244: dgl_signal_blocked = 0;
245: }
246: }
247:
248:
249: /* ************************************************************* */
250:
251: char *
252: get_mainmenu_name()
253: {
254: if (loggedin) {
255: if (me && (me->flags & DGLACCT_ADMIN)) return "mainmenu_admin";
256: return "mainmenu_user";
257: }
258: return "mainmenu_anon";
259: }
260:
261:
262: char*
263: gen_ttyrec_filename ()
264: {
265: time_t rawtime;
266: struct tm *ptm;
267: char *ttyrec_filename = calloc(100, sizeof(char));
268:
269: /* append time to filename */
270: time (&rawtime);
271: ptm = gmtime (&rawtime);
272: snprintf (ttyrec_filename, 100, "%04i-%02i-%02i.%02i:%02i:%02i.ttyrec",
273: ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
274: ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
275: return ttyrec_filename;
276: }
277:
278: /* ************************************************************* */
279:
280: char*
281: gen_inprogress_lock (int game, pid_t pid, char* ttyrec_filename)
282: {
283: char *lockfile = NULL, filebuf[80];
284: int fd;
285: size_t len, wrlen;
286: struct flock fl = { 0 };
287:
288: snprintf (filebuf, sizeof(filebuf), "%d\n%d\n%d\n",
289: pid, win.ws_row, win.ws_col);
290:
291: wrlen = strlen(filebuf);
292:
293: fl.l_type = F_WRLCK;
294: fl.l_whence = SEEK_SET;
295: fl.l_start = 0;
296: fl.l_len = 0;
297:
298: len = strlen(dgl_format_str(game, me, myconfig[game]->inprogressdir, NULL)) + strlen(me->username) + strlen(ttyrec_filename) + 13;
299: lockfile = calloc(len, sizeof(char));
300:
301: snprintf (lockfile, len, "%s%s:%s", dgl_format_str(game, me, myconfig[game]->inprogressdir, NULL),
302: me->username, ttyrec_filename);
303:
304: fd = open (lockfile, O_WRONLY | O_CREAT, 0644);
305: if (fcntl (fd, F_SETLKW, &fl) == -1) {
306: debug_write("cannot fnctl inprogress-lock");
307: graceful_exit (68);
308: }
309:
310: if (write (fd, filebuf, wrlen) != wrlen) {
311: debug_write("inprogress-lock write");
312: graceful_exit(70);
313: }
314:
315: return lockfile;
316: }
317:
318: /* ************************************************************* */
319:
320: #ifdef USE_SHMEM
321: int hup_shm_idx = -1;
322: char *hup_shm_ttyrec_fn = NULL;
323: #endif
324:
325: void
326: catch_sighup (int signum)
327: {
328: if (child)
329: {
330: sleep (10);
331: kill (child, SIGHUP);
332: sleep (5);
333: }
334: #ifdef USE_SHMEM
335: signals_block();
336: if (hup_shm_idx != -1) {
337: struct dg_shm *shm_dg_data = NULL;
338: struct dg_shm_game *shm_dg_game = NULL;
339: shm_init(&shm_dg_data, &shm_dg_game);
340:
341: shm_sem_wait(shm_dg_data);
342: if (shm_dg_game[hup_shm_idx].in_use &&
343: !strcmp(shm_dg_game[hup_shm_idx].ttyrec_fn, hup_shm_ttyrec_fn) &&
344: (shm_dg_game[hup_shm_idx].nwatchers > 0)) {
345: shm_dg_game[hup_shm_idx].nwatchers--;
346: }
347: shm_sem_post(shm_dg_data);
348: hup_shm_idx = -1;
349: free(hup_shm_ttyrec_fn);
350: }
351: signals_release();
352: #endif
353: debug_write("catchup sighup");
354: graceful_exit (7);
355: }
356:
357: /* ************************************************************* */
358:
359: int
360: dgl_getch(void)
361: {
362: const int c = getch();
363: idle_alarm_reset();
364: return c;
365: }
366:
367: /* ************************************************************* */
368:
369: static void
370: dgl_idle_kill(int signal)
371: {
372: kill(0, SIGHUP);
373: }
374:
375: void
376: idle_alarm_set_enabled(int enabled)
377: {
378: signal(SIGALRM, SIG_IGN);
379: g_idle_alarm_enabled = enabled;
380: idle_alarm_reset();
381: if (enabled)
382: signal(SIGALRM, dgl_idle_kill);
383: }
384:
385: void
386: idle_alarm_reset(void)
387: {
388: if (g_idle_alarm_enabled && globalconfig.menu_max_idle_time > 0)
389: alarm(globalconfig.menu_max_idle_time);
390: }
391:
392: /* ************************************************************* */
393:
394:
395: char *
396: bannerstrmangle(char *buf, char *bufnew, int buflen, char *fromstr, char *tostr)
397: {
398: char *loc;
399: char *b = buf;
400:
401: memset (bufnew, 0, buflen);
402:
403: if (strstr(b, fromstr)) {
404: int i = 0;
405: while ((loc = strstr (b, fromstr)) != NULL) {
406: for (; i < buflen; i++) {
407: if (loc != b)
408: bufnew[i] = *(b++);
409: else {
410: strlcat (bufnew, tostr, buflen);
411: b += strlen(fromstr);
412: i += strlen(tostr);
413: break;
414: }
415:
416: if (strlen (b) == 0)
417: break;
418: }
419: }
420:
421: if (*b)
422: strlcat(bufnew, b, buflen);
423: } else strncpy(bufnew, buf, buflen);
424: return bufnew;
425: }
426:
427: void
428: banner_var_add(char *name, char *value, int special)
429: {
430: struct dg_banner_var *tmp = (struct dg_banner_var *)malloc(sizeof(struct dg_banner_var));
431:
432: if (!tmp) return;
433:
434: tmp->name = strdup(name);
435: tmp->value = strdup(value);
436: tmp->special = special;
437: tmp->next = globalconfig.banner_var_list;
438: globalconfig.banner_var_list = tmp;
439: }
440:
441: void
442: banner_var_free()
443: {
444: struct dg_banner_var *tmp;
445: struct dg_banner_var *bv = globalconfig.banner_var_list;
446: while (bv) {
447: tmp = bv->next;
448: free(bv->name);
449: free(bv->value);
450: free(bv);
451: bv = tmp;
452: }
453: globalconfig.banner_var_list = NULL;
454: }
455:
456: char *
457: banner_var_resolve(struct dg_banner_var *bv)
458: {
459: static char tmpbuf[DGL_BANNER_LINELEN+1];
460: time_t tstamp;
461: struct tm *ptm;
462: if (!bv) return NULL;
463: if (!bv->special) return bv->value;
464: time(&tstamp);
465: ptm = gmtime(&tstamp);
466: strftime(tmpbuf, DGL_BANNER_LINELEN, bv->value, ptm);
467: return tmpbuf;
468: }
469:
470: char *
471: banner_var_value(char *name)
472: {
473: struct dg_banner_var *bv = globalconfig.banner_var_list;
474: while (bv) {
475: if (!strcmp(bv->name, name)) return banner_var_resolve(bv);
476: bv = bv->next;
477: }
478: return NULL;
479: }
480:
481: void
482: freebanner(struct dg_banner *ban)
483: {
484: unsigned int l;
485: if (!ban) return;
486: l = ban->len;
487:
488: while (l > 0) {
489: l--;
490: free(ban->lines[l]);
491: }
492: free(ban->lines);
493: ban->len = 0;
494: ban->lines = NULL;
495: }
496:
497: void
498: banner_addline(struct dg_banner *ban, char *line)
499: {
500: size_t len = strlen(line);
501: if (!ban) return;
502: ban->len++;
503: ban->lines = realloc (ban->lines, sizeof (char *) * ban->len);
504: if (len >= DGL_BANNER_LINELEN) {
505: len = DGL_BANNER_LINELEN;
506: ban->lines[ban->len - 1] = malloc(len);
507: strncpy(ban->lines[ban->len - 1], line, len);
508: ban->lines[ban->len - 1][len-1] = '\0';
509: } else
510: ban->lines[ban->len - 1] = strdup(line);
511: }
512:
513: void
514: loadbanner (char *fname, struct dg_banner *ban)
515: {
516: FILE *bannerfile;
517: char buf[DGL_BANNER_LINELEN+1];
518: if (ban->len > 23) return;
519:
520: memset (buf, 0, DGL_BANNER_LINELEN);
521:
522: bannerfile = fopen (fname, "r");
523:
524: if (!bannerfile)
525: {
526: if (ban->len == 0)
527: banner_addline(ban, "### dgamelaunch " PACKAGE_VERSION " - network console game launcher");
528: snprintf(buf, DGL_BANNER_LINELEN, "### NOTE: administrator has not installed a %s file", fname);
529: banner_addline(ban, buf);
530: return;
531: }
532:
533: while (fgets (buf, DGL_BANNER_LINELEN, bannerfile) != NULL)
534: {
535: char bufnew[DGL_BANNER_LINELEN+1];
536: int slen;
537:
538: memset (bufnew, 0, DGL_BANNER_LINELEN);
539:
540: slen = strlen(buf);
541: if ((slen > 0) && (buf[slen-1] == '\n')) buf[slen-1] = '\0';
542:
543: strncpy(bufnew, buf, DGL_BANNER_LINELEN);
544: if (strstr(bufnew, "$INCLUDE(")) {
545: char *fn = bufnew + 9;
546: char *fn_end = strchr(fn, ')');
547: if (fn_end) {
548: *fn_end = '\0';
549: if (strcmp(fname, fn)) {
550: loadbanner(fn, ban);
551: }
552: }
553: } else {
554: char tmpbufnew[DGL_BANNER_LINELEN+1];
555: struct dg_banner_var *bv = globalconfig.banner_var_list;
556: while (bv) {
557: strncpy(bufnew, bannerstrmangle(bufnew, tmpbufnew, DGL_BANNER_LINELEN, bv->name, banner_var_resolve(bv)), DGL_BANNER_LINELEN);
558: bv = bv->next;
559: }
560: strncpy(bufnew, bannerstrmangle(bufnew, tmpbufnew, DGL_BANNER_LINELEN, "$VERSION", PACKAGE_STRING), DGL_BANNER_LINELEN);
561: if (me && loggedin) {
562: strncpy(bufnew, bannerstrmangle(bufnew, tmpbufnew, DGL_BANNER_LINELEN, "$USERNAME", me->username), DGL_BANNER_LINELEN);
563: } else {
564: strncpy(bufnew, bannerstrmangle(bufnew, tmpbufnew, DGL_BANNER_LINELEN, "$USERNAME", "[Anonymous]"), DGL_BANNER_LINELEN);
565: }
566: banner_addline(ban, bufnew);
567: }
568:
569: memset (buf, 0, DGL_BANNER_LINELEN);
570:
571: if (ban->len >= 24)
572: break;
573: }
574:
575: fclose (bannerfile);
576: }
577:
578: void
579: drawbanner (struct dg_banner *ban)
580: {
581: unsigned int i;
582: char *tmpch, *tmpch2, *splch;
583: int attr = 0, oattr = 0;
584:
585: if (!ban) return;
586:
587: for (i = 0; i < ban->len; i++) {
588: char *tmpbuf = strdup(ban->lines[i]);
589: char *tmpbuf2 = tmpbuf;
590: int ok = 0;
591: int x = 1;
592: do {
593: ok = 0;
594: if ((tmpch = strstr(tmpbuf2, "$ATTR("))) {
595: if ((tmpch2 = strstr(tmpch, ")"))) {
596: int spl = 0;
597: char *nxttmpch;
598: ok = 1;
599: oattr = attr;
600: attr = A_NORMAL;
601: *tmpch = *tmpch2 = '\0';
602: tmpch += 6;
603: nxttmpch = tmpch;
604: do {
605: spl = 0;
606: splch = strchr(tmpch, ';');
607: if (splch && *splch) {
608: spl = 1;
609: nxttmpch = splch;
610: *nxttmpch = '\0';
611: nxttmpch++;
612: }
613: if (tmpch && *tmpch) {
614: switch (*tmpch) {
615: default: break;
616: case '0': case '1': case '2': case '3': case '4':
617: case '5': case '6': case '7': case '8': case '9':
618: {
619: int num = atoi(tmpch);
620: if (num >= 0 && num <= 15)
621: attr |= color_remap[num];
622: }
623: break;
624: case 'b': attr |= A_BOLD; break;
625: case 's': attr |= A_STANDOUT; break;
626: case 'u': attr |= A_UNDERLINE; break;
627: case 'r': attr |= A_REVERSE; break;
628: case 'd': attr |= A_DIM; break;
629: }
630: } else attr = A_NORMAL;
631: tmpch = nxttmpch;
632: } while (spl);
633:
634: mvaddstr(1 + i, x, tmpbuf2);
635: if (oattr) attroff(oattr);
636: if (attr) attron(attr);
637: x += strlen(tmpbuf2);
638: tmpch2++;
639: tmpbuf2 = tmpch2;
640: } else
641: mvaddstr (1 + i, x, tmpbuf2);
642: } else
643: mvaddstr (1 + i, x, tmpbuf2);
644: } while (ok);
645: free(tmpbuf);
646: }
647: }
648:
649: void
650: shm_sem_wait(struct dg_shm *shm_dg_data)
651: {
652: #ifdef USE_SHMEM
653: if (sem_wait(&(shm_dg_data->dg_sem)) == -1) {
654: debug_write("sem_wait");
655: graceful_exit(77);
656: }
657: #endif
658: }
659:
660: void
661: shm_sem_post(struct dg_shm *shm_dg_data)
662: {
663: #ifdef USE_SHMEM
664: if (sem_post(&(shm_dg_data->dg_sem)) == -1) {
665: debug_write("sem_post");
666: graceful_exit(78);
667: }
668: #endif
669: }
670:
671: void
672: shm_update(struct dg_shm *shm_dg_data, struct dg_game **games, int len)
673: {
674: #ifdef USE_SHMEM
675: int di, i;
676: struct dg_shm_game *shm_dg_game = (struct dg_shm_game *)(shm_dg_data + sizeof(struct dg_shm));
677:
678: signals_block();
679: shm_sem_wait(shm_dg_data);
680:
681: for (di = 0; di < shm_dg_data->max_n_games; di++)
682: if (shm_dg_game[di].in_use) {
683: int delgame = 1;
684: for (i = 0; i < len; i++) {
685: if (!strcmp(games[i]->ttyrec_fn, shm_dg_game[di].ttyrec_fn)) {
686: delgame = 0;
687: games[i]->is_in_shm = 1;
688: games[i]->shm_idx = di;
689: games[i]->nwatchers = shm_dg_game[di].nwatchers;
690: break;
691: }
692: }
693: if (delgame) {
694: shm_dg_game[di].in_use = 0;
695: if (shm_dg_data->cur_n_games > 0) shm_dg_data->cur_n_games--;
696: }
697: }
698:
699: if (shm_dg_data->cur_n_games < shm_dg_data->max_n_games) {
700: for (i = 0; i < len; i++)
701: if (!games[i]->is_in_shm) {
702: for (di = 0; di < shm_dg_data->max_n_games; di++)
703: if (!shm_dg_game[di].in_use) {
704: shm_dg_game[di].in_use = 1;
705: shm_dg_game[di].nwatchers = 0;
706: games[i]->nwatchers = 0;
707: games[i]->is_in_shm = 1;
708: games[i]->shm_idx = di;
709: shm_dg_data->cur_n_games++;
710: strncpy(shm_dg_game[di].ttyrec_fn, games[i]->ttyrec_fn, 150);
711: break;
712: }
713: }
714: }
715:
716: shm_sem_post(shm_dg_data);
717: signals_release();
718: #endif
719: }
720:
721: void
722: shm_mk_keys(key_t *shm_key, key_t *shm_sem_key)
723: {
724: #ifdef USE_SHMEM
725: if ((*shm_key = ftok(globalconfig.passwd, 'R')) == -1) {
726: debug_write("ftok shm_key");
727: graceful_exit(71);
728: }
729: if ((*shm_sem_key = ftok(globalconfig.passwd, 'S')) == -1) {
730: debug_write("ftok shm_sem_key");
731: graceful_exit(72);
732: }
733: #endif
734: }
735:
736: #ifdef USE_SHMEM
737: int
738: shm_free()
739: {
740: key_t shm, sem;
741: int shm_id;
742: int shm_size = sizeof(struct dg_shm) + shm_n_games * sizeof(struct dg_shm_game);
743: shm_mk_keys(&shm, &sem);
744: if ((shm_id = shmget(shm, shm_size, 0644)) != -1) {
745: shmctl(shm_id, IPC_RMID, NULL);
746: return 0;
747: }
748: return 1;
749: }
750: #endif
751:
752: void
753: shm_init(struct dg_shm **shm_dg_data, struct dg_shm_game **shm_dg_game)
754: {
755: #ifdef USE_SHMEM
756: key_t shm_key;
757: key_t shm_sem_key;
758: int shm_id;
759: int shm_size;
760: void *shm_data = NULL;
761: int shm_data_existed = 0;
762:
763: shm_mk_keys(&shm_key, &shm_sem_key);
764:
765: /* max. shm_n_games simultaneous games recorded in the shared memory */
766: shm_size = sizeof(struct dg_shm) + shm_n_games * sizeof(struct dg_shm_game);
767:
768: /* connect to (and possibly create) the segment */
769: if ((shm_id = shmget(shm_key, shm_size, 0644 | IPC_CREAT | IPC_EXCL)) == -1) {
770: /* creation failed, so it already exists. attach to it */
771: shm_data_existed = 1;
772: if ((shm_id = shmget(shm_key, shm_size, 0644)) == -1) {
773: debug_write("shmget");
774: graceful_exit(73);
775: }
776: }
777:
778: /* attach to the segment to get a pointer to it: */
779: shm_data = shmat(shm_id, (void *)0, 0);
780: if (shm_data == (char *)(-1)) {
781: debug_write("shmat");
782: graceful_exit(74);
783: }
784: if (!shm_data) {
785: debug_write("shm_data == null");
786: graceful_exit(75);
787: }
788:
789: (*shm_dg_data) = (struct dg_shm *)shm_data;
790: (*shm_dg_game) = (struct dg_shm_game *)((*shm_dg_data) + sizeof(struct dg_shm));
791:
792: if (!shm_data_existed && shm_data) {
793: memset(*shm_dg_game, 0, shm_n_games*sizeof(struct dg_shm_game));
794: (*shm_dg_data)->max_n_games = shm_n_games;
795: (*shm_dg_data)->cur_n_games = 0;
796: if (sem_init(&((*shm_dg_data)->dg_sem), 1,1) == -1) {
797: debug_write("sem_init");
798: graceful_exit(76);
799: }
800: }
801: #endif /* USE_SHMEM */
802: }
803:
804: #ifdef USE_SHMEM
805: void
806: shm_dump()
807: {
808: struct dg_shm *shm_dg_data = NULL;
809: struct dg_shm_game *shm_dg_game = NULL;
810: int di, unused = -1;
811: shm_init(&shm_dg_data, &shm_dg_game);
812: shm_sem_wait(shm_dg_data);
813: for (di = 0; di < shm_dg_data->max_n_games; di++) {
814: if (shm_dg_game[di].in_use) {
815: if (unused != -1) {
816: if (unused != di-1)
817: fprintf(stderr, "%i-%i:\tunused\n", unused, di-1);
818: else
819: fprintf(stderr, "%i:\tunused\n", unused);
820: unused = -1;
821: }
822: fprintf(stderr, "%i:\t\"%s\"\twatchers:%li\n", di, shm_dg_game[di].ttyrec_fn, shm_dg_game[di].nwatchers);
823: } else {
824: if (unused == -1) unused = di;
825: }
826: }
827: if (unused != -1) {
828: if (unused != di-1)
829: fprintf(stderr, "%i-%i:\tunused\n", unused, di-1);
830: else
831: fprintf(stderr, "%i:\tunused\n", unused);
832: unused = -1;
833: }
834: shm_sem_post(shm_dg_data);
835: shmdt(shm_dg_data);
836: }
837: #endif
838:
839: static
840: struct dg_watchcols **
841: globalconfig_watch_columns()
842: {
843: if (globalconfig.n_watch_columns)
844: return globalconfig.watch_columns;
845:
846: if (!*default_watchcols_list) {
847: int i;
848: for (i = 0; i < ARRAY_SIZE(default_watchcols); ++i)
849: default_watchcols_list[i] = &default_watchcols[i];
850: }
851: return default_watchcols_list;
852: }
853:
854: static
855: int
856: watchcol_find_index(struct dg_watchcols **watchcols,
857: int sortmode)
858: {
859: int i;
860: for (i = 0; watchcols[i]; ++i)
861: if (watchcols[i]->sortmode == sortmode)
862: return i;
863: return -1;
864: }
865:
866: static
867: void
868: sortmode_increment(struct dg_watchcols **watchcols,
869: dg_sortmode *sortmode,
870: int direction)
871: {
872: int watch_column_index = watchcol_find_index(watchcols, *sortmode);
873: int n_watchcols;
874: int wrap_count = 0;
875: const dg_sortmode old_sortmode = *sortmode;
876:
877: for (n_watchcols = 0; watchcols[n_watchcols]; ++n_watchcols)
878: ;
879:
880: if (watch_column_index == -1 || !n_watchcols)
881: return;
882:
883: do {
884: watch_column_index += direction;
885:
886: if (watch_column_index < 0) {
887: watch_column_index = n_watchcols - 1;
888: ++wrap_count;
889: } else if (watch_column_index >= n_watchcols) {
890: watch_column_index = 0;
891: ++wrap_count;
892: }
893:
894: *sortmode = watchcols[watch_column_index]->sortmode;
895: } while (wrap_count < 2 && !*sortmode);
896:
897: if (!*sortmode)
898: *sortmode = old_sortmode;
899: }
900:
901: char *
902: get_timediff(time_t ctime, long seconds)
903: {
904: static char data[32];
905: long secs, mins, hours;
906:
907: secs = (ctime - seconds);
908:
909: if (showplayers) {
910: snprintf(data, 10, "%ld", secs);
911: return data;
912: }
913:
914: hours = (secs / 3600);
915: secs -= (hours * 3600);
916: mins = (secs / 60) % 60;
917: secs -= (mins*60);
918: if (hours)
919: snprintf(data, 10, "%ldh %ldm", hours, mins);
920: else if (mins)
921: snprintf(data, 10, "%ldm %lds", mins, secs);
922: else if (secs > 4)
923: snprintf(data, 30, "%lds", secs);
924: else
925: snprintf(data, 10, " ");
926: return data;
927: }
928:
929: static
930: void
931: game_get_column_data(struct dg_game *game,
932: char selectorchar,
933: time_t ctime, struct dg_shm_game *shm_dg_game,
934: char *data, int bufsz, int *hilite,
935: dg_sortmode which_data)
936: {
937: *data = 0;
938:
939: switch (which_data) {
940: default: break;
941: case SORTMODE_NONE:
942: data[0] = selectorchar; data[1] = '\0';
943: break;
944:
945: case SORTMODE_USERNAME:
946: snprintf(data, bufsz, "%s", game->name);
947: break;
948:
949: case SORTMODE_GAMENUM:
950: snprintf(data, bufsz, "%s",
951: myconfig[game->gamenum]->shortname);
952: break;
953:
954: case SORTMODE_WINDOWSIZE:
955: snprintf(data, bufsz, "%3dx%3d", game->ws_col, game->ws_row);
956: if (showplayers)
957: snprintf(data, bufsz, "%dx%d", game->ws_col, game->ws_row);
958: else
959: snprintf(data, bufsz, "%3dx%3d", game->ws_col, game->ws_row);
960: if ((game->ws_col > COLS || game->ws_row > LINES))
961: *hilite = CLR_RED;
962: break;
963:
964: case SORTMODE_STARTTIME:
965: snprintf(data, bufsz, "%s %s", game->date,
966: game->time);
967: break;
968:
969: case SORTMODE_DURATION:
970: {
971: /* TODO: populate_games() should put st_ctime into game struct */
972: struct tm timetm;
973: char tmptimebuf[32];
974: snprintf(tmptimebuf, 30, "%s %s", game->date, game->time);
975: tmptimebuf[31] = '\0';
976: strptime(tmptimebuf, "%Y-%m-%d %H:%M:%S", &timetm);
977: snprintf(data, 10, "%s", get_timediff(ctime, mktime(&timetm)));
978: }
979: break;
980:
981: case SORTMODE_IDLETIME:
982: snprintf(data, 10, "%s", get_timediff(ctime, game->idle_time));
983: break;
984:
985: case SORTMODE_EXTRA_INFO:
986: if (game->extra_info)
987: strlcpy(data, game->extra_info, bufsz);
988: break;
989:
990: #ifdef USE_SHMEM
991: case SORTMODE_WATCHERS:
992: snprintf(data, bufsz, "%li",
993: (game->is_in_shm ?
994: shm_dg_game[game->shm_idx].nwatchers : -1));
995: break;
996: #endif
997: }
998: data[bufsz - 1] = '\0';
999: }
1000:
1001: void
1002: inprogressmenu (int gameid)
1003: {
1004: const char *selectorchars = "abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ";
1005: int i, menuchoice, len = 20, offset = 0;
1006: static dg_sortmode sortmode = NUM_SORTMODES;
1007: struct dg_game **games = NULL;
1008: char ttyrecname[130], gametype[10], idletime[10];
1009: sigset_t oldmask, toblock;
1010: int idx = -1;
1011: int shm_idx = -1;
1012: int max_height = -1;
1013: int selected = -1;
1014:
1015: int resizex = -1;
1016: int resizey = -1;
1017:
1018: char *selectedgame = NULL;
1019:
1020: int abs_max_height;
1021: int top_banner_hei = 5;
1022: int btm_banner_hei = 3;
1023: int btm;
1024:
1025: int title_attr = A_STANDOUT;
1026: int selected_attr = A_BOLD;
1027:
1028: int require_enter = 0; /* TODO: make configurable */
1029:
1030: time_t ctime;
1031:
1032: struct dg_shm *shm_dg_data = NULL;
1033: struct dg_shm_game *shm_dg_game = NULL;
1034:
1035: struct dg_watchcols **watchcols = globalconfig_watch_columns();
1036: struct dg_watchcols **curr_watchcol;
1037:
1038: if (sortmode == NUM_SORTMODES)
1039: sortmode = globalconfig.sortmode;
1040:
1041: abs_max_height = strlen(selectorchars);
1042:
1043: shm_init(&shm_dg_data, &shm_dg_game);
1044:
1045: games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
1046: shm_update(shm_dg_data, games, len);
1047: games = sort_games (games, len, sortmode);
1048:
1049: while (1)
1050: {
1051: term_resize_check();
1052: max_height = dgl_local_LINES - (top_banner_hei + btm_banner_hei) - 1;
1053: if (max_height < 2) {
1054: free_populated_games(games, len);
1055: return;
1056: }
1057: if (max_height > abs_max_height) max_height = abs_max_height;
1058:
1059: if (len == 0)
1060: offset = 0;
1061:
1062: erase ();
1063: drawbanner (&banner);
1064:
1065: if (len > 0) {
1066: while (offset >= len) { offset -= max_height; }
1067: if (offset < 0) offset = 0;
1068: mvaddstr (3, 1, "The following games are in progress:");
1069:
1070: for (curr_watchcol = watchcols; *curr_watchcol; ++curr_watchcol) {
1071: struct dg_watchcols *wcol = *curr_watchcol;
1072: char *col = wcol->colname;
1073: int x = wcol->x;
1074: while (*col == ' ') { x++; col++; }
1075: if (sortmode == wcol->sortmode) attron(title_attr);
1076: mvprintw(top_banner_hei, x, col);
1077: if (sortmode == wcol->sortmode) attroff(title_attr);
1078: }
1079: }
1080:
1081: signals_block();
1082: shm_sem_wait(shm_dg_data);
1083:
1084: (void) time(&ctime);
1085:
1086: for (i = 0; i < max_height; i++)
1087: {
1088: if (i + offset >= len)
1089: break;
1090:
1091: if (i + offset == selected) attron(selected_attr);
1092:
1093: for (curr_watchcol = watchcols; *curr_watchcol; ++curr_watchcol) {
1094: struct dg_watchcols *col = *curr_watchcol;
1095: char tmpbuf[80];
1096: int hilite = 0;
1097: game_get_column_data(games[i + offset],
1098: selectorchars[i],
1099: ctime, shm_dg_game,
1100: tmpbuf, sizeof tmpbuf, &hilite,
1101: (dg_sortmode)col->dat);
1102: if (hilite) attron(hilite);
1103: mvprintw(top_banner_hei + 1 + i, col->x, col->fmt, tmpbuf);
1104: if (hilite) {
1105: attron(CLR_NORMAL);
1106: hilite = 0;
1107: }
1108: }
1109:
1110: if (i + offset == selected) attroff(selected_attr);
1111:
1112: }
1113:
1114: shm_sem_post(shm_dg_data);
1115: signals_release();
1116:
1117: btm = dgl_local_LINES-btm_banner_hei-top_banner_hei;
1118: if (len <= max_height)
1119: btm = i+1;
1120:
1121: if (len > 0) {
1122: mvprintw ((btm+top_banner_hei), 1, "(%d-%d of %d)", offset + 1, offset + i, len);
1123: mvaddstr ((btm+2+top_banner_hei), 1, "Watch which game? ('?' for help) => ");
1124: } else {
1125: mvprintw(top_banner_hei,4,"Sorry, no games available for viewing.");
1126: mvaddstr((btm+2+top_banner_hei), 1, "Press 'q' to return, or '?' for help => ");
1127: }
1128:
1129: refresh ();
1130:
1131: switch ((menuchoice = dgl_getch ()))
1132: {
1133: case KEY_DOWN:
1134: selected++;
1135: if (selected >= len) selected = 0;
1136: while (selected < offset) offset -= max_height;
1137: while (selected >= offset+max_height) offset += max_height;
1138: break;
1139: case KEY_UP:
1140: if (selected != -1) {
1141: if (selected == 0) selected = len;
1142: selected--;
1143: } else selected = len-1;
1144: while (selected < offset) offset -= max_height;
1145: while (selected >= offset+max_height) offset += max_height;
1146: break;
1147: case '*':
1148: if (len > 0) {
1149: int cnt = 20;
1150: (void) time(&ctime);
1151: do {
1152: idx = random() % len;
1153: } while ((--cnt > 0) ||
1154: !((games[idx]->ws_col <= COLS) &&
1155: (games[idx]->ws_row <= LINES) &&
1156: ((ctime - games[idx]->idle_time) < 15)));
1157: selected = idx;
1158: goto watchgame;
1159: }
1160: break;
1161: case '?':
1162: (void) runmenuloop(dgl_find_menu("watchmenu_help"));
1163: break;
1164: case '/':
1165: {
1166: int match = -1;
1167: int firstmatch = -1;
1168: int nmatches = 0;
1169: char findname[DGL_PLAYERNAMELEN+1];
1170: if (len <= 0) break;
1171: findname[0] = '\0';
1172: mvprintw ((btm+2+top_banner_hei), 1, "Watch which player? => "); /* stupid... */
1173: mvaddstr ((btm+2+top_banner_hei), 1, "Watch which player? => ");
1174: if ((mygetnstr(findname, DGL_PLAYERNAMELEN, 1) == OK) && (strlen(findname) > 1)) {
1175: int mlen = strlen(findname);
1176: for (i = 0; i < len; i++)
1177: if (!strncasecmp(games[i]->name, findname, mlen)) {
1178: if (firstmatch == -1) firstmatch = i;
1179: match = i;
1180: nmatches++;
1181: }
1182: if (nmatches > 1)
1183: match = firstmatch;
1184: if (match > -1) {
1185: idx = match;
1186: selected = idx;
1187: goto watchgame;
1188: }
1189: }
1190: }
1191: break;
1192: case KEY_NPAGE:
1193: case '>':
1194: if ((offset + max_height) < len) offset += max_height;
1195: break;
1196: case KEY_PPAGE:
1197: case '<':
1198: if ((offset - max_height) < 0)
1199: offset = 0;
1200: else
1201: offset -= max_height;
1202: break;
1203:
1204: case ERR:
1205: case 'q': case 'Q':
1206: case '\x1b':
1207: goto leavewatchgame;
1208: case KEY_RIGHT:
1209: case '.':
1210: sortmode_increment(watchcols, &sortmode, 1);
1211: break;
1212: case KEY_LEFT:
1213: case ',':
1214: sortmode_increment(watchcols, &sortmode, -1);
1215: break;
1216:
1217: case 12: case 18: /* ^L, ^R */
1218: if (globalconfig.utf8esc) (void) write(1, "\033%G", 3);
1219: clear ();
1220: break;
1221:
1222: case 13:
1223: case 10:
1224: case KEY_ENTER:
1225: if (selected >= 0 && selected < len) {
1226: idx = selected;
1227: goto watchgame;
1228: }
1229: break;
1230:
1231: default:
1232: if (strchr(selectorchars, menuchoice) && (len > 0)) {
1233: int sidx = strchr(selectorchars, menuchoice) - selectorchars;
1234:
1235: if ((sidx > max_height) || (sidx >= len)) {
1236: selected = -1;
1237: break;
1238: }
1239:
1240: idx = sidx + offset;
1241: if (require_enter) {
1242: if (selected == idx) selected = -1;
1243: else selected = idx;
1244: break;
1245: } else selected = idx;
1246: watchgame:
1247: if (!(idx >= 0 && idx < len && games[idx] && games[idx]->name))
1248: goto leavewatchgame;
1249: /* valid choice has been made */
1250: chosen_name = strdup (games[idx]->name);
1251: snprintf (ttyrecname, 130, "%s",
1252: games[idx]->ttyrec_fn);
1253:
1254: clear ();
1255: refresh ();
1256: endwin ();
1257: if (globalconfig.utf8esc) (void) write(1, "\033%G", 3);
1258: #ifdef USE_SHMEM
1259: signals_block();
1260: if (games[idx]->is_in_shm) {
1261: shm_idx = games[idx]->shm_idx;
1262: shm_sem_wait(shm_dg_data);
1263: if (shm_dg_game[shm_idx].in_use &&
1264: !strcmp(shm_dg_game[shm_idx].ttyrec_fn, games[idx]->ttyrec_fn)) {
1265: shm_dg_game[shm_idx].nwatchers++;
1266: games[idx]->nwatchers++;
1267: }
1268: hup_shm_idx = shm_idx;
1269: hup_shm_ttyrec_fn = strdup(games[idx]->ttyrec_fn);
1270: shm_sem_post(shm_dg_data);
1271: }
1272: signals_release();
1273: #endif
1274: resizey = games[idx]->ws_row;
1275: resizex = games[idx]->ws_col;
1276: if (loggedin)
1277: setproctitle("%s [watching %s]", me->username, chosen_name);
1278: else
1279: setproctitle("<Anonymous> [watching %s]", chosen_name);
1280: ttyplay_main (ttyrecname, 1, resizex, resizey);
1281: if (loggedin)
1282: setproctitle("%s", me->username);
1283: else
1284: setproctitle("<Anonymous>");
1285: #ifdef USE_SHMEM
1286: signals_block();
1287: if (games[idx]->is_in_shm) {
1288: hup_shm_idx = -1;
1289: free(hup_shm_ttyrec_fn);
1290: shm_sem_wait(shm_dg_data);
1291: if (shm_dg_game[shm_idx].in_use &&
1292: !strcmp(shm_dg_game[shm_idx].ttyrec_fn, games[idx]->ttyrec_fn) &&
1293: (shm_dg_game[shm_idx].nwatchers > 0)) {
1294: shm_dg_game[shm_idx].nwatchers--;
1295: games[idx]->nwatchers--;
1296: }
1297: shm_sem_post(shm_dg_data);
1298: }
1299: signals_release();
1300: #endif
1301: initcurses ();
1302: redrawwin(stdscr);
1303: }
1304: }
1305:
1306: if (selected >= 0 && selected < len)
1307: selectedgame = strdup(games[selected]->name);
1308: games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
1309: shm_update(shm_dg_data, games, len);
1310: games = sort_games (games, len, sortmode);
1311: if (selectedgame) {
1312: selected = -1;
1313: for (i = 0; i < len; i++)
1314: if (!strcmp(games[i]->name, selectedgame)) {
1315: selected = i;
1316: break;
1317: }
1318: free(selectedgame);
1319: selectedgame = NULL;
1320: }
1321: }
1322: leavewatchgame:
1323: free_populated_games(games, len);
1324: #ifdef USE_SHMEM
1325: shmdt(shm_dg_data);
1326: #endif
1327: }
1328:
1329: void
1330: inprogressdisplay (int gameid)
1331: {
1332: const char *selectorchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ";
1333: int i, len = 20;
1334: static dg_sortmode sortmode = NUM_SORTMODES;
1335: struct dg_game **games = NULL;
1336: int shm_idx = -1;
1337:
1338: time_t ctime;
1339:
1340: struct dg_shm *shm_dg_data = NULL;
1341: struct dg_shm_game *shm_dg_game = NULL;
1342:
1343: struct dg_watchcols **watchcols = globalconfig_watch_columns();
1344: struct dg_watchcols **curr_watchcol;
1345:
1346: if (sortmode == NUM_SORTMODES)
1347: sortmode = globalconfig.sortmode;
1348:
1349: shm_init(&shm_dg_data, &shm_dg_game);
1350:
1351: games = populate_games (gameid, &len, NULL); /* FIXME: should be 'me' instead of 'NULL' */
1352: shm_update(shm_dg_data, games, len);
1353: games = sort_games (games, len, sortmode);
1354:
1355: signals_block();
1356: shm_sem_wait(shm_dg_data);
1357:
1358: (void) time(&ctime);
1359:
1360: for (i = 0; i < 100; i++) {
1361: if (i >= len)
1362: break;
1363:
1364: for (curr_watchcol = watchcols; *curr_watchcol; ++curr_watchcol) {
1365: struct dg_watchcols *col = *curr_watchcol;
1366: if ((dg_sortmode)col->dat == SORTMODE_NONE)
1367: continue;
1368: char tmpbuf[80];
1369: int hilite = 0;
1370: game_get_column_data(games[i],
1371: selectorchars[i],
1372: ctime, shm_dg_game,
1373: tmpbuf, sizeof tmpbuf, &hilite, (dg_sortmode)col->dat);
1374: fprintf(stdout, "%s#", tmpbuf); /* format in col->fmt */
1375: }
1376: fprintf(stdout, "\n");
1377: }
1378:
1379: shm_sem_post(shm_dg_data);
1380: signals_release();
1381:
1382: free_populated_games(games, len);
1383:
1384: #ifdef USE_SHMEM
1385: shmdt(shm_dg_data);
1386: #endif
1387: }
1388:
1389: /* ************************************************************* */
1390:
1391: /*
1392: * Check email address, returns 1 if valid, 0 otherwise.
1393: * Doesn't recognize addresses with parts in double-quotes.
1394: * Addresses with a colon in them are always rejected.
1395: */
1396: int
1397: check_email (char *s)
1398: {
1399: char *atomchars = "!#$%&'*+-/=?^_`{|}~" "0123456789"
1400: "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1401: int f;
1402:
1403: if (*s == '@')
1404: return 0;
1405:
1406: while (*s != '\0' && *s != '@')
1407: {
1408: if (strchr(atomchars, *s) == NULL)
1409: return 0;
1410: s++;
1411: if (*s == '.')
1412: s++;
1413: }
1414:
1415: if (*s == '\0')
1416: return 0;
1417: s++;
1418:
1419: f = 0;
1420: while (*s != '\0')
1421: {
1422: if (strchr(atomchars, *s) == NULL)
1423: return 0;
1424: s++;
1425: if (*s == '.')
1426: s++, f = 1;
1427: }
1428:
1429: return f;
1430: }
1431:
1432: void
1433: change_email ()
1434: {
1435: char buf[81];
1436:
1437: clear();
1438:
1439: if (me->flags & DGLACCT_EMAIL_LOCK) {
1440: drawbanner(&banner);
1441: mvprintw(5, 1, "Sorry, you cannot change the email.--More--");
1442: dgl_getch();
1443: return;
1444: }
1445:
1446: for (;;)
1447: {
1448: drawbanner(&banner);
1449:
1450: mvprintw(3, 1, "Your current email is: %s", me->email);
1451: mvaddstr(4, 1, "Please enter a new one (max 80 chars; blank line aborts)");
1452: mvaddstr(6, 1, "=> ");
1453:
1454: if (mygetnstr (buf, 80, 1) != OK)
1455: return;
1456:
1457: if (*buf == '\0')
1458: return;
1459: else if (!strcmp(me->email, buf))
1460: {
1461: clear();
1462: mvaddstr (8, 1, "That's the same one as before. Try again?");
1463: move(1,1);
1464: }
1465: else if (check_email (buf))
1466: {
1467: mvprintw (8, 1, "Changing email address to '%s'. Confirm (y/n): ", buf);
1468: if (dgl_getch() == 'y')
1469: {
1470: free(me->email);
1471: me->email = strdup(buf);
1472: writefile(0);
1473: return;
1474: }
1475: else
1476: {
1477: mvaddstr(9, 1, "No changes made. Press any key to continue...");
1478: dgl_getch();
1479: return;
1480: }
1481: }
1482: else
1483: {
1484: clear();
1485: mvaddstr (8, 1, "That doesn't look like an email address to me.");
1486: move(1,1);
1487: }
1488: }
1489: }
1490:
1491: int
1492: changepw (int dowrite)
1493: {
1494: char buf[DGL_PASSWDLEN+1];
1495: int error = 2;
1496:
1497: /* A precondition is that struct `me' exists because we can be not-yet-logged-in. */
1498: if (!me) {
1499: debug_write("no 'me' in changepw");
1500: graceful_exit (122); /* Die. */
1501: }
1502:
1503: if (me->flags & DGLACCT_PASSWD_LOCK) {
1504: clear();
1505: drawbanner(&banner);
1506: mvprintw(5, 1, "Sorry, you cannot change the password.--More--");
1507: dgl_getch();
1508: return 0;
1509: }
1510:
1511: while (error)
1512: {
1513: char repeatbuf[DGL_PASSWDLEN+1];
1514: clear ();
1515:
1516: drawbanner (&banner);
1517:
1518: mvprintw (5, 1,
1519: "Please enter a%s password. Remember that this is sent over the net",
1520: loggedin ? " new" : "");
1521: mvaddstr (6, 1,
1522: "in plaintext, so make it something new and expect it to be relatively");
1523: mvaddstr (7, 1, "insecure.");
1524: mvprintw (8, 1,
1525: "%i character max. No ':' characters. Blank line to abort.", DGL_PASSWDLEN);
1526: mvaddstr (10, 1, "=> ");
1527:
1528: if (error == 1)
1529: {
1530: mvaddstr (15, 1, "Sorry, the passwords don't match. Try again.");
1531: move (10, 4);
1532: }
1533:
1534: refresh ();
1535:
1536: if (mygetnstr (buf, DGL_PASSWDLEN, 0) != OK)
1537: return 0;
1538:
1539: if (*buf == '\0')
1540: return 0;
1541:
1542: if (strchr (buf, ':') != NULL) {
1543: debug_write("cannot have ':' in passwd");
1544: graceful_exit (112);
1545: }
1546:
1547: mvaddstr (12, 1, "And again:");
1548: mvaddstr (13, 1, "=> ");
1549:
1550: if (mygetnstr (repeatbuf, DGL_PASSWDLEN, 0) != OK)
1551: return 0;
1552:
1553: if (!strcmp (buf, repeatbuf))
1554: error = 0;
1555: else
1556: error = 1;
1557: }
1558:
1559: free(me->password);
1.2 rubenllo 1560: #if defined(__OpenBSD__)
1561: me->password = (char *) malloc((_PASSWORD_LEN+1)*sizeof(char));
1562: if ( crypt_newhash(buf, "blowfish,12", me->password, _PASSWORD_LEN) != 0)
1563: graceful_exit(1);
1564: #else
1.1 rubenllo 1565: me->password = strdup (crypt (buf, buf));
1.2 rubenllo 1566: #endif
1.1 rubenllo 1567:
1568: if (dowrite)
1569: writefile (0);
1570:
1571: return 1;
1572: }
1573:
1574: /* ************************************************************* */
1575:
1576: void
1577: wall_email(char *from, char *msg)
1578: {
1579: int len, i;
1580: struct dg_game **games = NULL;
1581: char spool_fn[1024+1];
1582: FILE *user_spool = NULL;
1583: struct flock fl = { 0 };
1584:
1585: fl.l_type = F_WRLCK;
1586: fl.l_whence = SEEK_SET;
1587: fl.l_start = 0;
1588: fl.l_len = 0;
1589:
1590: if (!from || !msg) return;
1591:
1592: if (strlen(from) < 1) {
1593: fprintf(stderr, "Error: wall: 'from' username is too short!\n");
1594: debug_write("wall: 'from' username too short");
1595: graceful_exit(121);
1596: }
1597:
1598: if (strlen(msg) >= DGL_MAILMSGLEN) {
1599: fprintf(stderr, "Error: wall: message too long!\n");
1600: debug_write("wall: message too long");
1601: graceful_exit(120);
1602: }
1603:
1604: games = populate_games(-1, &len, me);
1605:
1606: if (len == 0) {
1607: fprintf(stderr, "Error: wall: no one's logged in!\n");
1608: debug_write("wall: no people playing");
1609: graceful_exit(118);
1610: }
1611:
1612: for (i = 0; i < len; i++) {
1613: int game = games[i]->gamenum;
1614: int fnamelen;
1615: if (strlen(myconfig[game]->spool) < 1) continue;
1616:
1617: snprintf (spool_fn, 1024, "%s/%s", myconfig[game]->spool, games[i]->name);
1618:
1619: if ((user_spool = fopen (spool_fn, "a")) == NULL) continue;
1620:
1621: while (fcntl(fileno(user_spool), F_SETLK, &fl) == -1) {
1622: if (errno != EAGAIN) continue;
1623: sleep (1);
1624: }
1625: fprintf(user_spool, "%s:%s\n", from, msg);
1626: fclose(user_spool);
1627: }
1628: free_populated_games(games, len);
1629: }
1630:
1631: void
1632: domailuser (char *username)
1633: {
1634: unsigned int len, i;
1635: char *spool_fn, message[DGL_MAILMSGLEN+1];
1636: FILE *user_spool = NULL;
1637: time_t now;
1638: int mail_empty = 1;
1639: int game;
1640: struct flock fl = { 0 };
1641:
1642: fl.l_type = F_WRLCK;
1643: fl.l_whence = SEEK_SET;
1644: fl.l_start = 0;
1645: fl.l_len = 0;
1646:
1647: assert (loggedin);
1648:
1649: game = 0; /*TODO: find_curr_player_game(username) */
1650:
1651: if (strlen(myconfig[game]->spool) < 1) return;
1652:
1653: len = strlen(myconfig[game]->spool) + strlen (username) + 1;
1654: spool_fn = malloc (len + 1);
1655: time (&now);
1656: snprintf (spool_fn, len + 1, "%s/%s", myconfig[game]->spool, username);
1657:
1658: /* print the enter your message line */
1659: clear ();
1660: drawbanner (&banner);
1661: mvprintw (5, 1,
1662: "Enter your message here. It is to be one line only and %i characters or less.",
1663: DGL_MAILMSGLEN);
1664: mvaddstr (7, 1, "=> ");
1665:
1666: if (mygetnstr (message, DGL_MAILMSGLEN, 1) != OK)
1667: return;
1668:
1669: for (i = 0; i < strlen (message); i++)
1670: {
1671: if (message[i] != ' ' && message[i] != '\n' && message[i] != '\t')
1672: mail_empty = 0;
1673: }
1674:
1675: if (mail_empty)
1676: {
1677: mvaddstr (9, 1, "This scroll appears to be blank.");
1678: mvaddstr (10, 1, "(Aborting your message.)");
1679: mvaddstr (12, 1, "--More--");
1680: dgl_getch ();
1681: return;
1682: }
1683:
1684: if ((user_spool = fopen (spool_fn, "a")) == NULL)
1685: {
1686: mvaddstr (9, 1,
1687: "You fall into the water! You sink like a rock.");
1688: mvprintw (10, 1,
1689: "(Couldn't open %s'%c spool file. Aborting.)",
1690: username, (username[strlen (username) - 1] != 's') ? 's' : 0);
1691: mvaddstr (12, 1, "--More--");
1692: dgl_getch ();
1693: return;
1694: }
1695:
1696: mvaddstr (9, 1, "Sending your scroll...");
1697: refresh ();
1698:
1699: /* Getting a lock on the mailspool... */
1700: while (fcntl (fileno (user_spool), F_SETLK, &fl) == -1)
1701: {
1702: if (errno != EAGAIN)
1703: {
1704: mvaddstr (10, 1,
1705: "(Received a weird error from fcntl. Aborting.)");
1706: mvaddstr (12, 1, "--More--");
1707: dgl_getch ();
1708: return;
1709: }
1710: sleep (1);
1711: }
1712:
1713: fprintf (user_spool, "%s:%s\n", me->username, message);
1714:
1715: /*
1716: * Don't unlock the file ourselves, this way it will be done automatically
1717: * after all data has been written. (Using file locking with stdio is icky.)
1718: */
1719:
1720: fclose (user_spool);
1721:
1722: mvaddstr (9, 1, "Scroll delivered! ");
1723: move(9, 19); /* Pedantry! */
1724: refresh ();
1725: sleep (2);
1726:
1727: return;
1728: }
1729:
1730:
1731: /* ************************************************************* */
1732:
1733: void
1734: freefile ()
1735: {
1736: #ifndef USE_SQLITE3
1737: int i;
1738:
1739: /* free existing mem, clear existing entries */
1740: for (i = 0; i < f_num; i++)
1741: {
1742: if (users[i] != me)
1743: {
1744: free (users[i]->password);
1745: free (users[i]->username);
1746: free (users[i]->email);
1747: free (users[i]->env);
1748: free (users[i]);
1749: }
1750: }
1751:
1752: if (users)
1753: free (users);
1754:
1755: users = NULL;
1756: f_num = 0;
1757: #endif
1758: }
1759:
1760: /* ************************************************************* */
1761:
1762: void
1763: initcurses ()
1764: {
1765: printf("\033[2J");
1766: if (newterm(NULL, stdout, stdin) == NULL) {
1767: if (!globalconfig.defterm || (newterm(globalconfig.defterm, stdout, stdin) == NULL)) {
1768: debug_write("cannot create newterm");
1769: graceful_exit(60);
1770: }
1771: mysetenv("TERM", globalconfig.defterm, 1);
1772: }
1773: cbreak ();
1774: noecho ();
1775: nonl ();
1776: intrflush (stdscr, FALSE);
1777: keypad (stdscr, TRUE);
1778: #ifdef USE_NCURSES_COLOR
1779: start_color();
1780: use_default_colors();
1781:
1782: init_pair(COLOR_BLACK, COLOR_WHITE, COLOR_BLACK);
1783: init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
1784: init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
1785: init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
1786: init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
1787: init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
1788: init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
1789: init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
1790: init_pair(9, 0, COLOR_BLACK);
1791: init_pair(10, COLOR_BLACK, COLOR_BLACK);
1792: init_pair(11, -1, -1);
1793:
1794: if (globalconfig.utf8esc) (void) write(1, "\033%G", 3);
1795: #endif
1796: clear();
1797: refresh();
1798: }
1799:
1800: /* ************************************************************* */
1801:
1802: void
1803: autologin (char* user, char *pass)
1804: {
1805: struct dg_user *tmp;
1806: tmp = userexist(user, 0);
1807: if (tmp) {
1808: me = cpy_me(tmp);
1809: if ((passwordgood(pass) || initplayer == 1) && !(me->flags & DGLACCT_LOGIN_LOCK)) {
1810: loggedin = 1;
1811: setproctitle ("%s", me->username);
1812: dgl_exec_cmdqueue(globalconfig.cmdqueue[DGLTIME_LOGIN], 0, me);
1813: }
1814: }
1815: }
1816:
1817: void
1818: loginprompt (int from_ttyplay)
1819: {
1820: char user_buf[DGL_PLAYERNAMELEN+1], pw_buf[DGL_PASSWDLEN+2];
1821: int error = 2;
1822:
1823: loggedin = 0;
1824:
1825: while (error)
1826: {
1827: clear ();
1828:
1829: drawbanner (&banner);
1830:
1831: if (from_ttyplay == 1)
1832: mvaddstr (4, 1, "This operation requires you to be logged in.");
1833:
1834: mvaddstr (5, 1,
1835: "Please enter your username. (blank entry aborts)");
1836: mvaddstr (7, 1, "=> ");
1837:
1838: if (error == 1)
1839: {
1840: mvaddstr (9, 1, "There was a problem with your last entry.");
1841: move (7, 4);
1842: }
1843:
1844: refresh ();
1845:
1846: if (mygetnstr (user_buf, DGL_PLAYERNAMELEN, 1) != OK)
1847: return;
1848:
1849: if (*user_buf == '\0')
1850: return;
1851:
1852: error = 1;
1853:
1854: {
1855: struct dg_user *tmpme;
1856: if ((tmpme = userexist(user_buf, 0))) {
1857: me = cpy_me(tmpme);
1858: error = 0;
1859: }
1860: }
1861: }
1862:
1863: clear ();
1864:
1865: drawbanner (&banner);
1866:
1867: mvaddstr (5, 1, "Please enter your password.");
1868: mvaddstr (7, 1, "=> ");
1869:
1870: refresh ();
1871:
1872: if (mygetnstr (pw_buf, DGL_PASSWDLEN, 0) != OK)
1873: return;
1874:
1875: if (passwordgood (pw_buf))
1876: {
1877: if (me->flags & DGLACCT_LOGIN_LOCK) {
1878: clear ();
1879: mvprintw(5, 1, "Sorry, that account has been banned.--More--");
1880: dgl_getch();
1881: return;
1882: }
1883:
1884: loggedin = 1;
1885: if (from_ttyplay)
1886: setproctitle("%s [watching %s]", me->username, chosen_name);
1887: else
1888: setproctitle("%s", me->username);
1889: dgl_exec_cmdqueue(globalconfig.cmdqueue[DGLTIME_LOGIN], 0, me);
1890: }
1891: else
1892: {
1893: me = NULL;
1894: if (from_ttyplay == 1)
1895: {
1896: mvaddstr(9, 1, "Login failed. Returning to game.");
1897: refresh();
1898: sleep(2);
1899: }
1900: }
1901: }
1902:
1903: /* ************************************************************* */
1904:
1905: void
1906: newuser ()
1907: {
1908: char buf[1024], dirname[100];
1909: int error = 2;
1910: unsigned int i;
1911:
1912: loggedin = 0;
1913:
1914: #ifndef USE_SQLITE3
1915: if (f_num >= globalconfig.max)
1916: {
1917: clear ();
1918:
1919: drawbanner (&banner);
1920:
1921: mvaddstr (5, 1, "Sorry, too many users have registered now.");
1922: mvaddstr (6, 1, "You might email the server administrator.");
1923: mvaddstr (7, 1, "Press return to return to the menu. ");
1924: dgl_getch ();
1925:
1926: return;
1927: }
1928: #endif
1929:
1930: if (me)
1931: free (me);
1932:
1933: me = calloc (1, sizeof (struct dg_user));
1934:
1935: while (error)
1936: {
1937: clear ();
1938:
1939: sprintf(buf, "%i character max.", globalconfig.max_newnick_len);
1940:
1941: drawbanner (&banner);
1942:
1943: mvaddstr (5, 1, "Welcome new user. Please enter a username.");
1944: mvaddstr (6, 1,
1945: "Only characters and numbers are allowed, with no spaces.");
1946: mvaddstr (7, 1, buf);
1947: mvaddstr (9, 1, "=> ");
1948:
1949: if (error == 1)
1950: {
1951: mvaddstr (11, 1, "There was a problem with your last entry.");
1952: move (9, 4);
1953: }
1954:
1955: refresh ();
1956:
1957: if (mygetnstr (buf, globalconfig.max_newnick_len, 1) != OK)
1958: buf[0] = 0;
1959:
1960: if (*buf == '\0') {
1961: free(me);
1962: me = NULL;
1963: return;
1964: }
1965:
1966: if (!userexist(buf, 1)) {
1967: error = 0;
1968: } else
1969: error = 1;
1970:
1971: for (i = 0; i < strlen (buf); i++)
1972: {
1973: if (!isalnum((int)buf[i]))
1974: error = 1;
1975: }
1976:
1977: if (strlen (buf) < 2)
1978: error = 1;
1979:
1980: if (strlen (buf) == 0)
1981: {
1982: free(me);
1983: me = NULL;
1984: return;
1985: }
1986: }
1987:
1988: me->username = strdup (buf);
1989:
1990: /* password step */
1991:
1992: clear ();
1993:
1994: if (!changepw (0)) /* Calling changepw instead to prompt twice. */
1995: {
1996: free(me->username);
1997: free(me);
1998: me = NULL;
1999: return;
2000: }
2001:
2002: /* email step */
2003:
2004: error = 2;
2005: while (error != 0)
2006: {
2007: clear ();
2008:
2009: drawbanner (&banner);
2010:
2011: mvaddstr (5, 1, "Please enter your email address.");
2012: mvaddstr (6, 1, "This is sent _nowhere_ but will be used if you ask"
2013: " the sysadmin for lost");
2014: mvaddstr (7, 1, "password help. Please use a correct one. It only"
2015: " benefits you.");
2016: mvaddstr (8, 1, "80 character max. No ':' characters. Blank line"
2017: " aborts.");
2018: mvaddstr (10, 1, "=> ");
2019:
2020: if (error == 1)
2021: {
2022: mvaddstr (12, 1, "There was a problem with your last entry.");
2023: move (10, 4);
2024: }
2025:
2026: refresh ();
2027: if (mygetnstr (buf, 80, 1) != OK)
2028: buf[0] = 0;
2029:
2030: if (check_email (buf))
2031: error = 0;
2032: else
2033: error = 1;
2034:
2035: if (*buf == '\0')
2036: {
2037: free (me->username);
2038: free (me->password);
2039: free (me);
2040: me = NULL;
2041: return;
2042: }
2043: }
2044:
2045: me->email = strdup (buf);
2046: me->env = calloc (1, 1);
2047: me->flags = 0;
2048:
2049: loggedin = 1;
2050:
2051: setproctitle ("%s", me->username);
2052:
2053: dgl_exec_cmdqueue(globalconfig.cmdqueue[DGLTIME_REGISTER], 0, me);
2054:
2055: writefile (1);
2056: }
2057:
2058: /* ************************************************************* */
2059:
2060: int
2061: passwordgood (char *cpw)
2062: {
1.2 rubenllo 2063: #if defined(__OpenBSD__)
2064: if ( crypt_checkpass(cpw, me->password) == 0)
2065: return 1;
2066: return 0;
2067: #else
1.1 rubenllo 2068: char *crypted;
2069: assert (me != NULL);
2070:
2071: crypted = crypt (cpw, cpw);
2072: if (crypted == NULL)
2073: return 0;
2074: if (!strncmp (crypted, me->password, DGL_PASSWDLEN))
2075: return 1;
2076: if (!strncmp (cpw, me->password, DGL_PASSWDLEN))
2077: return 1;
2078:
2079: return 0;
1.2 rubenllo 2080: #endif
1.1 rubenllo 2081: }
2082:
2083: /* ************************************************************* */
2084:
2085: int
2086: readfile (int nolock)
2087: {
2088: #ifndef USE_SQLITE3
2089: FILE *fp = NULL, *fpl = NULL;
2090: char buf[1200];
2091: struct flock fl = { 0 };
2092:
2093: fl.l_type = F_RDLCK;
2094: fl.l_whence = SEEK_SET;
2095: fl.l_start = 0;
2096: fl.l_len = 0;
2097:
2098: memset (buf, 0, 1024);
2099:
2100: /* read new stuff */
2101:
2102: if (!nolock)
2103: {
2104: fpl = fopen (globalconfig.lockfile, "r");
2105: if (!fpl) {
2106: debug_write("cannot fopen lockfile");
2107: graceful_exit (106);
2108: }
2109: if (fcntl (fileno (fpl), F_SETLKW, &fl) == -1) {
2110: debug_write("cannot fcntl lockfile");
2111: graceful_exit (95);
2112: }
2113: }
2114:
2115: fp = fopen (globalconfig.passwd, "r");
2116: if (!fp) {
2117: debug_write("cannot fopen passwd file");
2118: graceful_exit (106);
2119: }
2120:
2121: /* once per name in the file */
2122: while (fgets (buf, 1200, fp))
2123: {
2124: char *b = buf, *n = buf;
2125:
2126: users = realloc (users, sizeof (struct dg_user *) * (f_num + 1));
2127: users[f_num] = malloc (sizeof (struct dg_user));
2128: users[f_num]->username = (char *) calloc (DGL_PLAYERNAMELEN+2, sizeof (char));
2129: users[f_num]->email = (char *) calloc (82, sizeof (char));
2130: users[f_num]->password = (char *) calloc (DGL_PASSWDLEN+2, sizeof (char));
2131: users[f_num]->env = (char *) calloc (1026, sizeof (char));
2132:
2133: /* name field, must be valid */
2134: while (*b != ':')
2135: {
2136: if (!isalnum((int)*b))
2137: return 1;
2138: users[f_num]->username[(b - n)] = *b;
2139: b++;
2140: if ((b - n) >= DGL_PLAYERNAMELEN) {
2141: debug_write("name field too long");
2142: graceful_exit (100);
2143: }
2144: }
2145:
2146: /* advance to next field */
2147: n = b + 1;
2148: b = n;
2149:
2150: /* email field */
2151: while (*b != ':')
2152: {
2153: users[f_num]->email[(b - n)] = *b;
2154: b++;
2155: if ((b - n) > 80) {
2156: debug_write("email field too long");
2157: graceful_exit (101);
2158: }
2159: }
2160:
2161: /* advance to next field */
2162: n = b + 1;
2163: b = n;
2164:
2165: /* pw field */
2166: while (*b != ':')
2167: {
2168: users[f_num]->password[(b - n)] = *b;
2169: b++;
2170: if ((b - n) >= DGL_PASSWDLEN) {
2171: debug_write("passwd field too long");
2172: graceful_exit (102);
2173: }
2174: }
2175:
2176: /* advance to next field */
2177: n = b + 1;
2178: b = n;
2179:
2180: /* env field */
2181: while ((*b != '\n') && (*b != 0) && (*b != EOF))
2182: {
2183: users[f_num]->env[(b - n)] = *b;
2184: b++;
2185: if ((b - n) >= 1024) {
2186: debug_write("env field too long");
2187: graceful_exit (103);
2188: }
2189: }
2190:
2191: f_num++;
2192: /* prevent a buffer overrun here */
2193: if (f_num > globalconfig.max)
2194: {
2195: fprintf(stderr,"ERROR: number of users in database exceeds maximum. Exiting.\n");
2196: debug_write("too many users in database");
2197: graceful_exit (109);
2198: }
2199: }
2200:
2201: if (!nolock)
2202: fclose (fpl);
2203: fclose (fp);
2204: #endif
2205: return 0;
2206: }
2207:
2208: /* ************************************************************* */
2209:
2210: #ifndef USE_SQLITE3
2211: struct dg_user *userexist_tmp_me = NULL;
2212:
2213: struct dg_user *
2214: userexist (char *cname, int isnew)
2215: {
2216: int i;
2217:
2218: if (userexist_tmp_me) {
2219: free(userexist_tmp_me->username);
2220: free(userexist_tmp_me->email);
2221: free(userexist_tmp_me->env);
2222: free(userexist_tmp_me->password);
2223: free(userexist_tmp_me);
2224: userexist_tmp_me = NULL;
2225: }
2226:
2227: for (i = 0; i < f_num; i++)
2228: {
2229: if (!strncasecmp (cname, users[i]->username, (isnew ? globalconfig.max_newnick_len : DGL_PLAYERNAMELEN))) {
2230: userexist_tmp_me = cpy_me(users[i]);
2231: return userexist_tmp_me;
2232: }
2233: }
2234:
2235: return NULL;
2236: }
2237: #else
2238:
2239: struct dg_user *userexist_tmp_me = NULL;
2240:
2241: static int
2242: userexist_callback(void *NotUsed, int argc, char **argv, char **colname)
2243: {
2244: int i;
2245: NotUsed = NULL;
2246:
2247: userexist_tmp_me = malloc(sizeof(struct dg_user));
2248:
2249: for (i = 0; i < argc; i++) {
2250: if (!strcmp(colname[i], "username"))
2251: userexist_tmp_me->username = strdup(argv[i]);
2252: else if (!strcmp(colname[i], "email"))
2253: userexist_tmp_me->email = strdup(argv[i]);
2254: else if (!strcmp(colname[i], "env"))
2255: userexist_tmp_me->env = strdup(argv[i]);
2256: else if (!strcmp(colname[i], "password"))
2257: userexist_tmp_me->password = strdup(argv[i]);
2258: else if (!strcmp(colname[i], "flags"))
2259: userexist_tmp_me->flags = atoi(argv[i]);
2260: else if (!strcmp(colname[i], "id"))
2261: userexist_tmp_me->id = atoi(argv[i]);
2262: }
2263: return 0;
2264: }
2265:
2266: struct dg_user *
2267: userexist (char *cname, int isnew)
2268: {
2269: sqlite3 *db;
2270: char *errmsg = NULL;
2271: int ret, retry = 10;
2272:
2273: char *qbuf;
2274:
2275: char tmpbuf[DGL_PLAYERNAMELEN+2];
2276:
2277: memset(tmpbuf, 0, DGL_PLAYERNAMELEN+2);
2278: strncpy(tmpbuf, cname, (isnew ? globalconfig.max_newnick_len : DGL_PLAYERNAMELEN));
2279:
2280: /* Check that the nick doesn't interfere with already registered nicks */
2281: if (isnew && (strlen(cname) >= globalconfig.max_newnick_len))
2282: strcat(tmpbuf, "%");
2283:
2284: qbuf = sqlite3_mprintf("select * from dglusers where username = '%q' collate nocase limit 1", tmpbuf);
2285:
2286: ret = sqlite3_open(globalconfig.passwd, &db);
2287: if (ret) {
2288: sqlite3_close(db);
2289: debug_write("sqlite3_open failed");
2290: graceful_exit(96);
2291: }
2292:
2293: if (userexist_tmp_me) {
2294: free(userexist_tmp_me->username);
2295: free(userexist_tmp_me->email);
2296: free(userexist_tmp_me->env);
2297: free(userexist_tmp_me->password);
2298: free(userexist_tmp_me);
2299: userexist_tmp_me = NULL;
2300: }
2301:
2302: sqlite3_busy_timeout(db, 10000);
2303: ret = sqlite3_exec(db, qbuf, userexist_callback, 0, &errmsg);
2304:
2305: sqlite3_free(qbuf);
2306:
2307: if (ret != SQLITE_OK) {
2308: sqlite3_close(db);
2309: debug_write("sqlite3_exec failed");
2310: graceful_exit(108);
2311: }
2312: sqlite3_close(db);
2313:
2314: return userexist_tmp_me;
2315: }
2316: #endif
2317:
2318: /* ************************************************************* */
2319:
2320: void
2321: write_canned_rcfile (int game, char *target)
2322: {
2323: FILE *canned, *newfile;
2324: char buf[1024], *rfn;
2325: size_t bytes, len;
2326:
2327: len = strlen(myconfig[game]->rcfile) + 2;
2328: rfn = malloc(len);
2329: snprintf (rfn, len, "/%s", myconfig[game]->rcfile);
2330:
2331: if (!(newfile = fopen (target, "w")))
2332: {
2333: bail:
2334: mvaddstr (13, 1,
2335: "You don't know how to write that! You write \"%s was here\" and the scroll disappears.");
2336: mvaddstr (14, 1,
2337: "(Sorry, but I couldn't open one of the config files. This is a bug.)");
2338: return;
2339: }
2340:
2341: if (!(canned = fopen (rfn, "r")))
2342: goto bail;
2343:
2344: free(rfn);
2345:
2346: while ((bytes = fread (buf, 1, 1024, canned)) > 0)
2347: {
2348: if (fwrite (buf, 1, bytes, newfile) != bytes)
2349: {
2350: if (ferror (newfile))
2351: {
2352: mvaddstr (13, 1, "Your hand slips while engraving.");
2353: mvaddstr (14, 1,
2354: "(Encountered a problem writing the new file. This is a bug.)");
2355: fclose (canned);
2356: fclose (newfile);
2357: return;
2358: }
2359: }
2360: }
2361:
2362: fclose (canned);
2363: fclose (newfile);
2364: chmod (target, default_fmode);
2365: }
2366:
2367:
2368:
2369: /* ************************************************************* */
2370:
2371: #ifndef USE_SQLITE3
2372: void
2373: writefile (int requirenew)
2374: {
2375: FILE *fp, *fpl;
2376: int i = 0;
2377: int my_done = 0;
2378: struct flock fl = { 0 };
2379:
2380: fl.l_type = F_WRLCK;
2381: fl.l_whence = SEEK_SET;
2382: fl.l_start = 0;
2383: fl.l_len = 0;
2384:
2385: signals_block();
2386:
2387: fpl = fopen (globalconfig.lockfile, "r+");
2388: if (!fpl)
2389: {
2390: signals_release();
2391: debug_write("writefile locking failed");
2392: graceful_exit (115);
2393: }
2394: if (fcntl (fileno (fpl), F_SETLK, &fl))
2395: {
2396: signals_release();
2397: debug_write("writefile fcntl failed");
2398: graceful_exit (107);
2399: }
2400:
2401: fl.l_type = F_UNLCK;
2402:
2403: freefile ();
2404: readfile (1);
2405:
2406: fp = fopen (globalconfig.passwd, "w");
2407: if (!fp)
2408: {
2409: signals_release();
2410: debug_write("passwd file fopen failed");
2411: graceful_exit (99);
2412: }
2413:
2414: for (i = 0; i < f_num; i++)
2415: {
2416: if (loggedin && !strncmp (me->username, users[i]->username, DGL_PLAYERNAMELEN))
2417: {
2418: if (requirenew)
2419: {
2420: /* this is if someone managed to register at the same time
2421: * as someone else. just die. */
2422: fclose(fp);
2423: fclose(fpl);
2424: signals_release();
2425: debug_write("two users registering at the same time");
2426: graceful_exit (111);
2427: }
2428: fprintf (fp, "%s:%s:%s:%s\n", me->username, me->email, me->password,
2429: me->env);
2430: my_done = 1;
2431: }
2432: else
2433: {
2434: fprintf (fp, "%s:%s:%s:%s\n", users[i]->username, users[i]->email,
2435: users[i]->password, users[i]->env);
2436: }
2437: }
2438: if (loggedin && !my_done)
2439: { /* new entry */
2440: if (f_num < globalconfig.max)
2441: fprintf (fp, "%s:%s:%s:%s\n", me->username, me->email, me->password,
2442: me->env);
2443: else /* Oops, someone else registered the last available slot first */
2444: {
2445: fclose(fp);
2446: fclose(fpl);
2447: signals_release();
2448: debug_write("too many users in passwd db already");
2449: graceful_exit (116);
2450: }
2451: }
2452:
2453: fclose (fp);
2454: fclose (fpl);
2455:
2456: signals_release();
2457: }
2458: #else
2459: void
2460: writefile (int requirenew)
2461: {
2462: sqlite3 *db;
2463: char *errmsg = NULL;
2464: int ret, retry = 10;
2465:
2466: char *qbuf;
2467:
2468: if (requirenew) {
2469: qbuf = sqlite3_mprintf("insert into dglusers (username, email, env, password, flags) values ('%q', '%q', '%q', '%q', %li)", me->username, me->email, me->env, me->password, me->flags);
2470: } else {
2471: qbuf = sqlite3_mprintf("update dglusers set username='%q', email='%q', env='%q', password='%q', flags=%li where id=%i", me->username, me->email, me->env, me->password, me->flags, me->id);
2472: }
2473:
2474: ret = sqlite3_open(globalconfig.passwd, &db);
2475: if (ret) {
2476: sqlite3_close(db);
2477: debug_write("writefile sqlite3_open failed");
2478: graceful_exit(97);
2479: }
2480:
2481: sqlite3_busy_timeout(db, 10000);
2482: ret = sqlite3_exec(db, qbuf, NULL, NULL, &errmsg);
2483:
2484: sqlite3_free(qbuf);
2485:
2486: if (ret != SQLITE_OK) {
2487: sqlite3_close(db);
2488: debug_write("writefile sqlite3_exec failed");
2489: graceful_exit(98);
2490: }
2491: sqlite3_close(db);
2492: }
2493: #endif
2494:
2495: /* ************************************************************* */
2496:
2497: int
2498: purge_stale_locks (int game)
2499: {
2500: DIR *pdir;
2501: struct dirent *dent;
2502: char* dir;
2503: size_t len;
2504: short firsttime = 1;
2505:
2506: dir = strdup(dgl_format_str(game, me, myconfig[game]->inprogressdir, NULL));
2507:
2508: if (!(pdir = opendir (dir))) {
2509: debug_write("purge_stale_locks dir open failed");
2510: graceful_exit (200);
2511: }
2512:
2513: free(dir);
2514:
2515: while ((dent = readdir (pdir)) != NULL)
2516: {
2517: FILE *ipfile;
2518: char *colon, *fn;
2519: char buf[16];
2520: pid_t pid;
2521: size_t len;
2522: int seconds = 0;
2523:
2524: if (!strcmp (dent->d_name, ".") || !strcmp (dent->d_name, ".."))
2525: continue;
2526:
2527: colon = strchr (dent->d_name, ':');
2528: /* should never happen */
2529: if (!colon) {
2530: debug_write("purge_stale_locks !colon");
2531: graceful_exit (201);
2532: }
2533: if (colon - dent->d_name != strlen(me->username))
2534: continue;
2535: if (strncmp (dent->d_name, me->username, colon - dent->d_name))
2536: continue;
2537:
2538: len = strlen (dent->d_name) + strlen(dgl_format_str(game, me, myconfig[game]->inprogressdir, NULL)) + 1;
2539: fn = malloc (len);
2540:
2541: snprintf (fn, len, "%s%s", dgl_format_str(game, me, myconfig[game]->inprogressdir, NULL), dent->d_name);
2542:
2543: if (!(ipfile = fopen (fn, "r"))) {
2544: debug_write("purge_stale_locks fopen inprogressdir fail");
2545: graceful_exit (202);
2546: }
2547:
2548: if (fgets (buf, 16, ipfile) == NULL) {
2549: debug_write("purge_stale_locks fgets ipfile fail");
2550: graceful_exit (203);
2551: }
2552:
2553: fclose (ipfile);
2554:
2555: if (firsttime)
2556: {
2557: clear ();
2558: drawbanner (&banner);
2559:
2560: #define HUP_WAIT 10 /* seconds before HUPPING */
2561: mvprintw (3, 1,
2562: "There are some stale %s processes, will recover in %d seconds.",
2563: myconfig[game]->game_name, HUP_WAIT);
2564: mvaddstr (4, 1,
2565: "Press a key NOW if you don't want this to happen!");
2566:
2567: move (3, 51 + strlen(myconfig[game]->game_name)); /* pedantry */
2568: halfdelay(10);
2569:
2570: for (seconds = HUP_WAIT - 1; seconds >= 0; seconds--)
2571: {
2572: if (dgl_getch() != ERR)
2573: {
2574: nocbreak(); /* leave half-delay */
2575: cbreak();
2576: return 0;
2577: }
2578: mvprintw (3, 50 + strlen(myconfig[game]->game_name), "%d%s", seconds, (seconds > 9) ? "" : " ");
2579: }
2580:
2581: nocbreak();
2582: cbreak();
2583:
2584: firsttime = 0;
2585: }
2586:
2587: clear ();
2588: refresh ();
2589:
2590: pid = atoi (buf);
2591:
2592: kill (pid, SIGHUP);
2593:
2594: errno = 0;
2595:
2596: /* Wait for it to stop running */
2597: seconds = 0;
2598: while (kill (pid, 0) == 0)
2599: {
2600: seconds++;
2601: sleep (1);
2602: if (seconds == 10)
2603: {
2604: mvprintw (3, 1,
2605: "Couldn't terminate one of your stale %s processes gracefully.", myconfig[game]->game_name);
2606: mvaddstr (4, 1, "Force its termination? [yn] ");
2607: if (tolower (dgl_getch ()) == 'y')
2608: {
2609: kill (pid, SIGTERM);
2610: break;
2611: }
2612: else
2613: {
2614: endwin ();
2615: fprintf (stderr, "Sorry, no %s for you now, please "
2616: "contact the admin.\n", myconfig[game]->game_name);
2617: debug_write("could not terminate stale processes");
2618: graceful_exit (1);
2619: }
2620: }
2621: }
2622:
2623: /* Don't remove the lock file until the process is dead. */
2624: unlink (fn);
2625: free (fn);
2626: }
2627:
2628: closedir (pdir);
2629: return 1;
2630: }
2631:
2632:
2633: int
2634: runmenuloop(struct dg_menu *menu)
2635: {
2636: struct dg_banner ban;
2637: struct dg_menuoption *tmpopt;
2638: int userchoice = 0;
2639: int doclear = 1;
2640:
2641: if (!menu) return 1;
2642:
2643: ban.lines = NULL;
2644: ban.len = 0;
2645:
2646: loadbanner(menu->banner_fn, &ban);
2647: while (1) {
2648: term_resize_check();
2649: if (doclear) {
2650: doclear = 0;
2651: if (globalconfig.utf8esc) (void) write(1, "\033%G", 3);
2652: clear();
2653: }
2654: drawbanner(&ban);
2655: if (menu->cursor_x >= 0 && menu->cursor_y >= 0)
2656: mvprintw(menu->cursor_y, menu->cursor_x, "");
2657: refresh();
2658: userchoice = dgl_getch();
2659: if (userchoice == ERR) {
2660: freebanner(&ban);
2661: return 1;
2662: }
2663: tmpopt = menu->options;
2664: while (tmpopt) {
2665: if (strchr(tmpopt->keys, userchoice)) {
2666: dgl_exec_cmdqueue(tmpopt->cmdqueue, selected_game, me);
2667: doclear = 1;
2668: break;
2669: } else {
2670: tmpopt = tmpopt->next;
2671: }
2672: }
2673:
2674: if (return_from_submenu) {
2675: freebanner(&ban);
2676: return_from_submenu = 0;
2677: return 0;
2678: }
2679:
2680: if (check_retard(0)) {
2681: freebanner(&ban);
2682: debug_write("retard");
2683: graceful_exit(119);
2684: }
2685: }
2686: }
2687:
2688: int
2689: main (int argc, char** argv)
2690: {
2691: /* for chroot and program execution */
2692: char atrcfilename[81], *p, *auth = NULL;
2693: unsigned int len;
2694: int c, i;
2695: int userchoice;
2696: char *tmp;
2697: char *wall_email_str = NULL;
2698: #ifdef USE_RLIMIT
2699: struct rlimit lim;
2700: #endif
2701:
2702: #ifndef HAVE_SETPROCTITLE
2703: /* save argc, argv */
2704: char** saved_argv;
2705: int saved_argc;
2706:
2707: saved_argc = argc;
2708:
2709: saved_argv = malloc(sizeof(char**) * (argc + 1));
2710: for (i = 0; i < argc; i++)
2711: saved_argv[i] = strdup(argv[i]);
2712: saved_argv[i] = '\0';
2713:
2714: compat_init_setproctitle(argc, argv);
2715: argv = saved_argv;
2716: #endif
2717:
2718: p = getenv("DGLAUTH");
2719:
2720: /* Linux telnetd allows importing the USER envvar via telnet,
2721: * while FreeBSD does not. FreeBSD, on the other hand, does allow
2722: * the LOGNAME envvar. Check USER first, then LOGNAME.
2723: */
2724: if (p == NULL) {
2725: p = getenv("USER");
2726: }
2727:
2728: if (p == NULL) {
2729: p = getenv("LOGNAME");
2730: }
2731:
2732: if (p && *p != '\0')
2733: auth = strdup(p);
2734: /* else auth is still NULL */
2735:
2736: /* just to be sure */
2737: unsetenv("DGLAUTH"); unsetenv("USER"); unsetenv("LOGNAME");
2738:
2739: __progname = basename(strdup(argv[0]));
2740:
2741: while ((c = getopt(argc, argv, "csqh:pi:aeW:SD")) != -1)
2742: {
2743: /* Stop processing arguments at -c, so that user-provided
2744: * commands (via ssh for example) to the dgamelaunch login
2745: * shell are ignored.
2746: */
2747: if (c == 'c') break;
2748: switch (c)
2749: {
2750: case 's':
2751: showplayers = 1; break;
2752:
2753: case 'q':
2754: silent = 1; break;
2755:
2756: case 'i':
2757: if (optarg && *optarg != '\0') {
2758: if (p && *p != '\0')
2759: *p = '\0';
2760:
2761: p = strdup(optarg);
2762: initplayer = 1;
2763:
2764: if (auth && *auth != '\0')
2765: *auth = '\0';
2766: }
2767: break;
2768:
2769: case 'W':
2770: wall_email_str = strdup(optarg);
2771: break;
2772:
2773: case 'S': /* Free the shared memory block */
2774: #ifdef USE_SHMEM
2775: if (shm_free()) {
2776: if (!silent) fprintf(stderr, "nonexistent shmem block.\n");
2777: } else {
2778: if (!silent) fprintf(stderr, "shmem block freed.\n");
2779: }
2780: #else
2781: if (!silent) fprintf(stderr, "warning: dgamelaunch was compiled without shmem.\n");
2782: #endif
2783: graceful_exit(0);
2784: break;
2785:
2786: case 'D': /* dump the shared memory block data */
2787: #ifdef USE_SHMEM
2788: shm_dump();
2789: #else
2790: if (!silent) fprintf(stderr, "warning: dgamelaunch was compiled without shmem.\n");
2791: #endif
2792: graceful_exit(0);
2793: break;
2794:
2795: default:
2796: break; /*ignore */
2797: }
2798: }
2799:
2800: while (optind < argc)
2801: {
2802: size_t len = strlen(argv[optind]);
2803: memset(argv[optind++], 0, len);
2804: }
2805: setproctitle("<Anonymous>");
2806:
2807: srand(time(0));
2808:
2809: create_config();
2810:
2811: /* signal handlers */
2812: signal (SIGHUP, catch_sighup);
2813: signal (SIGINT, catch_sighup);
2814: signal (SIGQUIT, catch_sighup);
2815: signal (SIGTERM, catch_sighup);
2816:
2817: (void) tcgetattr (0, &tt);
2818:
2819: if (!globalconfig.flowctrl) {
2820: tt.c_iflag &= ~(IXON | IXOFF | IXANY); /* Disable XON/XOFF */
2821: (void) tcsetattr(0, TCSANOW, &tt);
2822: }
2823:
2824: if (-1 == ioctl (0, TIOCGWINSZ, (char *) &win) || win.ws_row < 4 ||
2825: win.ws_col < 4) /* Rudimentary validity check */
2826: {
2827: win.ws_row = 24;
2828: win.ws_col = 80;
2829: win.ws_xpixel = win.ws_col * 8;
2830: win.ws_ypixel = win.ws_row * 8;
2831: }
2832:
2833: /* get master tty just before chroot (lives in /dev) */
2834: ttyrec_getpty ();
2835:
2836: #ifdef USE_RLIMIT
2837: #ifdef USE_RLIMIT_CORE
2838: /* enable and set core dump size */
2839: if (!getrlimit(RLIMIT_CORE, &lim)) {
2840: lim.rlim_cur = USE_RLIMIT_CORE;
2841: setrlimit(RLIMIT_CORE, &lim);
2842: }
2843: #endif
2844: #ifdef USE_RLIMIT_AS
2845: /* set maximum memory usage */
2846: if (!getrlimit(RLIMIT_AS, &lim)) {
2847: lim.rlim_cur = USE_RLIMIT_AS;
2848: setrlimit(RLIMIT_AS, &lim);
2849: }
2850: #endif
2851: #endif
2852:
2853: if (geteuid () != globalconfig.shed_uid)
2854: {
2855: /* chroot */
2856: if (chroot (globalconfig.chroot))
2857: {
2858: perror ("cannot change root directory");
2859: graceful_exit (2);
2860: }
2861:
2862: if (chdir ("/"))
2863: {
2864: perror ("cannot chdir to root directory");
2865: graceful_exit (3);
2866: }
2867:
2868: /* shed privs. this is done immediately after chroot. */
2869: if (setgroups (1, &globalconfig.shed_gid) == -1)
2870: {
2871: perror ("setgroups");
2872: graceful_exit (4);
2873: }
2874:
2875: if (setgid (globalconfig.shed_gid) == -1)
2876: {
2877: perror ("setgid");
2878: graceful_exit (5);
2879: }
2880:
2881: if (setuid (globalconfig.shed_uid) == -1)
2882: {
2883: perror ("setuid");
2884: graceful_exit (6);
2885: }
2886: }
2887:
2888: if (globalconfig.locale) {
2889: setlocale(LC_CTYPE, globalconfig.locale);
2890: }
2891:
2892: if (showplayers) {
2893: inprogressdisplay(-1);
2894: graceful_exit (0);
2895: }
2896:
2897: if (wall_email_str) {
2898: char *emailfrom = wall_email_str;
2899: char *emailmsg = strchr(wall_email_str, ':');
2900: if (!emailmsg) {
2901: debug_write("wall: no mail msg");
2902: graceful_exit(117);
2903: }
2904: *emailmsg = '\0';
2905: emailmsg++;
2906: if (emailmsg)
2907: wall_email(emailfrom, emailmsg);
2908: graceful_exit(0);
2909: }
2910:
2911: banner.len = 0;
2912: banner.lines = NULL;
2913: loadbanner(globalconfig.banner, &banner);
2914:
2915: dgl_exec_cmdqueue(globalconfig.cmdqueue[DGLTIME_DGLSTART], 0, NULL);
2916:
2917: if (initplayer) {
2918: char *user, *pass;
2919:
2920: user = strdup(p);
2921: pass = strdup(p);
2922:
2923: autologin(user, pass);
2924:
2925: if (loggedin) {
2926: dgl_exec_cmdqueue(globalconfig.cmdqueue[DGLTIME_REGISTER], 0, me);
2927: fprintf(stdout, "Setup of %s succeeded.\n", me->username);
2928: graceful_exit(0);
2929: }
2930: else {
2931: fprintf(stdout, "Setup of %s failed.\n", p);
2932: graceful_exit(10);
2933: }
2934: }
2935:
2936: /* simple login routine, uses ncurses */
2937: if (readfile (0)) {
2938: debug_write("log in fail");
2939: graceful_exit (110);
2940: }
2941:
2942: if (auth)
2943: {
2944: char *user, *pass, *p;
2945:
2946: p = strchr(auth, ':');
2947:
2948: if (p)
2949: {
2950: pass = p + 1;
2951:
2952: if (*pass != '\0')
2953: {
2954: *p = '\0';
2955: user = auth;
2956: autologin(user, pass);
2957: }
2958: }
2959: }
2960:
2961: initcurses ();
2962:
2963: g_chain_winch = signal(SIGWINCH, sigwinch_func);
2964:
2965: term_resize_check();
2966:
2967: idle_alarm_set_enabled(1);
2968:
2969: while (1) {
2970: if (runmenuloop(dgl_find_menu(get_mainmenu_name())))
2971: break;
2972: }
2973:
2974: idle_alarm_set_enabled(0);
2975:
2976: /* NOW we can safely kill this */
2977: freefile ();
2978:
2979: if (me)
2980: free (me);
2981:
2982: freebanner(&banner);
2983: banner_var_free();
2984: graceful_exit (20);
2985:
2986: return 1;
2987: }
CVSweb