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