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.
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_onjoinself(const char* chan
, const char* nick
) {
137 jscb_strings_command("selfjoin", 2, chan
, nick
);
139 static void jscb_botnick(const char* nick
) {
140 jscb_strings_command("botnick", 1, nick
);
142 static int on_joinself(const char* chan
, const char* nick
) {
143 jscb_onjoinself(chan
, nick
);
147 /* join: mask, cmd, chan
148 part: mask, cmd, chan, :"msg"
149 quit: mask, cmd, :msg
150 kick: mask, cmd, chan, whom, :msg
151 notice: mask, cmd, dest, :msg
152 privmsg: mask, cmd, dest, :msg
155 a_join
= 0, a_part
, a_quit
, a_kick
,
158 static const char action_args
[] = {
159 [a_join
] = 1, [a_part
] = 2,
160 [a_quit
] = 1, [a_kick
] = 3,
161 [a_notice
] = 2, [a_privmsg
] = 2,
163 static const char actionarg_msgadd
[][3] = { /* this is to add 1 to the msg argument so the leading ':' is skipped */
164 [a_join
] = "\0\0\0", [a_part
] = "\0\1\0",
165 [a_quit
] = "\1\0\0", [a_kick
] = "\0\0\1",
166 [a_notice
] = "\0\1\0", [a_privmsg
] = "\0\1\0",
169 /* nick+mask always go together in that order, i.e. 0,1. */
170 static const char action_order
[][5] = {
171 [a_join
] ="\2\0\1\n\n", [a_part
] = "\2\0\1\3\n",
172 [a_quit
] ="\0\1\2\n\n", [a_kick
] = "\0\1\3\2\4",
173 [a_notice
]="\2\0\1\3\n", [a_privmsg
]= "\2\0\1\3\n",
175 static const char dispatchtbl
[][14]={
176 [a_join
] = "joinhandler", [a_part
] = "parthandler",
177 [a_quit
] = "quithandler", [a_kick
] = "kickhandler",
178 [a_notice
] = "noticehandler", [a_privmsg
] = "msghandler",
180 static const char *action_arg(enum action a
, int pos
, const char* args
[]) {
181 int l
= action_order
[a
][pos
];
182 return l
== '\n' ? 0 : args
[l
];
184 static void action_dispatch(enum action a
, const char* args
[]) {
185 const char *a0
= action_arg(a
, 0, args
);
186 const char *a1
= action_arg(a
, 1, args
);
187 const char *a2
= action_arg(a
, 2, args
);
188 const char *a3
= action_arg(a
, 3, args
);
189 const char *a4
= action_arg(a
, 4, args
);
190 jscb_strings_command(dispatchtbl
[a
], action_args
[a
]+2, a0
, a1
, a2
, a3
, a4
);
192 static void prep_action_handler(char *buf
, size_t cmdpos
, enum action a
) {
200 a1
[0] = a2
[0] = a3
[0] = i
= 0;
201 split(buf
+1, ' ', 2+action_args
[a
], mask
, cmd
, a1
, a2
, a3
);
202 while(mask
[i
] != '!' && mask
[i
] != ' ') i
++;
203 memcpy(nick
, mask
, i
);
205 unsigned a1off
= a1
[0] ? actionarg_msgadd
[a
][0] : 0;
206 unsigned a2off
= a2
[0] ? actionarg_msgadd
[a
][1] : 0;
207 unsigned a3off
= a3
[0] ? actionarg_msgadd
[a
][2] : 0;
208 const char* args
[5] = {nick
, mask
, a1
+a1off
, a2
+a2off
, a3
+a3off
};
209 action_dispatch(a
, args
);
212 static int motd_finished() {
217 void switch_names(void) {
219 own_nick
= alternate_nick
;
221 rsirc_sendlinef(irc
, "NICK %s", own_nick
);
224 int read_cb(char* buf
, size_t bufsize
) {
227 while(!isspace(buf
[i
])) i
++;
228 int cmd
= atoi(buf
+i
);
230 case 0: /* no number */
232 while(!isspace(buf
[j
])) j
++;
235 if(!memcmp(buf
+i
,"JOIN", 4))
236 prep_action_handler(buf
, i
, a_join
);
237 else if(!memcmp(buf
+i
,"PART", 4))
238 prep_action_handler(buf
, i
, a_part
);
239 else if(!memcmp(buf
+i
,"QUIT", 4))
240 prep_action_handler(buf
, i
, a_quit
);
241 else if(!memcmp(buf
+i
,"KICK", 4))
242 prep_action_handler(buf
, i
, a_kick
);
245 if(!memcmp(buf
+i
,"PRIVMSG", 7))
246 prep_action_handler(buf
, i
, a_privmsg
);
249 if(!memcmp(buf
+i
,"NOTICE", 6))
250 prep_action_handler(buf
, i
, a_notice
);
255 /* status messages having the bot nickname in it, like:
256 :rajaniemi.freenode.net 255 foobot :I have 8369 clients and 1 servers */
257 case 5: case 250: case 251: case 252: case 253:
258 case 254: case 255: case 265: case 266: case 375:
260 while(!isspace(buf
[i
])) i
++;
262 while(!isspace(buf
[i
])) i
++;
264 jscb_botnick(buf
+ j
);
266 case 376: motd_finished(); break;
267 //:kornbluth.freenode.net 433 * foobot :Nickname is already in use.
269 if(alternate_nick
) switch_names();
271 rocksock_disconnect(s
);
277 while(!isspace(buf
[++i
]));
280 while(!isspace(buf
[++i
]));
283 while(!isspace(buf
[++i
]));
285 on_joinself(buf
+ j
, buf
+ k
);
290 while(!isspace(buf
[i
])) i
++;
291 if(i
== 4 && !memcmp(buf
, "PING", 4)) ping_handler(buf
+ 5);
297 static int load_cfg(void) {
298 FILE *cfg
= cfg_open(cfgfilename
);
299 if(!cfg
) { perror("fopen"); return 0; }
300 cfg_getstr(cfg
, "nick1", nick1
, sizeof(nick1
));
302 cfg_getstr(cfg
, "nick2", nick2
, sizeof(nick2
));
303 cfg_getstr(cfg
, "nick3", nick3
, sizeof(nick3
));
304 cfg_getstr(cfg
, "proxy", proxy
, sizeof(proxy
));
305 int hostnr
= (rand()%2)+1;
308 snprintf(hb
, sizeof hb
, "host%d", hostnr
);
309 if(!cfg_getstr(cfg
, hb
, host
, sizeof(host
)) && hostnr
== 2) { hostnr
= 1; goto again
; }
310 port
= cfg_getint(cfg
, "port");
311 use_ssl
= cfg_getint(cfg
, "ssl");
312 cfg_getstr(cfg
, "savefile", savefile
, sizeof savefile
);
314 dprintf(2, "error: savefile config item not set!\n");
319 if(*nick2
) alternate_nick
= nick2
;
323 int connect_it(void) {
326 rocksock_disconnect(s
);
329 rocksock_init(s
, proxies
);
331 rocksock_set_timeout(s
, 36000);
333 chk(rocksock_add_proxy_fromstring(s
, proxy
), exit(1));
334 chk(rocksock_connect(s
, host
, port
, use_ssl
), goto err
);
335 chk(rsirc_init(irc
, s
), goto err
);
336 chk(rsirc_handshake(irc
, host
, own_nick
, "foo"), goto err
);
340 usleep(TRANSACT_TIME
);
344 static void js_sendline(js_State
*J
) {
345 const char* msg
= js_tostring(J
, 1);
346 int ret
= rsirc_sendline(irc
, msg
);
347 js_pushnumber(J
, ret
);
350 static void js_privmsg(js_State
*J
) {
351 const char* chan
= js_tostring(J
, 1);
352 const const char* msg
= js_tostring(J
, 2);
353 int ret
= rsirc_privmsg(irc
, chan
, msg
);
354 js_pushnumber(J
, ret
);
357 static void js_errmsg(js_State
*J
) {
358 js_pushstring(J
, rocksock_strerror(s
));
361 static int reload_script() {
362 if (js_dofile(J
, "ircbot.js"))
368 static void js_reload(js_State
*J
) {
369 int ret
= reload_script();
370 js_pushboolean(J
, !ret
);
373 static void js_disconnect(js_State
*J
) {
374 rocksock_disconnect(s
);
378 static void js_debugprint(js_State
*J
) {
379 const char* msg
= js_tostring(J
, 1);
380 dprintf(2, "%s\n", msg
);
384 static void js_writesettings(js_State
*J
) {
385 int fd
, fail
= 1; size_t l
;
386 const char* contents
= js_tostring(J
, 1);
387 if( 0 == contents
) goto err
;
388 if( 0 == (l
= strlen(contents
))) goto err
;
389 if(-1 == (fd
= open(savefile
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0660))) goto err
;
390 if( l
!= write(fd
, contents
, l
)) goto fderr
;
395 js_pushboolean(J
, !fail
);
398 static void js_readsettings(js_State
*J
) {
402 if(stat(savefile
, &st
)) goto err
;
403 if(-1 == (fd
= open(savefile
, O_RDONLY
))) goto err
;
404 if( 0 == (contents
= malloc(st
.st_size
+ 1))) goto fderr
;
405 if(st
.st_size
!= read(fd
, contents
, st
.st_size
)) goto mallerr
;
407 contents
[st
.st_size
] = 0;
408 js_pushstring(J
, contents
);
414 if(failed
) js_pushundefined(J
);
417 static int syntax() { dprintf(2, "need filename of cfg file\n"); return 1; }
418 int main(int argc
, char** argv
) {
419 if(argc
<= 1) return syntax();
420 cfgfilename
= argv
[1];
422 if(!load_cfg()) return 1;
424 J
= js_newstate(NULL
, NULL
, JS_STRICT
);
426 js_newcfunction(J
, js_privmsg
, "privmsg", 2);
427 js_setglobal(J
, "privmsg");
429 js_newcfunction(J
, js_sendline
, "send", 1);
430 js_setglobal(J
, "send");
432 js_newcfunction(J
, js_errmsg
, "errmsg", 0);
433 js_setglobal(J
, "errmsg");
435 js_newcfunction(J
, js_reload
, "reload", 0);
436 js_setglobal(J
, "reload");
438 js_newcfunction(J
, js_writesettings
, "writesettings", 1);
439 js_setglobal(J
, "writesettings");
441 js_newcfunction(J
, js_readsettings
, "readsettings", 0);
442 js_setglobal(J
, "readsettings");
444 js_newcfunction(J
, js_disconnect
, "disconnect", 0);
445 js_setglobal(J
, "disconnect");
447 js_newcfunction(J
, js_debugprint
, "debugprint", 1);
448 js_setglobal(J
, "debugprint");
450 if(reload_script()) {
451 dprintf(2, "error: loading ircbot.js failed\n");
460 while(!connect_it());
464 char decodebuf
[512*4];
465 chk(rsirc_process(irc
, line
, &rcvd
), goto conn
);
467 dprintf(2, "LEN %zu - %s\n", rcvd
, decode(line
, decodebuf
));
468 chk(read_cb(line
, sizeof line
), goto conn
);
474 rocksock_disconnect(s
);