PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/map/irc-bot.c

https://gitlab.com/evol/hercules
C | 533 lines | 332 code | 88 blank | 113 comment | 96 complexity | 2674fbca542ed56c0942e87ef798225b MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.0
  1. /**
  2. * This file is part of Hercules.
  3. * http://herc.ws - http://github.com/HerculesWS/Hercules
  4. *
  5. * Copyright (C) 2013-2015 Hercules Dev Team
  6. *
  7. * Hercules is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /**
  21. * Base Author: shennetsind @ http://herc.ws
  22. */
  23. #define HERCULES_CORE
  24. #include "irc-bot.h"
  25. #include "map/channel.h"
  26. #include "map/map.h"
  27. #include "map/pc.h"
  28. #include "common/cbasetypes.h"
  29. #include "common/memmgr.h"
  30. #include "common/nullpo.h"
  31. #include "common/random.h"
  32. #include "common/showmsg.h"
  33. #include "common/socket.h"
  34. #include "common/strlib.h"
  35. #include "common/timer.h"
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <string.h>
  39. //#define IRCBOT_DEBUG
  40. struct irc_bot_interface irc_bot_s;
  41. struct irc_bot_interface *ircbot;
  42. char send_string[IRC_MESSAGE_LENGTH];
  43. /**
  44. * Timer callback to (re-)connect to an IRC server
  45. * @see timer->do_timer
  46. */
  47. int irc_connect_timer(int tid, int64 tick, int id, intptr_t data) {
  48. struct hSockOpt opt;
  49. if( ircbot->isOn || ++ircbot->fails >= 3 )
  50. return 0;
  51. opt.silent = 1;
  52. opt.setTimeo = 0;
  53. ircbot->last_try = timer->gettick();
  54. if ((ircbot->fd = sockt->make_connection(ircbot->ip, channel->config->irc_server_port, &opt)) > 0) {
  55. sockt->session[ircbot->fd]->func_parse = ircbot->parse;
  56. sockt->session[ircbot->fd]->flag.server = 1;
  57. timer->add(timer->gettick() + 3000, ircbot->identify_timer, 0, 0);
  58. ircbot->isOn = true;
  59. }
  60. return 0;
  61. }
  62. /**
  63. * Timer callback to send identification commands to an IRC server
  64. * @see timer->do_timer
  65. */
  66. int irc_identify_timer(int tid, int64 tick, int id, intptr_t data) {
  67. if( !ircbot->isOn )
  68. return 0;
  69. sprintf(send_string, "USER HerculesWS%d 8 * : Hercules IRC Bridge",rnd()%777);
  70. ircbot->send(send_string);
  71. sprintf(send_string, "NICK %s", channel->config->irc_nick);
  72. ircbot->send(send_string);
  73. timer->add(timer->gettick() + 3000, ircbot->join_timer, 0, 0);
  74. return 0;
  75. }
  76. /**
  77. * Timer callback to join channels (and optionally send NickServ commands)
  78. * @see timer->do_timer
  79. */
  80. int irc_join_timer(int tid, int64 tick, int id, intptr_t data) {
  81. if( !ircbot->isOn )
  82. return 0;
  83. if (channel->config->irc_nick_pw[0] != '\0') {
  84. sprintf(send_string, "PRIVMSG NICKSERV : IDENTIFY %s", channel->config->irc_nick_pw);
  85. ircbot->send(send_string);
  86. if (channel->config->irc_use_ghost) {
  87. sprintf(send_string, "PRIVMSG NICKSERV : GHOST %s %s", channel->config->irc_nick, channel->config->irc_nick_pw);
  88. }
  89. }
  90. sprintf(send_string, "JOIN %s", channel->config->irc_channel);
  91. ircbot->send(send_string);
  92. ircbot->isIn = true;
  93. return 0;
  94. }
  95. /**
  96. * Search the handler for a given IRC received command
  97. * @param function_name Name of the received IRC command
  98. * @return Function pointer to the command handler, NULL in case
  99. * of unhandled commands
  100. */
  101. struct irc_func* irc_func_search(char* function_name) {
  102. int i;
  103. nullpo_retr(NULL, function_name);
  104. for(i = 0; i < ircbot->funcs.size; i++) {
  105. if( strcmpi(ircbot->funcs.list[i]->name, function_name) == 0 ) {
  106. return ircbot->funcs.list[i];
  107. }
  108. }
  109. return NULL;
  110. }
  111. /**
  112. * Parser for the IRC server connection
  113. * @see do_sockets
  114. */
  115. int irc_parse(int fd) {
  116. char *parse_string = NULL, *str_safe = NULL;
  117. if (sockt->session[fd]->flag.eof) {
  118. sockt->close(fd);
  119. ircbot->fd = 0;
  120. ircbot->isOn = false;
  121. ircbot->isIn = false;
  122. ircbot->fails = 0;
  123. ircbot->ip = sockt->host2ip(channel->config->irc_server);
  124. timer->add(timer->gettick() + 120000, ircbot->connect_timer, 0, 0);
  125. return 0;
  126. }
  127. if( !RFIFOREST(fd) )
  128. return 0;
  129. parse_string = (char*)RFIFOP(fd,0);
  130. parse_string[ RFIFOREST(fd) - 1 ] = '\0';
  131. parse_string = strtok_r(parse_string,"\r\n",&str_safe);
  132. while (parse_string != NULL) {
  133. ircbot->parse_sub(fd,parse_string);
  134. parse_string = strtok_r(NULL,"\r\n",&str_safe);
  135. }
  136. RFIFOSKIP(fd, RFIFOREST(fd));
  137. RFIFOFLUSH(fd);
  138. return 0;
  139. }
  140. /**
  141. * Parse the source from a received irc message
  142. * @param source Source string, as reported by the server
  143. * @param nick Pointer to a string where to return the nick (may not be NULL,
  144. * needs to be able to fit an IRC_NICK_LENGTH long string)
  145. * @param ident Pointer to a string where to return the ident (may not be
  146. * NULL, needs to be able to fit an IRC_IDENT_LENGTH long string)
  147. * @param host Pointer to a string where to return the hostname (may not be
  148. * NULL, needs to be able to fit an IRC_HOST_LENGTH long string)
  149. */
  150. void irc_parse_source(char *source, char *nick, char *ident, char *host) {
  151. int i, pos = 0;
  152. size_t len;
  153. unsigned char stage = 0;
  154. nullpo_retv(source);
  155. len = strlen(source);
  156. nullpo_retv(nick);
  157. nullpo_retv(ident);
  158. nullpo_retv(host);
  159. for(i = 0; i < len; i++) {
  160. if( stage == 0 && source[i] == '!' ) {
  161. safestrncpy(nick, &source[0], min(i + 1, IRC_NICK_LENGTH));
  162. pos = i+1;
  163. stage = 1;
  164. } else if( stage == 1 && source[i] == '@' ) {
  165. safestrncpy(ident, &source[pos], min(i - pos + 1, IRC_IDENT_LENGTH));
  166. safestrncpy(host, &source[i+1], min(len - i, IRC_HOST_LENGTH));
  167. break;
  168. }
  169. }
  170. }
  171. /**
  172. * Parse a received message from the irc server, and do the appropriate action
  173. * for the detected command
  174. * @param fd IRC server connection file descriptor
  175. * @param str Raw received message
  176. */
  177. void irc_parse_sub(int fd, char *str) {
  178. char source[180], command[60], buf1[IRC_MESSAGE_LENGTH], buf2[IRC_MESSAGE_LENGTH];
  179. char *target = buf1, *message = buf2;
  180. struct irc_func *func;
  181. nullpo_retv(str);
  182. source[0] = command[0] = buf1[0] = buf2[0] = '\0';
  183. if( str[0] == ':' )
  184. str++;
  185. if (sscanf(str, "%179s %59s %499s :%499[^\r\n]", source, command, buf1, buf2) == 3 && buf1[0] == ':') {
  186. // source command :message (i.e. QUIT)
  187. message = buf1+1;
  188. target = buf2;
  189. }
  190. if( command[0] == '\0' )
  191. return;
  192. if ((func = ircbot->func_search(command)) == NULL && (func = ircbot->func_search(source)) == NULL) {
  193. #ifdef IRCBOT_DEBUG
  194. ShowWarning("Unknown command received %s from %s\n",command,source);
  195. #endif // IRCBOT_DEBUG
  196. return;
  197. }
  198. func->func(fd,command,source,target,message);
  199. }
  200. /**
  201. * Send a raw command to the irc server
  202. * @param str Command to send
  203. */
  204. void irc_send(char *str) {
  205. size_t len;
  206. nullpo_retv(str);
  207. len = strlen(str) + 2;
  208. if (len > IRC_MESSAGE_LENGTH-3)
  209. len = IRC_MESSAGE_LENGTH-3;
  210. WFIFOHEAD(ircbot->fd, len);
  211. snprintf((char*)WFIFOP(ircbot->fd,0),IRC_MESSAGE_LENGTH, "%s\r\n", str);
  212. WFIFOSET(ircbot->fd, len);
  213. }
  214. /**
  215. * Handler for the PING IRC command (send back a PONG)
  216. * @see irc_parse_sub
  217. */
  218. void irc_pong(int fd, char *cmd, char *source, char *target, char *msg) {
  219. nullpo_retv(cmd);
  220. snprintf(send_string, IRC_MESSAGE_LENGTH, "PONG %s", cmd);
  221. ircbot->send(send_string);
  222. }
  223. /**
  224. * Handler for CTCP commands received via PRIVMSG
  225. * @see irc_privmsg
  226. */
  227. void irc_privmsg_ctcp(int fd, char *cmd, char *source, char *target, char *msg) {
  228. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  229. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  230. nullpo_retv(source);
  231. if( source[0] != '\0' )
  232. ircbot->parse_source(source,source_nick,source_ident,source_host);
  233. if( strcmpi(cmd,"ACTION") == 0 ) {
  234. if( ircbot->channel ) {
  235. snprintf(send_string, 150, "[ #%s ] * IRC.%s %s *",ircbot->channel->name,source_nick,msg);
  236. clif->channel_msg2(ircbot->channel,send_string);
  237. }
  238. } else if( strcmpi(cmd,"ERRMSG") == 0 ) {
  239. // Ignore it
  240. } else if( strcmpi(cmd,"FINGER") == 0 ) {
  241. // Ignore it
  242. } else if( strcmpi(cmd,"PING") == 0 ) {
  243. snprintf(send_string, IRC_MESSAGE_LENGTH, "NOTICE %s :\001PING %s\001",source_nick,msg);
  244. ircbot->send(send_string);
  245. } else if( strcmpi(cmd,"TIME") == 0 ) {
  246. time_t time_server; // variable for number of seconds (used with time() function)
  247. struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ...
  248. char temp[CHAT_SIZE_MAX];
  249. memset(temp, '\0', sizeof(temp));
  250. time(&time_server); // get time in seconds since 1/1/1970
  251. datetime = localtime(&time_server); // convert seconds in structure
  252. // like sprintf, but only for date/time (Sunday, November 02 2003 15:12:52)
  253. strftime(temp, sizeof(temp)-1, msg_txt(230), datetime); // Server time (normal time): %A, %B %d %Y %X.
  254. snprintf(send_string, IRC_MESSAGE_LENGTH, "NOTICE %s :\001TIME %s\001",source_nick,temp);
  255. ircbot->send(send_string);
  256. } else if( strcmpi(cmd,"VERSION") == 0 ) {
  257. snprintf(send_string, IRC_MESSAGE_LENGTH, "NOTICE %s :\001VERSION Hercules.ws IRC Bridge\001",source_nick);
  258. ircbot->send(send_string);
  259. #ifdef IRCBOT_DEBUG
  260. } else {
  261. ShowWarning("Unknown CTCP command received %s (%s) from %s\n",cmd,msg,source);
  262. #endif // IRCBOT_DEBUG
  263. }
  264. }
  265. /**
  266. * Handler for the PRIVMSG IRC command (action depends on the message contents)
  267. * @see irc_parse_sub
  268. */
  269. void irc_privmsg(int fd, char *cmd, char *source, char *target, char *msg) {
  270. size_t len = msg ? strlen(msg) : 0;
  271. nullpo_retv(source);
  272. nullpo_retv(target);
  273. if (msg && *msg == '\001' && len > 2 && msg[len - 1] == '\001') {
  274. // CTCP
  275. char command[IRC_MESSAGE_LENGTH], message[IRC_MESSAGE_LENGTH];
  276. command[0] = message[0] = '\0';
  277. sscanf(msg, "\001%499[^\001\r\n ] %499[^\r\n\001]\001", command, message);
  278. irc_privmsg_ctcp(fd, command, source, target, message);
  279. #ifdef IRCBOT_DEBUG
  280. } else if (strcmpi(target, channel->config->irc_nick) == 0) {
  281. ShowDebug("irc_privmsg: Received message from %s: '%s'\n", source ? source : "(null)", msg);
  282. #endif // IRCBOT_DEBUG
  283. } else if (msg && strcmpi(target, channel->config->irc_channel) == 0) {
  284. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  285. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  286. if( source[0] != '\0' )
  287. ircbot->parse_source(source,source_nick,source_ident,source_host);
  288. if( ircbot->channel ) {
  289. size_t padding_len = strlen(ircbot->channel->name) + strlen(source_nick) + 13;
  290. while (1) {
  291. snprintf(send_string, 150, "[ #%s ] IRC.%s : %s",ircbot->channel->name,source_nick,msg);
  292. clif->channel_msg2(ircbot->channel,send_string);
  293. //break; // Uncomment this line to truncate long messages instead of posting them as multiple lines
  294. if (strlen(msg) <= 149 - padding_len)
  295. break;
  296. msg += 149 - padding_len;
  297. }
  298. }
  299. }
  300. }
  301. /**
  302. * Handler for the JOIN IRC command (notify an in-game channel of users joining
  303. * the IRC channel)
  304. * @see irc_parse_sub
  305. */
  306. void irc_userjoin(int fd, char *cmd, char *source, char *target, char *msg) {
  307. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  308. nullpo_retv(source);
  309. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  310. if( source[0] != '\0' )
  311. ircbot->parse_source(source,source_nick,source_ident,source_host);
  312. if( ircbot->channel ) {
  313. snprintf(send_string, 150, "[ #%s ] User IRC.%s joined the channel.",ircbot->channel->name,source_nick);
  314. clif->channel_msg2(ircbot->channel,send_string);
  315. }
  316. }
  317. /**
  318. * Handler for the PART and QUIT IRC commands (notify an in-game channel of
  319. * users leaving the IRC channel)
  320. * @see irc_parse_sub
  321. */
  322. void irc_userleave(int fd, char *cmd, char *source, char *target, char *msg) {
  323. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  324. nullpo_retv(source);
  325. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  326. if( source[0] != '\0' )
  327. ircbot->parse_source(source,source_nick,source_ident,source_host);
  328. if( ircbot->channel ) {
  329. if (!strcmpi(cmd, "QUIT"))
  330. snprintf(send_string, 150, "[ #%s ] User IRC.%s left the channel. [Quit: %s]",ircbot->channel->name,source_nick,msg);
  331. else
  332. snprintf(send_string, 150, "[ #%s ] User IRC.%s left the channel. [%s]",ircbot->channel->name,source_nick,msg);
  333. clif->channel_msg2(ircbot->channel,send_string);
  334. }
  335. }
  336. /**
  337. * Handler for the NICK IRC commands (notify an in-game channel of users
  338. * changing their name while in the IRC channel)
  339. * @see irc_parse_sub
  340. */
  341. void irc_usernick(int fd, char *cmd, char *source, char *target, char *msg) {
  342. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  343. nullpo_retv(source);
  344. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  345. if( source[0] != '\0' )
  346. ircbot->parse_source(source,source_nick,source_ident,source_host);
  347. if( ircbot->channel ) {
  348. snprintf(send_string, 150, "[ #%s ] User IRC.%s is now known as IRC.%s",ircbot->channel->name,source_nick,msg);
  349. clif->channel_msg2(ircbot->channel,send_string);
  350. }
  351. }
  352. /**
  353. * Relay a chat message to the irc channel the bot is connected to
  354. * @param name Sender's name
  355. * @param msg Message text
  356. */
  357. void irc_relay(const char *name, const char *msg)
  358. {
  359. if (!ircbot->isIn)
  360. return;
  361. nullpo_retv(msg);
  362. if (name)
  363. sprintf(send_string,"PRIVMSG %s :[ %s ] : %s", channel->config->irc_channel, name, msg);
  364. else
  365. sprintf(send_string,"PRIVMSG %s :%s", channel->config->irc_channel, msg);
  366. ircbot->send(send_string);
  367. }
  368. /**
  369. * IRC bot initializer
  370. */
  371. void irc_bot_init(bool minimal) {
  372. /// Command handlers
  373. const struct irc_func irc_func_base[] = {
  374. { "PING" , ircbot->pong },
  375. { "PRIVMSG", ircbot->privmsg },
  376. { "JOIN", ircbot->userjoin },
  377. { "QUIT", ircbot->userleave },
  378. { "PART", ircbot->userleave },
  379. { "NICK", ircbot->usernick },
  380. };
  381. struct irc_func* function;
  382. int i;
  383. if (minimal)
  384. return;
  385. if (!channel->config->irc)
  386. return;
  387. if (!(ircbot->ip = sockt->host2ip(channel->config->irc_server))) {
  388. ShowError("Unable to resolve '%s' (irc server), disabling irc channel...\n", channel->config->irc_server);
  389. channel->config->irc = false;
  390. return;
  391. }
  392. ircbot->funcs.size = ARRAYLENGTH(irc_func_base);
  393. CREATE(ircbot->funcs.list,struct irc_func*,ircbot->funcs.size);
  394. for( i = 0; i < ircbot->funcs.size; i++ ) {
  395. CREATE(function, struct irc_func, 1);
  396. safestrncpy(function->name, irc_func_base[i].name, sizeof(function->name));
  397. function->func = irc_func_base[i].func;
  398. ircbot->funcs.list[i] = function;
  399. }
  400. ircbot->fails = 0;
  401. ircbot->fd = 0;
  402. ircbot->isIn = false;
  403. ircbot->isOn = false;
  404. timer->add_func_list(ircbot->connect_timer, "irc_connect_timer");
  405. timer->add(timer->gettick() + 7000, ircbot->connect_timer, 0, 0);
  406. }
  407. /**
  408. * IRC bot finalizer
  409. */
  410. void irc_bot_final(void) {
  411. int i;
  412. if (!channel->config->irc)
  413. return;
  414. if( ircbot->isOn ) {
  415. ircbot->send("QUIT :Hercules is shutting down");
  416. sockt->close(ircbot->fd);
  417. }
  418. for( i = 0; i < ircbot->funcs.size; i++ ) {
  419. aFree(ircbot->funcs.list[i]);
  420. }
  421. aFree(ircbot->funcs.list);
  422. }
  423. /**
  424. * IRC bot interface defaults initializer
  425. */
  426. void ircbot_defaults(void) {
  427. ircbot = &irc_bot_s;
  428. ircbot->channel = NULL;
  429. ircbot->init = irc_bot_init;
  430. ircbot->final = irc_bot_final;
  431. ircbot->parse = irc_parse;
  432. ircbot->parse_sub = irc_parse_sub;
  433. ircbot->parse_source = irc_parse_source;
  434. ircbot->func_search = irc_func_search;
  435. ircbot->connect_timer = irc_connect_timer;
  436. ircbot->identify_timer = irc_identify_timer;
  437. ircbot->join_timer = irc_join_timer;
  438. ircbot->send = irc_send;
  439. ircbot->relay = irc_relay;
  440. ircbot->pong = irc_pong;
  441. ircbot->privmsg = irc_privmsg;
  442. ircbot->userjoin = irc_userjoin;
  443. ircbot->userleave = irc_userleave;
  444. ircbot->usernick = irc_usernick;
  445. }