]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - .inspircd.inc
Add a general xml syntax checker for config files. It parses includes and warns of...
[user/henk/code/inspircd.git] / .inspircd.inc
1 #!/usr/bin/perl
2 #       +------------------------------------+
3 #       | Inspire Internet Relay Chat Daemon |
4 #       +------------------------------------+
5 #
6 #  Inspire is copyright (C) 2002-2004 ChatSpike-Dev.
7 #                       E-mail:
8 #                <brain@chatspike.net>
9 #                <Craig@chatspike.net>
10 #
11 # Written by Craig Edwards, Craig McLure, and others.
12 # This program is free but copyrighted software; see
13 #            the file COPYING for details.
14 #
15 #               I HATE PERL.. kthxbye
16 # ---------------------------------------------------
17 use POSIX;
18
19 my $basepath    =       "@BASE_DIR@";
20 my $confpath    =       "@CONFIG_DIR@/";
21 my $binpath     =       "@BINARY_DIR@";
22 my $libpath     =       "@LIBRARY_DIR@";
23 my $executable  =       "@EXECUTABLE@";
24 my $version     =       "@VERSION@";
25 my @filesparsed;
26 my @filechecked;
27
28 # Lets see what they want to do.. Set the variable (Cause i'm a lazy coder)
29 my $arg = $ARGV[0];
30 getpidfile($confpath."inspircd.conf");
31
32 if ($arg eq "start") { start(); exit(); }
33 if ($arg eq "debug") { debug(); exit(); }
34 if ($arg eq "screendebug") { screendebug(); exit() }
35 if ($arg eq "valdebug") { valdebug(); exit(); }
36 if ($arg eq "screenvaldebug") { screenvaldebug(); exit(); }
37 if ($arg eq "stop") { stop(); exit(); }
38 if ($arg eq "status") {
39         if (getstatus() == 1) {
40                 my $pid = getprocessid();
41                 print "InspIRCd is running (PID: $pid)\n";
42                 exit();
43         } else {
44                 print "InspIRCd is not running. (Or PID File not found)\n";
45                 exit();
46         }
47 }
48 if ($arg eq "rehash") {
49         if (getstatus() == 1) {
50                 my $pid = getprocessid();
51                 system("kill -HUP $pid >/dev/null 2>&1");
52                 print "InspIRCd rehashed (pid: $pid).\n";
53                 exit();
54         } else {
55                 print "InspIRCd is not running. (Or PID File not found)\n";
56                 exit();
57         }
58 }
59
60 if ($arg eq "cron") {
61         if (getstatus() == 0) { start(); }
62         exit();
63 }
64
65 if ($arg eq "version") {
66         print "InspIRCd version: $version\n";
67         exit();
68 }
69
70 if ($arg eq "restart") {
71         stop();
72         unlink($pidfile) if (-e $pidfile);
73         start();
74         # kthxbye();
75         exit();
76 }
77
78 if ($arg eq "checkconf") {
79         checkconf();
80         exit();
81 }
82
83 if ($arg eq "Cheese-Sandwich") {
84         print "Creating Cheese Sandwich..\n";
85         print "Done.\n";
86         exit();
87 }
88
89 ###
90 # If we get here.. bad / no parameters.
91 ###
92 print "Invalid Argument: $arg\n";
93 print "Usage: inspircd (start|stop|restart|rehash|status|cron|checkconf|version)\n";
94 exit();
95
96 ###
97 # Generic Helper Functions.
98 ###
99
100 sub start {
101         # Check to see its not 'running' already.
102         if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
103         # If we are still alive here.. Try starting the IRCd..
104         print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
105
106         system("$binpath/$executable");
107         return 1;
108 }
109
110 sub debug {
111         # Check to see its not 'running' already.
112         if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
113
114         print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
115
116         # Check we have gdb
117         checkgdb();
118
119         # If we are still alive here.. Try starting the IRCd..
120         system("gdb --command=$basepath/.gdbargs --args $binpath/$executable -nofork -debug -nolog");
121 }
122
123 sub screendebug
124 {
125         # Check to see its not 'running' already.
126         if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
127
128         print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
129
130         #Check we have gdb
131         checkgdb();
132         checkscreen();
133
134         # If we are still alive here.. Try starting the IRCd..
135         print "Starting InspIRCd in `screen`, type `screen -r` when the ircd crashes to view the gdb output and get a backtrace.\n";
136         print "Once you're inside the screen session press ^C + d to re-detach from the session\n";
137         system("screen -m -d gdb --command=$basepath/.gdbargs --args $binpath/$executable -nofork -debug -nolog");
138 }
139
140 sub valdebug
141 {
142         # Check to see its not 'running' already.
143         if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
144
145         print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
146
147         # Check we have valgrind and gdb
148         checkvalgrind();
149         checkgdb();
150
151         # If we are still alive here.. Try starting the IRCd..
152         # May want to do something with these args at some point: --suppressions=.inspircd.sup --gen-suppressions=yes
153         # Could be useful when we want to stop it complaining about things we're sure aren't issues.
154         system("valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=10 $binpath/$executable -nofork -debug -nolog");
155 }
156
157 sub screenvaldebug
158 {
159         # Check to see its not 'running' already.
160         if (getstatus() == 1) { print "InspIRCd is already running.\n"; return 0; }
161
162         print "$binpath/$executable doesn't exist\n" and return 0 unless(-e "$binpath/$executable");
163
164         #Check we have gdb
165         checkvalgrind();
166         checkgdb();
167         checkscreen();
168
169         # If we are still alive here.. Try starting the IRCd..
170         print "Starting InspIRCd in `screen`, type `screen -r` when the ircd crashes to view the valgrind and gdb output and get a backtrace.\n";
171         print "Once you're inside the screen session press ^C + d to re-detach from the session\n";
172         system("screen -m -d valgrind -v --tool=memcheck --leak-check=yes --db-attach=yes --num-callers=10 $binpath/$executable -nofork -debug -nolog");
173 }
174
175 sub stop {
176         if (getstatus() == 0) { print "InspIRCd is not running. (Or PID File not found)\n"; return 0; }
177         # Get to here, we have something to kill.
178         my $pid = getprocessid();
179         print "Stopping InspIRCd (pid: $pid)...\n";
180         system("kill -TERM $pid >/dev/null 2>&1");
181         # Give it a second to exit
182         sleep(1);
183         if (getstatus() == 1)
184         {
185                 print "InspIRCd not dying quietly -- forcing kill\n";
186                 system("kill -9 $pid >/dev/null 2>&1");
187         }
188         print "InspIRCd Stopped.\n";
189 }
190
191 # GetPidfile Version 2 - Now With Include Support..
192 # I beg for months for include support in insp, then..
193 # when it is added, it comes around and BITES ME IN THE ASS,
194 # because i then have to code support into this script.. Evil.
195
196 # Craig got bitten in the ass again --
197 # in 1.1 beta the include file is manditory, therefore
198 # if we cant find it, default to %conf%/inspircd.pid.
199 # Note, this also contains a fix for when the pid file is
200 # defined, but defined in a comment (line starts with #)
201 # -- Brain
202
203 sub getpidfile {
204   my ($file) = @_;
205   # Before we start, do we have a PID already? (Should never occur)
206   if ($pid ne "") {
207     return;
208   }
209   # Are We using a relative path?
210   if ($file !~ /^\//) {
211     # Convert it to a full path..
212     $file = $confpath . $file;
213   }
214
215   # Have we checked this file before?
216   for (my $i = 0; $i < $filesparsed; $i++) {
217     if ($filesparsed[$i] eq $file) {
218       # Already Parsed, Possible recursive loop..
219       return;
220     }
221   }
222
223   # If we get here, Mark as 'Read'
224   $filesparsed[$filesparsed] = $file;
225
226   # Open the File..
227   open INFILE, "< $file" or die "Unable to open file $file\n";
228   # Grab entire file contents..
229   my(@lines) = <INFILE>;
230   # Close the file
231   close INFILE;
232
233   # remove trailing spaces
234   chomp(@lines);
235   foreach $i (@lines) {
236     # clean it up
237     $i =~ s/[^=]+=\s(.*)/\1/;
238     # Does this file have a pid?
239     if (($i =~ /<pid file=\"(\S+)\">/i) && ($i !~ /^#/))
240     {
241       # Set the PID file and return.
242       $pidfile = $1;
243       return;
244     }
245   }
246
247   # If we get here, NO PID FILE! -- Check for includes
248   foreach $i (@lines) {
249     $i =~ s/[^=]+=\s(.*)/\1/;
250     if (($i =~ s/\<include file=\"(.+?)\"\>//i) && ($i !~ /^#/))
251     {
252       # Decend into that file, and check for PIDs.. (that sounds like an STD ;/)
253       getpidfile($1);
254       # Was a PID found?
255       if ($pidfile ne "") {
256         # Yes, Return.
257         return;
258       }
259     }
260   }
261
262   # End of includes / No includes found. Using default.
263   $pidfile = $confpath . "inspircd.pid";
264 }
265
266 sub getstatus {
267         my $pid = getprocessid();
268         if ($pid == 0) { return 0; }
269         $status = system("kill -0 $pid >/dev/null 2>&1") / 256;
270         if ($status == 0) { return 1; }
271         else { return 0; }
272 }
273
274
275 sub getprocessid {
276         my $pid;
277         open PIDFILE, "< $pidfile" or return 0;
278         while($i = <PIDFILE>)
279         {
280                 $pid = $i;
281         }
282         close PIDFILE;
283         return $pid;
284 }
285
286 sub checkvalgrind
287 {
288         unless(`valgrind --version`)
289         {
290                 print "Couldn't start valgrind: $!\n";
291                 exit;
292         }
293 }
294
295 sub checkgdb
296 {
297         unless(`gdb --version`)
298         {
299                 print "Couldn't start gdb: $!\n";
300                 exit;
301         }
302 }
303
304 sub checkscreen
305 {
306         unless(`screen --version`)
307         {
308                 print "Couldn't start screen: $!\n";
309                 exit;
310         }
311 }
312
313 sub checkxmllint
314 {
315         open(FH, "xmllint|") or die "Couldn't start xmllint: $!\n";
316 }
317
318 sub checkconf
319 {
320         checkxmllint();
321         validateconf($confpath."inspircd.conf");
322         print "Config check complete\n";
323 }
324
325 sub validateconf
326 {
327         my ($file) = @_;
328
329         # Are We using a relative path?
330         if ($file !~ /^\//) {
331                 # Convert it to a full path..
332                 $file = $confpath . $file;
333         }
334
335         # Have we checked this file before?
336         for (my $i = 0; $i < $filechecked; $i++) {
337                 if ($filechecked[$i] eq $file) {
338                         # Already Parsed, Possible recursive loop..
339                         return;
340                 }
341         }
342
343         # If we get here, Mark as 'Read'
344         $filechecked[$filechecked] = $file;
345
346         # Open the File..
347         open INFILE, "< $file" or die "Unable to open file $file\n";
348         # Grab entire file contents..
349         my(@lines) = <INFILE>;
350         # Close the file
351         close INFILE;
352
353         # remove trailing spaces
354         chomp(@lines);
355
356         my @newlines = ();
357         my @blanks = ();
358         my $conline;
359
360         push @newlines, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
361 #       push @newlines, "<!DOCTYPE config SYSTEM \"".$confpath."inspircd.dtd\">";
362         push @newlines, "<config>";
363
364         foreach $i (@lines) {
365                 # remove trailing newlines
366                 chomp($i);
367
368                 # clean up
369                 $i =~ s/[^=]+=\s(.*)/\1/;
370
371                 # convert tabs to spaces
372                 $i =~ s/\t/ /g;
373
374                 # remove leading spaces
375                 $i =~ s/^ *//;
376
377                 # remove comments
378                 $i =~ s/^#.*//;
379
380                 # remove trailing #s
381                 $i =~ s/(.*)#$/\1/;
382
383                 # remove trailing spaces
384                 $i =~ s/ *$//;
385
386                 # include file?
387                 if (($i =~ s/\<include file=\"(.+?)\"\>//i) && ($i !~ /^#/))
388                 {
389                         # yes, process it
390                         validateconf($1);
391                 }
392
393                 if ($i =~ /^<.*/ && $conline =~ /^<.*/)
394                 {
395                         push @newlines, $conline;
396                         push @newlines, @blanks;
397                         $conline = $i;
398                 }
399
400                 if ($i =~ /^<.*>$/)
401                 {
402                         $i =~ s/(.*)>$/\1 \/>/;
403                         push @newlines, $i;
404                 }
405                 elsif ($i =~ /.*>$/)
406                 {
407                         $conline .= " $i";
408                         $conline =~ s/(.*)>$/\1 \/>/;
409                         push @blanks, "";
410                         push @newlines, $conline;
411                         push @newlines, @blanks;
412                         $conline = "";
413                         undef @blanks;
414                 }
415                 elsif ($i =~ /^<.*/)
416                 {
417                         $conline = $i;
418                 }
419                 elsif ($conline =~ /^<.*/ && $i)
420                 {
421                         $conline .= " $i";
422                         push @blanks, "";
423                 }
424                 else
425                 {
426                         if ($conline)
427                         {
428                                 push @blanks, $i;
429                         }
430                         else
431                         {
432                                 push @newlines, $i;
433                         }
434                 }
435         }
436         if ($conline)
437         {
438                 push @newlines, $conline;
439                 push @newlines, @blanks;
440         }
441
442         push @newlines, "</config>";
443
444         my $tmpfile;
445         do
446         {
447                 $tmpfile = tmpnam();
448         } until sysopen(TF, $tmpfile, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0700);
449
450         foreach $n (@newlines)
451         {
452                 print TF "$n\n";
453         }
454         close TF;
455
456         my @result = `xmllint -noout $tmpfile 2>&1`;
457         chomp(@result);
458
459         my $skip = 0;
460         foreach $n (@result)
461         {
462                 if ($skip)
463                 {
464                         $skip = 0;
465                         next;
466                 }
467                 $n =~ s/$tmpfile\:\d*\: *//g;
468                 if ($n =~ /.*config>.*/)
469                 {
470                         $n = "";
471                         $skip = 1;
472                 }
473
474                 if ($n && !$skip)
475                 {
476                         if ($n =~ /line \d*/)
477                         {
478                                 my $lineno = $n;
479                                 $lineno =~ s/.*line (\d*).*/\1/;
480                                 $lineno = $lineno-2;
481                                 $n =~ s/line (\d*)/line $lineno/;
482                         }
483                         print "$file : $n\n";
484                 }
485         }
486         unlink($tmpfile);
487 }