]> git.donarmstrong.com Git - infobot.git/blob - src/core.pl
1b7a68c02fded05360f467f97e7318adfecfeea6
[infobot.git] / src / core.pl
1 #
2 #   core.pl: Important functions stuff...
3 #    Author: dms
4 #   Version: v0.4 (20000718)
5 #   Created: 20000322
6 #
7
8 use strict;
9
10 # scalar. MUST BE REDUCED IN SIZE!!!
11 ### TODO: reorder.
12 use vars qw(
13         $bot_misc_dir $bot_pid $bot_base_dir $bot_src_dir
14         $bot_data_dir $bot_config_dir $bot_state_dir $bot_run_dir
15         $answer $correction_plausible $talkchannel $bot_release
16         $statcount $memusage $user $memusageOld $bot_version $dbh
17         $shm $host $msg $noreply $conn $irc $learnok $nick $ident
18         $force_public_reply $addrchar $userHandle $addressedother
19         $floodwho $chan $msgtime $server $firsttime $wingaterun
20         $flag_quit $msgType $no_syscall
21         $utime_userfile $wtime_userfile $ucount_userfile
22         $utime_chanfile $wtime_chanfile $ucount_chanfile
23         $pubsize $pubcount $pubtime
24         $msgsize $msgcount $msgtime
25         $notsize $notcount $nottime
26         $running
27 );
28
29 # array.
30 use vars qw(@ircServers @wingateBad @wingateNow @wingateCache
31 );
32
33 ### hash. MUST BE REDUCED IN SIZE!!!
34 #
35 use vars qw(%count %netsplit %netsplitservers %flood %dcc %orig
36             %nuh %talkWho %seen %floodwarn %param %dbh %ircPort
37             %topic %moduleAge %last %time %mask %file
38             %forked %chanconf %channels %cache
39 );
40
41 # Signals.
42 $SIG{'HUP'}  = 'restart'; #  1.
43 $SIG{'INT'}  = 'doExit';  #  2.
44 $SIG{'KILL'} = 'doExit';  #  9. DOES NOT WORK. 'man perlipc' for details.
45 $SIG{'TERM'} = 'doExit';  # 15.
46 $SIG{'__WARN__'} = 'doWarn';
47
48 # initialize variables.
49 $last{buflen}   = 0;
50 $last{say}      = '';
51 $last{msg}      = '';
52 $userHandle     = "_default";
53 $wingaterun     = time();
54 $firsttime      = 1;
55 $utime_userfile = 0;
56 $wtime_userfile = 0;
57 $ucount_userfile = 0;
58 $utime_chanfile = 0;
59 $wtime_chanfile = 0;
60 $ucount_chanfile = 0;
61 $running        = 0;
62 ### more variables...
63 # static scalar variables.
64 $mask{ip}       = '(\d+)\.(\d+)\.(\d+)\.(\d+)';
65 $mask{host}     = '[\d\w\_\-\/]+\.[\.\d\w\_\-\/]+';
66 $mask{chan}     = '[\#\&]\S*|_default';
67 my $isnick1     = 'a-zA-Z\[\]\{\}\_\`\^\|\\\\';
68 my $isnick2     = '0-9\-';
69 $mask{nick}     = "[$isnick1]{1}[$isnick1$isnick2]*";
70 $mask{nuh}      = '\S*!\S*\@\S*';
71 $msgtime        = time();
72 $msgsize        = 0;
73 $msgcount       = 0;
74 $pubtime        = 0;
75 $pubsize        = 0;
76 $pubcount       = 0;
77 $nottime        = 0;
78 $notsize        = 0;
79 $notcount       = 0;
80 ###
81 $bot_release    = "1.3.3";
82 if ( -d 'CVS' ) {
83     use POSIX qw(strftime);
84     $bot_release        .= strftime(" cvs (%Y%m%d)", gmtime( (stat('CVS'))[9] ) );
85 }
86 $bot_version    = "blootbot $bot_release -- $^O";
87 $noreply        = 'NOREPLY';
88
89 ##########
90 ### misc commands.
91 ###
92
93 sub whatInterface {
94     if (!&IsParam('Interface') or $param{'Interface'} =~ /IRC/) {
95         return 'IRC';
96     } else {
97         return 'CLI';
98     }
99 }
100
101 sub doExit {
102     my ($sig)   = @_;
103
104     if (defined $flag_quit) {
105         &WARN("doExit: quit already called.");
106         return;
107     }
108     $flag_quit  = 1;
109
110     if (!defined $bot_pid) {    # independent.
111         exit 0;
112     } elsif ($bot_pid == $$) {  # parent.
113         &status("parent caught SIG$sig (pid $$).") if (defined $sig);
114
115         &status("--- Start of quit.");
116         $ident ||= 'blootbot';  # lame hack.
117
118         &status("Memory Usage: $memusage KiB");
119
120         &closePID();
121         &closeStats();
122         # shutdown IRC and related components.
123         if (&whatInterface() =~ /IRC/) {
124             &closeDCC();
125             &seenFlush();
126             &quit($param{'quitMsg'});
127         }
128         &writeUserFile();
129         &writeChanFile();
130         &uptimeWriteFile()      if (&IsParam('Uptime'));
131         &sqlCloseDB();
132         &closeSHM($shm);
133
134         if (&IsParam('dumpvarsAtExit')) {
135             &loadMyModule('DumpVars');
136             &dumpallvars();
137         }
138         &symdumpAll()           if (&IsParam('symdumpAtExit'));
139         &closeLog();
140         &closeSQLDebug()        if (&IsParam('SQLDebug'));
141
142         &status("--- QUIT.");
143     } else {                                    # child.
144         &status("child caught SIG$sig (pid $$).");
145     }
146
147     exit 0;
148 }
149
150 sub doWarn {
151     $SIG{__WARN__} = sub { warn $_[0]; };
152
153     foreach (@_) {
154         &WARN("PERL: $_");
155     }
156
157     $SIG{__WARN__} = 'doWarn';  # ???
158 }
159
160 # Usage: &IsParam($param);
161 # blootbot.config specific.
162 sub IsParam {
163     my $param = $_[0];
164
165     return 0 unless (defined $param);
166     return 0 unless (exists $param{$param});
167     return 0 unless ($param{$param});
168     return 0 if $param{$param} =~ /^false$/i;
169     return 1;
170 }
171
172 #####
173 #  Usage: &ChanConfList($param)
174 #  About: gets channels with 'param' enabled. (!!!)
175 # Return: array of channels
176 sub ChanConfList {
177     my $param   = $_[0];
178     return unless (defined $param);
179     my %chan    = &getChanConfList($param);
180
181     if (exists $chan{_default}) {
182         return keys %chanconf;
183     } else {
184         return keys %chan;
185     }
186 }
187
188 #####
189 #  Usage: &getChanConfList($param)
190 #  About: gets channels with 'param' enabled, internal use only.
191 # Return: hash of channels
192 sub getChanConfList {
193     my $param   = $_[0];
194     my %chan;
195
196     return unless (defined $param);
197
198     foreach (keys %chanconf) {
199         my $chan        = $_;
200         my @array       = grep /^$param$/, keys %{ $chanconf{$chan} };
201         #&DEBUG("gCCL param => $param, chan => $chan, keys => " . join(':',keys %{ $chanconf{$chan} }) . " array => " . join(':', @array)) if ($param eq 'whatever');
202
203         next unless (scalar @array);
204
205         if (scalar @array > 1) {
206             &WARN("multiple items found?");
207         }
208
209         if ($chanconf{$chan}{$param} eq '0') {
210             $chan{$chan}        = -1;
211         } else {
212             $chan{$chan}        =  1;
213         }
214     }
215
216     return %chan;
217 }
218
219 #####
220 #  Usage: &IsChanConf($param);
221 #  About: Check for 'param' on the basis of channel config.
222 # Return: 1 for enabled, 0 for passive disable, -1 for active disable.
223 sub IsChanConf {
224     my($param)  = shift;
225
226     # knocked tons of bugs with this! :)
227     my $debug   = 0; # 1 if ($param eq 'whatever');
228
229     if (!defined $param) {
230         &WARN("IsChanConf: param == NULL.");
231         return 0;
232     }
233
234     # these should get moved to your .chan file instead of the .config
235     # .config items overide any .chan entries
236     if (&IsParam($param)) {
237         &WARN("ICC: found '$param' option in main config file.");
238         return 1;
239     }
240
241     $chan ||= "_default";
242
243     my $old = $chan;
244     if ($chan =~ tr/A-Z/a-z/) {
245         &WARN("IsChanConf: lowercased chan. ($old)");
246     }
247
248     ### TODO: VERBOSITY on how chanconf returned 1 or 0 or -1.
249     my %chan    = &getChanConfList($param);
250     my $nomatch = 0;
251     if (!defined $msgType) {
252         $nomatch++;
253     } else {
254         $nomatch++ if ($msgType eq '');
255         $nomatch++ unless ($msgType =~ /^(public|private)$/i);
256     }
257
258 ### debug purposes only.
259 #    if ($debug) {
260 #       &DEBUG("param => $param, msgType => $msgType.");
261 #       foreach (keys %chan) {
262 #           &DEBUG("   $_ => $chan{$_}");
263 #       }
264 #    }
265
266     if ($nomatch) {
267         if ($chan{$chan}) {
268             &DEBUG("ICC: other: $chan{$chan} (_default/$param)") if ($debug);
269         } elsif ($chan{_default}) {
270             &DEBUG("ICC: other: $chan{_default} (_default/$param)") if ($debug);
271         } else {
272             &DEBUG("ICC: other: 0 ($param)") if ($debug);
273         }
274         return $chan{$chan} || $chan{_default} || 0;
275     } elsif ($msgType =~ /^(public|private)$/i) {
276         if ($chan{$chan}) {
277             &DEBUG("ICC: $msgType: $chan{$chan} ($chan/$param)") if ($debug);
278         } elsif ($chan{_default}) {
279             &DEBUG("ICC: $msgType: $chan{_default} (_default/$param)") if ($debug);
280         } else {
281             &DEBUG("ICC: $msgType: 0 ($param)") if ($debug);
282         }
283         return $chan{$chan} || $chan{_default} || 0;
284     }
285
286     &DEBUG("ICC: no-match: 0/$param (msgType = $msgType)");
287
288     return 0;
289 }
290
291 #####
292 #  Usage: &getChanConf($param);
293 #  About: Retrieve value for 'param' value in current/default chan.
294 # Return: scalar for success, undef for failure.
295 sub getChanConf {
296     my($param,$c)       = @_;
297
298     if (!defined $param) {
299         &WARN("gCC: param == NULL.");
300         return 0;
301     }
302
303     # this looks evil...
304     if (0 and !defined $chan) {
305         &DEBUG("gCC: ok !chan... doing _default instead.");
306     }
307
308     $c          ||= $chan;
309     $c          ||= "_default";
310     $c          = "_default" if ($c eq "*");    # FIXME
311     my @c       = grep /^\Q$c\E$/i, keys %chanconf;
312
313     if (@c) {
314         if (0 and $c[0] ne $c) {
315             &WARN("c ne chan ($c[0] ne $chan)");
316         }
317         if (!defined $chanconf{$c[0]}{$param} and ($c ne '_default')) {
318             return &getChanConf($param, '_default');
319         }
320         &DEBUG("gCC: $param,$c \"" . $chanconf{$c[0]}{$param} . '"');
321         return $chanconf{$c[0]}{$param};
322     }
323
324     #&DEBUG("gCC: returning _default... " . $chanconf{"_default"}{$param});
325     return $chanconf{"_default"}{$param};
326 }
327
328 sub getChanConfDefault {
329     my($what, $default, $chan) = @_;
330     $chan       ||= "_default";
331
332     if (exists $param{$what}) {
333         if (!exists $cache{config}{$what}) {
334             &status("config ($chan): backward-compatible option: found param{$what} ($param{$what}) instead of chan option");
335             $cache{config}{$what} = 1;
336         }
337
338         return $param{$what};
339     }
340     my $val = &getChanConf($what, $chan);
341     return $val if (defined $val);
342
343     $param{$what}       = $default;
344     &status("config ($chan): auto-setting param{$what} = $default");
345     $cache{config}{$what} = 1;
346     return $default;
347 }
348
349
350 #####
351 #  Usage: &findChanConf($param);
352 #  About: Retrieve value for 'param' value from any chan.
353 # Return: scalar for success, undef for failure.
354 sub findChanConf {
355     my($param)  = @_;
356
357     if (!defined $param) {
358         &WARN("param == NULL.");
359         return 0;
360     }
361
362     my $c;
363     foreach $c (keys %chanconf) {
364         foreach (keys %{ $chanconf{$c} }) {
365             next unless (/^$param$/);
366
367             return $chanconf{$c}{$_};
368         }
369     }
370
371     return;
372 }
373
374 sub showProc {
375     my ($prefix) = $_[0] || '';
376
377     if ($^O eq 'linux') {
378         if (!open(IN, "/proc/$$/status")) {
379             &ERROR("cannot open '/proc/$$/status'.");
380             return;
381         }
382
383         while (<IN>) {
384             $memusage = $1 if (/^VmSize:\s+(\d+) kB/);
385         }
386         close IN;
387
388     } elsif ($^O eq 'netbsd') {
389         $memusage = int( (stat "/proc/$$/mem")[7]/1024 );
390
391     } elsif ($^O =~ /^(free|open)bsd$/) {
392         my @info  = split /\s+/, `/bin/ps -l -p $$`;
393         $memusage = $info[20];
394
395     } else {
396         $memusage = 'UNKNOWN';
397         return;
398     }
399
400     if (defined $memusageOld and &IsParam('DEBUG')) {
401         # it's always going to be increase.
402         my $delta = $memusage - $memusageOld;
403         my $str;
404         if ($delta == 0) {
405             return;
406         } elsif ($delta > 500) {
407             $str = "MEM:$prefix increased by $delta KiB. (total: $memusage KiB)";
408         } elsif ($delta > 0) {
409             $str = "MEM:$prefix increased by $delta KiB";
410         } else {        # delta < 0.
411             $delta = -$delta;
412             # never knew RSS could decrease, probably Size can't?
413             $str = "MEM:$prefix decreased by $delta KiB.";
414         }
415
416         &status($str);
417     }
418     $memusageOld = $memusage;
419 }
420
421 ######
422 ###### SETUP
423 ######
424
425 sub setup {
426     &showProc(" (\&openLog before)");
427     &openLog();         # write, append.
428     &status("--- Started logging.");
429
430     # read.
431     &loadLang($bot_data_dir. "/blootbot.lang");
432     &loadIRCServers();
433     &readUserFile();
434     &readChanFile();
435     &loadMyModulesNow();        # must be after chan file.
436
437     $shm = &openSHM();
438     &openSQLDebug()     if (&IsParam('SQLDebug'));
439     &sqlOpenDB($param{'DBName'}, $param{'DBType'}, $param{'SQLUser'},
440         $param{'SQLPass'});
441     &checkTables();
442
443     &status("Setup: ". &countKeys('factoids') ." factoids.");
444     &getChanConfDefault('sendPrivateLimitLines', 3, $chan);
445     &getChanConfDefault('sendPrivateLimitBytes', 1000, $chan);
446     &getChanConfDefault('sendPublicLimitLines', 3, $chan);
447     &getChanConfDefault('sendPublicLimitBytes', 1000, $chan);
448     &getChanConfDefault('sendNoticeLimitLines', 3, $chan);
449     &getChanConfDefault('sendNoticeLimitBytes', 1000, $chan);
450
451     $param{tempDir} =~ s#\~/#$ENV{HOME}/#;
452
453     &status("Initial memory usage: $memusage KiB");
454     &status("-------------------------------------------------------");
455 }
456
457 sub setupConfig {
458     $param{'VERBOSITY'} = 1;
459     &loadConfig($bot_config_dir."/blootbot.config");
460
461     foreach ( qw(ircNick ircUser ircName DBType tempDir) ) {
462         next if &IsParam($_);
463         &ERROR("Parameter $_ has not been defined.");
464         exit 1;
465     }
466
467     if ($param{tempDir} =~ s#\~/#$ENV{HOME}/#) {
468         &VERB("Fixing up tempDir.",2);
469     }
470
471     if ($param{tempDir} =~ /~/) {
472         &ERROR("parameter tempDir still contains tilde.");
473         exit 1;
474     }
475
476     if (! -d $param{tempDir}) {
477         &status("making $param{tempDir}...");
478         mkdir $param{tempDir}, 0755;
479     }
480
481     # static scalar variables.
482     $file{utm}  = "$bot_state_dir/$param{'ircUser'}.uptime";
483     $file{PID}  = "$bot_run_dir/$param{'ircUser'}.pid";
484 }
485
486 sub startup {
487     if (&IsParam('DEBUG')) {
488         &status("enabling debug diagnostics.");
489         # I thought disabling this reduced memory usage by 1000 KiB.
490         use diagnostics;
491     }
492
493     $count{'Question'}  = 0;
494     $count{'Update'}    = 0;
495     $count{'Dunno'}     = 0;
496     $count{'Moron'}     = 0;
497 }
498
499 sub shutdown {
500     my ($sig) = @_;
501     # reverse order of &setup().
502     &status("--- shutdown called.");
503
504     # hack.
505     $ident ||=  'blootbot';
506
507     if (!&isFileUpdated("$bot_state_dir/blootbot.users", $wtime_userfile)) {
508         &writeUserFile()
509     }
510
511     if (!&isFileUpdated("$bot_state_dir/blootbot.chan", $wtime_chanfile)) {
512         &writeChanFile();
513     }
514
515     &sqlCloseDB();
516     # aswell. TODO: use this in &doExit?
517     &closeSHM($shm);
518     &closeLog();
519 }
520
521 sub restart {
522     my ($sig) = @_;
523
524     if ($$ == $bot_pid) {
525         &status("--- $sig called.");
526
527         ### crappy bug in Net::IRC?
528         my $delta = time() - $msgtime;
529         &DEBUG("restart: dtime = $delta");
530         if (!$conn->connected or time() - $msgtime > 900) {
531             &status("reconnecting because of uncaught disconnect \@ ".scalar(gmtime) );
532 ###         $irc->start;
533             &clearIRCVars();
534             $conn->connect();
535 ###         return;
536         }
537
538         &ircCheck();    # heh, evil!
539
540         &DCCBroadcast("-HUP called.",'m');
541         &shutdown($sig);
542         &loadConfig($bot_config_dir."/blootbot.config");
543         &reloadAllModules() if (&IsParam('DEBUG'));
544         &setup();
545
546         &status("--- End of $sig.");
547     } else {
548         &status("$sig called; ignoring restart.");
549     }
550 }
551
552 # File: Configuration.
553 sub loadConfig {
554     my ($file) = @_;
555
556     if (!open(FILE, $file)) {
557         &ERROR("Failed to read configuration file ($file): $!");
558         &status("Please read the INSTALL file on how to install and setup this file.");
559         exit 0;
560     }
561
562     my $count = 0;
563     while (<FILE>) {
564         chomp;
565         next if /^\s*\#/;
566         next unless /\S/;
567         my ($set,$key,$val) = split(/\s+/, $_, 3);
568
569         if ($set ne 'set') {
570             &status("loadConfig: invalid line '$_'.");
571             next;
572         }
573
574         # perform variable interpolation
575         $val =~ s/(\$(\w+))/$param{$2}/g;
576
577         $param{$key} = $val;
578
579         ++$count;
580     }
581     close FILE;
582
583     $file =~ s/^.*\///;
584     &status("Loaded config $file ($count items)");
585 }
586
587 1;