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