fixed path of mysql/errmsg.h (OSX compilation fix)
[NeonServV5.git] / src / bots.c
1 /* bots.c - NeonServ v5.6
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18 #include "bots.h"
19 #include "timeq.h"
20 #include "mysqlConn.h"
21 #include "ClientSocket.h"
22 #include "UserNode.h"
23 #include "ChanNode.h"
24 #include "ChanUser.h"
25 #include "version.h"
26 #include "modcmd.h"
27 #include "DBHelper.h"
28 #include "IRCEvents.h"
29
30 struct cmd_bot_alias {
31     int botid;
32     char *alias;
33     struct cmd_bot_alias *next;
34 };
35
36 static struct cmd_bot_alias *bot_aliases = NULL;
37
38 static void start_zero_bots() {
39     struct ClientSocket *client;
40     MYSQL_RES *res, *res2;
41     MYSQL_ROW row;
42     
43     printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '0' AND `active` = '1'");
44     res = mysql_use();
45     
46     while ((row = mysql_fetch_row(res)) != NULL) {
47         client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
48         client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
49         client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
50         client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
51         client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
52         client->botid = 0;
53         client->clientid = atoi(row[7]);
54         connect_socket(client);
55         
56         printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d'", client->clientid);
57         res2 = mysql_use();
58         while ((row = mysql_fetch_row(res2)) != NULL) {
59             if(bind_botwise_cmd_to_command(0, client->clientid, row[0], row[1])) {
60                 if(row[2] && strcmp(row[2], "")) {
61                     bind_botwise_set_parameters(0, client->clientid, row[0], row[2]);
62                 }
63                 if(row[3]) {
64                     bind_botwise_set_global_access(0, client->clientid, row[0], atoi(row[3]));
65                 }
66                 if(row[4]) {
67                     bind_botwise_set_channel_access(0, client->clientid, row[0], row[4]);
68                 }
69                 if(strcmp(row[5], "0"))
70                     bind_botwise_set_bind_flags(0, client->clientid, row[0], atoi(row[5]));
71             }
72         }
73         bind_botwise_unbound_required_functions(0, client->clientid);
74     }
75 }
76
77 static void zero_bots_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
78     MYSQL_RES *res;
79     MYSQL_ROW row;
80     loadChannelSettings(chan);
81     if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
82         strcpy(trigger, "+");
83         return;
84     }
85     printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '0' AND `botid` = '%d'", chan->channel_id, clientid);
86     res = mysql_use();
87     if(!(row = mysql_fetch_row(res))) {
88         strcpy(trigger, "");
89         return;
90     }
91     if(row[0] && *row[0])
92         strcpy(trigger, row[0]);
93     else
94         strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "+"));
95 }
96
97 static void zero_bots_bot_ready(struct ClientSocket *client) {
98     if(client->botid != 0)
99         return;
100     MYSQL_RES *res;
101     MYSQL_ROW row;
102     
103     printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
104     res = mysql_use();
105     if ((row = mysql_fetch_row(res)) != NULL) {
106         if(row[1] && row[2]) {
107             putsock(client, "OPER %s %s", row[1], row[2]);
108         }
109         putsock(client, "MODE %s +%s", client->user->nick, row[0]);
110     }
111     
112     printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
113     res = mysql_use();
114     
115     while ((row = mysql_fetch_row(res)) != NULL) {
116         putsock(client, "JOIN %s %s", row[0], row[1]);
117     }
118 }
119
120 static TIMEQ_CALLBACK(load_timed_bans) {
121     MYSQL_RES *res;
122     MYSQL_ROW row;
123     //load all timed bans for the next 7 days
124     printf_mysql_query("SELECT `ban_id`, `ban_timeout` FROM `bans` WHERE `ban_timeout` > 0 AND `ban_timeout` < (UNIX_TIMESTAMP() + (86400 * 7))");
125     res = mysql_use();
126     char nameBuf[20];
127     while ((row = mysql_fetch_row(res)) != NULL) {
128         if(atol(row[1]) - time(0) > 0) {
129             sprintf(nameBuf, "ban_%s", row[0]);
130             timeq_add_name(nameBuf, atol(row[1]) - time(0), 0, channel_ban_timeout, strdup(row[0]));
131         } else {
132             //timed out
133             printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[0]);
134         }
135     }
136     timeq_add(86400*7, 0, load_timed_bans, NULL);
137 }
138
139 void init_bots() {
140     set_bot_alias(0, "0");
141     start_zero_bots();
142     set_trigger_callback(0, 0, zero_bots_trigger_callback);
143         bind_bot_ready(zero_bots_bot_ready, 0);
144     
145     timeq_add(10, 0, load_timed_bans, NULL);
146     
147 }
148
149 struct ClientSocket *getChannelBot(struct ChanNode *chan, int botid) {
150     struct ClientSocket *bot, *use_bot = NULL, *second_bot = NULL, *third_bot = NULL;
151     struct ChanUser *chanuser;
152     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
153         if(botid && bot->botid != botid) continue;
154         if(!chan || (chanuser = getChanUser(bot->user, chan)) != NULL) {
155             if(chan && (chanuser->flags & CHANUSERFLAG_OPPED)) {
156                 use_bot = bot;
157                 if(bot->flags & SOCKET_FLAG_PREFERRED) break;
158             } else if(bot->flags & SOCKET_FLAG_PREFERRED)
159                 second_bot = bot;
160             else
161                 third_bot = bot;
162         }
163     }
164     if(!use_bot) use_bot = second_bot;
165     if(!use_bot) use_bot = third_bot;
166     return use_bot;
167 }
168
169 void requestOp(struct UserNode *user, struct ChanNode *chan) {
170     struct ClientSocket *bot, *userbot = NULL;
171     struct ChanUser *chanuser = getChanUser(user, chan);
172     char opped = 0;
173     if(!chanuser) return;
174     if((chanuser->flags & CHANUSERFLAG_OPPED)) return;
175     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
176         if(!opped && (chanuser = getChanUser(bot->user, chan)) != NULL && (chanuser->flags & CHANUSERFLAG_OPPED)) {
177             opped = 1;
178             putsock(bot, "MODE %s +o %s", chan->name, user->nick);
179         }
180         if(bot->user == user) {
181             userbot = bot;
182         }
183     }
184     if(!opped) {
185         if(userbot && (isUserModeSet(user, 'o') || isUserModeSet(user, 'O') || isUserModeSet(user, 'k') || isUserModeSet(user, 'X'))) {
186             putsock(userbot, "MODE %s +o %s", chan->name, user->nick);
187             putsock(userbot, "OPMODE %s +o %s", chan->name, user->nick);
188         }
189     }
190 }
191
192 void requestInvite(struct UserNode *user, struct ChanNode *chan) {
193     struct ClientSocket *bot;
194     struct ChanUser *chanuser = getChanUser(user, chan);
195     char invited = 0;
196     if(chanuser) return;
197     for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
198         if(!invited && (chanuser = getChanUser(bot->user, chan)) != NULL && (chanuser->flags & CHANUSERFLAG_OPPED)) {
199             invited = 1;
200             putsock(bot, "INVITE %s %s", user->nick, chan->name);
201         }
202     }
203 }
204
205 TIMEQ_CALLBACK(channel_ban_timeout) {
206     char *str_banid = data;
207     MYSQL_RES *res;
208     MYSQL_ROW row;
209     printf_mysql_query("SELECT `ban_mask`, `channel_name` FROM `bans` LEFT JOIN `channels` ON `ban_channel` = `channel_id` WHERE `ban_id` = '%s'", str_banid);
210     res = mysql_use();
211     struct ChanNode *chan;
212     if((row = mysql_fetch_row(res)) != NULL && (chan = getChanByName(row[1])) != NULL) {
213         struct ClientSocket *use_bot = getChannelBot(chan, 0);
214         if(use_bot) {
215             putsock(use_bot, "MODE %s -b %s", chan->name, row[0]);
216         }
217         printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", str_banid);
218     }
219     free(str_banid);
220 }
221
222 static int general_ctcp(char *buffer, char *command, char *text);
223
224 void general_event_privctcp(struct UserNode *user, struct UserNode *target, char *command, char *text) {
225     char ctcpBuf[MAXLEN];
226     if(general_ctcp(ctcpBuf, command, text)) {
227         struct ClientSocket *bot;
228         for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
229             if(bot->user == target) break;
230         }
231         if(bot)
232             putsock(bot, "NOTICE %s :\001%s\001", user->nick, ctcpBuf);
233     }
234 }
235
236 static int general_ctcp(char *buffer, char *command, char *text) {
237     if(!stricmp(command, "VERSION")) {
238         sprintf(buffer, "VERSION NeonServ v%s.%d by pk910 (%s)", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"));
239         return 1;
240     }
241     if(!stricmp(command, "FINGER")) {
242         sprintf(buffer, "FINGER NeonServ v%s.%d (%s) build %s lines C code using " COMPILER " (see +netinfo)", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"), codelines);
243         return 1;
244     }
245     if(!stricmp(command, "PING")) {
246         sprintf(buffer, "PING %s", (text ? text : "0"));
247         return 1;
248     }
249     if(!stricmp(command, "TIME")) {
250         time_t rawtime;
251         struct tm *timeinfo;
252         char timeBuf[80];
253         time(&rawtime);
254         timeinfo = localtime(&rawtime);
255         strftime(timeBuf, 80, "%c", timeinfo);
256         sprintf(buffer, "TIME %s", timeBuf);
257         return 1;
258     }
259     return 0;
260 }
261
262 void set_bot_alias(int botid, char *alias) {
263     struct cmd_bot_alias *botalias, *existing_alias = NULL;
264     for(botalias = bot_aliases; botalias; botalias = botalias->next) {
265         if(!stricmp(botalias->alias, alias))
266             return;
267         if(botalias->botid == botid)
268             existing_alias = botalias;
269     }
270     if(existing_alias) {
271         free(existing_alias->alias);
272         existing_alias->alias = strdup(alias);
273         return;
274     }
275     botalias = malloc(sizeof(*botalias));
276     if (!botalias) {
277         perror("malloc() failed");
278         return;
279     }
280     botalias->botid = botid;
281     botalias->alias = strdup(alias);
282     botalias->next = bot_aliases;
283     bot_aliases = botalias;
284 }
285
286 const char *resolve_botid(int botid) {
287     struct cmd_bot_alias *botalias;
288     for(botalias = bot_aliases; botalias; botalias = botalias->next) {
289         if(botalias->botid == botid)
290             return botalias->alias;
291     }
292     return NULL;
293 }
294
295 int resolve_botalias(const char *alias) {
296     struct cmd_bot_alias *botalias;
297     for(botalias = bot_aliases; botalias; botalias = botalias->next) {
298         if(!stricmp(botalias->alias, alias))
299             return botalias->botid;
300     }
301     return -1;
302 }