added configuration (re)load event
[NeonServV5.git] / src / main.c
1 /* main.c - NeonServ v5.5
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 "main.h"
19 #include "signal.h"
20 #include "ClientSocket.h"
21 #include "UserNode.h"
22 #include "ChanNode.h"
23 #include "IRCEvents.h"
24 #include "IRCParser.h"
25 #include "modcmd.h"
26 #include "WHOHandler.h"
27 #include "bots.h"
28 #include "mysqlConn.h"
29 #include "HandleInfoHandler.h"
30 #include "lang.h"
31 #include "tools.h"
32 #include "timeq.h"
33 #include "EventLogger.h"
34 #include "ModeNode.h"
35 #include "IRCQueue.h"
36 #include "DBHelper.h"
37 #include "ConfigParser.h"
38 #include "ssl.h"
39 #include "QServer.h"
40 #include "version.h"
41 #include "modules.h"
42 #include "module_commands.h"
43 #include "ModuleFunctions.h"
44
45 time_t start_time;
46 static int running, hard_restart;
47 static int statistics_requested_lusers = 0;
48 int statistics_enabled;
49 TIMEQ_CALLBACK(main_statistics);
50 TIMEQ_CALLBACK(main_checkauths);
51 static int daemonized = 0;
52 static int print_loglevel = 0;
53 static FILE *log_fptr = NULL;
54 static int process_argc;
55 static char **process_argv;
56 #ifdef HAVE_THREADS
57 int running_threads;
58 pthread_mutex_t cache_sync;
59 pthread_mutex_t whohandler_sync, whohandler_mass_sync;
60 static pthread_mutex_t log_sync;
61 #endif
62
63 static void check_firstrun();
64
65 void cleanup() {
66     stop_modules();
67     free_sockets();
68     qserver_free();
69     free_parser();
70     free_UserNode();
71     free_ChanNode();
72     free_bind();
73     free_modcmd();
74     free_whoqueue();
75     free_mysql();
76     free_handleinfohandler();
77     free_lang();
78 }
79
80 static int load_mysql_config() {
81     char *mysql_host, *mysql_user, *mysql_pass, *mysql_base;
82     int mysql_serverport;
83     
84     mysql_host = get_string_field("MySQL.host");
85     if(!mysql_host) {
86         perror("invalid neonserv.conf: missing MySQL.host");
87         return 0;
88     }
89     mysql_serverport = get_int_field("MySQL.port");
90     if(!mysql_serverport)
91         mysql_serverport = 3306;
92     mysql_user = get_string_field("MySQL.user");
93     if(!mysql_user) {
94         perror("invalid neonserv.conf: missing MySQL.user");
95         return 0;
96     }
97     mysql_pass = get_string_field("MySQL.pass");
98     if(!mysql_pass) {
99         perror("invalid neonserv.conf: missing MySQL.pass");
100         return 0;
101     }
102     mysql_base = get_string_field("MySQL.base");
103     if(!mysql_base) {
104         perror("invalid neonserv.conf: missing MySQL base");
105         return 0;
106     }
107     init_mysql(mysql_host, mysql_serverport, mysql_user, mysql_pass, mysql_base);
108     return 1;
109 }
110
111 #ifdef HAVE_THREADS
112 pthread_t *current_threads = NULL;
113
114 void * thread_main(void *arg) {
115     time_t socket_wait;
116     while(running) {
117         socket_wait = time(0) + SOCKET_SELECT_TIME;
118         do {
119             if(!socket_loop(SOCKET_SELECT_TIME)) {
120                 if(!running) break;
121                 putlog(LOGLEVEL_ERROR, "No more active Bots... shutting down.\n");
122                 cleanup();
123                 exit(0);
124             }
125         } while(time(0) < socket_wait);
126         if(!running) break;
127         clearTempUsers();
128         destroyEvents();
129         mysql_free();
130     }
131     running_threads--;
132     return NULL;
133 }
134
135 int getCurrentThreadID() {
136     if(!current_threads) return 0;
137     int i;
138     unsigned int my_tid = (unsigned int) pthread_self_tid();
139     for(i = 0; i < running_threads; i++) {
140         #ifdef WIN32
141         if((unsigned int) current_threads[i].p == my_tid)
142         #else
143         if((unsigned int) current_threads[i] == my_tid)
144         #endif
145             return i+1;
146     }
147     return 0;
148 }
149
150 #endif
151
152 void exit_daemon() {
153     running = 0;
154     if(daemonized) {
155         remove(PID_FILE);
156     }
157     if(log_fptr) {
158         fclose(log_fptr);
159         log_fptr = NULL;
160     }
161 }
162
163 int main(int argc, char *argv[]) {
164     int run_as_daemon = 1;
165     int c;
166     process_argv = argv;
167     process_argc = argc;
168     printf("NeonServ v%s\n\n", NEONSERV_VERSION);
169     struct option options[] = {
170         {"show", 1, 0, 's'},
171         {"foreground", 0, 0, 'f'},
172         {"help", 0, 0, 'h'},
173         {"version", 0, 0, 'v'},
174         {0, 0, 0, 0}
175     };
176     while ((c = getopt_long(argc, argv, "s:fvh", options, NULL)) != -1) {
177         switch (c) {
178         case 's':
179             print_loglevel = atoi(optarg);
180             break;
181         case 'f':
182             run_as_daemon = 0;
183             break;
184         case 'v':
185             printf("Version: %s.%d (%s)\n", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"));
186             printf("Build: #%s %s (%s lines, " COMPILER ")\n", compilation, creation, codelines);
187             exit(0);
188             break;
189         case 'h':
190             printf("Usage: ./neonserv [-s loglevel] [-f] [-h|-v]\n");
191             printf(" -s, --show           show log lines matching loglevel in stdout.\n");
192             printf(" -f, --foreground     run NeonServ in the foreground.\n");
193             printf(" -h, --help           prints this usage message.\n");
194             printf(" -v, --version        prints this program's version.\n");
195             exit(0);
196             break;
197         }
198     }
199     #ifndef WIN32
200     if(geteuid() == 0 || getuid() == 0) {
201         fprintf(stderr, "NeonServ may not be run with super user privileges.\n");
202         exit(0);
203     }
204     #endif
205     #ifdef ENABLE_MEMORY_DEBUG
206     initMemoryDebug();
207     #endif
208     if(!loadConfig(CONF_FILE)) {
209         fprintf(stderr, "Unable to load " CONF_FILE "\n");
210         exit(0);
211     }
212     event_reload(1);
213     #if HAVE_THREADS
214     THREAD_MUTEX_INIT(log_sync);
215     #endif
216     if(!load_mysql_config()) {
217         fprintf(stderr, "Unable to connect to MySQL\n");
218         exit(0);
219     }
220     check_firstrun();
221     char **modulelist = get_all_fieldnames("modules");
222     if(!modulelist || !modulelist[0]) {
223         fprintf(stderr, "no modules loaded... Please update your configuration!\n");
224         exit(0);
225     }
226     free(modulelist);
227     if (run_as_daemon) {
228         #ifndef WIN32
229         /* Attempt to fork into the background if daemon mode is on. */
230         pid_t pid = fork();
231         if (pid < 0) {
232             fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
233         } else if (pid > 0) {
234             printf("Forking into the background (pid: %d)...\n", pid);
235             putlog(LOGLEVEL_INFO, "Forking into the background (pid: %d)...\n", pid);
236             exit(0);
237         }
238         setsid();
239         daemonized = 1;
240         atexit(exit_daemon);
241         FILE *pidfile = fopen(PID_FILE, "w");
242         if (pidfile == NULL) {
243             fprintf(stderr, "Unable to create PID file: %s\n", strerror(errno));
244             putlog(LOGLEVEL_ERROR, "Unable to create PID file: %s\n", strerror(errno));
245         } else {
246             fprintf(pidfile, "%i\n", (int)getpid());
247             fclose(pidfile);
248         }
249                 FILE *retn;
250         fclose(stdin); retn = fopen("/dev/null", "r");
251         fclose(stdout); retn = fopen("/dev/null", "w");
252         fclose(stderr); retn = fopen("/dev/null", "w");
253         #endif
254     }
255     
256 main:
257     signal(SIGABRT, sighandler);
258     signal(SIGFPE, sighandler);
259     signal(SIGILL, sighandler);
260     signal(SIGINT, sighandler);
261     signal(SIGSEGV, sighandler);
262     signal(SIGTERM, sighandler);
263     
264     start_time = time(0);
265     
266     #ifdef WIN32
267     int res;
268     WSADATA wsadata;
269     // Start Windows Sockets.
270     res = WSAStartup(MAKEWORD(2, 0), &wsadata);
271     if (res)
272     {
273         perror("Unable to start Windows Sockets");
274         return 0;
275     }
276     #endif
277     
278     statistics_enabled = get_int_field("statistics.enable");
279     
280     #ifdef HAVE_THREADS
281     THREAD_MUTEX_INIT(cache_sync);
282     THREAD_MUTEX_INIT(whohandler_sync);
283     THREAD_MUTEX_INIT(whohandler_mass_sync);
284     #endif
285     
286     queue_init();
287     init_sockets();
288     init_timeq();
289     init_lang();
290     ssl_init();
291     init_parser();
292     init_UserNode();
293     init_ChanNode();
294     init_ModeNode();
295     init_bind();
296         init_modcmd();
297     register_module_commands();
298     init_handleinfohandler();
299     init_tools();
300     init_modulefunctions();
301     loadModules();
302     init_bots();
303     init_DBHelper();
304     qserver_init();
305     
306     load_languages();
307     int update_minutes = get_int_field("statistics.frequency");
308     if(!update_minutes) update_minutes = 2;
309     timeq_add(update_minutes * 60 + 10, 0, main_statistics, NULL);
310     
311     timeq_add(90, 0, main_checkauths, NULL);
312     
313     int worker_threads = get_int_field("General.worker_threads");
314     if(!worker_threads) worker_threads = 1;
315     running = 1;
316     #ifdef HAVE_THREADS
317     int tid_id = 0;
318     current_threads = calloc(worker_threads, sizeof(*current_threads));
319     for(tid_id = 0; tid_id < worker_threads; tid_id++) {
320         running_threads++;
321         pthread_create(&current_threads[tid_id], NULL, thread_main, NULL);
322     }
323     int usleep_delay = 1000000 / TICKS_PER_SECOND;
324     while(running) {
325         timeq_tick();
326         loop_modules();
327         qserver_loop();
328         queue_loop();
329         mysql_free();
330         usleep(usleep_delay);
331     }
332     for(tid_id = 0; tid_id < worker_threads; tid_id++) {
333         pthread_join(current_threads[tid_id], NULL);
334     }
335     running_threads = 0;
336     #else
337     time_t socket_wait;
338     while(running) {
339         socket_wait = time(0) + SOCKET_SELECT_TIME;
340         do {
341             if(!socket_loop(SOCKET_SELECT_TIME)) {
342                 putlog(LOGLEVEL_ERROR, "No more active Bots... shutting down.\n");
343                 cleanup();
344                 exit(0);
345             }
346         } while(time(0) < socket_wait);
347         timeq_tick();
348         loop_modules();
349         clearTempUsers();
350         destroyEvents();
351         qserver_loop();
352         queue_loop();
353         mysql_free();
354     }
355     #endif
356     cleanup();
357     if(hard_restart) {
358         restart_process();
359     }
360     goto main;
361 }
362
363 void restart_process() {
364     /* Append a NULL to the end of argv[]. */
365     char **restart_argv = (char **)alloca((process_argc + 1) * sizeof(char *));
366     memcpy(restart_argv, process_argv, process_argc * sizeof(char *));
367     restart_argv[process_argc] = NULL;
368     #ifdef WIN32
369     execv(process_argv[0], (const char * const*)restart_argv);
370     #else
371     execv(process_argv[0], restart_argv);
372     #endif
373 }
374
375 int stricmp (const char *s1, const char *s2)
376 {
377    if (s1 == NULL) return s2 == NULL ? 0 : -(*s2);
378    if (s2 == NULL) return *s1;
379    char c1, c2;
380    while ((c1 = tolower (*s1)) == (c2 = tolower (*s2)))
381    {
382      if (*s1 == '\0') break;
383      ++s1; ++s2;
384    }
385    return c1 - c2;
386 }
387
388 int stricmplen (const char *s1, const char *s2, int len)
389 {
390    if (s1 == NULL) return s2 == NULL ? 0 : -(*s2);
391    if (s2 == NULL) return *s1;
392    char c1, c2;
393    int i = 0;
394    while ((c1 = tolower (*s1)) == (c2 = tolower (*s2)))
395    {
396      i++;
397      if (*s1 == '\0') break;
398      ++s1; ++s2;
399      if(i == len) break;
400    }
401    return c1 - c2;
402 }
403
404 void restart_bot(int do_hard_restart) {
405     hard_restart = do_hard_restart;
406     running = 0;
407 }
408
409 void stop_bot() {
410     cleanup();
411     exit(0);
412 }
413
414 void reload_config() {
415     loadConfig(CONF_FILE);
416     event_reload(0);
417 }
418
419 static int getCurrentSecondsOfDay() {
420     time_t now = time(0);
421     struct tm *timeofday = localtime(&now);
422     int seconds = 0;
423     seconds += timeofday->tm_hour * 3600;
424     seconds += timeofday->tm_min * 60;
425     seconds += timeofday->tm_sec;
426     return seconds;
427 }
428
429 static AUTHLOOKUP_CALLBACK(main_checkauths_callback) {
430     //check if registered is still valid
431     MYSQL_RES *res;
432     MYSQL_ROW row;
433     printf_mysql_query("SELECT `user_id`, `user_registered` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
434     res = mysql_use();
435     if ((row = mysql_fetch_row(res)) != NULL) {
436         int diff = registered - atoi(row[1]);
437         if(diff < 0)
438             diff *= -1;
439         if(!exists || (strcmp(row[1], "0") && diff > 86400)) {
440             //User is no longer valid! Delete it...
441             deleteUser(atoi(row[0]));
442             char *alertchan = get_string_field("General.CheckAuths.alertchan");
443             if(alertchan) {
444                 char reason[MAXLEN];
445                 if(!exists) {
446                     strcpy(reason, "USER_NOT_EXISTS");
447                 } else {
448                     sprintf(reason, "USER_REGISTERED_MISSMATCH: %lu, expected %d (diff: %d)", (unsigned long) registered, atoi(row[1]), diff);
449                 }
450                 struct ChanNode *alertchan_chan = getChanByName(alertchan);
451                 struct ClientSocket *alertclient;
452                 if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
453                     putsock(alertclient, "PRIVMSG %s :Deleted User %s (%s)", alertchan_chan->name, auth, reason);
454                 }
455             }
456         } else if(exists && !strcmp(row[1], "0")) {
457             printf_mysql_query("UPDATE `users` SET `user_registered` = '%lu', `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", (unsigned long) registered, row[0]);
458         } else {
459             printf_mysql_query("UPDATE `users` SET `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", row[0]);
460         }
461     }
462 }
463
464 TIMEQ_CALLBACK(main_checkauths) {
465     int next_call = 600;
466     if(get_int_field("General.CheckAuths.enabled")) {
467         int check_start_time = get_int_field("General.CheckAuths.start_time") * 3600;
468         int duration = get_int_field("General.CheckAuths.duration") * 60;
469         int now = getCurrentSecondsOfDay();
470         if(now < check_start_time && check_start_time+duration >= 86400) {
471             check_start_time -= 86400;
472         }
473         if(now >= check_start_time && now < (check_start_time + duration)) {
474             next_call = get_int_field("General.CheckAuths.interval");
475             //get the "longest-unchecked-user"
476             MYSQL_RES *res;
477             MYSQL_ROW row;
478             int lastcheck;
479             time_t unixtime = time(0);
480             int min_unckecked = get_int_field("General.CheckAuths.min_unckecked");
481             printf_mysql_query("SELECT `user_user`, `user_lastcheck` FROM `users` ORDER BY `user_lastcheck` ASC LIMIT 1");
482             res = mysql_use();
483             if ((row = mysql_fetch_row(res)) != NULL) {
484                 lastcheck = atoi(row[1]);
485                 if(!lastcheck || unixtime - lastcheck >= min_unckecked) {
486                     lookup_authname(row[0], 0, main_checkauths_callback, NULL);
487                 } else 
488                     next_call = 300;
489             }
490         } else {
491             int pending;
492             if(now > check_start_time)
493                 pending = 86400 - now + check_start_time;
494             else
495                 pending = check_start_time - now;
496             if(pending < 600)
497                 next_call = pending;
498         }
499         
500     }
501     timeq_add(next_call, 0, main_checkauths, NULL);
502 }
503
504 TIMEQ_CALLBACK(main_statistics) {
505     int update_minutes = get_int_field("statistics.frequency");
506     if(!update_minutes) update_minutes = 2;
507     timeq_add(update_minutes * 60, 0, main_statistics, NULL);
508     if(get_int_field("statistics.enable")) {
509         statistics_enabled = 1;
510         statistics_requested_lusers = 1;
511         if(get_int_field("statistics.include_lusers")) {
512             struct ClientSocket *bot, *lusersbot = NULL;
513             for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
514                 if(bot->flags & SOCKET_FLAG_PREFERRED)
515                     lusersbot = bot;
516             }
517             bot = lusersbot;
518             if(bot == NULL) bot = getBots(SOCKET_FLAG_READY, NULL);
519             putsock(bot, "LUSERS");
520         } else {
521             statistics_update();
522         }
523     } else
524         statistics_enabled = 0;
525 }
526
527 int statistics_update() {
528     if(get_int_field("statistics.enable") && statistics_requested_lusers && get_string_field("statistics.execute")) {
529         statistics_requested_lusers = 0;
530         char command[MAXLEN];
531         /* parameters:
532          - visible users
533          - visible chanusers
534          - visible channels
535          - privmsg per minute
536          - commands per minute
537          - network users
538          - network channels
539         */
540         sprintf(command, "%s %d %d %d %d %d %d %d", get_string_field("statistics.execute"), getUserCount(), getChanUserCount(), getChannelCount(), statistics_privmsg, statistics_commands, statistics_network_users, statistics_network_channels);
541         statistics_privmsg = 0;
542         statistics_commands = 0;
543         return system(command);
544     }
545         return -1;
546 }
547
548 time_t getStartTime() {
549     return start_time;
550 }
551
552 int getRunningThreads() {
553         #ifdef HAVE_THREADS
554     return running_threads;
555         #else
556         return 1;
557         #endif
558 }
559
560 void write_log(int loglevel, const char *line, int len) {
561     SYNCHRONIZE(log_sync);
562     if(!daemonized && (print_loglevel & loglevel)) {
563         printf("%s", line);
564     } else if(!daemonized && loglevel == LOGLEVEL_ERROR) {
565         fprintf(stderr, "%s", line);
566     }
567     if(get_int_field("log.loglevel") & loglevel) {
568         if(!log_fptr) {
569             log_fptr = fopen(LOG_FILE, "a");
570             if(!log_fptr) goto write_log_end;
571         }
572         time_t rawtime;
573         struct tm *timeinfo;
574         time(&rawtime);
575         timeinfo = localtime(&rawtime);
576         char timestr[20];
577         int timepos = strftime(timestr, 20, "%x %X ", timeinfo);
578         fwrite(timestr, 1, timepos, log_fptr);
579         fwrite(line, 1, len, log_fptr);
580     }
581     write_log_end:
582     DESYNCHRONIZE(log_sync);
583 }
584
585 void putlog(int loglevel, const char *text, ...) {
586     va_list arg_list;
587     char logBuf[MAXLOGLEN];
588     int pos;
589     logBuf[0] = '\0';
590     va_start(arg_list, text);
591     pos = vsnprintf(logBuf, MAXLOGLEN - 1, text, arg_list);
592     va_end(arg_list);
593     if (pos < 0 || pos > (MAXLOGLEN - 1)) pos = MAXLOGLEN - 1;
594     logBuf[pos] = '\0';
595     write_log(loglevel, logBuf, pos);
596 }
597
598 static void check_firstrun() {
599     MYSQL_RES *res;
600     MYSQL_ROW row;
601     printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_access` = 1000 LIMIT 1");
602     res = mysql_use();
603     if (mysql_fetch_row(res) == NULL) {
604         //first run!
605         printf("No superuser found...\n");
606         check_firstrun_admin:
607         printf("AuthServ account name of admin user: ");
608         char *ptr;
609         char adminuser[31];
610         ptr = fgets(adminuser, 30, stdin);
611         for(ptr = adminuser; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
612         if(strlen(adminuser) < 2) goto check_firstrun_admin;
613         printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(adminuser));
614         res = mysql_use();
615         if ((row = mysql_fetch_row(res)) != NULL)
616             printf_mysql_query("UPDATE `users` SET `user_access` = 1000 WHERE `user_id` = '%s'", row[0]);
617         else
618             printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`) VALUES ('%s', 1000)", escape_string(adminuser));
619     }
620     printf_mysql_query("SELECT `id` FROM `bots` WHERE `active` = 1 LIMIT 1");
621     res = mysql_use();
622     if (mysql_fetch_row(res) == NULL) {
623         //no bot active
624         printf("No active bot found...\n\n");
625         printf("ADD NEW BOT\n");
626         char *ptr;
627         char bot_nick[31];
628         check_firstrun_bot_nick:
629         printf("Nick: ");
630         ptr = fgets(bot_nick, 30, stdin);
631         for(ptr = bot_nick; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
632         if(strlen(bot_nick) < 2) goto check_firstrun_bot_nick;
633         char bot_ident[16];
634         check_firstrun_bot_ident:
635         printf("Ident: ");
636         ptr = fgets(bot_ident, 15, stdin);
637         for(ptr = bot_ident; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
638         if(strlen(bot_ident) < 2) goto check_firstrun_bot_ident;
639         char bot_realname[101];
640         check_firstrun_bot_realname:
641         printf("Realname: ");
642         ptr = fgets(bot_realname, 100, stdin);
643         for(ptr = bot_realname; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
644         if(strlen(bot_realname) < 2) goto check_firstrun_bot_realname;
645         char bot_server[101];
646         check_firstrun_bot_server:
647         printf("Server: [irc.onlinegamesnet.net] ");
648         ptr = fgets(bot_server, 100, stdin);
649         for(ptr = bot_server; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
650         if(*bot_server && strlen(bot_nick) < 5) goto check_firstrun_bot_server;
651         if(!*bot_server)
652             strcpy(bot_server, "irc.onlinegamesnet.net");
653         int bot_port;
654         char bot_port_buf[7];
655         printf("Port: [6667] ");
656         ptr = fgets(bot_port_buf, 6, stdin);
657         for(ptr = bot_port_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
658         if(!*bot_port_buf)
659             bot_port = 6667;
660         else
661             bot_port = atoi(bot_port_buf);
662         int bot_ssl;
663         char bot_ssl_buf[5];
664         check_firstrun_bot_ssl:
665         printf("SSL: [y/N] ");
666         ptr = fgets(bot_ssl_buf, 4, stdin);
667         for(ptr = bot_ssl_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
668         if(!*bot_ssl_buf || tolower(*bot_ssl_buf) == 'n')
669             bot_ssl = 0;
670         else if(tolower(*bot_ssl_buf) == 'y')
671             bot_ssl = 1;
672         else
673             goto check_firstrun_bot_ssl;
674         char bot_pass[101];
675         printf("Server Password: [] ");
676         ptr = fgets(bot_pass, 100, stdin);
677         for(ptr = bot_pass; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
678         int bot_maxchan;
679         char bot_maxchan_buf[5];
680         printf("MaxChannel: [20] ");
681         ptr = fgets(bot_maxchan_buf, 5, stdin);
682         for(ptr = bot_maxchan_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
683         if(*bot_maxchan_buf)
684             bot_maxchan = atoi(bot_maxchan_buf);
685         else
686             bot_maxchan = 20;
687         int bot_queue;
688         char bot_queue_buf[5];
689         check_firstrun_bot_queue:
690         printf("Queue (prevents excess floods): [Y/n] ");
691         ptr = fgets(bot_queue_buf, 4, stdin);
692         for(ptr = bot_queue_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
693         if(!*bot_queue_buf || tolower(*bot_queue_buf) == 'y')
694             bot_queue = 1;
695         else if(tolower(*bot_queue_buf) == 'n')
696             bot_queue = 0;
697         else
698             goto check_firstrun_bot_queue;
699         printf_mysql_query("INSERT INTO `bots` (`active`, `nick`, `server`, `port`, `pass`, `ssl`, `ident`, `realname`, `botclass`, `textbot`, `queue`, `defaulttrigger`, `max_channels`) VALUES ('1', '%s', '%s', '%d', '%s', '%d', '%s', '%s', '1', '1', '%d', '+', '%d')", escape_string(bot_nick), escape_string(bot_server), bot_port, escape_string(bot_pass), bot_ssl, escape_string(bot_ident), escape_string(bot_realname), bot_queue, bot_maxchan);
700     }
701 }
702