Bug 4460 - Amazon's AssociateID tag not used in links so referred revenue lost
[koha.git] / C4 / Auth.pm
1 package C4::Auth;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
24 use URI::Escape;
25 use CGI::Session;
26
27 require Exporter;
28 use C4::Context;
29 use C4::Templates;    # to get the template
30 use C4::Branch; # GetBranches
31 use C4::VirtualShelves;
32 use POSIX qw/strftime/;
33 use List::MoreUtils qw/ any /;
34
35 # use utf8;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
37
38 BEGIN {
39     sub psgi_env { any { /^psgi\./ } keys %ENV }
40     sub safe_exit {
41         if ( psgi_env ) { die 'psgi:exit' }
42         else { exit }
43     }
44     $VERSION     = 3.07.00.049;   # set version for version checking
45
46     $debug       = $ENV{DEBUG};
47     @ISA         = qw(Exporter);
48     @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49     @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
50     %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
51     $ldap        = C4::Context->config('useldapserver') || 0;
52     $cas         = C4::Context->preference('casAuthentication');
53     $caslogout   = C4::Context->preference('casLogout');
54     require C4::Auth_with_cas;             # no import
55     if ($ldap) {
56         require C4::Auth_with_ldap;
57         import C4::Auth_with_ldap qw(checkpw_ldap);
58     }
59     if ($cas) {
60         import  C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
61     }
62
63 }
64
65 =head1 NAME
66
67 C4::Auth - Authenticates Koha users
68
69 =head1 SYNOPSIS
70
71   use CGI;
72   use C4::Auth;
73   use C4::Output;
74
75   my $query = new CGI;
76
77   my ($template, $borrowernumber, $cookie)
78     = get_template_and_user(
79         {
80             template_name   => "opac-main.tmpl",
81             query           => $query,
82       type            => "opac",
83       authnotrequired => 1,
84       flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
85   }
86     );
87
88   output_html_with_http_headers $query, $cookie, $template->output;
89
90 =head1 DESCRIPTION
91
92 The main function of this module is to provide
93 authentification. However the get_template_and_user function has
94 been provided so that a users login information is passed along
95 automatically. This gets loaded into the template.
96
97 =head1 FUNCTIONS
98
99 =head2 get_template_and_user
100
101  my ($template, $borrowernumber, $cookie)
102      = get_template_and_user(
103        {
104          template_name   => "opac-main.tmpl",
105          query           => $query,
106          type            => "opac",
107          authnotrequired => 1,
108          flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
109        }
110      );
111
112 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
113 to C<&checkauth> (in this module) to perform authentification.
114 See C<&checkauth> for an explanation of these parameters.
115
116 The C<template_name> is then used to find the correct template for
117 the page. The authenticated users details are loaded onto the
118 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
119 C<sessionID> is passed to the template. This can be used in templates
120 if cookies are disabled. It needs to be put as and input to every
121 authenticated page.
122
123 More information on the C<gettemplate> sub can be found in the
124 Output.pm module.
125
126 =cut
127
128 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
129 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time            )
130 VALUES                    (     ?,         ?,          ?,         ?,          ?, FROM_UNIXTIME(?))
131 EOQ
132 sub get_template_and_user {
133     my $in       = shift;
134     my $template =
135       C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
136     my ( $user, $cookie, $sessionID, $flags );
137     if ( $in->{'template_name'} !~m/maintenance/ ) {
138         ( $user, $cookie, $sessionID, $flags ) = checkauth(
139             $in->{'query'},
140             $in->{'authnotrequired'},
141             $in->{'flagsrequired'},
142             $in->{'type'}
143         );
144     }
145
146     my $borrowernumber;
147     my $insecure = C4::Context->preference('insecure');
148     if ($user or $insecure) {
149         require C4::Members;
150         # It's possible for $user to be the borrowernumber if they don't have a
151         # userid defined (and are logging in through some other method, such
152         # as SSL certs against an email address)
153         $borrowernumber = getborrowernumber($user) if defined($user);
154         if (!defined($borrowernumber) && defined($user)) {
155             my $borrower = C4::Members::GetMember(borrowernumber => $user);
156             if ($borrower) {
157                 $borrowernumber = $user;
158                 # A bit of a hack, but I don't know there's a nicer way
159                 # to do it.
160                 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
161             }
162         }
163
164         # user info
165         $template->param( loggedinusername => $user );
166         $template->param( sessionID        => $sessionID );
167
168         my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
169         $template->param(
170             pubshelves     => $total->{pubtotal},
171             pubshelvesloop => $pubshelves,
172             barshelves      => $total->{bartotal},
173             barshelvesloop  => $barshelves,
174         );
175
176         my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
177         my @bordat;
178         $bordat[0] = $borr;
179         $template->param( "USER_INFO" => \@bordat );
180
181         my $all_perms = get_all_subpermissions();
182
183         my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
184                             editcatalogue updatecharges management tools editauthorities serials reports acquisition);
185         # We are going to use the $flags returned by checkauth
186         # to create the template's parameters that will indicate
187         # which menus the user can access.
188         if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
189             $template->param( CAN_user_circulate        => 1 );
190             $template->param( CAN_user_catalogue        => 1 );
191             $template->param( CAN_user_parameters       => 1 );
192             $template->param( CAN_user_borrowers        => 1 );
193             $template->param( CAN_user_permissions      => 1 );
194             $template->param( CAN_user_reserveforothers => 1 );
195             $template->param( CAN_user_borrow           => 1 );
196             $template->param( CAN_user_editcatalogue    => 1 );
197             $template->param( CAN_user_updatecharges     => 1 );
198             $template->param( CAN_user_acquisition      => 1 );
199             $template->param( CAN_user_management       => 1 );
200             $template->param( CAN_user_tools            => 1 );
201             $template->param( CAN_user_editauthorities  => 1 );
202             $template->param( CAN_user_serials          => 1 );
203             $template->param( CAN_user_reports          => 1 );
204             $template->param( CAN_user_staffaccess      => 1 );
205             foreach my $module (keys %$all_perms) {
206                 foreach my $subperm (keys %{ $all_perms->{$module} }) {
207                     $template->param( "CAN_user_${module}_${subperm}" => 1 );
208                 }
209             }
210         }
211
212         if ( $flags ) {
213             foreach my $module (keys %$all_perms) {
214                 if ( $flags->{$module} == 1) {
215                     foreach my $subperm (keys %{ $all_perms->{$module} }) {
216                         $template->param( "CAN_user_${module}_${subperm}" => 1 );
217                     }
218                 } elsif ( ref($flags->{$module}) ) {
219                     foreach my $subperm (keys %{ $flags->{$module} } ) {
220                         $template->param( "CAN_user_${module}_${subperm}" => 1 );
221                     }
222                 }
223             }
224         }
225
226         if ($flags) {
227             foreach my $module (keys %$flags) {
228                 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
229                     $template->param( "CAN_user_$module" => 1 );
230                     if ($module eq "parameters") {
231                         $template->param( CAN_user_management => 1 );
232                     }
233                 }
234             }
235         }
236                 # Logged-in opac search history
237                 # If the requested template is an opac one and opac search history is enabled
238                 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
239                         my $dbh = C4::Context->dbh;
240                         my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
241                         my $sth = $dbh->prepare($query);
242                         $sth->execute($borrowernumber);
243                         
244                         # If at least one search has already been performed
245                         if ($sth->fetchrow_array > 0) { 
246                         # We show the link in opac
247                         $template->param(ShowOpacRecentSearchLink => 1);
248                         }
249
250                         # And if there's a cookie with searches performed when the user was not logged in, 
251                         # we add them to the logged-in search history
252                         my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
253                         if ($searchcookie){
254                                 $searchcookie = uri_unescape($searchcookie);
255                                 my @recentSearches = @{thaw($searchcookie) || []};
256                                 if (@recentSearches) {
257                                         my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
258                                         $sth->execute( $borrowernumber,
259                                                        $in->{'query'}->cookie("CGISESSID"),
260                                                        $_->{'query_desc'},
261                                                        $_->{'query_cgi'},
262                                                        $_->{'total'},
263                                                        $_->{'time'},
264                             ) foreach @recentSearches;
265
266                                         # And then, delete the cookie's content
267                                         my $newsearchcookie = $in->{'query'}->cookie(
268                                                                                                 -name => 'KohaOpacRecentSearches',
269                                                                                                 -value => freeze([]),
270                                                                                                 -expires => ''
271                                                                                          );
272                                         $cookie = [$cookie, $newsearchcookie];
273                                 }
274                         }
275                 }
276     }
277         else {  # if this is an anonymous session, setup to display public lists...
278
279         $template->param( sessionID        => $sessionID );
280         
281         my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
282         $template->param(
283             pubshelves     => $total->{pubtotal},
284             pubshelvesloop => $pubshelves,
285         );
286     }
287         # Anonymous opac search history
288         # If opac search history is enabled and at least one search has already been performed
289         if (C4::Context->preference('EnableOpacSearchHistory')) {
290                 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
291                 if ($searchcookie){
292                         $searchcookie = uri_unescape($searchcookie);
293                         my @recentSearches = @{thaw($searchcookie) || []};
294             # We show the link in opac
295                         if (@recentSearches) {
296                                 $template->param(ShowOpacRecentSearchLink => 1);
297                         }
298             }
299         }
300
301     if(C4::Context->preference('dateformat')){
302         if(C4::Context->preference('dateformat') eq "metric"){
303             $template->param(dateformat_metric => 1);
304         } elsif(C4::Context->preference('dateformat') eq "us"){
305             $template->param(dateformat_us => 1);
306         } else {
307             $template->param(dateformat_iso => 1);
308         }
309     } else {
310         $template->param(dateformat_iso => 1);
311     }
312
313     # these template parameters are set the same regardless of $in->{'type'}
314     $template->param(
315             "BiblioDefaultView".C4::Context->preference("BiblioDefaultView")         => 1,
316             EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
317             GoogleJackets                => C4::Context->preference("GoogleJackets"),
318             OpenLibraryCovers            => C4::Context->preference("OpenLibraryCovers"),
319             KohaAdminEmailAddress        => "" . C4::Context->preference("KohaAdminEmailAddress"),
320             LoginBranchcode              => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
321             LoginFirstname               => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
322             LoginSurname                 => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
323             TagsEnabled                  => C4::Context->preference("TagsEnabled"),
324             hide_marc                    => C4::Context->preference("hide_marc"),
325             item_level_itypes            => C4::Context->preference('item-level_itypes'),
326             patronimages                 => C4::Context->preference("patronimages"),
327             singleBranchMode             => C4::Context->preference("singleBranchMode"),
328             XSLTDetailsDisplay           => C4::Context->preference("XSLTDetailsDisplay"),
329             XSLTResultsDisplay           => C4::Context->preference("XSLTResultsDisplay"),
330             using_https                  => $in->{'query'}->https() ? 1 : 0,
331             noItemTypeImages             => C4::Context->preference("noItemTypeImages"),
332             marcflavour                  => C4::Context->preference("marcflavour"),
333     );
334
335     if ( $in->{'type'} eq "intranet" ) {
336         $template->param(
337             AmazonContent               => C4::Context->preference("AmazonContent"),
338             AmazonAssocTag              => C4::Context->preference("AmazonAssocTag"),
339             AmazonCoverImages           => C4::Context->preference("AmazonCoverImages"),
340             AmazonEnabled               => C4::Context->preference("AmazonEnabled"),
341             AmazonSimilarItems          => C4::Context->preference("AmazonSimilarItems"),
342             AutoLocation                => C4::Context->preference("AutoLocation"),
343             "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
344             CalendarFirstDayOfWeek      => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
345             CircAutocompl               => C4::Context->preference("CircAutocompl"),
346             FRBRizeEditions             => C4::Context->preference("FRBRizeEditions"),
347             IndependantBranches         => C4::Context->preference("IndependantBranches"),
348             IntranetNav                 => C4::Context->preference("IntranetNav"),
349             IntranetmainUserblock       => C4::Context->preference("IntranetmainUserblock"),
350             LibraryName                 => C4::Context->preference("LibraryName"),
351             LoginBranchname             => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
352             advancedMARCEditor          => C4::Context->preference("advancedMARCEditor"),
353             canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
354             intranetcolorstylesheet     => C4::Context->preference("intranetcolorstylesheet"),
355             IntranetFavicon             => C4::Context->preference("IntranetFavicon"),
356             intranetreadinghistory      => C4::Context->preference("intranetreadinghistory"),
357             intranetstylesheet          => C4::Context->preference("intranetstylesheet"),
358             IntranetUserCSS             => C4::Context->preference("IntranetUserCSS"),
359             intranetuserjs              => C4::Context->preference("intranetuserjs"),
360             intranetbookbag             => C4::Context->preference("intranetbookbag"),
361             suggestion                  => C4::Context->preference("suggestion"),
362             virtualshelves              => C4::Context->preference("virtualshelves"),
363             StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
364             NoZebra                     => C4::Context->preference('NoZebra'),
365             EasyAnalyticalRecords       => C4::Context->preference('EasyAnalyticalRecords'),
366             LocalCoverImages            => C4::Context->preference('LocalCoverImages'),
367             OPACLocalCoverImages        => C4::Context->preference('OPACLocalCoverImages'),
368             AllowMultipleCovers         => C4::Context->preference('AllowMultipleCovers'),
369             EnableBorrowerFiles         => C4::Context->preference('EnableBorrowerFiles'),
370         );
371     }
372     else {
373         warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
374         #TODO : replace LibraryName syspref with 'system name', and remove this html processing
375         my $LibraryNameTitle = C4::Context->preference("LibraryName");
376         $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
377         $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
378         # clean up the busc param in the session if the page is not opac-detail
379         if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
380             my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
381             $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
382         }
383         # variables passed from CGI: opac_css_override and opac_search_limits.
384         my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
385         my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
386         my $opac_name = '';
387         if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
388             $opac_name = $1;   # opac_search_limit is a branch, so we use it.
389         } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
390             $opac_name = C4::Context->userenv->{'branch'};
391         }
392         my $checkstyle = C4::Context->preference("opaccolorstylesheet");
393         if ($checkstyle =~ /http/)
394         {
395                 $template->param( opacexternalsheet => $checkstyle);
396         } else
397         {
398                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
399             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
400         }
401         $template->param(
402             AmazonContent             => "" . C4::Context->preference("AmazonContent"),
403             AmazonAssocTag            => C4::Context->preference("AmazonAssocTag"),
404             AnonSuggestions           => "" . C4::Context->preference("AnonSuggestions"),
405             AuthorisedValueImages     => C4::Context->preference("AuthorisedValueImages"),
406             BranchesLoop              => GetBranchesLoop($opac_name),
407             CalendarFirstDayOfWeek      => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
408             LibraryName               => "" . C4::Context->preference("LibraryName"),
409             LibraryNameTitle          => "" . $LibraryNameTitle,
410             LoginBranchname           => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
411             OPACAmazonEnabled         => C4::Context->preference("OPACAmazonEnabled"),
412             OPACAmazonSimilarItems    => C4::Context->preference("OPACAmazonSimilarItems"),
413             OPACAmazonCoverImages     => C4::Context->preference("OPACAmazonCoverImages"),
414             OPACAmazonReviews         => C4::Context->preference("OPACAmazonReviews"),
415             OPACFRBRizeEditions       => C4::Context->preference("OPACFRBRizeEditions"),
416             OpacHighlightedWords       => C4::Context->preference("OpacHighlightedWords"),
417             OPACItemHolds             => C4::Context->preference("OPACItemHolds"),
418             OPACShelfBrowser          => "". C4::Context->preference("OPACShelfBrowser"),
419             OpacShowRecentComments    => C4::Context->preference("OpacShowRecentComments"),
420             OPACURLOpenInNewWindow    => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
421             OPACUserCSS               => "". C4::Context->preference("OPACUserCSS"),
422             OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
423             OpacAuthorities           => C4::Context->preference("OpacAuthorities"),
424             OPACBaseURL               => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
425                    ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
426             opac_css_override           => $ENV{'OPAC_CSS_OVERRIDE'},
427             opac_search_limit         => $opac_search_limit,
428             opac_limit_override       => $opac_limit_override,
429             OpacBrowser               => C4::Context->preference("OpacBrowser"),
430             OpacCloud                 => C4::Context->preference("OpacCloud"),
431             OpacKohaUrl               => C4::Context->preference("OpacKohaUrl"),
432             OpacMainUserBlock         => "" . C4::Context->preference("OpacMainUserBlock"),
433             OpacNav                   => "" . C4::Context->preference("OpacNav"),
434             OpacNavRight              => "" . C4::Context->preference("OpacNavRight"),
435             OpacNavBottom             => "" . C4::Context->preference("OpacNavBottom"),
436             OpacPasswordChange        => C4::Context->preference("OpacPasswordChange"),
437             OPACPatronDetails        => C4::Context->preference("OPACPatronDetails"),
438             OPACPrivacy               => C4::Context->preference("OPACPrivacy"),
439             OPACFinesTab              => C4::Context->preference("OPACFinesTab"),
440             OpacTopissue              => C4::Context->preference("OpacTopissue"),
441             RequestOnOpac             => C4::Context->preference("RequestOnOpac"),
442             'Version'                 => C4::Context->preference('Version'),
443             hidelostitems             => C4::Context->preference("hidelostitems"),
444             mylibraryfirst            => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
445             opaclayoutstylesheet      => "" . C4::Context->preference("opaclayoutstylesheet"),
446             opacstylesheet            => "" . C4::Context->preference("opacstylesheet"),
447             opacbookbag               => "" . C4::Context->preference("opacbookbag"),
448             opaccredits               => "" . C4::Context->preference("opaccredits"),
449             OpacFavicon               => C4::Context->preference("OpacFavicon"),
450             opacheader                => "" . C4::Context->preference("opacheader"),
451             opaclanguagesdisplay      => "" . C4::Context->preference("opaclanguagesdisplay"),
452             opacreadinghistory        => C4::Context->preference("opacreadinghistory"),
453             opacsmallimage            => "" . C4::Context->preference("opacsmallimage"),
454             opacuserjs                => C4::Context->preference("opacuserjs"),
455             opacuserlogin             => "" . C4::Context->preference("opacuserlogin"),
456             reviewson                 => C4::Context->preference("reviewson"),
457             ShowReviewer              => C4::Context->preference("ShowReviewer"),
458             ShowReviewerPhoto         => C4::Context->preference("ShowReviewerPhoto"),
459             suggestion                => "" . C4::Context->preference("suggestion"),
460             virtualshelves            => "" . C4::Context->preference("virtualshelves"),
461             OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
462             OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
463             OPACXSLTDetailsDisplay           => C4::Context->preference("OPACXSLTDetailsDisplay"),
464             OPACXSLTResultsDisplay           => C4::Context->preference("OPACXSLTResultsDisplay"),
465             SyndeticsClientCode          => C4::Context->preference("SyndeticsClientCode"),
466             SyndeticsEnabled             => C4::Context->preference("SyndeticsEnabled"),
467             SyndeticsCoverImages         => C4::Context->preference("SyndeticsCoverImages"),
468             SyndeticsTOC                 => C4::Context->preference("SyndeticsTOC"),
469             SyndeticsSummary             => C4::Context->preference("SyndeticsSummary"),
470             SyndeticsEditions            => C4::Context->preference("SyndeticsEditions"),
471             SyndeticsExcerpt             => C4::Context->preference("SyndeticsExcerpt"),
472             SyndeticsReviews             => C4::Context->preference("SyndeticsReviews"),
473             SyndeticsAuthorNotes         => C4::Context->preference("SyndeticsAuthorNotes"),
474             SyndeticsAwards              => C4::Context->preference("SyndeticsAwards"),
475             SyndeticsSeries              => C4::Context->preference("SyndeticsSeries"),
476             SyndeticsCoverImageSize      => C4::Context->preference("SyndeticsCoverImageSize"),
477             OPACLocalCoverImages         => C4::Context->preference("OPACLocalCoverImages"),
478         );
479
480         $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
481     }
482     return ( $template, $borrowernumber, $cookie, $flags);
483 }
484
485 =head2 checkauth
486
487   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
488
489 Verifies that the user is authorized to run this script.  If
490 the user is authorized, a (userid, cookie, session-id, flags)
491 quadruple is returned.  If the user is not authorized but does
492 not have the required privilege (see $flagsrequired below), it
493 displays an error page and exits.  Otherwise, it displays the
494 login page and exits.
495
496 Note that C<&checkauth> will return if and only if the user
497 is authorized, so it should be called early on, before any
498 unfinished operations (e.g., if you've opened a file, then
499 C<&checkauth> won't close it for you).
500
501 C<$query> is the CGI object for the script calling C<&checkauth>.
502
503 The C<$noauth> argument is optional. If it is set, then no
504 authorization is required for the script.
505
506 C<&checkauth> fetches user and session information from C<$query> and
507 ensures that the user is authorized to run scripts that require
508 authorization.
509
510 The C<$flagsrequired> argument specifies the required privileges
511 the user must have if the username and password are correct.
512 It should be specified as a reference-to-hash; keys in the hash
513 should be the "flags" for the user, as specified in the Members
514 intranet module. Any key specified must correspond to a "flag"
515 in the userflags table. E.g., { circulate => 1 } would specify
516 that the user must have the "circulate" privilege in order to
517 proceed. To make sure that access control is correct, the
518 C<$flagsrequired> parameter must be specified correctly.
519
520 Koha also has a concept of sub-permissions, also known as
521 granular permissions.  This makes the value of each key
522 in the C<flagsrequired> hash take on an additional
523 meaning, i.e.,
524
525  1
526
527 The user must have access to all subfunctions of the module
528 specified by the hash key.
529
530  *
531
532 The user must have access to at least one subfunction of the module
533 specified by the hash key.
534
535  specific permission, e.g., 'export_catalog'
536
537 The user must have access to the specific subfunction list, which
538 must correspond to a row in the permissions table.
539
540 The C<$type> argument specifies whether the template should be
541 retrieved from the opac or intranet directory tree.  "opac" is
542 assumed if it is not specified; however, if C<$type> is specified,
543 "intranet" is assumed if it is not "opac".
544
545 If C<$query> does not have a valid session ID associated with it
546 (i.e., the user has not logged in) or if the session has expired,
547 C<&checkauth> presents the user with a login page (from the point of
548 view of the original script, C<&checkauth> does not return). Once the
549 user has authenticated, C<&checkauth> restarts the original script
550 (this time, C<&checkauth> returns).
551
552 The login page is provided using a HTML::Template, which is set in the
553 systempreferences table or at the top of this file. The variable C<$type>
554 selects which template to use, either the opac or the intranet
555 authentification template.
556
557 C<&checkauth> returns a user ID, a cookie, and a session ID. The
558 cookie should be sent back to the browser; it verifies that the user
559 has authenticated.
560
561 =cut
562
563 sub _version_check ($$) {
564     my $type = shift;
565     my $query = shift;
566     my $version;
567     # If Version syspref is unavailable, it means Koha is beeing installed,
568     # and so we must redirect to OPAC maintenance page or to the WebInstaller
569         # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
570         if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
571         warn "OPAC Install required, redirecting to maintenance";
572         print $query->redirect("/cgi-bin/koha/maintenance.pl");
573     }
574     unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
575         if ( $type ne 'opac' ) {
576             warn "Install required, redirecting to Installer";
577             print $query->redirect("/cgi-bin/koha/installer/install.pl");
578         } else {
579             warn "OPAC Install required, redirecting to maintenance";
580             print $query->redirect("/cgi-bin/koha/maintenance.pl");
581         }
582         safe_exit;
583     }
584
585     # check that database and koha version are the same
586     # there is no DB version, it's a fresh install,
587     # go to web installer
588     # there is a DB version, compare it to the code version
589     my $kohaversion=C4::Context::KOHAVERSION;
590     # remove the 3 last . to have a Perl number
591     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
592     $debug and print STDERR "kohaversion : $kohaversion\n";
593     if ($version < $kohaversion){
594         my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
595         if ($type ne 'opac'){
596             warn sprintf($warning, 'Installer');
597             print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
598         } else {
599             warn sprintf("OPAC: " . $warning, 'maintenance');
600             print $query->redirect("/cgi-bin/koha/maintenance.pl");
601         }
602         safe_exit;
603     }
604 }
605
606 sub _session_log {
607     (@_) or return 0;
608     open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
609     printf L join("\n",@_);
610     close L;
611 }
612
613 sub checkauth {
614     my $query = shift;
615         $debug and warn "Checking Auth";
616     # $authnotrequired will be set for scripts which will run without authentication
617     my $authnotrequired = shift;
618     my $flagsrequired   = shift;
619     my $type            = shift;
620     $type = 'opac' unless $type;
621
622     my $dbh     = C4::Context->dbh;
623     my $timeout = C4::Context->preference('timeout');
624     # days
625     if ($timeout =~ /(\d+)[dD]/) {
626         $timeout = $1 * 86400;
627     };
628     $timeout = 600 unless $timeout;
629
630     _version_check($type,$query);
631     # state variables
632     my $loggedin = 0;
633     my %info;
634     my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
635     my $logout = $query->param('logout.x');
636
637     # This parameter is the name of the CAS server we want to authenticate against,
638     # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
639     my $casparam = $query->param('cas');
640
641     if ( $userid = $ENV{'REMOTE_USER'} ) {
642         # Using Basic Authentication, no cookies required
643         $cookie = $query->cookie(
644             -name    => 'CGISESSID',
645             -value   => '',
646             -expires => ''
647         );
648         $loggedin = 1;
649     }
650     elsif ( $sessionID = $query->cookie("CGISESSID")) {     # assignment, not comparison
651         my $session = get_session($sessionID);
652         C4::Context->_new_userenv($sessionID);
653         my ($ip, $lasttime, $sessiontype);
654         if ($session){
655             C4::Context::set_userenv(
656                 $session->param('number'),       $session->param('id'),
657                 $session->param('cardnumber'),   $session->param('firstname'),
658                 $session->param('surname'),      $session->param('branch'),
659                 $session->param('branchname'),   $session->param('flags'),
660                 $session->param('emailaddress'), $session->param('branchprinter')
661             );
662             C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
663             C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
664             C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
665             $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
666             $ip       = $session->param('ip');
667             $lasttime = $session->param('lasttime');
668             $userid   = $session->param('id');
669                         $sessiontype = $session->param('sessiontype');
670         }
671         if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
672           || ( $cas && $query->param('ticket') ) ) {
673             #if a user enters an id ne to the id in the current session, we need to log them in...
674             #first we need to clear the anonymous session...
675             $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
676             $session->flush;      
677             $session->delete();
678             C4::Context->_unset_userenv($sessionID);
679                         $sessionID = undef;
680                         $userid = undef;
681                 }
682         elsif ($logout) {
683             # voluntary logout the user
684             $session->flush;
685             $session->delete();
686             C4::Context->_unset_userenv($sessionID);
687             #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
688             $sessionID = undef;
689             $userid    = undef;
690
691             if ($cas and $caslogout) {
692                 logout_cas($query);
693             }
694         }
695         elsif ( $lasttime < time() - $timeout ) {
696             # timed logout
697             $info{'timed_out'} = 1;
698             $session->delete() if $session;
699             C4::Context->_unset_userenv($sessionID);
700             #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
701             $userid    = undef;
702             $sessionID = undef;
703         }
704         elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
705             # Different ip than originally logged in from
706             $info{'oldip'}        = $ip;
707             $info{'newip'}        = $ENV{'REMOTE_ADDR'};
708             $info{'different_ip'} = 1;
709             $session->delete();
710             C4::Context->_unset_userenv($sessionID);
711             #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
712             $sessionID = undef;
713             $userid    = undef;
714         }
715         else {
716             $cookie = $query->cookie( CGISESSID => $session->id );
717             $session->param('lasttime',time());
718             unless ( $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
719                 $flags = haspermission($userid, $flagsrequired);
720                 if ($flags) {
721                     $loggedin = 1;
722                 } else {
723                     $info{'nopermission'} = 1;
724                 }
725             }
726         }
727     }
728     unless ($userid || $sessionID) {
729         #we initiate a session prior to checking for a username to allow for anonymous sessions...
730         my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
731         my $sessionID = $session->id;
732         C4::Context->_new_userenv($sessionID);
733         $cookie = $query->cookie( CGISESSID => $sessionID );
734         $userid = $query->param('userid');
735         if (   ( $cas && $query->param('ticket') )
736             || $userid
737             || ( my $pki_field = C4::Context->preference('AllowPKIAuth') ) ne
738             'None' )
739         {
740             my $password = $query->param('password');
741             my ( $return, $cardnumber );
742             if ( $cas && $query->param('ticket') ) {
743                 my $retuserid;
744                 ( $return, $cardnumber, $retuserid ) =
745                   checkpw( $dbh, $userid, $password, $query );
746                 $userid = $retuserid;
747                 $info{'invalidCasLogin'} = 1 unless ($return);
748             }
749             elsif (
750                 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
751                 || (   $pki_field eq 'emailAddress'
752                     && $ENV{'SSL_CLIENT_S_DN_Email'} )
753               )
754             {
755                 my $value;
756                 if ( $pki_field eq 'Common Name' ) {
757                     $value = $ENV{'SSL_CLIENT_S_DN_CN'};
758                 }
759                 elsif ( $pki_field eq 'emailAddress' ) {
760                     $value = $ENV{'SSL_CLIENT_S_DN_Email'};
761
762               # If we're looking up the email, there's a chance that the person
763               # doesn't have a userid. So if there is none, we pass along the
764               # borrower number, and the bits of code that need to know the user
765               # ID will have to be smart enough to handle that.
766                     require C4::Members;
767                     my @users_info = C4::Members::GetBorrowersWithEmail($value);
768                     if (@users_info) {
769
770                         # First the userid, then the borrowernum
771                         $value = $users_info[0][1] || $users_info[0][0];
772                     } else {
773                         undef $value;
774                     }
775                 }
776
777                 # 0 for no user, 1 for normal, 2 for demo user.
778                 $return = $value ? 1 : 0;
779                 $userid = $value;
780             }
781             else {
782                 my $retuserid;
783                 ( $return, $cardnumber, $retuserid ) =
784                   checkpw( $dbh, $userid, $password, $query );
785                 $userid = $retuserid if ( $retuserid ne '' );
786             }
787                 if ($return) {
788                #_session_log(sprintf "%20s from %16s logged in  at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
789                 if ( $flags = haspermission(  $userid, $flagsrequired ) ) {
790                                         $loggedin = 1;
791                 }
792                         else {
793                         $info{'nopermission'} = 1;
794                         C4::Context->_unset_userenv($sessionID);
795                 }
796                 my ($borrowernumber, $firstname, $surname, $userflags,
797                     $branchcode, $branchname, $branchprinter, $emailaddress);
798
799                 if ( $return == 1 ) {
800                     my $select = "
801                     SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
802                     branches.branchname    as branchname,
803                     branches.branchprinter as branchprinter,
804                     email
805                     FROM borrowers
806                     LEFT JOIN branches on borrowers.branchcode=branches.branchcode
807                     ";
808                     my $sth = $dbh->prepare("$select where userid=?");
809                     $sth->execute($userid);
810                     unless ($sth->rows) {
811                         $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
812                         $sth = $dbh->prepare("$select where cardnumber=?");
813                         $sth->execute($cardnumber);
814
815                         unless ($sth->rows) {
816                             $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
817                             $sth->execute($userid);
818                             unless ($sth->rows) {
819                                 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
820                             }
821                         }
822                     }
823                     if ($sth->rows) {
824                         ($borrowernumber, $firstname, $surname, $userflags,
825                             $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
826                         $debug and print STDERR "AUTH_3 results: " .
827                         "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
828                     } else {
829                         print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
830                     }
831
832 # launch a sequence to check if we have a ip for the branch, i
833 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
834
835                     my $ip       = $ENV{'REMOTE_ADDR'};
836                     # if they specify at login, use that
837                     if ($query->param('branch')) {
838                         $branchcode  = $query->param('branch');
839                         $branchname = GetBranchName($branchcode);
840                     }
841                     my $branches = GetBranches();
842                     if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
843                         # we have to check they are coming from the right ip range
844                         my $domain = $branches->{$branchcode}->{'branchip'};
845                         if ($ip !~ /^$domain/){
846                             $loggedin=0;
847                             $info{'wrongip'} = 1;
848                         }
849                     }
850
851                     my @branchesloop;
852                     foreach my $br ( keys %$branches ) {
853                         #     now we work with the treatment of ip
854                         my $domain = $branches->{$br}->{'branchip'};
855                         if ( $domain && $ip =~ /^$domain/ ) {
856                             $branchcode = $branches->{$br}->{'branchcode'};
857
858                             # new op dev : add the branchprinter and branchname in the cookie
859                             $branchprinter = $branches->{$br}->{'branchprinter'};
860                             $branchname    = $branches->{$br}->{'branchname'};
861                         }
862                     }
863                     $session->param('number',$borrowernumber);
864                     $session->param('id',$userid);
865                     $session->param('cardnumber',$cardnumber);
866                     $session->param('firstname',$firstname);
867                     $session->param('surname',$surname);
868                     $session->param('branch',$branchcode);
869                     $session->param('branchname',$branchname);
870                     $session->param('flags',$userflags);
871                     $session->param('emailaddress',$emailaddress);
872                     $session->param('ip',$session->remote_addr());
873                     $session->param('lasttime',time());
874                     $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
875                 }
876                 elsif ( $return == 2 ) {
877                     #We suppose the user is the superlibrarian
878                     $borrowernumber = 0;
879                     $session->param('number',0);
880                     $session->param('id',C4::Context->config('user'));
881                     $session->param('cardnumber',C4::Context->config('user'));
882                     $session->param('firstname',C4::Context->config('user'));
883                     $session->param('surname',C4::Context->config('user'));
884                     $session->param('branch','NO_LIBRARY_SET');
885                     $session->param('branchname','NO_LIBRARY_SET');
886                     $session->param('flags',1);
887                     $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
888                     $session->param('ip',$session->remote_addr());
889                     $session->param('lasttime',time());
890                 }
891                 C4::Context::set_userenv(
892                     $session->param('number'),       $session->param('id'),
893                     $session->param('cardnumber'),   $session->param('firstname'),
894                     $session->param('surname'),      $session->param('branch'),
895                     $session->param('branchname'),   $session->param('flags'),
896                     $session->param('emailaddress'), $session->param('branchprinter')
897                 );
898
899             }
900                 else {
901                 if ($userid) {
902                         $info{'invalid_username_or_password'} = 1;
903                         C4::Context->_unset_userenv($sessionID);
904                 }
905                         }
906         }       # END if ( $userid    = $query->param('userid') )
907                 elsif ($type eq "opac") {
908             # if we are here this is an anonymous session; add public lists to it and a few other items...
909             # anonymous sessions are created only for the OPAC
910                         $debug and warn "Initiating an anonymous session...";
911
912                         # setting a couple of other session vars...
913                         $session->param('ip',$session->remote_addr());
914                         $session->param('lasttime',time());
915                         $session->param('sessiontype','anon');
916                 }
917     }   # END unless ($userid)
918     my $insecure = C4::Context->boolean_preference('insecure');
919
920     # finished authentification, now respond
921     if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
922     {
923         # successful login
924         unless ($cookie) {
925             $cookie = $query->cookie( CGISESSID => '' );
926         }
927         return ( $userid, $cookie, $sessionID, $flags );
928     }
929
930 #
931 #
932 # AUTH rejected, show the login/password template, after checking the DB.
933 #
934 #
935
936     # get the inputs from the incoming query
937     my @inputs = ();
938     foreach my $name ( param $query) {
939         (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
940         my $value = $query->param($name);
941         push @inputs, { name => $name, value => $value };
942     }
943     # get the branchloop, which we need for authentication
944     my $branches = GetBranches();
945     my @branch_loop;
946     for my $branch_hash (sort keys %$branches) {
947                 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
948     }
949
950     my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
951     my $template = C4::Templates::gettemplate( $template_name, $type, $query );
952     $template->param(branchloop => \@branch_loop,);
953     my $checkstyle = C4::Context->preference("opaccolorstylesheet");
954     if ($checkstyle =~ /\//)
955         {
956                 $template->param( opacexternalsheet => $checkstyle);
957         } else
958         {
959                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
960             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
961         }
962     $template->param(
963     login        => 1,
964         INPUTS               => \@inputs,
965         casAuthentication    => C4::Context->preference("casAuthentication"),
966         suggestion           => C4::Context->preference("suggestion"),
967         virtualshelves       => C4::Context->preference("virtualshelves"),
968         LibraryName          => C4::Context->preference("LibraryName"),
969         opacuserlogin        => C4::Context->preference("opacuserlogin"),
970         OpacNav              => C4::Context->preference("OpacNav"),
971         OpacNavRight         => C4::Context->preference("OpacNavRight"),
972         OpacNavBottom        => C4::Context->preference("OpacNavBottom"),
973         opaccredits          => C4::Context->preference("opaccredits"),
974         OpacFavicon          => C4::Context->preference("OpacFavicon"),
975         opacreadinghistory   => C4::Context->preference("opacreadinghistory"),
976         opacsmallimage       => C4::Context->preference("opacsmallimage"),
977         opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
978         opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
979         opacuserjs           => C4::Context->preference("opacuserjs"),
980         opacbookbag          => "" . C4::Context->preference("opacbookbag"),
981         OpacCloud            => C4::Context->preference("OpacCloud"),
982         OpacTopissue         => C4::Context->preference("OpacTopissue"),
983         OpacAuthorities      => C4::Context->preference("OpacAuthorities"),
984         OpacBrowser          => C4::Context->preference("OpacBrowser"),
985         opacheader           => C4::Context->preference("opacheader"),
986         TagsEnabled                  => C4::Context->preference("TagsEnabled"),
987         OPACUserCSS           => C4::Context->preference("OPACUserCSS"),
988         opacstylesheet       => C4::Context->preference("opacstylesheet"),
989         intranetcolorstylesheet =>
990                                                                 C4::Context->preference("intranetcolorstylesheet"),
991         intranetstylesheet => C4::Context->preference("intranetstylesheet"),
992         intranetbookbag    => C4::Context->preference("intranetbookbag"),
993         IntranetNav        => C4::Context->preference("IntranetNav"),
994         intranetuserjs     => C4::Context->preference("intranetuserjs"),
995         IndependantBranches=> C4::Context->preference("IndependantBranches"),
996         AutoLocation       => C4::Context->preference("AutoLocation"),
997                 wrongip            => $info{'wrongip'},
998     );
999
1000     $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1001     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1002
1003     if ($cas) {
1004
1005         # Is authentication against multiple CAS servers enabled?
1006         if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1007             my $casservers = C4::Auth_with_cas::getMultipleAuth();                  
1008             my @tmplservers;
1009             foreach my $key (keys %$casservers) {
1010                 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1011             }
1012             #warn Data::Dumper::Dumper(\@tmplservers);
1013             $template->param(
1014                 casServersLoop => \@tmplservers
1015             );
1016         } else {
1017         $template->param(
1018             casServerUrl    => login_cas_url($query),
1019             );
1020         }
1021
1022         $template->param(
1023             invalidCasLogin => $info{'invalidCasLogin'}
1024         );
1025     }
1026
1027     my $self_url = $query->url( -absolute => 1 );
1028     $template->param(
1029         url         => $self_url,
1030         LibraryName => C4::Context->preference("LibraryName"),
1031     );
1032     $template->param( %info );
1033 #    $cookie = $query->cookie(CGISESSID => $session->id
1034 #   );
1035     print $query->header(
1036         -type   => 'text/html',
1037         -charset => 'utf-8',
1038         -cookie => $cookie
1039       ),
1040       $template->output;
1041     safe_exit;
1042 }
1043
1044 =head2 check_api_auth
1045
1046   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1047
1048 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1049 cookie, determine if the user has the privileges specified by C<$userflags>.
1050
1051 C<check_api_auth> is is meant for authenticating users of web services, and
1052 consequently will always return and will not attempt to redirect the user
1053 agent.
1054
1055 If a valid session cookie is already present, check_api_auth will return a status
1056 of "ok", the cookie, and the Koha session ID.
1057
1058 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1059 parameters and create a session cookie and Koha session if the supplied credentials
1060 are OK.
1061
1062 Possible return values in C<$status> are:
1063
1064 =over
1065
1066 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1067
1068 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1069
1070 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1071
1072 =item "expired -- session cookie has expired; API user should resubmit userid and password
1073
1074 =back
1075
1076 =cut
1077
1078 sub check_api_auth {
1079     my $query = shift;
1080     my $flagsrequired = shift;
1081
1082     my $dbh     = C4::Context->dbh;
1083     my $timeout = C4::Context->preference('timeout');
1084     $timeout = 600 unless $timeout;
1085
1086     unless (C4::Context->preference('Version')) {
1087         # database has not been installed yet
1088         return ("maintenance", undef, undef);
1089     }
1090     my $kohaversion=C4::Context::KOHAVERSION;
1091     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1092     if (C4::Context->preference('Version') < $kohaversion) {
1093         # database in need of version update; assume that
1094         # no API should be called while databsae is in
1095         # this condition.
1096         return ("maintenance", undef, undef);
1097     }
1098
1099     # FIXME -- most of what follows is a copy-and-paste
1100     # of code from checkauth.  There is an obvious need
1101     # for refactoring to separate the various parts of
1102     # the authentication code, but as of 2007-11-19 this
1103     # is deferred so as to not introduce bugs into the
1104     # regular authentication code for Koha 3.0.
1105
1106     # see if we have a valid session cookie already
1107     # however, if a userid parameter is present (i.e., from
1108     # a form submission, assume that any current cookie
1109     # is to be ignored
1110     my $sessionID = undef;
1111     unless ($query->param('userid')) {
1112         $sessionID = $query->cookie("CGISESSID");
1113     }
1114     if ($sessionID && not ($cas && $query->param('PT')) ) {
1115         my $session = get_session($sessionID);
1116         C4::Context->_new_userenv($sessionID);
1117         if ($session) {
1118             C4::Context::set_userenv(
1119                 $session->param('number'),       $session->param('id'),
1120                 $session->param('cardnumber'),   $session->param('firstname'),
1121                 $session->param('surname'),      $session->param('branch'),
1122                 $session->param('branchname'),   $session->param('flags'),
1123                 $session->param('emailaddress'), $session->param('branchprinter')
1124             );
1125
1126             my $ip = $session->param('ip');
1127             my $lasttime = $session->param('lasttime');
1128             my $userid = $session->param('id');
1129             if ( $lasttime < time() - $timeout ) {
1130                 # time out
1131                 $session->delete();
1132                 C4::Context->_unset_userenv($sessionID);
1133                 $userid    = undef;
1134                 $sessionID = undef;
1135                 return ("expired", undef, undef);
1136             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1137                 # IP address changed
1138                 $session->delete();
1139                 C4::Context->_unset_userenv($sessionID);
1140                 $userid    = undef;
1141                 $sessionID = undef;
1142                 return ("expired", undef, undef);
1143             } else {
1144                 my $cookie = $query->cookie( CGISESSID => $session->id );
1145                 $session->param('lasttime',time());
1146                 my $flags = haspermission($userid, $flagsrequired);
1147                 if ($flags) {
1148                     return ("ok", $cookie, $sessionID);
1149                 } else {
1150                     $session->delete();
1151                     C4::Context->_unset_userenv($sessionID);
1152                     $userid    = undef;
1153                     $sessionID = undef;
1154                     return ("failed", undef, undef);
1155                 }
1156             }
1157         } else {
1158             return ("expired", undef, undef);
1159         }
1160     } else {
1161         # new login
1162         my $userid = $query->param('userid');
1163         my $password = $query->param('password');
1164         my ($return, $cardnumber);
1165
1166         # Proxy CAS auth
1167         if ($cas && $query->param('PT')) {
1168             my $retuserid;
1169             $debug and print STDERR "## check_api_auth - checking CAS\n";
1170             # In case of a CAS authentication, we use the ticket instead of the password
1171             my $PT = $query->param('PT');
1172             ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query);    # EXTERNAL AUTH
1173         } else {
1174             # User / password auth
1175             unless ($userid and $password) {
1176                 # caller did something wrong, fail the authenticateion
1177                 return ("failed", undef, undef);
1178             }
1179             ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1180         }
1181
1182         if ($return and haspermission(  $userid, $flagsrequired)) {
1183             my $session = get_session("");
1184             return ("failed", undef, undef) unless $session;
1185
1186             my $sessionID = $session->id;
1187             C4::Context->_new_userenv($sessionID);
1188             my $cookie = $query->cookie(CGISESSID => $sessionID);
1189             if ( $return == 1 ) {
1190                 my (
1191                     $borrowernumber, $firstname,  $surname,
1192                     $userflags,      $branchcode, $branchname,
1193                     $branchprinter,  $emailaddress
1194                 );
1195                 my $sth =
1196                   $dbh->prepare(
1197 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
1198                   );
1199                 $sth->execute($userid);
1200                 (
1201                     $borrowernumber, $firstname,  $surname,
1202                     $userflags,      $branchcode, $branchname,
1203                     $branchprinter,  $emailaddress
1204                 ) = $sth->fetchrow if ( $sth->rows );
1205
1206                 unless ($sth->rows ) {
1207                     my $sth = $dbh->prepare(
1208 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where cardnumber=?"
1209                       );
1210                     $sth->execute($cardnumber);
1211                     (
1212                         $borrowernumber, $firstname,  $surname,
1213                         $userflags,      $branchcode, $branchname,
1214                         $branchprinter,  $emailaddress
1215                     ) = $sth->fetchrow if ( $sth->rows );
1216
1217                     unless ( $sth->rows ) {
1218                         $sth->execute($userid);
1219                         (
1220                             $borrowernumber, $firstname, $surname, $userflags,
1221                             $branchcode, $branchname, $branchprinter, $emailaddress
1222                         ) = $sth->fetchrow if ( $sth->rows );
1223                     }
1224                 }
1225
1226                 my $ip       = $ENV{'REMOTE_ADDR'};
1227                 # if they specify at login, use that
1228                 if ($query->param('branch')) {
1229                     $branchcode  = $query->param('branch');
1230                     $branchname = GetBranchName($branchcode);
1231                 }
1232                 my $branches = GetBranches();
1233                 my @branchesloop;
1234                 foreach my $br ( keys %$branches ) {
1235                     #     now we work with the treatment of ip
1236                     my $domain = $branches->{$br}->{'branchip'};
1237                     if ( $domain && $ip =~ /^$domain/ ) {
1238                         $branchcode = $branches->{$br}->{'branchcode'};
1239
1240                         # new op dev : add the branchprinter and branchname in the cookie
1241                         $branchprinter = $branches->{$br}->{'branchprinter'};
1242                         $branchname    = $branches->{$br}->{'branchname'};
1243                     }
1244                 }
1245                 $session->param('number',$borrowernumber);
1246                 $session->param('id',$userid);
1247                 $session->param('cardnumber',$cardnumber);
1248                 $session->param('firstname',$firstname);
1249                 $session->param('surname',$surname);
1250                 $session->param('branch',$branchcode);
1251                 $session->param('branchname',$branchname);
1252                 $session->param('flags',$userflags);
1253                 $session->param('emailaddress',$emailaddress);
1254                 $session->param('ip',$session->remote_addr());
1255                 $session->param('lasttime',time());
1256             } elsif ( $return == 2 ) {
1257                 #We suppose the user is the superlibrarian
1258                 $session->param('number',0);
1259                 $session->param('id',C4::Context->config('user'));
1260                 $session->param('cardnumber',C4::Context->config('user'));
1261                 $session->param('firstname',C4::Context->config('user'));
1262                 $session->param('surname',C4::Context->config('user'));
1263                 $session->param('branch','NO_LIBRARY_SET');
1264                 $session->param('branchname','NO_LIBRARY_SET');
1265                 $session->param('flags',1);
1266                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1267                 $session->param('ip',$session->remote_addr());
1268                 $session->param('lasttime',time());
1269             }
1270             C4::Context::set_userenv(
1271                 $session->param('number'),       $session->param('id'),
1272                 $session->param('cardnumber'),   $session->param('firstname'),
1273                 $session->param('surname'),      $session->param('branch'),
1274                 $session->param('branchname'),   $session->param('flags'),
1275                 $session->param('emailaddress'), $session->param('branchprinter')
1276             );
1277             return ("ok", $cookie, $sessionID);
1278         } else {
1279             return ("failed", undef, undef);
1280         }
1281     }
1282 }
1283
1284 =head2 check_cookie_auth
1285
1286   ($status, $sessionId) = check_api_auth($cookie, $userflags);
1287
1288 Given a CGISESSID cookie set during a previous login to Koha, determine
1289 if the user has the privileges specified by C<$userflags>.
1290
1291 C<check_cookie_auth> is meant for authenticating special services
1292 such as tools/upload-file.pl that are invoked by other pages that
1293 have been authenticated in the usual way.
1294
1295 Possible return values in C<$status> are:
1296
1297 =over
1298
1299 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1300
1301 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1302
1303 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1304
1305 =item "expired -- session cookie has expired; API user should resubmit userid and password
1306
1307 =back
1308
1309 =cut
1310
1311 sub check_cookie_auth {
1312     my $cookie = shift;
1313     my $flagsrequired = shift;
1314
1315     my $dbh     = C4::Context->dbh;
1316     my $timeout = C4::Context->preference('timeout');
1317     $timeout = 600 unless $timeout;
1318
1319     unless (C4::Context->preference('Version')) {
1320         # database has not been installed yet
1321         return ("maintenance", undef);
1322     }
1323     my $kohaversion=C4::Context::KOHAVERSION;
1324     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1325     if (C4::Context->preference('Version') < $kohaversion) {
1326         # database in need of version update; assume that
1327         # no API should be called while databsae is in
1328         # this condition.
1329         return ("maintenance", undef);
1330     }
1331
1332     # FIXME -- most of what follows is a copy-and-paste
1333     # of code from checkauth.  There is an obvious need
1334     # for refactoring to separate the various parts of
1335     # the authentication code, but as of 2007-11-23 this
1336     # is deferred so as to not introduce bugs into the
1337     # regular authentication code for Koha 3.0.
1338
1339     # see if we have a valid session cookie already
1340     # however, if a userid parameter is present (i.e., from
1341     # a form submission, assume that any current cookie
1342     # is to be ignored
1343     unless (defined $cookie and $cookie) {
1344         return ("failed", undef);
1345     }
1346     my $sessionID = $cookie;
1347     my $session = get_session($sessionID);
1348     C4::Context->_new_userenv($sessionID);
1349     if ($session) {
1350         C4::Context::set_userenv(
1351             $session->param('number'),       $session->param('id'),
1352             $session->param('cardnumber'),   $session->param('firstname'),
1353             $session->param('surname'),      $session->param('branch'),
1354             $session->param('branchname'),   $session->param('flags'),
1355             $session->param('emailaddress'), $session->param('branchprinter')
1356         );
1357
1358         my $ip = $session->param('ip');
1359         my $lasttime = $session->param('lasttime');
1360         my $userid = $session->param('id');
1361         if ( $lasttime < time() - $timeout ) {
1362             # time out
1363             $session->delete();
1364             C4::Context->_unset_userenv($sessionID);
1365             $userid    = undef;
1366             $sessionID = undef;
1367             return ("expired", undef);
1368         } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1369             # IP address changed
1370             $session->delete();
1371             C4::Context->_unset_userenv($sessionID);
1372             $userid    = undef;
1373             $sessionID = undef;
1374             return ("expired", undef);
1375         } else {
1376             $session->param('lasttime',time());
1377             my $flags = haspermission($userid, $flagsrequired);
1378             if ($flags) {
1379                 return ("ok", $sessionID);
1380             } else {
1381                 $session->delete();
1382                 C4::Context->_unset_userenv($sessionID);
1383                 $userid    = undef;
1384                 $sessionID = undef;
1385                 return ("failed", undef);
1386             }
1387         }
1388     } else {
1389         return ("expired", undef);
1390     }
1391 }
1392
1393 =head2 get_session
1394
1395   use CGI::Session;
1396   my $session = get_session($sessionID);
1397
1398 Given a session ID, retrieve the CGI::Session object used to store
1399 the session's state.  The session object can be used to store
1400 data that needs to be accessed by different scripts during a
1401 user's session.
1402
1403 If the C<$sessionID> parameter is an empty string, a new session
1404 will be created.
1405
1406 =cut
1407
1408 sub get_session {
1409     my $sessionID = shift;
1410     my $storage_method = C4::Context->preference('SessionStorage');
1411     my $dbh = C4::Context->dbh;
1412     my $session;
1413     if ($storage_method eq 'mysql'){
1414         $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1415     }
1416     elsif ($storage_method eq 'Pg') {
1417         $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1418     }
1419     elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1420         $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1421     }
1422     else {
1423         # catch all defaults to tmp should work on all systems
1424         $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1425     }
1426     return $session;
1427 }
1428
1429 sub checkpw {
1430
1431     my ( $dbh, $userid, $password, $query ) = @_;
1432     if ($ldap) {
1433         $debug and print STDERR "## checkpw - checking LDAP\n";
1434         my ($retval,$retcard,$retuserid) = checkpw_ldap(@_);    # EXTERNAL AUTH
1435         ($retval) and return ($retval,$retcard,$retuserid);
1436     }
1437
1438     if ($cas && $query && $query->param('ticket')) {
1439         $debug and print STDERR "## checkpw - checking CAS\n";
1440         # In case of a CAS authentication, we use the ticket instead of the password
1441         my $ticket = $query->param('ticket');
1442         my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query);    # EXTERNAL AUTH
1443         ($retval) and return ($retval,$retcard,$retuserid);
1444         return 0;
1445     }
1446
1447     # INTERNAL AUTH
1448     my $sth =
1449       $dbh->prepare(
1450 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1451       );
1452     $sth->execute($userid);
1453     if ( $sth->rows ) {
1454         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1455             $surname, $branchcode, $flags )
1456           = $sth->fetchrow;
1457         if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1458
1459             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1460                 $firstname, $surname, $branchcode, $flags );
1461             return 1, $cardnumber, $userid;
1462         }
1463     }
1464     $sth =
1465       $dbh->prepare(
1466 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1467       );
1468     $sth->execute($userid);
1469     if ( $sth->rows ) {
1470         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1471             $surname, $branchcode, $flags )
1472           = $sth->fetchrow;
1473         if ( md5_base64($password) eq $md5password ) {
1474
1475             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1476                 $firstname, $surname, $branchcode, $flags );
1477             return 1, $cardnumber, $userid;
1478         }
1479     }
1480     if (   $userid && $userid eq C4::Context->config('user')
1481         && "$password" eq C4::Context->config('pass') )
1482     {
1483
1484 # Koha superuser account
1485 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1486         return 2;
1487     }
1488     if (   $userid && $userid eq 'demo'
1489         && "$password" eq 'demo'
1490         && C4::Context->config('demo') )
1491     {
1492
1493 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1494 # some features won't be effective : modify systempref, modify MARC structure,
1495         return 2;
1496     }
1497     return 0;
1498 }
1499
1500 =head2 getuserflags
1501
1502     my $authflags = getuserflags($flags, $userid, [$dbh]);
1503
1504 Translates integer flags into permissions strings hash.
1505
1506 C<$flags> is the integer userflags value ( borrowers.userflags )
1507 C<$userid> is the members.userid, used for building subpermissions
1508 C<$authflags> is a hashref of permissions
1509
1510 =cut
1511
1512 sub getuserflags {
1513     my $flags   = shift;
1514     my $userid  = shift;
1515     my $dbh     = @_ ? shift : C4::Context->dbh;
1516     my $userflags;
1517     $flags = 0 unless $flags;
1518     my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1519     $sth->execute;
1520
1521     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1522         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1523             $userflags->{$flag} = 1;
1524         }
1525         else {
1526             $userflags->{$flag} = 0;
1527         }
1528     }
1529
1530     # get subpermissions and merge with top-level permissions
1531     my $user_subperms = get_user_subpermissions($userid);
1532     foreach my $module (keys %$user_subperms) {
1533         next if $userflags->{$module} == 1; # user already has permission for everything in this module
1534         $userflags->{$module} = $user_subperms->{$module};
1535     }
1536
1537     return $userflags;
1538 }
1539
1540 =head2 get_user_subpermissions
1541
1542   $user_perm_hashref = get_user_subpermissions($userid);
1543
1544 Given the userid (note, not the borrowernumber) of a staff user,
1545 return a hashref of hashrefs of the specific subpermissions
1546 accorded to the user.  An example return is
1547
1548  {
1549     tools => {
1550         export_catalog => 1,
1551         import_patrons => 1,
1552     }
1553  }
1554
1555 The top-level hash-key is a module or function code from
1556 userflags.flag, while the second-level key is a code
1557 from permissions.
1558
1559 The results of this function do not give a complete picture
1560 of the functions that a staff user can access; it is also
1561 necessary to check borrowers.flags.
1562
1563 =cut
1564
1565 sub get_user_subpermissions {
1566     my $userid = shift;
1567
1568     my $dbh = C4::Context->dbh;
1569     my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1570                              FROM user_permissions
1571                              JOIN permissions USING (module_bit, code)
1572                              JOIN userflags ON (module_bit = bit)
1573                              JOIN borrowers USING (borrowernumber)
1574                              WHERE userid = ?");
1575     $sth->execute($userid);
1576
1577     my $user_perms = {};
1578     while (my $perm = $sth->fetchrow_hashref) {
1579         $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1580     }
1581     return $user_perms;
1582 }
1583
1584 =head2 get_all_subpermissions
1585
1586   my $perm_hashref = get_all_subpermissions();
1587
1588 Returns a hashref of hashrefs defining all specific
1589 permissions currently defined.  The return value
1590 has the same structure as that of C<get_user_subpermissions>,
1591 except that the innermost hash value is the description
1592 of the subpermission.
1593
1594 =cut
1595
1596 sub get_all_subpermissions {
1597     my $dbh = C4::Context->dbh;
1598     my $sth = $dbh->prepare("SELECT flag, code, description
1599                              FROM permissions
1600                              JOIN userflags ON (module_bit = bit)");
1601     $sth->execute();
1602
1603     my $all_perms = {};
1604     while (my $perm = $sth->fetchrow_hashref) {
1605         $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1606     }
1607     return $all_perms;
1608 }
1609
1610 =head2 haspermission
1611
1612   $flags = ($userid, $flagsrequired);
1613
1614 C<$userid> the userid of the member
1615 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}> 
1616
1617 Returns member's flags or 0 if a permission is not met.
1618
1619 =cut
1620
1621 sub haspermission {
1622     my ($userid, $flagsrequired) = @_;
1623     my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1624     $sth->execute($userid);
1625     my $flags = getuserflags($sth->fetchrow(), $userid);
1626     if ( $userid eq C4::Context->config('user') ) {
1627         # Super User Account from /etc/koha.conf
1628         $flags->{'superlibrarian'} = 1;
1629     }
1630     elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1631         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1632         $flags->{'superlibrarian'} = 1;
1633     }
1634
1635     return $flags if $flags->{superlibrarian};
1636
1637     foreach my $module ( keys %$flagsrequired ) {
1638         my $subperm = $flagsrequired->{$module};
1639         if ($subperm eq '*') {
1640             return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1641         } else {
1642             return 0 unless ( $flags->{$module} == 1 or
1643                                 ( ref($flags->{$module}) and
1644                                   exists $flags->{$module}->{$subperm} and
1645                                   $flags->{$module}->{$subperm} == 1
1646                                 )
1647                             );
1648         }
1649     }
1650     return $flags;
1651     #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1652 }
1653
1654
1655 sub getborrowernumber {
1656     my ($userid) = @_;
1657     my $userenv = C4::Context->userenv;
1658     if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1659         return $userenv->{number};
1660     }
1661     my $dbh = C4::Context->dbh;
1662     for my $field ( 'userid', 'cardnumber' ) {
1663         my $sth =
1664           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1665         $sth->execute($userid);
1666         if ( $sth->rows ) {
1667             my ($bnumber) = $sth->fetchrow;
1668             return $bnumber;
1669         }
1670     }
1671     return 0;
1672 }
1673
1674
1675 END { }    # module clean-up code here (global destructor)
1676 1;
1677 __END__
1678
1679 =head1 SEE ALSO
1680
1681 CGI(3)
1682
1683 C4::Output(3)
1684
1685 Digest::MD5(3)
1686
1687 =cut