]> git.ipfire.org Git - ipfire-2.x.git/blob - config/cfgroot/general-functions.pl
cd4bfd56610d0142944c3d21bfd0af07a96613ad
[ipfire-2.x.git] / config / cfgroot / general-functions.pl
1 # SmoothWall CGIs
2 #
3 # This code is distributed under the terms of the GPL
4 #
5 # (c) The SmoothWall Team
6 # Copyright (C) 2002 Alex Hudson - getcgihash() rewrite
7 # Copyright (C) 2002 Bob Grant <bob@cache.ucr.edu> - validmac()
8 # Copyright (c) 2002/04/13 Steve Bootes - add alias section, helper functions
9 # Copyright (c) 2002/08/23 Mark Wormgoor <mark@wormgoor.com> validfqdn()
10 # Copyright (c) 2003/09/11 Darren Critchley <darrenc@telus.net> srtarray()
11 #
12 # $Id: general-functions.pl,v 1.1.2.26 2006/01/04 16:33:55 franck78 Exp $
13 #
14
15 package General;
16
17 use strict;
18 use Socket;
19 use IO::Socket;
20 use Net::SSLeay;
21 use Net::IPv4Addr;
22
23 $|=1; # line buffering
24
25 $General::version = 'VERSION';
26 $General::swroot = 'CONFIG_ROOT';
27 $General::noipprefix = 'noipg-';
28 $General::adminmanualurl = 'http://wiki.ipfire.org';
29
30 #
31 # log ("message") use default 'ipcop' tag
32 # log ("tag","message") use your tag
33 #
34 sub log
35 {
36 my $tag='ipfire';
37 $tag = shift if (@_>1);
38 my $logmessage = $_[0];
39 $logmessage =~ /([\w\W]*)/;
40 $logmessage = $1;
41 system('logger', '-t', $tag, $logmessage);
42 }
43
44 sub readhash
45 {
46 my $filename = $_[0];
47 my $hash = $_[1];
48 my ($var, $val);
49
50
51 # Some ipcop code expects that readhash 'complete' the hash if new entries
52 # are presents. Not clear it !!!
53 #%$hash = ();
54
55 open(FILE, $filename) or die "Unable to read file $filename";
56
57 while (<FILE>)
58 {
59 chop;
60 ($var, $val) = split /=/, $_, 2;
61 if ($var)
62 {
63 $val =~ s/^\'//g;
64 $val =~ s/\'$//g;
65
66 # Untaint variables read from hash
67 # trim space from begin and end
68 $var =~ s/^\s+//;
69 $var =~ s/\s+$//;
70 $var =~ /([A-Za-z0-9_-]*)/;
71 $var = $1;
72 $val =~ /([\w\W]*)/;
73 $val = $1;
74 $hash->{$var} = $val;
75 }
76 }
77 close FILE;
78 }
79
80
81 sub writehash
82 {
83 my $filename = $_[0];
84 my $hash = $_[1];
85 my ($var, $val);
86
87 # write cgi vars to the file.
88 open(FILE, ">${filename}") or die "Unable to write file $filename";
89 flock FILE, 2;
90 foreach $var (keys %$hash)
91 {
92 if ( $var eq "__CGI__"){next;}
93 $val = $hash->{$var};
94 # Darren Critchley Jan 17, 2003 added the following because when submitting with a graphic, the x and y
95 # location of the mouse are submitted as well, this was being written to the settings file causing
96 # some serious grief! This skips the variable.x and variable.y
97 if (!($var =~ /(.x|.y)$/)) {
98 if ($val =~ / /) {
99 $val = "\'$val\'"; }
100 if (!($var =~ /^ACTION/)) {
101 print FILE "${var}=${val}\n"; }
102 }
103 }
104 close FILE;
105 }
106
107 sub writehashpart
108 {
109 # This function replaces the given hash in the original hash by keeping the old
110 # content and just replacing the new content
111
112 my $filename = $_[0];
113 my $newhash = $_[1];
114 my %oldhash;
115 my ($var, $val);
116
117 readhash("${filename}", \%oldhash);
118
119 foreach $var (keys %$newhash){
120 $oldhash{$var}=$newhash->{$var};
121 }
122
123 # write cgi vars to the file.
124 open(FILE, ">${filename}") or die "Unable to write file $filename";
125 flock FILE, 2;
126 foreach $var (keys %oldhash)
127 {
128 if ( $var eq "__CGI__"){next;}
129 $val = $oldhash{$var};
130 # Darren Critchley Jan 17, 2003 added the following because when submitting with a graphic, the x and y
131 # location of the mouse are submitted as well, this was being written to the settings file causing
132 # some serious grief! This skips the variable.x and variable.y
133 if (!($var =~ /(.x|.y)$/)) {
134 if ($val =~ / /) {
135 $val = "\'$val\'"; }
136 if (!($var =~ /^ACTION/)) {
137 print FILE "${var}=${val}\n"; }
138 }
139 }
140 close FILE;
141 }
142
143 sub age
144 {
145 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
146 $atime, $mtime, $ctime, $blksize, $blocks) = stat $_[0];
147 my $now = time;
148
149 my $totalsecs = $now - $mtime;
150 my $days = int($totalsecs / 86400);
151 my $totalhours = int($totalsecs / 3600);
152 my $hours = $totalhours % 24;
153 my $totalmins = int($totalsecs / 60);
154 my $mins = $totalmins % 60;
155 my $secs = $totalsecs % 60;
156
157 return "${days}d ${hours}h ${mins}m ${secs}s";
158 }
159
160 sub validip
161 {
162 my $ip = $_[0];
163
164 if (!($ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)) {
165 return 0; }
166 else
167 {
168 my @octets = ($1, $2, $3, $4);
169 foreach $_ (@octets)
170 {
171 if (/^0./) {
172 return 0; }
173 if ($_ < 0 || $_ > 255) {
174 return 0; }
175 }
176 return 1;
177 }
178 }
179
180 sub validmask
181 {
182 my $mask = $_[0];
183
184 # secord part an ip?
185 if (&validip($mask)) {
186 return 1; }
187 # second part a number?
188 if (/^0/) {
189 return 0; }
190 if (!($mask =~ /^\d+$/)) {
191 return 0; }
192 if ($mask >= 0 && $mask <= 32) {
193 return 1; }
194 return 0;
195 }
196
197 sub validipormask
198 {
199 my $ipormask = $_[0];
200
201 # see if it is a IP only.
202 if (&validip($ipormask)) {
203 return 1; }
204 # split it into number and mask.
205 if (!($ipormask =~ /^(.*?)\/(.*?)$/)) {
206 return 0; }
207 my $ip = $1;
208 my $mask = $2;
209 # first part not a ip?
210 if (!(&validip($ip))) {
211 return 0; }
212 return &validmask($mask);
213 }
214
215 sub validipandmask
216 {
217 my $ipandmask = $_[0];
218
219 # split it into number and mask.
220 if (!($ipandmask =~ /^(.*?)\/(.*?)$/)) {
221 return 0; }
222 my $ip = $1;
223 my $mask = $2;
224 # first part not a ip?
225 if (!(&validip($ip))) {
226 return 0; }
227 return &validmask($mask);
228 }
229
230 sub validport
231 {
232 $_ = $_[0];
233
234 if (!/^\d+$/) {
235 return 0; }
236 if (/^0./) {
237 return 0; }
238 if ($_ >= 1 && $_ <= 65535) {
239 return 1; }
240 return 0;
241 }
242
243 sub validproxyport
244 {
245 $_ = $_[0];
246
247 if (!/^\d+$/) {
248 return 0; }
249 if (/^0./) {
250 return 0; }
251 if ($_ == 53 || $_ == 222 || $_ == 444 || $_ == 81 ) {
252 return 0; }
253 elsif ($_ >= 1 && $_ <= 65535) {
254 return 1; }
255 return 0;
256 }
257
258 sub validmac
259 {
260 my $checkmac = $_[0];
261 my $ot = '[0-9a-f]{2}'; # 2 Hex digits (one octet)
262 if ($checkmac !~ /^$ot:$ot:$ot:$ot:$ot:$ot$/i)
263 {
264 return 0;
265 }
266 return 1;
267 }
268
269 sub validhostname
270 {
271 # Checks a hostname against RFC1035
272 my $hostname = $_[0];
273
274 # Each part should be at least two characters in length
275 # but no more than 63 characters
276 if (length ($hostname) < 1 || length ($hostname) > 63) {
277 return 0;}
278 # Only valid characters are a-z, A-Z, 0-9 and -
279 if ($hostname !~ /^[a-zA-Z0-9-]*$/) {
280 return 0;}
281 # First character can only be a letter or a digit
282 if (substr ($hostname, 0, 1) !~ /^[a-zA-Z0-9]*$/) {
283 return 0;}
284 # Last character can only be a letter or a digit
285 if (substr ($hostname, -1, 1) !~ /^[a-zA-Z0-9]*$/) {
286 return 0;}
287 return 1;
288 }
289
290 sub validdomainname
291 {
292 my $part;
293
294 # Checks a domain name against RFC1035
295 my $domainname = $_[0];
296 my @parts = split (/\./, $domainname); # Split hostname at the '.'
297
298 foreach $part (@parts) {
299 # Each part should be at least two characters in length
300 # but no more than 63 characters
301 if (length ($part) < 2 || length ($part) > 63) {
302 return 0;}
303 # Only valid characters are a-z, A-Z, 0-9 and -
304 if ($part !~ /^[a-zA-Z0-9-]*$/) {
305 return 0;}
306 # First character can only be a letter or a digit
307 if (substr ($part, 0, 1) !~ /^[a-zA-Z0-9]*$/) {
308 return 0;}
309 # Last character can only be a letter or a digit
310 if (substr ($part, -1, 1) !~ /^[a-zA-Z0-9]*$/) {
311 return 0;}
312 }
313 return 1;
314 }
315
316 sub validfqdn
317 {
318 my $part;
319
320 # Checks a fully qualified domain name against RFC1035
321 my $fqdn = $_[0];
322 my @parts = split (/\./, $fqdn); # Split hostname at the '.'
323 if (scalar(@parts) < 2) { # At least two parts should
324 return 0;} # exist in a FQDN
325 # (i.e. hostname.domain)
326 foreach $part (@parts) {
327 # Each part should be at least one character in length
328 # but no more than 63 characters
329 if (length ($part) < 1 || length ($part) > 63) {
330 return 0;}
331 # Only valid characters are a-z, A-Z, 0-9 and -
332 if ($part !~ /^[a-zA-Z0-9-]*$/) {
333 return 0;}
334 # First character can only be a letter or a digit
335 if (substr ($part, 0, 1) !~ /^[a-zA-Z0-9]*$/) {
336 return 0;}
337 # Last character can only be a letter or a digit
338 if (substr ($part, -1, 1) !~ /^[a-zA-Z0-9]*$/) {
339 return 0;}
340 }
341 return 1;
342 }
343
344 sub validportrange # used to check a port range
345 {
346 my $port = $_[0]; # port values
347 $port =~ tr/-/:/; # replace all - with colons just in case someone used -
348 my $srcdst = $_[1]; # is it a source or destination port
349
350 if (!($port =~ /^(\d+)\:(\d+)$/)) {
351
352 if (!(&validport($port))) {
353 if ($srcdst eq 'src'){
354 return $Lang::tr{'source port numbers'};
355 } else {
356 return $Lang::tr{'destination port numbers'};
357 }
358 }
359 }
360 else
361 {
362 my @ports = ($1, $2);
363 if ($1 >= $2){
364 if ($srcdst eq 'src'){
365 return $Lang::tr{'bad source range'};
366 } else {
367 return $Lang::tr{'bad destination range'};
368 }
369 }
370 foreach $_ (@ports)
371 {
372 if (!(&validport($_))) {
373 if ($srcdst eq 'src'){
374 return $Lang::tr{'source port numbers'};
375 } else {
376 return $Lang::tr{'destination port numbers'};
377 }
378 }
379 }
380 return;
381 }
382 }
383
384 # Test if IP is within a subnet
385 # Call: IpInSubnet (Addr, Subnet, Subnet Mask)
386 # Subnet can be an IP of the subnet: 10.0.0.0 or 10.0.0.1
387 # Everything in dottted notation
388 # Return: TRUE/FALSE
389 sub IpInSubnet
390 {
391 my $ip = unpack('N', &Socket::inet_aton(shift));
392 my $start = unpack('N', &Socket::inet_aton(shift));
393 my $mask = unpack('N', &Socket::inet_aton(shift));
394 $start &= $mask; # base of subnet...
395 my $end = $start + ~$mask;
396 return (($ip >= $start) && ($ip <= $end));
397 }
398
399 #
400 # Return the following IP (IP+1) in dotted notation.
401 # Call: NextIP ('1.1.1.1');
402 # Return: '1.1.1.2'
403 #
404 sub NextIP
405 {
406 return &Socket::inet_ntoa( pack("N", 1 + unpack('N', &Socket::inet_aton(shift))
407 )
408 );
409 }
410
411 sub ipcidr
412 {
413 my ($ip,$cidr) = &Net::IPv4Addr::ipv4_parse(shift);
414 return "$ip\/$cidr";
415 }
416
417 sub validemail {
418 my $mail = shift;
419 return 0 if ( $mail !~ /^[0-9a-zA-Z\.\-\_]+\@[0-9a-zA-Z\.\-]+$/ );
420 return 0 if ( $mail =~ /^[^0-9a-zA-Z]|[^0-9a-zA-Z]$/);
421 return 0 if ( $mail !~ /([0-9a-zA-Z]{1})\@./ );
422 return 0 if ( $mail !~ /.\@([0-9a-zA-Z]{1})/ );
423 return 0 if ( $mail =~ /.\.\-.|.\-\..|.\.\..|.\-\-./g );
424 return 0 if ( $mail =~ /.\.\_.|.\-\_.|.\_\..|.\_\-.|.\_\_./g );
425 return 0 if ( $mail !~ /\.([a-zA-Z]{2,4})$/ );
426 return 1;
427 }
428
429 #
430 # Currently only vpnmain use this three procs (readhasharray, writehasharray, findhasharray)
431 # The 'key' used is numeric but is perfectly unneeded! This will to be removed so don't use
432 # this code. Vpnmain will be splitted in parts: x509/pki, connection ipsec, connection other,... .
433 #
434 sub readhasharray {
435 my ($filename, $hash) = @_;
436 %$hash = ();
437
438 open(FILE, $filename) or die "Unable to read file $filename";
439
440 while (<FILE>) {
441 my ($key, $rest, @temp);
442 chomp;
443 ($key, $rest) = split (/,/, $_, 2);
444 if ($key =~ /^[0-9]+$/) {
445 @temp = split (/,/, $rest);
446 $hash->{$key} = \@temp;
447 }
448 }
449 close FILE;
450 return;
451 }
452
453 sub writehasharray {
454 my ($filename, $hash) = @_;
455 my ($key, @temp, $i);
456
457 open(FILE, ">$filename") or die "Unable to write to file $filename";
458
459 foreach $key (keys %$hash) {
460 if ($key =~ /^[0-9]+$/) {
461 print FILE "$key";
462 foreach $i (0 .. $#{$hash->{$key}}) {
463 print FILE ",$hash->{$key}[$i]";
464 }
465 print FILE "\n";
466 }
467 }
468 close FILE;
469 return;
470 }
471
472 sub findhasharraykey {
473 foreach my $i (1 .. 1000000) {
474 if ( ! exists $_[0]{$i}) {
475 return $i;
476 }
477 }
478 }
479
480 sub srtarray
481 # Darren Critchley - darrenc@telus.net - (c) 2003
482 # &srtarray(SortOrder, AlphaNumeric, SortDirection, ArrayToBeSorted)
483 # This subroutine will take the following parameters:
484 # ColumnNumber = the column which you want to sort on, starts at 1
485 # AlphaNumberic = a or n (lowercase) defines whether the sort should be alpha or numberic
486 # SortDirection = asc or dsc (lowercase) Ascending or Descending sort
487 # ArrayToBeSorted = the array that wants sorting
488 #
489 # Returns an array that is sorted to your specs
490 #
491 # If SortOrder is greater than the elements in array, then it defaults to the first element
492 #
493 {
494 my ($colno, $alpnum, $srtdir, @tobesorted) = @_;
495 my @tmparray;
496 my @srtedarray;
497 my $line;
498 my $newline;
499 my $ctr;
500 my $ttlitems = scalar @tobesorted; # want to know the number of rows in the passed array
501 if ($ttlitems < 1){ # if no items, don't waste our time lets leave
502 return (@tobesorted);
503 }
504 my @tmp = split(/\,/,$tobesorted[0]);
505 $ttlitems = scalar @tmp; # this should be the number of elements in each row of the passed in array
506
507 # Darren Critchley - validate parameters
508 if ($colno > $ttlitems){$colno = '1';}
509 $colno--; # remove one from colno to deal with arrays starting at 0
510 if($colno < 0){$colno = '0';}
511 if ($alpnum ne '') { $alpnum = lc($alpnum); } else { $alpnum = 'a'; }
512 if ($srtdir ne '') { $srtdir = lc($srtdir); } else { $srtdir = 'src'; }
513
514 foreach $line (@tobesorted)
515 {
516 chomp($line);
517 if ($line ne '') {
518 my @temp = split(/\,/,$line);
519 # Darren Critchley - juggle the fields so that the one we want to sort on is first
520 my $tmpholder = $temp[0];
521 $temp[0] = $temp[$colno];
522 $temp[$colno] = $tmpholder;
523 $newline = "";
524 for ($ctr=0; $ctr < $ttlitems ; $ctr++) {
525 $newline=$newline . $temp[$ctr] . ",";
526 }
527 chop($newline);
528 push(@tmparray,$newline);
529 }
530 }
531 if ($alpnum eq 'n') {
532 @tmparray = sort {$a <=> $b} @tmparray;
533 } else {
534 @tmparray = (sort @tmparray);
535 }
536 foreach $line (@tmparray)
537 {
538 chomp($line);
539 if ($line ne '') {
540 my @temp = split(/\,/,$line);
541 my $tmpholder = $temp[0];
542 $temp[0] = $temp[$colno];
543 $temp[$colno] = $tmpholder;
544 $newline = "";
545 for ($ctr=0; $ctr < $ttlitems ; $ctr++){
546 $newline=$newline . $temp[$ctr] . ",";
547 }
548 chop($newline);
549 push(@srtedarray,$newline);
550 }
551 }
552
553 if ($srtdir eq 'dsc') {
554 @tmparray = reverse(@srtedarray);
555 return (@tmparray);
556 } else {
557 return (@srtedarray);
558 }
559 }
560
561 sub FetchPublicIp {
562 my %proxysettings;
563 &General::readhash("${General::swroot}/proxy/settings", \%proxysettings);
564 if ($_=$proxysettings{'UPSTREAM_PROXY'}) {
565 my ($peer, $peerport) = (/^(?:[a-zA-Z ]+\:\/\/)?(?:[A-Za-z0-9\_\.\-]*?(?:\:[A-Za-z0-9\_\.\-]*?)?\@)?([a-zA-Z0-9\.\_\-]*?)(?:\:([0-9]{1,5}))?(?:\/.*?)?$/);
566 Net::SSLeay::set_proxy($peer,$peerport,$proxysettings{'UPSTREAM_USER'},$proxysettings{'UPSTREAM_PASSWORD'} );
567 }
568 my ($out, $response) = Net::SSLeay::get_http( 'checkip.dyndns.org',
569 80,
570 "/",
571 Net::SSLeay::make_headers('User-Agent' => 'IPFire' )
572 );
573 if ($response =~ m%HTTP/1\.. 200 OK%) {
574 $out =~ /Current IP Address: (\d+.\d+.\d+.\d+)/;
575 return $1;
576 }
577 return '';
578 }
579
580 #
581 # Check if hostname.domain provided have IP provided
582 # use gethostbyname to verify that
583 # Params:
584 # IP
585 # hostname
586 # domain
587 # Output
588 # 1 IP matches host.domain
589 # 0 not in sync
590 #
591 sub DyndnsServiceSync ($;$;$) {
592
593 my ($ip,$hostName,$domain) = @_;
594 my @addresses;
595
596 #fix me no ip GROUP, what is the name ?
597 $hostName =~ s/$General::noipprefix//;
598 if ($hostName) { #may be empty
599 $hostName = "$hostName.$domain";
600 @addresses = gethostbyname($hostName);
601 }
602
603 if ($addresses[0] eq '') { # nothing returned ?
604 $hostName = $domain; # try resolving with domain only
605 @addresses = gethostbyname($hostName);
606 }
607
608 if ($addresses[0] ne '') { # got something ?
609 #&General::log("name:$addresses[0], alias:$addresses[1]");
610 # Build clear text list of IP
611 @addresses = map ( &Socket::inet_ntoa($_), @addresses[4..$#addresses]);
612 if (grep (/$ip/, @addresses)) {
613 return 1;
614 }
615 }
616 return 0;
617 }
618 #
619 # This sub returns the red IP used to compare in DyndnsServiceSync
620 #
621 sub GetDyndnsRedIP {
622 my %settings;
623 &General::readhash("${General::swroot}/ddns/settings", \%settings);
624
625 open(IP, "${General::swroot}/red/local-ipaddress") or return 'unavailable';
626 my $ip = <IP>;
627 close(IP);
628 chomp $ip;
629
630 if (&General::IpInSubnet ($ip,'10.0.0.0','255.0.0.0') ||
631 &General::IpInSubnet ($ip,'172.16.0.0.','255.240.0.0') ||
632 &General::IpInSubnet ($ip,'192.168.0.0','255.255.0.0'))
633 {
634 if ($settings{'BEHINDROUTER'} eq 'FETCH_IP') {
635 my $RealIP = &General::FetchPublicIp;
636 $ip = (&General::validip ($RealIP) ? $RealIP : 'unavailable');
637 }
638 }
639 return $ip;
640 }
641
642 # Translate ICMP code to text
643 # ref: http://www.iana.org/assignments/icmp-parameters
644 sub GetIcmpDescription ($) {
645 my $index = shift;
646 my @icmp_description = (
647 'Echo Reply', #0
648 'Unassigned',
649 'Unassigned',
650 'Destination Unreachable',
651 'Source Quench',
652 'Redirect',
653 'Alternate Host Address',
654 'Unassigned',
655 'Echo',
656 'Router Advertisement',
657 'Router Solicitation', #10
658 'Time Exceeded',
659 'Parameter Problem',
660 'Timestamp',
661 'Timestamp Reply',
662 'Information Request',
663 'Information Reply',
664 'Address Mask Request',
665 'Address Mask Reply',
666 'Reserved (for Security)',
667 'Reserved (for Robustness Experiment)', #20
668 'Reserved',
669 'Reserved',
670 'Reserved',
671 'Reserved',
672 'Reserved',
673 'Reserved',
674 'Reserved',
675 'Reserved',
676 'Reserved',
677 'Traceroute', #30
678 'Datagram Conversion Error',
679 'Mobile Host Redirect',
680 'IPv6 Where-Are-You',
681 'IPv6 I-Am-Here',
682 'Mobile Registration Request',
683 'Mobile Registration Reply',
684 'Domain Name Request',
685 'Domain Name Reply',
686 'SKIP',
687 'Photur', #40
688 'Experimental');
689 if ($index>41) {return 'unknown'} else {return @icmp_description[$index]};
690 }
691 1;