#!/usr/bin/perl -w
# $Id$
=head1 NAME
boinc_authentication - functions to authenticate an user with Boinc,
and other miscellaneous helpers.
=head1 SYNOPSIS
lookup_email(project_url,email)
lookup_email_password(project_url,email,password)
=head1 DESCRIPTION
Connects to a boinc project and checks if an user exists, or if a
given username/password combination is valid. The boinc authenticator
can also be used in lieu of the password. Passwords are never sent in
clear text.
=head1 AUTHOR
Toni Giorgino
=head1 COPYRIGHT
This file is part of RemoteBOINC.
Copyright (C) 2010 Toni Giorgino, Universitat Pompeu Fabra
RemoteBOINC is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
RemoteBOINC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BOINC. If not, see .
=cut
use strict;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use Error qw(:try);
use LWP::Simple;
use URI::Escape;
use XML::Simple;
use constant LOOKUP_ACCOUNT_PHP => 'lookup_account.php';
use vars qw($config);
sub md5_file {
my $file = shift;
open(FILE, $file) or die 'Error file $file for md5: $!';
binmode(FILE);
my $md= Digest::MD5->new->addfile(*FILE)->hexdigest;
close FILE;
return $md;
}
sub lookup_email {
my $boinc_project_url=shift;
my $boinc_email=shift;
my $url=$boinc_project_url . "/" . LOOKUP_ACCOUNT_PHP;
my $query=$url."?email_addr=".uri_escape($boinc_email);
my $content=get($query);
die "Couldn't get it!" unless defined $content;
return $content;
}
sub lookup_email_password {
my $boinc_project_url=shift;
my $boinc_email=shift;
my $boinc_password=shift;
my $boinc_digesthash=md5_hex($boinc_password.$boinc_email);
my $url=$boinc_project_url . "/" . LOOKUP_ACCOUNT_PHP;
my $query=$url."?email_addr=".uri_escape($boinc_email).
"&passwd_hash=".uri_escape($boinc_digesthash);
my $content=get($query);
die "Couldn't get it!" unless defined $content;
return $content;
}
# Return an human-readable answer, for testing
sub voidAnswer {
my $response_header="Content-type: text/plain\n\n";
print $response_header;
print "Remote boinc CGI is alive.\n";
}
# Return an xml-encoded failure code
sub returnFailure {
my $xmlroot=shift;
my $reason=shift;
my $response_header="Content-type: text/plain\n\n";
my $r={};
$r->{Failure}={Reason=>$reason};
my $xr=XMLout($r, RootName => $xmlroot, AttrIndent => 1);
print $response_header;
print $xr;
}
# Returns false if tag contains forbidden chars, else true
sub isTagValid {
my $tag=shift;
if($tag =~ /^[a-zA-Z0-9_]+$/) {
return 1;
} else {
return 0;
}
}
# Returns false if tag contains forbidden chars in user name, else true
sub isUserValid {
my $tag=shift;
if($tag =~ /^[a-zA-Z0-9]+$/) {
return 1;
} else {
return 0;
}
}
sub isNameReserved {
my $name=shift;
my @res=("pool","process","process_stop");
foreach my $n (@res) {
if($name eq $n) {
return(1);
}
}
return 0;
}
# Template-handling
# Parse a template and return it as a hash
sub parse_template {
my $tpl=shift;
if(!isTagValid($tpl)) {
die "Invalid character in application request";
}
# Read the template's content
my $tfile=$config->{PROJECT_DIR}."/templates/";
$tfile=$tfile.$tpl;
open F,"<$tfile" or die "Error opening template: $!";
my @lines=;
close F;
my $ttext=join "",@lines;
# Add the root element, otherwise ill-formed
my $txml=XMLin("$ttext",
ForceArray => ["file_ref"]);
return $txml;
}
# Parse the wu template and return it as an hash
sub parse_wu_template {
my $tpl=shift;
return parse_template("rboinc_".$tpl."_wu");
}
# Parse the wu template and return it as an hash
sub parse_result_template {
my $tpl=shift;
return parse_template("rboinc_".$tpl."_result");
}
# Convert the input list into an ORDERED array of hash refs.
# Each hash ref contains info for an input file
sub build_input_files_list {
my $th=shift;
my $out=[];
foreach my $ir (@{$th->{workunit}->{file_ref}}) {
my $num=$ir->{file_number};
$out->[$num]=$ir;
}
return $out;
}
# Build list of chained files, inverting links in the
# results template
sub build_chain_files_list {
my $th=shift;
my $out={};
foreach my $ir (@{$th->{file_info}}) {
my $name=(%{$ir->{name}})[0]; # this will become the new input
$name=~/[0-9]+$/;
my $num=$&;
my $chain=$ir->{rboinc}->{chain}; # at this slot
if($chain) {
$out->{$chain}=$num;
}
}
return $out;
}
# Build a fragment of bash script
# applying the chain-rule replacements
sub build_do_chain_bash_function {
my $th=shift;
my $clist=build_chain_files_list($th);
my $scr=<<'EOF';
function do_chain {
iarray=($INPUT_LIST)
EOF
foreach my $in (sort keys %$clist) {
my $out=$clist->{$in};
$scr .= " iarray[$in]=\$1_$out\n";
}
$scr.=<<'EOF';
INPUT_LIST="${iarray[@]}"
}
EOF
return $scr;
}
# Parse result name and split into components
sub parseResultName {
my $n=shift;
my ($name,$user,$group,$step,$maxsteps,$rnd,$ext) = ($n=~/^(.+)-(.+)_(.+)-(.+)-(.+)-(.+)_(.+)$/);
my $r={
name => $name,
user => $user,
group => $group,
step => $step,
maxsteps => $maxsteps,
rnd => $rnd,
ext => $ext
};
return $r;
}
1;
# References
# http://snippets.dzone.com/posts/show/3163
# http://boinc.berkeley.edu/trac/wiki/WebRpc#lookup_account
# project_url/lookup_account.php
# http://search.cpan.org/dist/libwww-perl/lib/HTTP/Request/Common.pm