1 /* Copyright (C) 2017 rofl0r
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License along
14 with this program; if not, write to the Free Software Foundation, Inc.,
15 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 #pragma RcB2 LINK "-lmujs"
32 #include "simplecfg.c"
34 #define TRANSACT_TIME 500 * 1000
37 static const char hextab
[] = "0123456789abcdef";
39 static char* decode(const char *in
, char* out
) {
40 const unsigned char* pi
= (void*)in
;
43 if(*pi
> 20 && *pi
< 128) *po
++ = *pi
++;
47 *po
++= hextab
[(*pi
& 0xf0) >> 4];
48 *po
++= hextab
[*pi
++ & 15];
55 static int split(const char *in
, char sep
, int splitcount
, ...) {
57 va_start(ap
, splitcount
);
60 const char *start
= in
;
61 while(i
< splitcount
-1 || !splitcount
) {
62 char * out
= va_arg(ap
, char*);
63 if(!out
&& !splitcount
) break;
65 while(start
[idx
] && start
[idx
] != sep
) idx
++;
66 memcpy(out
, start
, idx
);
68 if(!start
[idx
]) { r
= 0; goto ret
; }
73 char * out
= va_arg(ap
, char*);
81 #define chk(X, ACTION) if(X) { \
82 rocksock_error_dprintf(2, s); \
85 #define chk2(X, ACTION) if(X) { ACTION; }
89 static rocksock rs
, *s
= &rs
;
90 static rsirc ircb
, *irc
= &ircb
;
91 static rs_proxy proxies
[2];
92 static int done_rs_init
;
94 //PING :kornbluth.freenode.net
95 static int ping_handler(char *buf
) {
97 snprintf(b
, sizeof(b
), "PONG %s", buf
+1);
98 return rsirc_sendline(irc
, b
);
101 static char *own_nick
;
102 static char *alternate_nick
;
103 static char nick1
[32];
104 static char nick2
[32];
105 static char nick3
[32];
106 static char proxy
[512];
107 static char savefile
[64];
108 static char host
[256];
113 static int want_quit
;
117 static void jscb_strings_command(const char* cmd
, int args
, ...) {
121 js_getglobal(J
, cmd
);
123 for (i
= 0; i
< args
; ++i
) {
124 const char *a
= va_arg(ap
, const char *);
128 if (js_pcall(J
, args
))
129 dprintf(2, "error calling %s: %s\n", cmd
, js_tostring(J
, -1));
133 static void jscb_onconnect(void) {
134 jscb_strings_command("connect", 0);
136 static void jscb_botnick(const char* nick
) {
137 jscb_strings_command("botnick", 1, nick
);
140 /* join: mask, cmd, chan
141 part: mask, cmd, chan, :"msg"
142 quit: mask, cmd, :msg
143 kick: mask, cmd, chan, whom, :msg
144 notice: mask, cmd, dest, :msg
145 privmsg: mask, cmd, dest, :msg
146 353: mask, cmd, who, =, chan, :names
147 366: mask, cmd, nick, chan, :End of /NAMES list.
148 nick: mask, cmd, :newname
151 a_join
= 0, a_part
, a_quit
, a_kick
,
152 a_notice
, a_privmsg
, a_names
, a_endnames
,
155 /* number of IRC arguments after the obligatory mask, cmd */
156 static const char action_args
[] = {
157 [a_join
] = 1, [a_part
] = 2,
158 [a_quit
] = 1, [a_kick
] = 3,
159 [a_notice
] = 2, [a_privmsg
] = 2,
160 [a_names
] = 4, [a_endnames
] = 3,
163 /* we care only about up to 4 IRC arguments after mask, cmd */
164 static const char actionarg_msgadd
[][4] = { /* this is to add 1 to the msg argument so the leading ':' is skipped */
165 [a_join
] = "\0\0\0\0", [a_part
] = "\0\1\0\0",
166 [a_quit
] = "\1\0\0\0", [a_kick
] = "\0\0\1\0",
167 [a_notice
] = "\0\1\0\0", [a_privmsg
] = "\0\1\0\0",
168 [a_names
] = "\0\0\0\1", [a_endnames
] = "\0\0\0\0",
169 [a_nick
] = "\1\0\0\0",
172 /* we pass up to 5 arguments to dispatch functions.
173 argument 0 is always nick, which is derived from mask.
174 the original CMD argument is dropped (for example KICK or 366)
175 nick+mask always go together in that order, i.e. 0,1. */
176 static const char action_order
[][5] = {
177 [a_join
] ="\2\0\1\n\n", [a_part
] = "\2\0\1\3\n",
178 [a_quit
] ="\0\1\2\n\n", [a_kick
] = "\0\1\3\2\4",
179 [a_notice
]="\2\0\1\3\n", [a_privmsg
]= "\2\0\1\3\n",
180 [a_names
] ="\4\5\n\n\n", [a_endnames
]="\3\2\n\n\n",
181 [a_nick
] = "\0\2\n\n\n",
183 static const char dispatchtbl
[][14]={
184 [a_join
] = "joinhandler", [a_part
] = "parthandler",
185 [a_quit
] = "quithandler", [a_kick
] = "kickhandler",
186 [a_notice
] = "noticehandler", [a_privmsg
] = "msghandler",
187 [a_names
] = "nameshandler", [a_endnames
] = "selfjoin", /*we use end of names as a signal that we're now in that chan*/
188 [a_nick
] = "nickchange",
190 static unsigned action_dispatch_argcount(enum action a
) {
192 while(i
< sizeof(action_order
[0]) && action_order
[a
][i
]!='\n') i
++;
195 static const char *action_arg(enum action a
, int pos
, const char* args
[]) {
196 int l
= action_order
[a
][pos
];
197 return l
== '\n' ? 0 : args
[l
];
199 static void action_dispatch(enum action a
, const char* args
[]) {
200 const char *a0
= action_arg(a
, 0, args
);
201 const char *a1
= action_arg(a
, 1, args
);
202 const char *a2
= action_arg(a
, 2, args
);
203 const char *a3
= action_arg(a
, 3, args
);
204 const char *a4
= action_arg(a
, 4, args
);
205 unsigned argcount
= action_dispatch_argcount(a
);
206 jscb_strings_command(dispatchtbl
[a
], argcount
, a0
, a1
, a2
, a3
, a4
);
208 static void prep_action_handler(char *buf
, size_t cmdpos
, enum action a
) {
217 a1
[0] = a2
[0] = a3
[0] = a4
[0] = i
= 0;
218 split(buf
+1, ' ', 2+action_args
[a
], mask
, cmd
, a1
, a2
, a3
, a4
);
219 while(mask
[i
] != '!' && mask
[i
] != ' ') i
++;
220 memcpy(nick
, mask
, i
);
222 unsigned a1off
= a1
[0] ? actionarg_msgadd
[a
][0] : 0;
223 unsigned a2off
= a2
[0] ? actionarg_msgadd
[a
][1] : 0;
224 unsigned a3off
= a3
[0] ? actionarg_msgadd
[a
][2] : 0;
225 unsigned a4off
= a4
[0] ? actionarg_msgadd
[a
][3] : 0;
226 const char* args
[6] = {nick
, mask
, a1
+a1off
, a2
+a2off
, a3
+a3off
, a4
+a4off
};
227 action_dispatch(a
, args
);
230 static int motd_finished() {
235 void switch_names(void) {
237 own_nick
= alternate_nick
;
239 rsirc_sendlinef(irc
, "NICK %s", own_nick
);
242 static unsigned atou(const char *n
) {
244 while(isspace(*n
)) n
++;
245 while(isdigit(*n
)) r
= r
*10+(*(n
++)-'0');
249 int read_cb(char* buf
, size_t bufsize
) {
252 while(!isspace(buf
[i
])) i
++;
253 unsigned cmd
= atou(buf
+i
);
255 case 0: /* no number */
257 while(!isspace(buf
[j
])) j
++;
260 if(!memcmp(buf
+i
,"JOIN", 4))
261 prep_action_handler(buf
, i
, a_join
);
262 else if(!memcmp(buf
+i
,"PART", 4))
263 prep_action_handler(buf
, i
, a_part
);
264 else if(!memcmp(buf
+i
,"QUIT", 4))
265 prep_action_handler(buf
, i
, a_quit
);
266 else if(!memcmp(buf
+i
,"KICK", 4))
267 prep_action_handler(buf
, i
, a_kick
);
268 else if(!memcmp(buf
+i
,"NICK", 4))
269 prep_action_handler(buf
, i
, a_nick
);
272 if(!memcmp(buf
+i
,"PRIVMSG", 7))
273 prep_action_handler(buf
, i
, a_privmsg
);
276 if(!memcmp(buf
+i
,"NOTICE", 6))
277 prep_action_handler(buf
, i
, a_notice
);
282 /* status messages having the bot nickname in it, like:
283 :rajaniemi.freenode.net 255 foobot :I have 8369 clients and 1 servers */
284 case 5: case 250: case 251: case 252: case 253:
285 case 254: case 255: case 265: case 266: case 375:
287 while(!isspace(buf
[i
])) i
++;
289 while(!isspace(buf
[i
])) i
++;
291 jscb_botnick(buf
+ j
);
293 case 376: motd_finished(); break;
294 //:kornbluth.freenode.net 433 * foobot :Nickname is already in use.
296 if(alternate_nick
) switch_names();
298 rocksock_disconnect(s
);
303 if(i
>= 512) dprintf(2, "caught canary bird\n");
306 prep_action_handler(buf
, i
, a_names
);
309 prep_action_handler(buf
, i
, a_endnames
);
314 while(!isspace(buf
[i
])) i
++;
315 if(i
== 4 && !memcmp(buf
, "PING", 4)) ping_handler(buf
+ 5);
321 static int load_cfg(void) {
322 FILE *cfg
= cfg_open(cfgfilename
);
323 if(!cfg
) { perror("fopen"); return 0; }
324 cfg_getstr(cfg
, "nick1", nick1
, sizeof(nick1
));
326 cfg_getstr(cfg
, "nick2", nick2
, sizeof(nick2
));
327 cfg_getstr(cfg
, "nick3", nick3
, sizeof(nick3
));
328 cfg_getstr(cfg
, "proxy", proxy
, sizeof(proxy
));
329 int hostnr
= (rand()%2)+1;
332 snprintf(hb
, sizeof hb
, "host%d", hostnr
);
333 if(!cfg_getstr(cfg
, hb
, host
, sizeof(host
)) && hostnr
== 2) { hostnr
= 1; goto again
; }
334 port
= cfg_getint(cfg
, "port");
335 use_ssl
= cfg_getint(cfg
, "ssl");
336 cfg_getstr(cfg
, "savefile", savefile
, sizeof savefile
);
338 dprintf(2, "error: savefile config item not set!\n");
343 if(*nick2
) alternate_nick
= nick2
;
347 int connect_it(void) {
350 rocksock_disconnect(s
);
353 rocksock_init(s
, proxies
);
355 rocksock_set_timeout(s
, 36000);
357 chk(rocksock_add_proxy_fromstring(s
, proxy
), exit(1));
358 chk(rocksock_connect(s
, host
, port
, use_ssl
), goto err
);
359 chk(rsirc_init(irc
, s
), goto err
);
360 chk(rsirc_handshake(irc
, host
, own_nick
, "foo"), goto err
);
364 usleep(TRANSACT_TIME
);
368 static void js_sendline(js_State
*J
) {
369 const char* msg
= js_tostring(J
, 1);
370 int ret
= rsirc_sendline(irc
, msg
);
371 js_pushnumber(J
, ret
);
374 static void js_privmsg(js_State
*J
) {
375 const char* chan
= js_tostring(J
, 1);
376 const const char* msg
= js_tostring(J
, 2);
377 int ret
= rsirc_privmsg(irc
, chan
, msg
);
378 js_pushnumber(J
, ret
);
381 static void js_errmsg(js_State
*J
) {
382 js_pushstring(J
, rocksock_strerror(s
));
385 static int reload_script() {
386 if (js_dofile(J
, "ircbot.js"))
392 static void js_reload(js_State
*J
) {
393 int ret
= reload_script();
394 js_pushboolean(J
, !ret
);
397 static void js_disconnect(js_State
*J
) {
398 rocksock_disconnect(s
);
402 static void js_debugprint(js_State
*J
) {
403 const char* msg
= js_tostring(J
, 1);
404 dprintf(2, "%s\n", msg
);
408 static void js_writesettings(js_State
*J
) {
409 int fd
, fail
= 1; size_t l
;
410 const char* contents
= js_tostring(J
, 1);
411 if( 0 == contents
) goto err
;
412 if( 0 == (l
= strlen(contents
))) goto err
;
413 if(-1 == (fd
= open(savefile
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0660))) goto err
;
414 if( l
!= write(fd
, contents
, l
)) goto fderr
;
419 js_pushboolean(J
, !fail
);
422 static void js_readsettings(js_State
*J
) {
426 if(stat(savefile
, &st
)) goto err
;
427 if(-1 == (fd
= open(savefile
, O_RDONLY
))) goto err
;
428 if( 0 == (contents
= malloc(st
.st_size
+ 1))) goto fderr
;
429 if(st
.st_size
!= read(fd
, contents
, st
.st_size
)) goto mallerr
;
431 contents
[st
.st_size
] = 0;
432 js_pushstring(J
, contents
);
438 if(failed
) js_pushundefined(J
);
441 static int syntax() { dprintf(2, "need filename of cfg file\n"); return 1; }
442 int main(int argc
, char** argv
) {
443 if(argc
<= 1) return syntax();
444 cfgfilename
= argv
[1];
446 if(!load_cfg()) return 1;
448 J
= js_newstate(NULL
, NULL
, JS_STRICT
);
450 js_newcfunction(J
, js_privmsg
, "privmsg", 2);
451 js_setglobal(J
, "privmsg");
453 js_newcfunction(J
, js_sendline
, "send", 1);
454 js_setglobal(J
, "send");
456 js_newcfunction(J
, js_errmsg
, "errmsg", 0);
457 js_setglobal(J
, "errmsg");
459 js_newcfunction(J
, js_reload
, "reload", 0);
460 js_setglobal(J
, "reload");
462 js_newcfunction(J
, js_writesettings
, "writesettings", 1);
463 js_setglobal(J
, "writesettings");
465 js_newcfunction(J
, js_readsettings
, "readsettings", 0);
466 js_setglobal(J
, "readsettings");
468 js_newcfunction(J
, js_disconnect
, "disconnect", 0);
469 js_setglobal(J
, "disconnect");
471 js_newcfunction(J
, js_debugprint
, "debugprint", 1);
472 js_setglobal(J
, "debugprint");
474 if(reload_script()) {
475 dprintf(2, "error: loading ircbot.js failed\n");
484 while(!connect_it());
486 char line
[512+4] = {0};
487 static const char canary
[4] = " 66\0";
488 time_t last_packet
= time(0);
491 char decodebuf
[512*4];
492 chk(rsirc_process(irc
, line
, &rcvd
), goto conn
);
494 last_packet
= time(0);
495 memcpy(line
+512, canary
, 4); /* protect against evil server */
496 dprintf(2, "LEN %zu - %s\n", rcvd
, decode(line
, decodebuf
));
497 chk(read_cb(line
, sizeof line
), goto conn
);
499 if(last_packet
+ 5*60 < time(0)) {
500 dprintf(2, "timeout occured, reconnecting\n");
508 rocksock_disconnect(s
);