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