File Coverage

blib/lib/Squid/Guard.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Squid::Guard;
2              
3 1     1   72708 use 5.008;
  1         4  
  1         50  
4 1     1   91 use strict;
  1         3  
  1         42  
5 1     1   5 use warnings;
  1         1  
  1         68  
6              
7             our @ISA = qw();
8              
9             our $VERSION = '0.15';
10              
11 1     1   6 use Carp;
  1         3  
  1         85  
12 1     1   9318 use DB_File;
  0            
  0            
13             use Fcntl;
14             use Squid::Guard::Request;
15              
16             =head1 NAME
17              
18             Squid::Guard - Redirector for the Squid web proxy
19              
20             =head1 SYNOPSYS
21              
22             use Squid::Guard;
23              
24             my $sg = Squid::Guard->new();
25              
26             $sg->redir("http://proxy/cgi-bin/deny.pl";);
27              
28             $sg->checkf(\&check);
29              
30             $sg->run;
31              
32             =head1 DESCRIPTION
33              
34             Squid::Guard is a module for creating a simple yet flexible
35             redirector for the Squid web cache engine.
36             This module was inspired by squidGuard, a popular squid redirector
37             written in C, but aims to be more flexible and in some ways simpler
38             to use.
39             I was happy with squidGuard and used it for years. But I needed
40             certain extra features like the ability to differentiate
41             between users based on some external program output, group
42             belongings etc.
43             squidGuard did not support this, so Squid::Guard was born.
44              
45              
46             =head2 Squid::Guard->new( opt => val, ...)
47              
48             API call to create a new server. Does not actually start running anything until you call C<-Erun()>.
49              
50             =cut
51              
52             sub new {
53             my $class = shift;
54             my %opts = @_;
55              
56             my $self = {};
57              
58             $self->{dbdir} = undef;
59             $self->{forcedbupdate} = 0;
60             $self->{checkf} = undef;
61             $self->{concurrency} = 0;
62             $self->{categ} = {};
63             $self->{redir} = ();
64             $self->{strictauth} = 0;
65             $self->{verbose} = 0;
66             $self->{debug} = 0;
67             $self->{oneshot} = 0;
68              
69             for( keys %opts ) {
70             $self->{$_} = $opts{$_};
71             }
72              
73             bless($self, $class);
74             return $self;
75             }
76              
77              
78             =head2 $sg->redir()
79              
80             Get/set the redir page.
81             The following macros are supported:
82              
83             =over
84              
85             =item %u the requested url
86              
87             =item %p the path and the optional query string of %u, but note for convenience without the leading "/"
88              
89             =item %a the client IP address
90              
91             =item %n the client FQDN
92              
93             =item %i the user name, if available
94              
95             =item %t the C function result (see)
96              
97             =item %% the % sign
98              
99             =back
100              
101             If set to the special value C, then the return value of the checkf function, if true, is used directly as the redirection url
102              
103             =cut
104              
105             sub redir {
106             my $self = shift;
107             if (@_) { $self->{redir} = shift }
108             return $self->{redir};
109             }
110              
111              
112             =head2 $sg->checkf()
113              
114             Sets the check callback function, which is called upn each request received.
115             The check function receives as arguments the current C object, and a L object on which the user can perform tests.
116             A false return value means no redirection is to be proformed. A true return value means that the request is to be redirected to what was declared with C.
117              
118             =cut
119              
120             sub checkf {
121             my $self = shift;
122             my $funcref = shift;
123             $self->{checkf} = $funcref;
124             }
125              
126              
127             =head2 $sg->concurrency()
128              
129             Enables the concurrency protocol. For now, the implementation is rather poor: the ID is simply read and echoed to squid.
130             See url_rewrite_concurrency in squid.conf
131              
132             =cut
133              
134             sub concurrency {
135             my $self = shift;
136             my $num = shift;
137             $self->{concurrency} = $num;
138             }
139              
140              
141             my $cachettl = 0;
142             my $cachepurgelastrun = 0;
143             my %cacheh; # this contains the real cache items
144             my @cachea; # this contains the cache keys with the time they where written in the cache.
145              
146             =head2 $sg->cache()
147              
148             Enables caching in expensive subs which involve spawning external processes. At the moment, caching is implemented in C (which calls wbinfo 3 times) and in C (which can be expensive in some nss configurations).
149             An argument must be suplied, representing the time to live of cached items, in seconds.
150             The time to live, as the whole cached objects, are shared among all the objects belonging to this class. No problem since usually only one object is in use.
151              
152             =cut
153              
154             sub cache {
155             my $self = shift;
156             my $ttl = shift;
157             $cachettl = $ttl;
158             }
159              
160              
161             sub _cachepurge() {
162             my $time = time();
163             return if $cachepurgelastrun == $time; # do not purge too often
164             $cachepurgelastrun = $time;
165              
166             my $t = $time - $cachettl;
167              
168             return unless @cachea; # try to avoid looping through if unnecessary
169             return if $cachea[0]->[0] > $t;
170              
171             my $ndel = 0;
172             LOOP: foreach my $p ( @cachea ) {
173             last LOOP if $p->[0] > $t;
174              
175             my $k = $p->[1];
176             delete( $cacheh{$k} ) if defined( $cacheh{$k} ) && $cacheh{$k}->[0] <= $t;
177              
178             $ndel++;
179             }
180            
181             $ndel and splice(@cachea, 0, $ndel);
182             }
183              
184              
185             sub _cachewr($$) {
186             my ($k, $v) = @_;
187             defined($v) or $v = ""; # be sure not to cache undef values since _cacherd returns undef when the value is not in the cache
188              
189             my $t = time;
190              
191             my @arr = ( $t, $v );
192             $cacheh{$k} = \@arr;
193              
194             my @arra = ($t, $k);
195             push @cachea, \@arra;
196             }
197              
198              
199             sub _cacherd($) {
200             my ($k) = @_;
201             # Purge the cache when reading from it. This also ensures that the remaining cache record are in their ttl. This could be done in other occasions too
202             _cachepurge();
203             return defined($cacheh{$k}) ? $cacheh{$k}->[1] : undef;
204             }
205              
206              
207             =head2 $sg->verbose()
208              
209             Get/set verbosity. Currently only one level of verbosity is supported
210              
211             =cut
212              
213             sub verbose {
214             my $self = shift;
215             if (@_) { $self->{verbose} = shift }
216             return $self->{verbose};
217             }
218              
219              
220             =head2 $sg->debug()
221              
222             Emit debug info
223              
224             =cut
225              
226             sub debug {
227             my $self = shift;
228             if (@_) { $self->{debug} = shift }
229             $self->{debug} and $self->{verbose} = $self->{debug};
230             return $self->{debug};
231             }
232              
233              
234             =head2 $sg->oneshot()
235              
236             Executes only a single iteration then exits (can be used when debugging)
237              
238             =cut
239              
240             sub oneshot {
241             my $self = shift;
242             if (@_) { $self->{oneshot} = shift }
243             return $self->{oneshot};
244             }
245              
246              
247             =head2 $sg->handle($req)
248              
249             Handles a request, returning the empty string or the redirected url.
250             The request can be either a string in the format passed to the redirector by squid, or a Squid::Guard::Request object.
251             This sub is usually called internally by run() to handle a request, but can be called directly too.
252              
253             =cut
254              
255             sub handle {
256             my $self = shift;
257              
258             return "" unless $self->{checkf};
259              
260             my $arg = shift;
261             my $req = ref($arg) ? $arg : Squid::Guard::Request->new($arg);
262              
263             my $redir = "";
264              
265             my $res = $self->{checkf}->( $self, $req );
266             if( $res ) {
267             if( $self->{redir} eq 'CHECKF' ) {
268             $redir = $res;
269             } else {
270             $redir = $self->{redir} || croak "A request was submitted, but redir url is not defined";
271             $redir =~ s/(?addr/ge;
272             $redir =~ s/(?fqdn or "unknown"/ge;
273             my $i = $req->ident || "unknown";
274             $i =~ s/([^-._A-Za-z0-9])/sprintf("%%%02X", ord($1))/eg;
275             $redir =~ s/(?
276             #$redir =~ s/(?
277             $redir =~ s/(?url/ge;
278             my $pq = $req->path_query;
279             $pq =~ s-^/--o;
280             $redir =~ s/(?
281             my $r = $res;
282             $r =~ s/([^-._A-Za-z0-9])/sprintf("%%%02X", ord($1))/eg;
283             $redir =~ s/(?
284             $redir =~ s/%%/%/;
285              
286             # Redirections seem not to be understood when the request was for HTTPS.
287             # Info taken from http://www.mail-archive.com/squid-users@squid-cache.org/msg58422.html :
288             # Squid is a little awkward:
289             # the URL returned by squidguard must have the protocol as the original URL.
290             # So for a URL with HTTPS protocol, squidguard must return a URL that uses the HTTPS protocol.
291             # This is really not nice but the workaround is to use a 302 redirection:
292             # redirect 302:http://www.internal-server.com/blocked.html
293              
294             # another one on the issue: http://www.techienuggets.com/Comments?tx=114527
295             # Blocking/filtering SSL pages with SquidGuard do not work very well. You
296             # need to use Squid acls for that, or wrap up SquidGuard as an external
297             # acl instead of url rewriter..
298             #
299             # The reason is that
300             # a) Most browsers will not accept a browser redirect in response to
301             # CONNECT.
302             #
303             # b) You can't rewrite a CONNECT request into a http:// requrest.
304             #
305             # c) Most browsers will be quite upset if you rewrite the CONNECT to a
306             # different host than requested.
307             #
308             # meaning that there is not much you actually can do with CONNECT requests
309             # in SquidGuard that won't make browsers upset.
310              
311             # So let's redirect.
312             # Maybe we should check if $url begins with http:// .
313              
314             if( $req->method eq 'CONNECT' ) {
315             $redir = "302:$redir";
316             }
317             }
318             }
319              
320             return $redir;
321             }
322              
323              
324             =head2 $sg->run()
325              
326             Starts handling the requests, reading them from one per line in the format used by Squid to talk to the url_rewrite_program
327              
328             =cut
329              
330             sub run {
331             my $self = shift;
332              
333             $self->{redir} || croak "Can not run when redir url is not defined";
334              
335             $|=1; # force a flush after every print on STDOUT
336              
337             while () {
338              
339             chomp;
340             $self->{verbose} and print STDERR "Examining $_\n";
341              
342             my $ret = "";
343             if( $self->{concurrency} ) {
344             s/^(\d+\s+)//o;
345             $ret = $1;
346             }
347              
348             my $redir = $self->handle($_);
349              
350             if( $redir ) {
351             $self->{verbose} and print STDERR "Returning $redir\n";
352             $ret .= $redir;
353             }
354              
355             print "$ret\n";
356              
357             last if $self->{oneshot};
358             }
359             }
360              
361              
362             =head2 Black/white-list support
363              
364             Squid::Guard provides support for using precompiled black or white lists, in a way similar to what squidGuard does. These lists are organized in categories. Each category has its own path (a directory) where three files can reside. These files are named domains, urls and expressions. There's no need for all three to be there, and in most situations only the domains and urls files are used. These files list domains, urls and/or (regular) expressions which describe if a request belong to the category. You can decide, in the checkf function, to allow or to redirect a request belonging to a certain category.
365             Similarly to what squidGuard does, the domains and urls files have to be compiled in .db form prior to be used. This makes it possible to run huge domains and urls lists, with acceptable performance.
366             You can find precompiled lists on the net, or create your own.
367             Beginning with version 0.13, there is EXPERIMENTAL support for the userdomains file. This file lists domains associated with users. The request will be checked against the domains only if the request has the associated identity corresponding to the user. The file is made of lines in the format C. At the moment, the file is entirely read in memory and no corresponding .db is generated/needed. The userdomain feature is EXPERIMENTAL and subject to change.
368              
369             =head2 $sg->dbdir()
370              
371             Get/set dbdir parameter, i.e. the directory where category subdirs are found. .db files generated from domains and urls files will reside here too.
372              
373             =cut
374              
375             sub dbdir {
376             my $self = shift;
377             if (@_) { $self->{dbdir} = shift }
378             return $self->{dbdir};
379             }
380              
381              
382             =head2 $sg->addcateg( name => path, ... )
383              
384             Adds one or more categories.
385             C is the directory, relative to dbdir, containing the C, C, C and C files.
386              
387             =cut
388              
389             sub addcateg {
390             my $self = shift;
391             my %h = ( @_ );
392             foreach my $cat (keys %h) {
393             $self->{categ}->{$cat}->{loc} = $h{$cat};
394              
395             my $l = $self->{dbdir} . '/' . $self->{categ}->{$cat}->{loc};
396             #print STDERR "$l\n";
397              
398             my $domsrc = "${l}/domains";
399             my $domdb = "${domsrc}.db";
400             if( -f $domsrc ) {
401             # tie .db for reading
402             my %h;
403             my $X = tie (%h, 'DB_File', $domdb, O_RDONLY, 0644, $DB_BTREE) || croak ("Cannot open $domdb: $!");
404             $self->{categ}->{$cat}->{d} = \%h;
405             $self->{categ}->{$cat}->{dX} = $X;
406             }
407              
408             my $urlsrc = "${l}/urls";
409             my $urldb = "${urlsrc}.db";
410             if( -f $urlsrc ) {
411             # tie .db for reading
412             my %h;
413             my $X = tie (%h, 'DB_File', $urldb, O_RDONLY, 0644, $DB_BTREE) || croak ("Cannot open $urldb: $!");
414             $self->{categ}->{$cat}->{u} = \%h;
415             $self->{categ}->{$cat}->{uX} = $X;
416             }
417              
418             my $e = "$l/expressions";
419             if( -f $e ) {
420             my @a;
421             open( E, "< $e" ) or croak "Cannot open $e: $!";
422             while( ) {
423             chomp;
424             s/#.*//o;
425             next if /^\s*$/o;
426             push @a, qr/$_/i; # array of regexps. Can't use 'o' regexp option, since I would put in the array always the same regexp (the first one). But it seems that with qr, 'o' is obsolete.
427             }
428             close E;
429             $self->{categ}->{$cat}->{e} = \@a;
430             }
431              
432             my $ud = "$l/userdomains";
433             if( -f $ud ) {
434             my $hr = {};
435             open( UD, "< $ud" ) or croak "Cannot open $ud: $!";
436             while( ) {
437             chomp;
438             s/#.*//o;
439             next unless /^\s*([^\|]+)\|(.*\S)/o;
440             $hr->{$1}->{$2} = 1;
441             }
442             close UD;
443             $self->{categ}->{$cat}->{ud} = $hr;
444             }
445             }
446             return 1;
447             }
448              
449              
450             =head2 $sg->mkdb( name => path, ... )
451              
452             Creates/updates the .db files for the categories.
453             Will search in C for the potential presence of the C and C plaintext files.
454             According to the value of the C flag (see), will create or update the .db file from them.
455              
456             =cut
457              
458             sub mkdb {
459             my $self = shift;
460             my %h = ( @_ );
461             foreach my $cat (keys %h) {
462             $self->{categ}->{$cat}->{loc} = $h{$cat};
463              
464             my $l = $self->{dbdir} . '/' . $self->{categ}->{$cat}->{loc};
465             #print STDERR "$l\n";
466              
467             my $domsrc = "${l}/domains";
468             my $domdb = "${domsrc}.db";
469             if( -f $domsrc ) {
470             # update .db, if needed
471             if( $self->{forcedbupdate} || (stat($domsrc))[9] > ( (stat($domdb))[9] || 0 ) ) {
472             $self->{verbose} and print STDERR "Making $domdb\n";
473             my %h;
474             my $X = tie (%h, 'DB_File', $domdb, O_CREAT|O_TRUNC|O_RDWR, 0644, $DB_BTREE) || croak ("Cannot create $domdb: $!");
475             open( F, "< $domsrc") or croak "Cannot open $domsrc";
476             while( ) {
477             chomp;
478             s/#.*//o;
479             next if /^\s*$/o;
480             $h{lc($_)} = undef;
481             }
482             close F;
483             undef $X;
484             untie %h;
485             } else {
486             $self->{verbose} and print STDERR "$domdb more recent than $domsrc, skipped\n";
487             }
488             }
489              
490             my $urlsrc = "${l}/urls";
491             my $urldb = "${urlsrc}.db";
492             if( -f $urlsrc ) {
493             # update .db, if needed
494             if( $self->{forcedbupdate} || (stat($urlsrc))[9] > ( (stat($urldb))[9] || 0 ) ) {
495             $self->{verbose} and print STDERR "Making $urldb\n";
496             my %h;
497             my $X = tie (%h, 'DB_File', $urldb, O_CREAT|O_TRUNC|O_RDWR, 0644, $DB_BTREE) || croak ("Cannot create $urldb: $!");
498             open( F, "< $urlsrc") or croak "Cannot open $urlsrc";
499             while( ) {
500             chomp;
501             s/#.*//o;
502             next if /^\s*$/o;
503             $h{lc($_)} = undef;
504             }
505             close F;
506             undef $X;
507             untie %h;
508             } else {
509             $self->{verbose} and print STDERR "$urldb more recent than $urlsrc, skipped\n";
510             }
511             }
512             }
513             return 1;
514             }
515              
516              
517             =head2 $sg->forcedbupdate()
518              
519             Controls whether mkdb should forcibly update the .db files.
520             If set to a false value (which is the default), existing .db files are refreshed only if older than the respective plaintext file.
521             If set to a true value, .db files are always (re)created.
522              
523             =cut
524              
525             sub forcedbupdate {
526             my $self = shift;
527             if (@_) { $self->{forcedbupdate} = shift }
528             return $self->{forcedbupdate};
529             }
530              
531              
532             #=head2 $sg->getcateg()
533             #
534             #Gets the defined categories
535             #
536             #=cut
537             #
538             #sub getcateg {
539             # my $self = shift;
540             # my %h;
541             # for( keys %{$self->{categ}} ) {
542             # $h{$_} = $self->{categ}->{$_}->{loc};
543             # }
544             # return %h;
545             #}
546              
547              
548             # =head2 $sg->_domains()
549             #
550             # Finds the super-domains where the given domain is nested.
551             # This is a helper sub for C
552             #
553             # =cut
554              
555             sub _domains($) {
556             my $host = shift;
557             return () unless $host;
558             # www . iotti . biz
559             # 0 1 2
560             my @a = split(/\./, $host);
561             my $num = $#a;
562             my @b;
563             for( 0 .. $num ) {
564             my $j = $num - $_;
565             push @b, join(".", @a[$j .. $num]);
566             }
567             return @b;
568             }
569              
570              
571             # =head2 $sg->_uris()
572             #
573             # Finds the uris containing the given uri.
574             # This is a helper sub for C
575             #
576             # =cut
577              
578             sub _uris($) {
579             my $uri = shift;
580             return () unless $uri;
581             # www.iotti.biz / dir1 / dir2 / dir3 / file
582             # 0 1 2 3 4
583             my @a = split(/\//, $uri);
584             my $num = $#a;
585             my @b;
586             for( 0 .. $num ) {
587             my $sub_uri = join("/", @a[0 .. $_]);
588             push @b, $sub_uri;
589             push @b, $sub_uri . '/' if $_ < $num; # check www.iotti.biz/dir/ too (with the trailing slashe) since some publicly-available lists carry urls with trailing slashes
590             }
591             return @b;
592             }
593              
594              
595             =head2 $sg->checkincateg($req, $categ, ... )
596              
597             Checks if a request is in one category or more
598              
599             =cut
600              
601             sub checkincateg($$@) {
602             my ( $self, $req, @categs ) = @_;
603              
604             foreach my $categ (@categs) {
605             my $catref = $self->{categ};
606             defined( $catref->{$categ} ) or croak "The requested category $categ does not exist";
607              
608             #print STDERR "s $req->scheme h $req->host p $req->path\n";
609             if( defined( $catref->{$categ}->{d} ) ) {
610             $self->{debug} and print STDERR " Check " . $req->host . " in $categ domains\n";
611             my $ref = $catref->{$categ}->{d};
612             foreach( _domains($req->host) ) {
613             $self->{debug} and print STDERR " Check $_\n";
614             if(exists($ref->{$_})) {
615             $self->{debug} and print STDERR " FOUND\n";
616             return $categ;
617             }
618             }
619             }
620             if( defined( $catref->{$categ}->{u} ) ) {
621             # in url checking, we test the authority part + the optional path part + the optional query part
622             my $what = $req->authority_path_query;
623             $self->{debug} and print STDERR " Check $what in $categ urls\n";
624             my $ref = $catref->{$categ}->{u};
625             foreach( _uris($what) ) {
626             $self->{debug} and print STDERR " Check $_\n";
627             if(exists($ref->{$_})) {
628             $self->{debug} and print STDERR " FOUND\n";
629             return $categ;
630             }
631             }
632             }
633             if( defined( $catref->{$categ}->{e} ) ) {
634             my $what = $req->url;
635             $self->{debug} and print STDERR " Check $what in $categ expressions\n";
636             my $ref = $catref->{$categ}->{e};
637             foreach( @$ref ) {
638             $self->{debug} and print STDERR " Check $_\n";
639             if( $what =~ /$_/i ) { # Can't use 'o' regexp option, since I would compare always the same regexp.
640             $self->{debug} and print STDERR " FOUND\n";
641             return $categ;
642             }
643             }
644             }
645             if( $req->ident and defined( $catref->{$categ}->{ud}->{$req->ident} ) ) {
646             $self->{debug} and print STDERR " Check " . $req->host . " in $categ userdomains for user " . $req->ident . "\n";
647             my $hr = $catref->{$categ}->{ud}->{$req->ident};
648             # TODO: optimization: precompile _domains($req->host) only once for domains and userdomains
649             foreach( _domains($req->host) ) {
650             $self->{debug} and print STDERR " Check $_\n";
651             if($hr->{$_}) {
652             $self->{debug} and print STDERR " FOUND\n";
653             return $categ;
654             }
655             }
656             }
657             }
658              
659             return '';
660             }
661              
662              
663             # Gets a passwd row, making use of the cache if enabled.
664              
665             sub _getpwnamcache($) {
666             my $nam = shift;
667             my $k = "PWNAM: $nam";
668              
669             if( $cachettl ) {
670             my $v = _cacherd( $k );
671             defined($v) and return split( /:/, $v );
672             }
673              
674             my @a = getpwnam($nam);
675              
676             if( $cachettl ) {
677             _cachewr( $k, join( ':', @a ) );
678             }
679              
680             return @a;
681             }
682              
683              
684             sub _getgrnamcache($) {
685             my $nam = shift;
686             my $k = "GRNAM: $nam";
687              
688             if( $cachettl ) {
689             my $v = _cacherd( $k );
690             defined($v) and return split( /:/, $v );
691             }
692              
693             my @a = getgrnam($nam);
694              
695             if( $cachettl ) {
696             _cachewr( $k, join( ':', @a ) );
697             }
698              
699             return @a;
700             }
701              
702              
703             # Runs a command, making use of the cache if enabled.
704              
705             sub _runcache($) {
706             my $cmd = shift;
707             my $k = "RUN: $cmd";
708              
709             my $v;
710             if( $cachettl ) {
711             $v = _cacherd( $k );
712             defined($v) and return $v;
713             }
714              
715             $v = `$cmd`;
716              
717             if( $cachettl ) {
718             _cachewr( $k, $v );
719             }
720              
721             return $v;
722             }
723              
724              
725             =head2 Other help subs that can be used in the checkf function
726              
727              
728             =head2 $sg->checkingroup($user, $group, ... )
729              
730             Checks if a user is in a UNIX grop
731              
732             =cut
733              
734             sub checkingroup($$@) {
735             my ( $self, $user, @groups ) = @_;
736              
737             return 0 unless $user;
738              
739             my @pwent = _getpwnamcache($user);
740             if( ! @pwent ) {
741             print STDERR "Can not find user $user\n";
742             return '';
743             }
744              
745             my $uid = $pwent[2];
746             my $uprimgid = $pwent[3];
747             if( ! defined $uid || ! defined $uprimgid ) {
748             print STDERR "Can not find uid and gid corresponding to $user\n";
749             return '';
750             }
751              
752             foreach my $group (@groups) {
753             my @grent = _getgrnamcache($group);
754             if( ! @grent ) {
755             print STDERR "Can not find group $group\n";
756             next;
757             }
758              
759             my $gid = $grent[2];
760             if( ! defined $gid ) {
761             print STDERR "Can not find gid corresponding to $group\n";
762             next;
763             }
764              
765             if( $uprimgid == $gid ) {
766             $self->{debug} and print STDERR "FOUND $user has primary group $group\n";
767             return $group;
768             }
769              
770             my @membri = split(/\s+/, $grent[3]);
771             $self->{debug} and print STDERR "Group $group contains:\n" . join("\n", @membri) . "\n";
772             for(@membri) {
773             my @pwent2 = _getpwnamcache($_);
774             my $uid2 = $pwent2[2];
775             if( ! defined $uid2 ) {
776             print STDERR "Can not find uid corresponding to $_\n";
777             next;
778             }
779             if( $uid2 == $uid ) {
780             $self->{debug} and print STDERR "FOUND $user is in $group\n";
781             return $group;
782             }
783             }
784             }
785              
786             return '';
787             }
788              
789              
790             =head2 $sg->checkinwbgroup($user, $group, ...)
791              
792             Checks if a user is in a WinBind grop
793              
794             =cut
795              
796             sub checkinwbgroup($$@) {
797             my ( $self, $user, @groups ) = @_;
798              
799             return '' unless $user;
800              
801             my $userSID = _runcache("wbinfo -n '$user'");
802             if( $? ) {
803             print STDERR "Can not find user $user in winbind\n";
804             return '';
805             }
806             $userSID =~ s/\s.*//o;
807             chomp $userSID;
808             $self->{debug} and print STDERR "Found user $user with SID $userSID\n";
809              
810             my %groupsSIDs;
811             foreach my $group (@groups) {
812             my $groupSID = _runcache("wbinfo -n '$group'");
813             if( $? ) {
814             print STDERR "Can not find group $group in winbind\n";
815             return '';
816             }
817             $groupSID =~ s/\s.*//o;
818             chomp $groupSID;
819             $self->{debug} and print STDERR "Found group $group with SID $groupSID\n";
820             $groupsSIDs{$groupSID} = $group;
821             }
822              
823             my @userInSIDs = _runcache("wbinfo --user-domgroups '$userSID'");
824             if( $? ) {
825             print STDERR "Can not find the SIDs of the groups of $user - $userSID\n";
826             return '';
827             }
828             $self->{debug} and print STDERR "$user is in the following groups:\n @userInSIDs";
829              
830             foreach ( @userInSIDs ) {
831             chomp;
832             if ( $groupsSIDs{$_} ) {
833             $self->{debug} and print STDERR " FOUND\n";
834             return $groupsSIDs{$_};
835             }
836             }
837              
838             return '';
839             }
840              
841              
842             =head2 $sg->checkinaddr($req)
843              
844             Checks if a request is for an explicit IP address
845              
846             =cut
847              
848             sub checkinaddr($$) {
849             my ( $self, $req ) = @_;
850             # TODO: Maybe the test should be more accurate and more general
851             return 1 if $req->host =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/o;
852             return 0;
853             }
854              
855              
856             1;
857              
858             __END__