comparison main.c @ 413:14b685cdbd2c

Rename files.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 24 May 2012 06:41:07 +0300
parents nnchat.c@3e64acb433e8
children 8263cb88556a
comparison
equal deleted inserted replaced
412:3e64acb433e8 413:14b685cdbd2c
1 /*
2 * NNChat - Custom chat client for NewbieNudes.com chatrooms
3 * Written by Matti 'ccr' Hämäläinen
4 * (C) Copyright 2008-2011 Tecnic Software productions (TNSP)
5 */
6 #include "util.h"
7 #include "network.h"
8 #include "th_args.h"
9 #include "th_config.h"
10 #ifdef __WIN32
11 /* Undefine because both windows.h and curses.h #define it */
12 #undef MOUSE_MOVED
13 #include <shlwapi.h>
14 #else
15 #include <sys/wait.h>
16 #endif
17 #ifdef HAVE_NCURSES_H
18 #include <ncurses.h>
19 #else
20 #include <curses.h>
21 #endif
22
23 #ifdef __WIN32
24 #define SET_CONFIG_FILE "nnchat.txt"
25 #define SET_DIR_SEPARATOR "\\"
26 #define SET_DELAY (0)
27 #else
28 #define SET_CONFIG_FILE ".nnchat"
29 #define SET_DIR_SEPARATOR "/"
30 #define SET_DELAY (5)
31 #endif
32
33 #define SET_NICK_SEPARATOR ':'
34
35 #define SET_MAX_HISTORY (16) /* Command history length */
36 #define SET_KEEPALIVE (15*60) /* Ping/keepalive period in seconds */
37 #define SET_MAX_WINDOWS (32)
38
39
40 /* Options
41 */
42 int optPort = 8005,
43 optProxyPort = 1080,
44 optProxyType = NN_PROXY_NONE;
45 int optUserColor = 0x000000;
46 char *optServer = "chat.newbienudes.com",
47 *optProxyServer = NULL,
48 *optUserName = NULL,
49 *optUserNameCmd = NULL,
50 *optUserNameEnc = NULL,
51 *optPassword = NULL,
52 *optPasswordCmd = NULL,
53 *optLogFilename = NULL,
54 *optSite = "NN",
55 *optNickSepStr = NULL;
56 char optNickSep;
57 BOOL optDaemon = FALSE;
58 FILE *optLogFile = NULL;
59 BOOL setIgnoreMode = FALSE;
60 BOOL optDebug = FALSE;
61 BOOL optLogEnable = FALSE;
62
63 nn_window_t *chatWindows[SET_MAX_WINDOWS],
64 *currWin = NULL;
65 WINDOW *mainWin = NULL,
66 *statusWin = NULL,
67 *editWin = NULL;
68
69 qlist_t *setIgnoreList = NULL,
70 *setIdleMessages = NULL;
71 nn_userhash_t *nnUsers = NULL;
72 char *setConfigFile = NULL,
73 *setBrowser = NULL;
74 cfgitem_t *cfg = NULL;
75
76
77 /* Logging mode flags
78 */
79 enum
80 {
81 LOG_FILE = 1,
82 LOG_WINDOW = 2,
83 LOG_STAMP = 4
84 };
85
86
87 /* Arguments
88 */
89 optarg_t optList[] =
90 {
91 { 0, '?', "help", "Show this help", OPT_NONE },
92 { 1, 'v', "verbose", "Be more verbose", OPT_NONE },
93 { 2, 'p', "port", "Connect to port", OPT_ARGREQ },
94 { 3, 's', "server", "Server to connect to", OPT_ARGREQ },
95 { 4, 'C', "color", "Initial color in RGB hex 000000", OPT_ARGREQ },
96 { 5, 'l', "logfile", "Log filename", OPT_ARGREQ },
97 { 6, 'D', "daemon", "A pseudo-daemon mode for logging", OPT_NONE },
98 { 7, 'f', "force-site", "Force site (default: NN)", OPT_ARGREQ },
99 { 8, 'd', "debug", "Enable various debug features", OPT_NONE },
100
101 {10, '4', "socks4", "SOCKS4 proxy server", OPT_ARGREQ },
102 {11, 'A', "socks4a", "SOCKS4A proxy server", OPT_ARGREQ },
103 {12, 'P', "proxy-port", "Proxy port (default: 1080)", OPT_ARGREQ },
104 };
105
106 const int optListN = (sizeof(optList) / sizeof(optList[0]));
107
108
109 void argShowHelp(void)
110 {
111 th_print_banner(stdout, th_prog_name,
112 "[options] <username> <password>");
113
114 th_args_help(stdout, optList, optListN);
115 }
116
117
118 BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
119 {
120 switch (optN)
121 {
122 case 0:
123 argShowHelp();
124 exit(0);
125 break;
126
127 case 1:
128 th_verbosityLevel++;
129 break;
130
131 case 2:
132 optPort = atoi(optArg);
133 break;
134
135 case 3:
136 optServer = optArg;
137 break;
138
139 case 4:
140 if ((optUserColor = th_get_hex_triplet(optArg)) < 0)
141 {
142 THERR("Invalid color argument '%s', should be a RGB hex triplet '000000'.\n",
143 optArg);
144 return FALSE;
145 }
146 THMSG(1, "Using color #%06x\n", optUserColor);
147 break;
148
149 case 5:
150 optLogFilename = optArg;
151 optLogEnable = TRUE;
152 break;
153
154 case 7:
155 optSite = optArg;
156 break;
157
158 case 6:
159 optDaemon = TRUE;
160 THMSG(1, "Running in pseudo-daemon mode.\n");
161 break;
162
163 case 8:
164 optDebug = TRUE;
165 THMSG(1, "Debug mode enabled.\n");
166 break;
167
168
169 case 10:
170 optProxyServer = optArg;
171 optProxyType = NN_PROXY_SOCKS4;
172 break;
173
174 case 11:
175 optProxyServer = optArg;
176 optProxyType = NN_PROXY_SOCKS4A;
177 break;
178
179 case 12:
180 optPort = atoi(optArg);
181 break;
182
183
184 default:
185 THERR("Unknown option '%s'.\n", currArg);
186 return FALSE;
187 }
188
189 return TRUE;
190 }
191
192
193 BOOL argHandleFile(char *currArg)
194 {
195 if (!optUserNameCmd)
196 optUserNameCmd = currArg;
197 else if (!optPasswordCmd)
198 optPasswordCmd = currArg;
199 else
200 {
201 THERR("Username '%s' already specified on commandline!\n", optUserNameCmd);
202 return FALSE;
203 }
204
205 return TRUE;
206 }
207
208 BOOL str_get_timestamp(char *str, size_t len, const char *fmt)
209 {
210 time_t stamp = time(NULL);
211 struct tm *stamp_tm;
212 if ((stamp_tm = localtime(&stamp)) != NULL)
213 {
214 strftime(str, len, fmt, stamp_tm);
215 return TRUE;
216 }
217 else
218 {
219 str[0] = 0;
220 return FALSE;
221 }
222 }
223
224
225 char * str_trim_left(char *buf)
226 {
227 while (*buf != 0 && th_isspace(*buf)) buf++;
228 return buf;
229 }
230
231 int compareUsername(const void *s1, const void *s2)
232 {
233 return th_strcasecmp((char *) s1, (char *) s2);
234 }
235
236 nn_window_t *findWindow(const char *id)
237 {
238 int i;
239
240 for (i = 0; i < SET_MAX_WINDOWS; i++)
241 if (chatWindows[i] != NULL &&
242 chatWindows[i]->id != NULL &&
243 th_strcasecmp(id, chatWindows[i]->id) == 0)
244 return chatWindows[i];
245
246 return NULL;
247 }
248
249
250 BOOL openWindow(const char *name, BOOL curwin)
251 {
252 int i;
253 nn_window_t *res;
254 if (name == NULL)
255 return FALSE;
256
257 if ((res = nn_window_new(name)) == NULL)
258 return FALSE;
259
260 for (i = 1; i < SET_MAX_WINDOWS; i++)
261 if (chatWindows[i] == NULL)
262 {
263 res->num = i;
264 chatWindows[i] = res;
265 if (curwin)
266 currWin = res;
267 return TRUE;
268 }
269
270 return FALSE;
271 }
272
273
274 void closeWindow(nn_window_t *win)
275 {
276 int i;
277 if (win == NULL) return;
278
279 for (i = 1; i < SET_MAX_WINDOWS; i++)
280 if (chatWindows[i] == win)
281 {
282 chatWindows[i] = NULL;
283 nn_window_free(win);
284 return;
285 }
286 }
287
288
289 void updateStatus(void)
290 {
291 char tmpStr[128];
292 int i;
293
294 if (statusWin == NULL) return;
295
296 str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S");
297
298 wbkgdset(statusWin, COLOR_PAIR(10));
299 werase(statusWin);
300
301 wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
302 mvwaddstr(statusWin, 0, 1, tmpStr);
303
304 wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
305 waddstr(statusWin, " | ");
306 wattrset(statusWin, A_BOLD | COLOR_PAIR(16));
307 waddstr(statusWin, optUserName);
308 wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
309
310 wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
311 waddstr(statusWin, " | ");
312 wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
313 snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor);
314 waddstr(statusWin, tmpStr);
315
316 wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
317 waddstr(statusWin, " | WIN: ");
318 snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d",
319 currWin->num + 1,
320 currWin->id != NULL ? currWin->id : "MAIN",
321 currWin->pos);
322 waddstr(statusWin, tmpStr);
323
324 wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
325 waddstr(statusWin, " | ");
326 wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
327
328 for (i = 0; i < SET_MAX_WINDOWS; i++)
329 if (chatWindows[i] != NULL && chatWindows[i]->dirty)
330 {
331 snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
332 waddstr(statusWin, tmpStr);
333 }
334
335 wrefresh(statusWin);
336 }
337
338
339 void printEditBuf(nn_editbuf_t *buf)
340 {
341 char *tmp;
342 if (editWin == NULL || buf == NULL) return;
343
344 buf->data[buf->len] = 0;
345 tmp = nn_username_decode(th_strdup(buf->data));
346
347 werase(editWin);
348
349 wattrset(editWin, A_NORMAL);
350
351 if (buf->pos < buf->len)
352 {
353 waddnstr(editWin, tmp, buf->pos);
354 wattrset(editWin, A_REVERSE);
355 waddch(editWin, tmp[buf->pos]);
356 wattrset(editWin, A_NORMAL);
357 waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1);
358 }
359 else
360 {
361 waddnstr(editWin, tmp, buf->len);
362 wattrset(editWin, A_REVERSE);
363 waddch(editWin, ' ');
364 wattrset(editWin, A_NORMAL);
365 }
366 wrefresh(editWin);
367 th_free(tmp);
368 }
369
370
371 int printWin(WINDOW *win, const char *fmt)
372 {
373 const char *s = fmt;
374 int col = 0;
375
376 while (*s)
377 {
378 if (*s == '½')
379 {
380 s++;
381 if (*s == '½')
382 {
383 waddch(win, ((unsigned char) *s) | col);
384 s++;
385 }
386 else
387 {
388 memcpy(&col, s, sizeof(int));
389 s += sizeof(int);
390 }
391 }
392 else
393 {
394 waddch(win, ((unsigned char) *s) | col);
395 s++;
396 }
397 }
398 return 0;
399 }
400
401
402 #define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch)
403
404 int nn_window_print(nn_window_t *win, const char *fmt)
405 {
406 const char *s = fmt;
407 int col = 0;
408 while (*s)
409 {
410 if (*s == '½')
411 {
412 s++;
413 if (*s == '½')
414 {
415 QPUTCH(*s);
416 QPUTCH(*s);
417 win->chlen++;
418 }
419 else
420 {
421 int val = 0;
422 while (*s >= '0' && *s <= '9')
423 {
424 val *= 10;
425 val += (*s - '0');
426 s++;
427 }
428 if (*s != '½') return -1;
429
430 if (val < 9)
431 col = A_DIM | COLOR_PAIR(val);
432 else if (val < 30)
433 col = A_BOLD | COLOR_PAIR(val - 9);
434
435 QPUTCH('½');
436
437 if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int)))
438 return -2;
439
440 memcpy(win->buf + win->len, &col, sizeof(int));
441 win->len += sizeof(int);
442 }
443 }
444 else if (*s == '\n')
445 {
446 QPUTCH('\n');
447 QPUTCH(0);
448 th_ringbuf_add(win->data, win->buf);
449 win->buf = NULL;
450 win->chlen = 0;
451 win->dirty = TRUE;
452 }
453 else if (*s != '\r')
454 {
455 QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
456 win->chlen++;
457 }
458
459 s++;
460 }
461
462 return 0;
463 }
464
465
466 BOOL updateMainWin(BOOL force)
467 {
468 int h, offs;
469 qringbuf_t *buf;
470
471 /* Check pointers */
472 if (mainWin == NULL || currWin == NULL)
473 return FALSE;
474
475 /* Check if update is forced or if the window is dirty */
476 if (!force && !currWin->dirty)
477 return FALSE;
478
479 /* Compute how many lines from backbuffer fit on the screen */
480 buf = currWin->data;
481 h = getmaxy(mainWin);
482
483 /* Clear and redraw window */
484 werase(mainWin);
485 scrollok(mainWin, 1);
486 for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++)
487 {
488 if (buf->data[offs] != NULL)
489 printWin(mainWin, buf->data[offs]);
490 }
491
492 currWin->dirty = FALSE;
493 wrefresh(mainWin);
494 return TRUE;
495 }
496
497
498 int printFile(FILE *outFile, const char *fmt)
499 {
500 const char *s = fmt;
501
502 while (*s)
503 {
504 if (*s == '½')
505 {
506 s++;
507 if (*s == '½')
508 {
509 fputc((unsigned char) *s, outFile);
510 s++;
511 }
512 else
513 {
514 while (*s && isdigit((int) *s)) s++;
515 if (*s != '½') return -1;
516 s++;
517 }
518 }
519 else
520 {
521 if ((unsigned char) *s == 255)
522 fputc(' ', outFile);
523 else
524 fputc((unsigned char) *s, outFile);
525 s++;
526 }
527 }
528
529 return 0;
530 }
531
532 void printMsgV(nn_window_t *win, int flags, const char *fmt, va_list ap)
533 {
534 char tmpStr[128], *buf;
535
536 str_get_timestamp(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ ");
537
538 buf = th_strdup_vprintf(fmt, ap);
539
540 if (optLogFile && (flags & LOG_FILE))
541 {
542 if (flags & LOG_STAMP) printFile(optLogFile, tmpStr);
543 printFile(optLogFile, buf);
544 fflush(optLogFile);
545 }
546
547 if (!optDaemon && (flags & LOG_WINDOW))
548 {
549 nn_window_t *tmp = win != NULL ? win : chatWindows[0];
550 if (flags & LOG_STAMP) nn_window_print(tmp, tmpStr);
551 nn_window_print(tmp, buf);
552 }
553
554 th_free(buf);
555 }
556
557 void printMsg(nn_window_t *win, const char *fmt, ...)
558 {
559 va_list ap;
560
561 va_start(ap, fmt);
562 printMsgV(win, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
563 va_end(ap);
564 }
565
566 void printMsgF(nn_window_t *win, int flags, const char *fmt, ...)
567 {
568 va_list ap;
569
570 va_start(ap, fmt);
571 printMsgV(win, flags | LOG_STAMP, fmt, ap);
572 va_end(ap);
573 }
574
575 void printMsgQ(nn_window_t *win, const char *fmt, ...)
576 {
577 va_list ap;
578
579 va_start(ap, fmt);
580 printMsgV(win, LOG_STAMP | LOG_WINDOW, fmt, ap);
581 va_end(ap);
582 }
583
584
585 char *errorMessages = NULL;
586
587 void errorMsgV(const char *fmt, va_list ap)
588 {
589 char *tmp = th_strdup_vprintf(fmt, ap);
590
591 printMsg(NULL, "%s", tmp);
592
593 if (errorMessages != NULL)
594 {
595 char *tmp2 = th_strdup_printf("%s%s", errorMessages, tmp);
596 th_free(errorMessages);
597 th_free(tmp);
598 errorMessages = tmp2;
599 }
600 else
601 errorMessages = tmp;
602 }
603
604 void errorMsg(const char *fmt, ...)
605 {
606 va_list ap;
607
608 va_start(ap, fmt);
609 errorMsgV(fmt, ap);
610 va_end(ap);
611 }
612
613 void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
614 {
615 (void) conn;
616 errorMsgV(fmt, ap);
617 }
618
619 void messageFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
620 {
621 (void) conn;
622 printMsgV(NULL, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap);
623 }
624
625
626 BOOL checkIgnoreList(const char *name)
627 {
628 qlist_t *node = setIgnoreList;
629 while (node != NULL)
630 {
631 if (th_strcasecmp(name, (char *) node->data) == 0)
632 return TRUE;
633 node = node->next;
634 }
635 return FALSE;
636 }
637
638
639 int nnproto_handle_user(nn_conn_t *conn)
640 {
641 static const char *msg = "</USER><MESSAGE>";
642 char *p = conn->ptr;
643 BOOL isMine, isIgnored = FALSE;
644 char *s, *t, *userName;
645
646 /* Find start of the message */
647 s = strstr(p, msg);
648 if (!s) return 1;
649 *s = 0;
650 s += strlen(msg);
651
652 /* Find end of the message */
653 t = strstr(s, "</MESSAGE>");
654 if (!t) return 3;
655 *t = 0;
656
657 /* Decode message string */
658 s = nn_decode_str1(s);
659 if (!s) return -1;
660
661 /* Decode username */
662 userName = nn_decode_str1(p);
663 if (!userName)
664 {
665 th_free(s);
666 return -2;
667 }
668
669 /* Check if the username is on our ignore list and
670 * that it is not our OWN username!
671 */
672 isMine = strcmp(userName, optUserName) == 0;
673 isIgnored = setIgnoreMode && !isMine && checkIgnoreList(userName);
674
675 /* Is it a special control message? */
676 if (*s == '/')
677 {
678 /* Ignore room join/leave messages */
679 if (!optDebug && (strstr(s, "left the room") || strstr(s, "joined the room from")))
680 goto done;
681
682 t = nn_strip_tags(s + 1);
683 if (!strncmp(t, "BPRV ", 5))
684 {
685 char *name, *tmp, *msg, *h;
686 nn_window_t *win;
687 h = nn_decode_str2(t + 1);
688
689 if (!strncmp(t, "BPRV from ", 10))
690 {
691 name = nn_decode_str2(t + 10);
692 isMine = FALSE;
693 }
694 else
695 {
696 name = nn_decode_str2(t + 8);
697 isMine = TRUE;
698 }
699
700 for (tmp = name; *tmp && *tmp != ':'; tmp++);
701 if (tmp[0] != 0 && tmp[1] == ' ')
702 msg = tmp + 2;
703 else
704 msg = "";
705 *tmp = 0;
706
707 isIgnored = setIgnoreMode && checkIgnoreList(name);
708 win = findWindow(name);
709
710 if (win != NULL)
711 {
712 printMsgF(win, isIgnored ? 0 : LOG_WINDOW,
713 "½5½<½%d½%s½5½>½0½ %s\n",
714 isMine ? 14 : 15, isMine ? optUserName : name, msg);
715
716 printMsgF(NULL, LOG_FILE, "½11½%s½0½\n", h);
717 }
718 else
719 {
720 printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
721 "½11½%s½0½\n", h);
722 }
723 th_free(name);
724 th_free(h);
725 }
726 else
727 {
728 /* It's an action (/me) */
729 char *h = nn_decode_str2(t);
730 printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
731 "½9½* %s½0½\n", h);
732 th_free(h);
733 }
734 th_free(t);
735 }
736 else
737 {
738 /* It's a normal message */
739 char *h;
740 t = nn_strip_tags(s);
741 h = nn_decode_str2(t);
742 printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE),
743 "½5½<½%d½%s½5½>½0½ %s\n", isMine ? 14 : 15, userName, h);
744 th_free(h);
745 th_free(t);
746 }
747
748 done:
749 th_free(s);
750 th_free(userName);
751 return 0;
752 }
753
754
755 int nnproto_handle_login(nn_conn_t *conn)
756 {
757 char tmpStr[256];
758 str_get_timestamp(tmpStr, sizeof(tmpStr), "%c");
759
760 if (!nn_conn_buf_strcmp(conn, "FAILURE>"))
761 {
762 printMsg(NULL, "½1½Login failure½0½ - ½3½%s½0½\n", tmpStr);
763 return -2;
764 }
765 else if (!nn_conn_buf_strcmp(conn, "SUCCESS>"))
766 {
767 printMsg(NULL, "½2½Login success½0½ - ½3½%s½0½\n", tmpStr);
768 nn_conn_send_msg(conn, optUserNameEnc, "%%2FRequestUserList");
769 return 0;
770 }
771 else
772 return 1;
773 }
774
775
776 int nnproto_handle_add_user(nn_conn_t *conn)
777 {
778 char *p, *s, *str = conn->ptr;
779 nn_window_t *win;
780
781 s = nn_conn_buf_strstr(conn, "</ADD_USER>");
782 if (!s) return 1;
783 *s = 0;
784
785 p = nn_dbldecode_str(str);
786 if (!p) return -1;
787
788 win = findWindow(p);
789 nn_userhash_insert(nnUsers, nn_username_encode(p));
790
791 printMsg(NULL, "! ½3½%s½0½ ½2½ADDED.½0½\n", p);
792 if (win != NULL)
793 printMsg(win, "! ½3½%s½0½ ½2½joined the chat.½0½\n", p);
794
795 th_free(p);
796 return 0;
797 }
798
799
800 int nnproto_handle_delete_user(nn_conn_t *conn)
801 {
802 char *p, *s, *str = conn->ptr;
803 nn_window_t *win;
804
805 s = nn_conn_buf_strstr(conn, "</DELETE_USER>");
806 if (!s) return 1;
807 *s = 0;
808
809 p = nn_dbldecode_str(str);
810 if (!p) return -1;
811
812 win = findWindow(p);
813 nn_userhash_delete(nnUsers, nn_username_encode(p));
814
815 printMsg(NULL, "! ½3½%s½0½ ½1½DELETED.½0½\n", p);
816 if (win != NULL)
817 printMsg(win, "! ½3½%s½0½ ½1½left the chat.½0½\n", p);
818
819 th_free(p);
820 return 0;
821 }
822
823
824 int nnproto_handle_num_clients(nn_conn_t *conn)
825 {
826 nn_conn_buf_strstr(conn, "</NUMCLIENTS>");
827 return 0;
828 }
829
830
831 int nnproto_handle_boot(nn_conn_t *conn)
832 {
833 (void) conn;
834 errorMsg("Booted by server.\n");
835 return -1;
836 }
837
838
839 typedef struct
840 {
841 char *cmd;
842 ssize_t len;
843 int (*handler)(nn_conn_t *);
844 } nn_protocolcmd_t;
845
846
847 static nn_protocolcmd_t protoCmds[] =
848 {
849 { "<USER>", -1, nnproto_handle_user },
850 { "<LOGIN_", -1, nnproto_handle_login },
851 { "<DELETE_USER>", -1, nnproto_handle_delete_user },
852 { "<ADD_USER>", -1, nnproto_handle_add_user },
853 { "<NUMCLIENTS>", -1, nnproto_handle_num_clients },
854 { "<BOOT />", -1, nnproto_handle_boot },
855 };
856
857 static const int nprotoCmds = sizeof(protoCmds) / sizeof(protoCmds[0]);
858
859
860 int nn_parse_protocol(nn_conn_t *conn)
861 {
862 static BOOL protoCmdsInit = FALSE;
863 int i;
864
865 if (!protoCmdsInit)
866 {
867 for (i = 0; i < nprotoCmds; i++)
868 protoCmds[i].len = strlen(protoCmds[i].cmd);
869
870 protoCmdsInit = TRUE;
871 }
872
873 for (i = 0; i < nprotoCmds; i++)
874 {
875 if (!nn_conn_buf_strncmp(conn, protoCmds[i].cmd, protoCmds[i].len))
876 return protoCmds[i].handler(conn);
877 }
878
879 if (optDebug)
880 {
881 printMsg(NULL, "Unknown protocmd: \"%s\"\n", conn->ptr);
882 return 0;
883 }
884 else
885 return 1;
886 }
887
888
889 int nn_handle_input(nn_conn_t *conn, char *buf, size_t bufLen)
890 {
891 char *tmpStr, tmpBuf[4096];
892 BOOL result;
893
894 /* Trim right */
895 bufLen--;
896 buf[bufLen--] = 0;
897 while (bufLen > 0 && th_isspace(buf[bufLen]))
898 buf[bufLen--] = 0;
899
900 /* Decode completed usernames */
901 nn_username_decode(buf);
902
903 /* Check for special user commands */
904 if (*buf == 0)
905 {
906 return 1;
907 }
908 else if (!th_strncasecmp(buf, "/color ", 7))
909 {
910 /* Change color */
911 int tmpInt;
912 if ((tmpInt = th_get_hex_triplet(str_trim_left(buf + 7))) < 0)
913 {
914 printMsgQ(currWin, "Invalid color value '%s'\n", buf+7);
915 return 1;
916 }
917 optUserColor = tmpInt;
918 printMsgQ(currWin, "Setting color to #%06x\n", optUserColor);
919 nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
920 return 0;
921 }
922 else if (!th_strncasecmp(buf, "/ignore", 7))
923 {
924 char *name = str_trim_left(buf + 7);
925 if (strlen(name) > 0)
926 {
927 /* Add or remove someone to/from ignore */
928 qlist_t *user = th_llist_find_func(setIgnoreList, name, compareUsername);
929 if (user != NULL)
930 {
931 printMsgQ(currWin, "Removed user '%s' from ignore.\n", name);
932 th_llist_delete_node(&setIgnoreList, user);
933 }
934 else
935 {
936 printMsgQ(currWin, "Now ignoring '%s'.\n", name);
937 th_llist_append(&setIgnoreList, th_strdup(name));
938 }
939 }
940 else
941 {
942 /* Just list whomever is in ignore now */
943 qlist_t *user = setIgnoreList;
944 ssize_t nuser = th_llist_length(setIgnoreList);
945 char *result = th_strdup_printf("Users ignored (%d): ", nuser);
946 while (user != NULL)
947 {
948 if (user->data != NULL)
949 {
950 th_pstr_printf(&result, "%s'%s'", result, (char *) user->data);
951 if (--nuser > 0)
952 th_pstr_printf(&result, "%s, ", result);
953 }
954 user = user->next;
955 }
956 printMsgQ(currWin, "%s\n", result);
957 th_free(result);
958 }
959 return 0;
960 }
961 else if (!th_strncasecmp(buf, "/query", 6))
962 {
963 char *name = str_trim_left(buf + 6);
964 if (strlen(name) > 0)
965 {
966 nn_user_t *user = nn_user_find(nnUsers, nn_username_encode(name));
967 if (user != NULL)
968 {
969 name = nn_username_decode(th_strdup(user->name));
970 printMsgQ(currWin, "Opening PRV query for '%s'.\n", name);
971 if (openWindow(name, TRUE))
972 printMsgQ(currWin, "In PRV query with '%s'.\n", name);
973 th_free(name);
974 }
975 }
976 else
977 {
978 printMsgQ(currWin, "Usage: /query username\n");
979 printMsgQ(currWin, "To close a PRV query, use /close [username]\n");
980 printMsgQ(currWin, "/close without username will close the current PRV window.\n");
981 }
982 return 0;
983 }
984 else if (!th_strncasecmp(buf, "/win", 4))
985 {
986 /* Change color */
987 char *tmp = str_trim_left(buf + 4);
988 if (strlen(tmp) > 0)
989 {
990 int val = atoi(tmp);
991 if (val >= 1 && val < SET_MAX_WINDOWS)
992 {
993 if (chatWindows[val - 1] != NULL)
994 currWin = chatWindows[val - 1];
995 }
996 else
997 {
998 printMsgQ(currWin, "Invalid window number '%s'\n", tmp);
999 return 1;
1000 }
1001 }
1002 else
1003 {
1004 printMsgQ(currWin, "Window : #%d\n", currWin->num);
1005 printMsgQ(currWin, "ID : %s\n", currWin->id);
1006 }
1007 return 0;
1008 }
1009 else if (!th_strncasecmp(buf, "/close", 6))
1010 {
1011 char *name = str_trim_left(buf + 6);
1012 if (strlen(name) > 0)
1013 {
1014 nn_window_t *win = findWindow(name);
1015 if (win != NULL)
1016 {
1017 closeWindow(win);
1018 printMsgQ(currWin, "Closed PRV query to '%s'.\n", name);
1019 }
1020 else
1021 {
1022 printMsgQ(currWin, "No PRV query by name '%s'.\n", name);
1023 }
1024 }
1025 else
1026 {
1027 if (currWin != chatWindows[0])
1028 {
1029 closeWindow(currWin);
1030 currWin = chatWindows[0];
1031 }
1032 }
1033 return 0;
1034 }
1035 else if (!th_strncasecmp(buf, "/save", 5))
1036 {
1037 /* Save configuration */
1038 FILE *cfgfile = fopen(setConfigFile, "w");
1039 if (cfgfile == NULL)
1040 {
1041 printMsgQ(currWin, "Could not create configuration to file '%s': %s\n",
1042 setConfigFile, strerror(errno));
1043 return 0;
1044 }
1045 printMsgQ(currWin, "Configuration saved in file '%s', res=%d\n",
1046 setConfigFile,
1047 th_cfg_write(cfgfile, setConfigFile, cfg));
1048
1049 fclose(cfgfile);
1050 return 0;
1051 }
1052 else if (!th_strncasecmp(buf, "/w ", 3))
1053 {
1054 /* Open given username's profile via firefox in a new tab */
1055 char *name = str_trim_left(buf + 3);
1056
1057 printMsg(currWin, "Opening profile for: '%s'\n", name);
1058
1059 tmpStr = nn_encode_str1(name);
1060 #ifdef __WIN32
1061 {
1062 HINSTANCE status;
1063 snprintf(tmpBuf, sizeof(tmpBuf), "http://www.newbienudes.com/profile/%s/", tmpStr);
1064 th_free(tmpStr);
1065 status = ShellExecute(NULL, "open", tmpBuf, NULL, NULL, SW_SHOWNA);
1066 if (status <= (HINSTANCE) 32)
1067 printMsgQ(currWin, "Could not launch default web browser: %d\n", status);
1068 }
1069 #else
1070 {
1071 int status;
1072 int fds[2];
1073 pid_t pid;
1074 snprintf(tmpBuf, sizeof(tmpBuf), "openurl(http://www.newbienudes.com/profile/%s/,new-tab)", tmpStr);
1075 th_free(tmpStr);
1076
1077 if (pipe(fds) == -1)
1078 {
1079 int ret = errno;
1080 printMsgQ(currWin, "Could not open process communication pipe! (%d, %s)\n", ret, strerror(ret));
1081 return 0;
1082 }
1083
1084 if ((pid = fork()) < 0)
1085 {
1086 printMsgQ(currWin, "Could not create sub-process!\n");
1087 }
1088 else if (pid == 0)
1089 {
1090 dup2(fds[1], STDOUT_FILENO);
1091 dup2(fds[0], STDERR_FILENO);
1092 execlp(setBrowser, setBrowser, "-remote", tmpBuf, (void *)NULL);
1093 _exit(errno);
1094 }
1095
1096 wait(&status);
1097 }
1098 #endif
1099 return 0;
1100 }
1101 else if (!th_strncasecmp(buf, "/who", 4))
1102 {
1103 /* Alias /who to /listallusers */
1104 snprintf(tmpBuf, sizeof(tmpBuf), "/listallusers");
1105 buf = tmpBuf;
1106 }
1107
1108 if (currWin != chatWindows[0])
1109 {
1110 if (currWin->id != NULL)
1111 {
1112 snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", currWin->id, buf);
1113 buf = tmpBuf;
1114 }
1115 else
1116 {
1117 printMsgQ(NULL, "No target set, exiting prv mode.\n");
1118 return 1;
1119 }
1120 }
1121
1122 /* Send double-encoded */
1123 tmpStr = nn_dblencode_str(nn_username_decode(buf));
1124 if (tmpStr == 0) return -2;
1125 result = nn_conn_send_msg(conn, optUserNameEnc, "%s", tmpStr);
1126 th_free(tmpStr);
1127
1128 return result ? 0 : -1;
1129 }
1130
1131
1132 void closeWindows(void)
1133 {
1134 if (mainWin) delwin(mainWin);
1135 if (statusWin) delwin(statusWin);
1136 if (editWin) delwin(editWin);
1137 }
1138
1139
1140 BOOL initializeWindows(void)
1141 {
1142 int w, h;
1143
1144 getmaxyx(stdscr, h, w);
1145
1146 closeWindows();
1147
1148 mainWin = subwin(stdscr, h - 4, w, 0, 0);
1149 statusWin = subwin(stdscr, 1, w, h - 4, 0);
1150 editWin = subwin(stdscr, 3, w, h - 3, 0);
1151
1152 if (mainWin == NULL || statusWin == NULL || editWin == NULL)
1153 return FALSE;
1154
1155 return TRUE;
1156 }
1157
1158
1159 void updateWindows(void)
1160 {
1161 if (mainWin) redrawwin(mainWin);
1162 if (statusWin) redrawwin(statusWin);
1163 if (editWin) redrawwin(editWin);
1164 }
1165
1166
1167 BOOL performTabCompletion(nn_editbuf_t *buf)
1168 {
1169 static char *previous = NULL, *pattern = NULL;
1170 BOOL again = FALSE, hasSeparator = FALSE, newPattern = FALSE, hasSpace = FALSE;
1171 char *str = buf->data;
1172 int mode = 0;
1173 ssize_t endPos, startPos = buf->pos;
1174
1175 /* previous word */
1176 if (startPos >= 2 && str[startPos - 1] == ' ' && str[startPos - 2] != ' ')
1177 {
1178 startPos -= 2;
1179 endPos = startPos;
1180 while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
1181 mode = 1;
1182 }
1183 else
1184 /* middle of a word, new pattern */
1185 if (startPos < buf->len && str[startPos] != ' ')
1186 {
1187 endPos = startPos;
1188 while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
1189 while (endPos < buf->len - 1 && str[endPos + 1] != ' ') endPos++;
1190 newPattern = TRUE;
1191 mode = 2;
1192 }
1193 else
1194 /* previous word, new pattern */
1195 if (startPos >= 1 && str[startPos - 1] != ' ')
1196 {
1197 startPos -= 1;
1198 endPos = startPos;
1199 while (startPos > 0 && str[startPos - 1] != ' ') startPos--;
1200 newPattern = TRUE;
1201 mode = 3;
1202 }
1203 else
1204 {
1205 if (optDebug)
1206 printMsg(currWin, "no mode\n");
1207 return FALSE;
1208 }
1209
1210 if (str[endPos] == optNickSep)
1211 {
1212 endPos--;
1213 if (startPos > 0)
1214 {
1215 if (optDebug)
1216 printMsg(currWin, "str[endPos] == optNickSep && startPos > 0 (%d)\n", startPos);
1217 return FALSE;
1218 }
1219 hasSeparator = TRUE;
1220 }
1221
1222 if (buf->pos > 0 && str[buf->pos - 1] == ' ')
1223 hasSpace = TRUE;
1224 if (buf->pos <= buf->len && str[buf->pos] == ' ')
1225 hasSpace = TRUE;
1226
1227 if (newPattern)
1228 {
1229 /* Get pattern, check if it matches previous pattern and set 'again' flag */
1230 char *npattern = nn_editbuf_get_string(buf, startPos, endPos);
1231 if (pattern && npattern && th_strcasecmp(npattern, pattern) == 0)
1232 again = TRUE;
1233
1234 th_free(pattern);
1235 pattern = npattern;
1236
1237 if (!again)
1238 {
1239 th_free(previous);
1240 previous = NULL;
1241 }
1242 }
1243
1244 if (optDebug)
1245 {
1246 printMsg(currWin, "sPos=%d, ePos=%d <-> bPos=%d, bufLen=%d : pat='%s' (again=%s, hassep=%s, hasspc=%s, newpat=%s, mode=%d)\n",
1247 startPos, endPos, buf->pos, buf->len, pattern,
1248 again ? "yes" : "no",
1249 hasSeparator ? "yes" : "no",
1250 hasSpace ? "yes" : "no",
1251 newPattern ? "yes" : "no", mode);
1252 }
1253
1254 if (pattern)
1255 {
1256 nn_user_t *user = nn_user_match(nnUsers, pattern, previous, again);
1257
1258 if (user)
1259 {
1260 int i;
1261 char *c = user->name;
1262 if (optDebug)
1263 printMsg(currWin, "match='%s' / prev='%s'\n", user->name, previous);
1264
1265 for (i = startPos; i <= endPos; i++)
1266 nn_editbuf_delete(buf, startPos);
1267
1268 for (i = startPos; *c; i++, c++)
1269 nn_editbuf_insert(buf, i, *c);
1270
1271 if (!hasSeparator && startPos == 0)
1272 {
1273 nn_editbuf_insert(buf, i++, optNickSep);
1274 startPos++;
1275 }
1276 if (hasSeparator)
1277 startPos++;
1278 if (!hasSpace)
1279 nn_editbuf_insert(buf, i++, ' ');
1280
1281 nn_editbuf_setpos(buf, startPos + 1 + strlen(user->name));
1282
1283 th_free(previous);
1284 previous = th_strdup(user->name);
1285
1286 return TRUE;
1287 }
1288 }
1289
1290 return FALSE;
1291 }
1292
1293
1294 #define VPUTCH(CH) th_vputch(&bufData, &bufSize, &bufLen, CH)
1295 #define VPUTS(STR) th_vputs(&bufData, &bufSize, &bufLen, STR)
1296
1297 char *logParseFilename(const char *fmt, int id)
1298 {
1299 size_t bufSize = strlen(fmt) + TH_BUFGROW, bufLen = 0;
1300 char *bufData = th_malloc(bufSize);
1301 char tmpBuf[32];
1302 const char *s = fmt;
1303
1304 while (*s)
1305 {
1306 if (*s == '%')
1307 {
1308 s++;
1309 switch (*s)
1310 {
1311 case 'i':
1312 snprintf(tmpBuf, sizeof(tmpBuf), "%05d", id);
1313 VPUTS(tmpBuf);
1314 break;
1315
1316 case 'd':
1317 snprintf(tmpBuf, sizeof(tmpBuf), "%d", id);
1318 VPUTS(tmpBuf);
1319 break;
1320
1321 case '%':
1322 VPUTCH('%');
1323 break;
1324 }
1325 s++;
1326 }
1327 else
1328 {
1329 VPUTCH(*s);
1330 s++;
1331 }
1332 }
1333
1334 VPUTCH(0);
1335 return bufData;
1336 }
1337
1338
1339 BOOL logFileOpen(void)
1340 {
1341 char *filename;
1342
1343 if (optLogFilename == NULL || !optLogEnable)
1344 return FALSE;
1345
1346 filename = logParseFilename(optLogFilename, optPort);
1347
1348 if ((optLogFile = fopen(filename, "a")) == NULL)
1349 {
1350 errorMsg("Could not open logfile '%s' for appending!\n", filename);
1351 th_free(filename);
1352 return FALSE;
1353 }
1354
1355 th_free(filename);
1356
1357 return TRUE;
1358 }
1359
1360
1361 void logFileClose(void)
1362 {
1363 if (optLogFile)
1364 {
1365 fclose(optLogFile);
1366 optLogFile = NULL;
1367 }
1368 }
1369
1370
1371 char *promptRequester(WINDOW *win, const char *info, BOOL allowEmpty)
1372 {
1373 char tmpBuf[512], *ptr;
1374 ssize_t pos;
1375 int curVis = curs_set(1);
1376
1377 echo();
1378 waddstr(win, info);
1379 wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1);
1380 noecho();
1381 if (curVis != ERR)
1382 curs_set(curVis);
1383
1384 for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--)
1385 tmpBuf[pos] = 0;
1386
1387 ptr = str_trim_left(tmpBuf);
1388
1389 if (allowEmpty || strlen(ptr) > 0)
1390 return th_strdup(ptr);
1391 else
1392 return NULL;
1393 }
1394
1395
1396 void printHelp(void)
1397 {
1398 printMsgQ(currWin, "\n"
1399 "NNChat Help\n"
1400 "===========\n"
1401 "\n"
1402 "F1 This help.\n"
1403 "F2 \n"
1404 );
1405 }
1406
1407
1408 int main(int argc, char *argv[])
1409 {
1410 nn_conn_t *conn = NULL;
1411 int curVis = ERR, updateCount = 0;
1412 BOOL argsOK, isError = FALSE,
1413 exitProg = FALSE,
1414 colorSet = FALSE,
1415 cursesInit = FALSE,
1416 networkInit = FALSE,
1417 insertMode = TRUE,
1418 firstUpdate = TRUE;
1419 time_t prevTime;
1420 char *tmpStr;
1421 nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
1422 nn_editbuf_t *histBuf[SET_MAX_HISTORY+2];
1423 int histPos = 0, histMax = 0;
1424
1425 cfgitem_t *tmpcfg;
1426 char *homeDir = NULL;
1427
1428 memset(histBuf, 0, sizeof(histBuf));
1429
1430 /* Initialize */
1431 th_init("NNChat", "Newbie Nudes chat client", NN_VERSION,
1432 "Written and designed by Anonymous Finnish Guy (C) 2008-2012",
1433 "This software is freeware, use and distribute as you wish.");
1434 th_verbosityLevel = 0;
1435
1436 /* Read configuration file */
1437 tmpcfg = NULL;
1438 th_cfg_add_comment(&tmpcfg, "General settings");
1439 th_cfg_add_string(&tmpcfg, "username", &optUserName, NULL);
1440 th_cfg_add_string(&tmpcfg, "password", &optPassword, NULL);
1441
1442 th_cfg_add_comment(&tmpcfg, "Default color as a hex-triplet");
1443 th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor);
1444
1445 th_cfg_add_comment(&tmpcfg, "Default setting of ignore mode");
1446 th_cfg_add_bool(&tmpcfg, "ignore", &setIgnoreMode, setIgnoreMode);
1447 th_cfg_add_comment(&tmpcfg, "People to be ignored when ignore mode is enabled");
1448 th_cfg_add_string_list(&tmpcfg, "ignore_list", &setIgnoreList);
1449
1450 th_cfg_add_comment(&tmpcfg, "Random messages for idle timeout protection. If none are set, plain '.' is used.");
1451 th_cfg_add_string_list(&tmpcfg, "idle_messages", &setIdleMessages);
1452
1453 th_cfg_add_comment(&tmpcfg, "Character used as nickname auto-completion separator (default is ':')");
1454 th_cfg_add_string(&tmpcfg, "nick_separator", &optNickSepStr, NULL);
1455
1456 th_cfg_add_section(&cfg, "general", tmpcfg);
1457
1458
1459 tmpcfg = NULL;
1460 th_cfg_add_comment(&tmpcfg, "Chat server hostname or IP address");
1461 th_cfg_add_string(&tmpcfg, "host", &optServer, optServer);
1462 th_cfg_add_comment(&tmpcfg, "Default port to connect to (8005 = main room, 8003 = passion pit)");
1463 th_cfg_add_int(&tmpcfg, "port", &optPort, optPort);
1464 th_cfg_add_section(&cfg, "server", tmpcfg);
1465
1466 tmpcfg = NULL;
1467 th_cfg_add_comment(&tmpcfg, "Proxy server type (0 = none, 1 = SOCKS 4, 2 = SOCKS 4a)");
1468 th_cfg_add_int(&tmpcfg, "type", &optProxyType, optProxyType);
1469 th_cfg_add_comment(&tmpcfg, "Proxy server host name");
1470 th_cfg_add_string(&tmpcfg, "host", &optProxyServer, optProxyServer);
1471 th_cfg_add_comment(&tmpcfg, "Proxy port, 1080 is the standard SOCKS port");
1472 th_cfg_add_int(&tmpcfg, "port", &optProxyPort, optProxyPort);
1473 th_cfg_add_section(&cfg, "proxy", tmpcfg);
1474
1475 tmpcfg = NULL;
1476 th_cfg_add_comment(&tmpcfg, "Enable logging");
1477 th_cfg_add_bool(&tmpcfg, "enable", &optLogEnable, optLogEnable);
1478 th_cfg_add_comment(&tmpcfg, "Log filename format");
1479 th_cfg_add_string(&tmpcfg, "filename", &optLogFilename, optLogFilename);
1480 th_cfg_add_section(&cfg, "logging", tmpcfg);
1481
1482 #ifdef __WIN32
1483 {
1484 char tmpPath[MAX_PATH];
1485 if (SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, tmpPath) == S_OK)
1486 homeDir = th_strdup(tmpPath);
1487
1488 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1489 }
1490 #else
1491 homeDir = th_strdup(getenv("HOME"));
1492 #endif
1493
1494 if (homeDir != NULL)
1495 {
1496 FILE *cfgfile;
1497 setConfigFile = th_strdup_printf("%s" SET_DIR_SEPARATOR "%s", homeDir, SET_CONFIG_FILE);
1498
1499 THMSG(0, "Reading configuration from '%s'.\n", setConfigFile);
1500
1501 if ((cfgfile = fopen(setConfigFile, "r")) != NULL)
1502 {
1503 th_cfg_read(cfgfile, setConfigFile, cfg);
1504 fclose(cfgfile);
1505 }
1506 }
1507
1508 if (optNickSepStr)
1509 optNickSep = optNickSepStr[0];
1510 else
1511 optNickSep = SET_NICK_SEPARATOR;
1512
1513
1514 setBrowser = getenv("BROWSER");
1515 if (setBrowser == NULL)
1516 setBrowser = "firefox";
1517
1518 /* Parse command line arguments */
1519 argsOK = th_args_process(argc, argv, optList, optListN,
1520 argHandleOpt, argHandleFile, FALSE);
1521
1522 if (optUserNameCmd != NULL)
1523 {
1524 optUserName = optUserNameCmd;
1525 optPassword = optPasswordCmd;
1526 }
1527
1528 if (!argsOK)
1529 return -2;
1530
1531 /* Allocate userhash */
1532 if ((nnUsers = nn_userhash_new()) == NULL)
1533 {
1534 THERR("Could not allocate userhash. Fatal error.\n");
1535 return -105;
1536 }
1537
1538 /* If no idle messages are set, add default */
1539 if (setIdleMessages == NULL)
1540 {
1541 th_llist_append(&setIdleMessages, th_strdup("."));
1542 }
1543
1544 /* Open logfile */
1545 logFileOpen();
1546
1547 /* Initialize network */
1548 if (!nn_network_init())
1549 {
1550 THERR("Could not initialize network subsystem.\n");
1551 goto err_exit;
1552 }
1553 else
1554 networkInit = TRUE;
1555
1556 /* Initialize NCurses */
1557 if (!optDaemon)
1558 {
1559 if (LINES < 0 || LINES > 1000) LINES = 24;
1560 if (COLS < 0 || COLS > 1000) COLS = 80;
1561 initscr();
1562 raw();
1563 keypad(stdscr, TRUE);
1564 noecho();
1565 meta(stdscr, TRUE);
1566 timeout(SET_DELAY);
1567 curVis = curs_set(0);
1568
1569 if (has_colors())
1570 {
1571 start_color();
1572
1573 init_pair( 1, COLOR_RED, COLOR_BLACK);
1574 init_pair( 2, COLOR_GREEN, COLOR_BLACK);
1575 init_pair( 3, COLOR_YELLOW, COLOR_BLACK);
1576 init_pair( 4, COLOR_BLUE, COLOR_BLACK);
1577 init_pair( 5, COLOR_MAGENTA, COLOR_BLACK);
1578 init_pair( 6, COLOR_CYAN, COLOR_BLACK);
1579 init_pair( 7, COLOR_WHITE, COLOR_BLACK);
1580 init_pair( 8, COLOR_BLACK, COLOR_BLACK);
1581
1582 init_pair(10, COLOR_BLACK, COLOR_RED);
1583 init_pair(11, COLOR_WHITE, COLOR_RED);
1584 init_pair(12, COLOR_GREEN, COLOR_RED);
1585 init_pair(13, COLOR_YELLOW, COLOR_RED);
1586 init_pair(14, COLOR_BLUE, COLOR_RED);
1587 init_pair(15, COLOR_MAGENTA, COLOR_RED);
1588 init_pair(16, COLOR_CYAN, COLOR_RED);
1589 }
1590
1591 cursesInit = TRUE;
1592
1593 if (!initializeWindows())
1594 goto err_exit;
1595
1596 #ifdef PDCURSES
1597 PDC_set_title("NNChat v" NN_VERSION);
1598 #endif
1599
1600 memset(chatWindows, 0, sizeof(chatWindows));
1601 chatWindows[0] = nn_window_new(NULL);
1602 currWin = chatWindows[0];
1603 updateStatus();
1604 }
1605
1606 /* Check if we have username and password */
1607 if (cursesInit && (optUserName == NULL || optPassword == NULL))
1608 {
1609 printWin(editWin, "You can avoid this prompt by issuing '/save' after logging in.\n");
1610 optUserName = promptRequester(editWin, "NN username: ", FALSE);
1611 optPassword = promptRequester(editWin, "NN password: ", TRUE);
1612 }
1613
1614 if (optUserName == NULL || optPassword == NULL)
1615 {
1616 errorMsg("Username and/or password not specified.\n");
1617 goto err_exit;
1618 }
1619
1620 /* Create a connection */
1621 conn = nn_conn_new(errorFunc, messageFunc);
1622 if (conn == NULL)
1623 {
1624 errorMsg("Could not create connection structure.\n");
1625 goto err_exit;
1626 }
1627
1628 /* Are we using a proxy? */
1629 if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL)
1630 {
1631 if (nn_conn_set_proxy(conn, optProxyType, optProxyPort, optProxyServer) != 0)
1632 {
1633 errorMsg("Error setting proxy information.\n");
1634 goto err_exit;
1635 }
1636 }
1637
1638 /* Okay ... */
1639 printMsg(currWin, "Trying to resolve host '%s' ...\n", optServer);
1640 conn->host = th_strdup(optServer);
1641 conn->hst = nn_resolve_host(conn, optServer);
1642 if (conn->hst == NULL)
1643 {
1644 errorMsg("Could not resolve hostname: %s.\n", strerror(h_errno));
1645 goto err_exit;
1646 }
1647
1648 #ifdef FINAL_BUILD
1649 /* To emulate the official client, we first make a request for
1650 * policy file, even though we don't use it for anything...
1651 */
1652 if (nn_conn_open(conn, 843, NULL) != 0)
1653 {
1654 errorMsg("Policy file request connection setup failed!\n");
1655 goto err_exit;
1656 }
1657
1658 tmpStr = "<policy-file-request/>";
1659 if (nn_conn_send_buf(conn, tmpStr, strlen(tmpStr) + 1) == FALSE)
1660 {
1661 errorMsg("Failed to send policy file request.\n");
1662 goto err_exit;
1663 }
1664 else
1665 {
1666 int cres = nn_conn_pull(conn);
1667 if (cres == 0)
1668 {
1669 printMsg(currWin, "Probe got: %s\n", conn->buf);
1670 }
1671 else
1672 {
1673 printMsg(currWin, "Could not get policy probe.\n");
1674 }
1675 }
1676 nn_conn_close(conn);
1677 #endif
1678
1679 /* Okay, now do the proper connection ... */
1680 if (nn_conn_open(conn, optPort, NULL) != 0)
1681 {
1682 errorMsg("Main connection setup failed!\n");
1683 goto err_exit;
1684 }
1685
1686 /* Send login command */
1687 optUserNameEnc = nn_dblencode_str(optUserName);
1688 tmpStr = nn_dblencode_str(optSite);
1689 nn_conn_send_msg(conn, optUserNameEnc, "%%2Flogin%%20%%2Dsite%%20%s%%20%%2Dpassword%%20%s", tmpStr, optPassword);
1690 th_free(tmpStr);
1691
1692 /* Initialize random numbers */
1693 prevTime = time(NULL);
1694 srandom((int) prevTime);
1695
1696 if (cursesInit)
1697 {
1698 /* Initialize rest of interactive UI code */
1699 nn_editbuf_clear(editBuf);
1700
1701 /* First update of screen */
1702 printEditBuf(editBuf);
1703 updateStatus();
1704
1705 printMsg(NULL, "%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname);
1706 printMsg(NULL, "%s\n", th_prog_author);
1707 printMsg(NULL, "%s\n", th_prog_license);
1708 }
1709
1710 /* Enter mainloop */
1711 nn_conn_reset(conn);
1712 while (!isError && !exitProg)
1713 {
1714 nn_conn_reset(conn);
1715 do {
1716 int cres = nn_conn_pull(conn);
1717 if (cres == 0 && *(conn->in_ptr - 1) == 0)
1718 {
1719 int result = nn_parse_protocol(conn);
1720 if (result > 0)
1721 {
1722 /* Couldn't handle the message for some reason */
1723 printMsg(currWin, "Could not handle: %s\n", conn->ptr);
1724 }
1725 else if (result < 0)
1726 {
1727 /* Fatal error, quit */
1728 errorMsg("Fatal error with message: %s\n", conn->ptr);
1729 isError = TRUE;
1730 }
1731 }
1732 else if (cres < 0)
1733 isError = TRUE;
1734 else
1735 break;
1736 }
1737 while (conn->total_bytes > 0 && !isError);
1738
1739 if (!nn_conn_check(conn))
1740 isError = TRUE;
1741
1742 /* Handle user input */
1743 if (cursesInit)
1744 {
1745 int c, cnt = 0;
1746 BOOL update = FALSE, updateMain = FALSE;
1747
1748 /* Handle several buffered keypresses at once */
1749 do
1750 {
1751 c = wgetch(stdscr);
1752
1753 /* Handle various problematic cases where terminal
1754 * keycodes do not get properly translated by curses
1755 */
1756 if (c == 0x1b)
1757 {
1758 /* ^[O */
1759 c = wgetch(stdscr);
1760 if (c == 'O')
1761 {
1762 c = wgetch(stdscr);
1763 switch (c)
1764 {
1765 case 'd':
1766 c = 0x204;
1767 break;
1768 case 'c':
1769 c = 0x206;
1770 break;
1771 default:
1772 if (optDebug)
1773 printMsg(currWin, "Unhandled ESC-O key sequence 0x%02x\n", c);
1774 break;
1775 }
1776 }
1777 /* ^[[ */
1778 else if (c == '[')
1779 {
1780 c = wgetch(stdscr);
1781 switch (c)
1782 {
1783 case 0x31:
1784 c = wgetch(stdscr);
1785 if (c >= 0x31 && c <= 0x39)
1786 c = KEY_F(c - 0x30);
1787 else
1788 c = ERR;
1789 break;
1790
1791 case 0x32:
1792 c = KEY_IC;
1793 break;
1794 case 0x33:
1795 c = KEY_DC;
1796 break;
1797
1798 case 0x35:
1799 c = KEY_PPAGE;
1800 break;
1801 case 0x36:
1802 c = KEY_NPAGE;
1803 break;
1804
1805 case 0x37:
1806 c = KEY_HOME;
1807 break;
1808 case 0x38:
1809 c = KEY_END;
1810 break;
1811
1812 default:
1813 if (optDebug)
1814 printMsg(currWin, "Unhandled ESC-[*~ key sequence 0x%02x\n", c);
1815 c = ERR;
1816 break;
1817 }
1818 /* Get the trailing ~ */
1819 if (c != ERR)
1820 wgetch(stdscr);
1821 }
1822 if (c >= 0x31 && c <= 0x39)
1823 {
1824 /* Chat window switching via Meta/Esc-[1..9] */
1825 int win = c - 0x31;
1826 if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
1827 {
1828 currWin = chatWindows[win];
1829 update = updateMain = TRUE;
1830 }
1831 c = ERR;
1832 }
1833 else
1834 {
1835 if (optDebug)
1836 printMsg(currWin, "Unhandled ESC key sequence 0x%02x\n", c);
1837 }
1838 }
1839 #if defined(__WIN32) && defined(PDCURSES)
1840 else if (c >= 0x198 && c <= 0x1a0)
1841 {
1842 /* Chat window switching via Meta/Esc-[1..9] */
1843 int win = c - 0x198;
1844 if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
1845 {
1846 currWin = chatWindows[win];
1847 update = updateMain = TRUE;
1848 }
1849 c = ERR;
1850 }
1851 #endif
1852
1853 switch (c)
1854 {
1855 #ifdef KEY_RESIZE
1856 case KEY_RESIZE:
1857 resize_term(0, 0);
1858 erase();
1859 timeout(SET_DELAY);
1860
1861 if (!initializeWindows())
1862 {
1863 errorMsg("Error resizing curses chatWindows\n");
1864 isError = TRUE;
1865 }
1866 update = updateMain = TRUE;
1867 break;
1868 #endif
1869
1870 case KEY_ENTER:
1871 case '\n':
1872 case '\r':
1873 /* Call the user input handler */
1874 if (editBuf->len > 0)
1875 {
1876 int result;
1877
1878 if (histMax > 0)
1879 {
1880 nn_editbuf_free(histBuf[SET_MAX_HISTORY+1]);
1881 histBuf[SET_MAX_HISTORY+1] = NULL;
1882 memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0]));
1883 }
1884
1885 histPos = 0;
1886 histBuf[1] = nn_editbuf_copy(editBuf);
1887 if (histMax < SET_MAX_HISTORY) histMax++;
1888
1889 nn_editbuf_insert(editBuf, editBuf->len, 0);
1890 result = nn_handle_input(conn, editBuf->data, editBuf->len);
1891
1892 nn_editbuf_clear(editBuf);
1893
1894 if (result < 0)
1895 {
1896 errorMsg("Fatal error handling user input: %s\n", editBuf->data);
1897 isError = TRUE;
1898 }
1899 else
1900 {
1901 /* Update time value of last sent message for unidle timeouts */
1902 prevTime = time(NULL);
1903 }
1904
1905 updateMain = update = TRUE;
1906 }
1907 break;
1908
1909 case KEY_UP: /* Backwards in input history */
1910 if (histPos == 0)
1911 {
1912 nn_editbuf_free(histBuf[0]);
1913 histBuf[0] = nn_editbuf_copy(editBuf);
1914 }
1915 if (histPos < histMax)
1916 {
1917 histPos++;
1918 nn_editbuf_free(editBuf);
1919 editBuf = nn_editbuf_copy(histBuf[histPos]);
1920 update = TRUE;
1921 }
1922 break;
1923
1924 case KEY_DOWN: /* Forwards in input history */
1925 if (histPos > 0)
1926 {
1927 histPos--;
1928 nn_editbuf_free(editBuf);
1929 editBuf = nn_editbuf_copy(histBuf[histPos]);
1930 update = TRUE;
1931 }
1932 break;
1933
1934 case 0x204: /* ctrl+left arrow = Skip words left */
1935 case 0x20b:
1936 while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
1937 editBuf->pos--;
1938 while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
1939 editBuf->pos--;
1940 update = TRUE;
1941 break;
1942
1943 case 0x206: /* ctrl+right arrow = Skip words right */
1944 case 0x210:
1945 while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
1946 editBuf->pos++;
1947 while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
1948 editBuf->pos++;
1949 update = TRUE;
1950 break;
1951
1952 case KEY_HOME:
1953 nn_editbuf_setpos(editBuf, 0);
1954 update = TRUE;
1955 break;
1956 case KEY_END:
1957 nn_editbuf_setpos(editBuf, editBuf->len);
1958 update = TRUE;
1959 break;
1960 case KEY_LEFT:
1961 nn_editbuf_setpos(editBuf, editBuf->pos - 1);
1962 update = TRUE;
1963 break;
1964 case KEY_RIGHT:
1965 nn_editbuf_setpos(editBuf, editBuf->pos + 1);
1966 update = TRUE;
1967 break;
1968
1969 case KEY_BACKSPACE:
1970 case 0x08:
1971 case 0x7f:
1972 nn_editbuf_delete(editBuf, editBuf->pos - 1);
1973 nn_editbuf_setpos(editBuf, editBuf->pos - 1);
1974 update = TRUE;
1975 break;
1976
1977 case KEY_DC: /* Delete character */
1978 nn_editbuf_delete(editBuf, editBuf->pos);
1979 update = TRUE;
1980 break;
1981
1982
1983 case KEY_IC: /* Ins = Toggle insert / overwrite mode */
1984 insertMode = !insertMode;
1985 update = TRUE;
1986 break;
1987
1988 case KEY_F(1): /* F1 = Print help */
1989 printHelp();
1990 updateMain = TRUE;
1991 break;
1992
1993 case KEY_F(2): /* F2 = Clear editbuffer */
1994 nn_editbuf_clear(editBuf);
1995 update = TRUE;
1996 break;
1997
1998 case KEY_F(5): /* F5 = Ignore mode */
1999 setIgnoreMode = !setIgnoreMode;
2000 printMsgQ(currWin, "Ignore mode = %s\n", setIgnoreMode ? "ON" : "OFF");
2001 break;
2002
2003 #if 0
2004 case KEY_F(8): /* F8 = Debug */
2005 optDebug = !optDebug;
2006 update = TRUE;
2007 break;
2008 #endif
2009
2010 case 0x03: /* ^C = quit */
2011 case KEY_F(9): /* F9 = Quit */
2012 printMsg(currWin, "Quitting per user request (%d/0x%x).\n", c, c);
2013 exitProg = TRUE;
2014 break;
2015
2016 case 0x09: /* Tab = complete username */
2017 performTabCompletion(editBuf);
2018 update = TRUE;
2019 break;
2020
2021 case 0x0c: /* Ctrl + L */
2022 updateWindows();
2023 update = updateMain = TRUE;
2024 break;
2025
2026 case KEY_NPAGE:
2027 case KEY_PPAGE:
2028 /* Page Up / Page Down */
2029 if (currWin != NULL)
2030 {
2031 int oldPos = currWin->pos;
2032
2033 currWin->pos += (c == KEY_NPAGE) ? -10 : +10;
2034
2035 if (currWin->pos < 0)
2036 currWin->pos = 0;
2037 else if (currWin->pos >= currWin->data->n - 10)
2038 currWin->pos = currWin->data->n - 10;
2039
2040 if (oldPos != currWin->pos)
2041 updateMain = TRUE;
2042 }
2043 break;
2044
2045 case ERR:
2046 /* Ignore */
2047 break;
2048
2049 default:
2050 if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6)
2051 {
2052 if (insertMode)
2053 nn_editbuf_insert(editBuf, editBuf->pos, c);
2054 else
2055 nn_editbuf_write(editBuf, editBuf->pos, c);
2056 nn_editbuf_setpos(editBuf, editBuf->pos + 1);
2057 update = TRUE;
2058 }
2059 else
2060 {
2061 if (optDebug)
2062 printMsg(currWin, "Unhandled key: 0x%02x\n", c);
2063 }
2064 break;
2065 }
2066 }
2067 while (c != ERR && !exitProg && ++cnt < 10);
2068
2069 updateMainWin(updateMain);
2070
2071 if (update || firstUpdate)
2072 {
2073 /* Update edit line */
2074 updateStatus();
2075 printEditBuf(editBuf);
2076 firstUpdate = FALSE; /* a nasty hack ... */
2077 }
2078
2079 } /* cursesInit */
2080
2081 if (++updateCount > 10)
2082 {
2083 time_t tmpTime = time(NULL);
2084 if (tmpTime - prevTime > SET_KEEPALIVE)
2085 {
2086 int n = random() % th_llist_length(setIdleMessages);
2087 qlist_t *node = th_llist_get_nth(setIdleMessages, n);
2088 nn_conn_send_msg(conn, optUserNameEnc, node->data);
2089 prevTime = tmpTime;
2090 }
2091
2092 if (!colorSet)
2093 {
2094 colorSet = TRUE;
2095 nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
2096 }
2097
2098 updateStatus();
2099 printEditBuf(editBuf);
2100 updateCount = 0;
2101 }
2102
2103 }
2104
2105 /* Shutdown */
2106 err_exit:
2107 th_cfg_free(cfg);
2108 th_free(homeDir);
2109 th_llist_free_func(setIdleMessages, th_free);
2110 nn_userhash_free(nnUsers);
2111 nn_editbuf_free(editBuf);
2112
2113 {
2114 int i;
2115 for (i = 0; i <= SET_MAX_HISTORY; i++)
2116 nn_editbuf_free(histBuf[i]);
2117
2118 for (i = 0; i < SET_MAX_WINDOWS; i++)
2119 nn_window_free(chatWindows[i]);
2120 }
2121
2122 #ifdef __WIN32
2123 if (errorMessages)
2124 {
2125 char *tmp;
2126 wclear(editWin);
2127 tmp = promptRequester(editWin, "Press enter to quit.\n", FALSE);
2128 th_free(tmp);
2129 }
2130 #endif
2131
2132 if (cursesInit)
2133 {
2134 if (curVis != ERR)
2135 curs_set(curVis);
2136 closeWindows();
2137 endwin();
2138 THMSG(1, "NCurses deinitialized.\n");
2139 }
2140
2141 #ifndef __WIN32
2142 if (errorMessages)
2143 THERR("%s", errorMessages);
2144 #endif
2145
2146 th_free(optUserNameEnc);
2147
2148 nn_conn_close(conn);
2149
2150 if (networkInit)
2151 nn_network_close();
2152
2153 THMSG(1, "Connection terminated.\n");
2154
2155 logFileClose();
2156
2157 return 0;
2158 }