Training, Open Source computer languages
PerlPHPPythonMySQLApache / TomcatTclRubyJavaC and C++LinuxCSS 
Search for:
Home Accessibility Courses Diary The Mouth Forum Resources Site Map About Us Contact
 
For 2023 (and 2024 ...) - we are now fully retired from IT training.
We have made many, many friends over 25 years of teaching about Python, Tcl, Perl, PHP, Lua, Java, C and C++ - and MySQL, Linux and Solaris/SunOS too. Our training notes are now very much out of date, but due to upward compatability most of our examples remain operational and even relevant ad you are welcome to make us if them "as seen" and at your own risk.

Lisa and I (Graham) now live in what was our training centre in Melksham - happy to meet with former delegates here - but do check ahead before coming round. We are far from inactive - rather, enjoying the times that we are retired but still healthy enough in mind and body to be active!

I am also active in many other area and still look after a lot of web sites - you can find an index ((here))
Authentication problems

Posted by mundo (mundo), 29 October 2003
I am trying to use AuthCookie.pm with Apache. The user details are kept in an Oracle database on a separate machine. I would like to use these details to authenticate and authorize use of my website. Should I be trying to load them into a hash? If so, how do I get the hash to be global to my functions in the AuthCookieHandler, and how do I keep it up to date? I tried creating a hash in the handler with
use vars qw(%all_la_users);
and updating it in the authen_cred subroutine (i.e. every time someone logged in), but had trouble 'seeing' it in the authen_ses_key subroutine. I'm not sure how global it would be to the different apache children.

Perhaps I should be tie-ing it to a view of the Oracle db or something? Apologies for sounding naiive about this, but am struggling here.

Attempt to update hash function:
Code:
sub load_la_data ($) {
     my $r = shift;
     $r->log_error("LOADING LA DATA FROM load_la_data FUNCTION");
     $LA_DATA = "/opt/LocalAuth/data/la-full-data";

     # Create big hash of all users
     open (USERDATA, "$LA_DATA") || die "Couldn't open user data file: $!\n";
     
     # Read the whole la-data-full file in one go, into an array:
     my ($line, $a_uid) = '';
     while ($line = <USERDATA>) {
           next if $line =~ /^\#/;
           chomp $line;
           ($a_uid) = $line =~ /^(.*?)\:/;
           ($all_la_users{$a_uid}{uid}, $all_la_users{$a_uid}{user}, $all_la_users{$a_uid}{pword}, $all_la_users{$a_uid}{msuser}, $all_la_users{$a_uid}{mspword}, $all_la_users{$a_uid}{del}) = split(/:/,$line);
     }
     close USERDATA;
     1;
}

Authentication function called by AuthCookie.pm when username and password submitted in form:
Code:
sub authen_cred ($$\@) {
     my $self = shift;
     my $r = shift;
     my ($sentuser, $sentpword) = @_;
     
     unless (defined($sentuser) && defined($sentpword) && $sentuser && $sentpword) {
           $r->subprocess_env('FailCookieReason', 'insuf_login_creds');
           return undef;
     }
     
     $sentuser = uc($sentuser);
     $sentpword = lc($sentpword);

     unless (load_la_data($r)) {
           $r->subprocess_env('FailCookieReason', 'load_la_fail');
           return undef;
     }
     
     $r->log_error("ME: $all_la_users{1?????0}{user}");
     
     my ($auth_cookie, $auth_uid, $auth_msuser) = '';
     $auth_cookie = check_login_creds($r,$sentuser,$sentpword);
     if ($auth_cookie) {
           #($auth_uid) = $auth_cookie =~ s/^(\d*?)\://;
           ($auth_uid) = split (/:/, $auth_cookie, 2);
           ($auth_msuser) = $auth_cookie =~ m#.*:(.*)$#;
           $auth_cookie =~ s/^\d*?\://;
     } else {
           $r->subprocess_env('FailCookieReason', 'login_creds_fail');
           return undef;
     }
     
     unless (set_uid_cookie($r, $auth_uid)) {
           $r->subprocess_env('FailCookieReason', 'set_new_uid_fail');
           return undef;
     }
     unless (set_grpint_cookie($r, $auth_msuser)) {
           $r->subprocess_env('FailCookieReason', 'set_new_grpint_fail');
           return undef;
     }
     
     my $crypt_auth_cookie = $cipher->encrypt_hex($auth_cookie);
     return($crypt_auth_cookie);

}


(1??0 has been used above to protect the innocent)
The '$r->log_error("ME: $all_la_users{1??0}{user}");' line produces no value in the log

Authorize function called by AuthCookie.pm on each request:
Code:
sub authen_ses_key ($$$) {
     my $self = shift;
     my $r = shift;
     my $crypt_cookie = shift;
     $r->log_error("ME2: $all_la_users{1?????0}{user}");
     
     my $cookie = $cipher->decrypt_hex($crypt_cookie);
     my ($user, $password, $msuser) = split(/:/, $cookie, 3);
     my ($uid, $last_use) = '';
     
     unless (($uid, $last_use) = get_uid_cookie($r) =~ /(\d+):(\d+)/) {
           $r->subprocess_env('FailCookieReason', 'no_uid_cookie');
           return undef;
     }
     unless (check_cookie_creds($r, $uid, $user, $password, $msuser)) {
           $r->subprocess_env('FailCookieReason', 'cookie_creds_fail');
           return undef;
     }
     unless (create_ms_auth ($r, $uid)) {
           $r->subprocess_env('FailCookieReason', 'ms_heads_fail');
           return undef;
     }
     return undef unless check_grpint_cookie ($r, $msuser, $uid, $last_use);
     
     'programmer';
}


Line '$r->log_error("ME2: $all_la_users{1??0}{user}");' successfully produces a value in the log file.

Posted by Custard (Custard), 29 October 2003
Hi,

Not sure if I fully understand your scenario.

Are you using mod_perl?

Even so, is it a good idea to keep that sort of thing as a global.
It's an even worse idea if you're using mod_perl as (afaicr) all perl are run under the same interpreter. Your users might get unexpected access!.

In my opinion what you are describing is 'session' data which you can retrieve from the database using the cookie.
Maybe you could create a session object containing the data and with class methods to retrieve it. At least that way the data is encapsulated in an object in your programs scope.

Maybe I have misunderstood horribly.

B


Posted by admin (Graham Ellis), 30 October 2003
Hi, Mundo, Welcome.  I'm taking it that you're using mod_perl as you refer to the various Apache children sharing globals ... if you were using CGI, then each process would be separate and would need separate loading of (or connection to) the Auth data.

You might be suprised how small a proportion of Perl programmers are working under mod_perl - it's quite a small fraction of Perl programmers writing for the web, who themselves are on a proportion of Perl users ... which all goes to mean that yours in not a daily question I can answer in my sleep / without reference back to other materials and trying things out for myself.

I'm going to post up a couple of thoughts / things you can experiment with and see if the ideas help.  If they do, great;  if not, please let me know (via a post) at the weekend and I'll spend an hour or so digging around - can't do it any earlier, as my only net connection this week is a slow modem in a hotel room.

Right - my own thought is that I would write my code to use a hash, which I would tie to the Oracle database.   That way, any updates in one Apache child would be reflected in the other children, data would be saved without the need to explicitly do so, there would be no need to load in what I guess could be quite a big chunk of data into each child, etc.   Just very neat and clean (and, I admit it, I like tie classes).  You could use shared memory or something, but you're still going to be left having to syncronise the shared memory to Oracle - why have a second copy when FETCH and STORE routines can so easily look up the data using a unique database column?

Custard's caution on globals is correct ... but I think you're looking to keep recortds or all logins (which needs to be global), and if someone changes their password in one session / child, it must be reflected next time they log in.    With mod_perl, you do need to restrict most variables to the session, as you want to avoid "a"'s session getting mixed up with "b"'s.

Posted by mundo (mundo), 31 October 2003
Thank you, Custard and Graham, for your replies. I guess I was trying too hard to make my post short and succinct that I left out some vital information. Yes, I am using mod_perl.

To rewind a little: some time ago we tried DBI with this, but my DBA tells me that it resulted in too much system resource being hogged by the many threads left open to the Oracle database (one for each child Apache process on top of threads to the doc management system). So they configured a system which creates a flat file which is read by the Apache auth process (basic auth). This file is updated every few minutes. I am currently trying to build a cookie auth system, and rather than have to open a file each time I want to check auth details, I thought I'd have a hash. There would be no writing to the hash or database from the Apache server, only reading. But it does need to allow for changes to passwords, etc. in reasonable real time (needs to be updated within 5 mins of a change at the back end, say). So what I wanted was a hash which could be globally seen in all parts of my AuthCookieHandler module, and by all Apache processes currently running. Is this possible? I like the idea of the tied hash, Graham, mainly because I've never used one before and I like to try new things (do I tie it with string? ), but will it do what I want or will it create a thread for each process?

A couple of other questions as a result of your replies:

1) I wasn't quite sure what "Custard's caution on globals" was?
2) What are the differences between the three concepts of session, child process and request (usually $r) within mod_perl?

Posted by Custard (Custard), 31 October 2003
Hi.

I haven't done much work with mod_perl, but from what I gather there is one running perl interpreter which runs code for all requests. Therefore, if you have a global $username then all processes will suddenly become $username.
On the other hand, from your description this is exactly the behaviour you would like with your global auth list.
If you create a global hash (say, %auth)
then tie it to a dbm file or a flat file with another tie:handler, then the contents of the hash will reflect the contents of the file.
This will (should) result in only there only being one instance of the global hash in the parent perl process.

I'm not too sure of the mod_perl definitions, but I would say that :-
a Session was a collection of data stored in a database keyed by a cookie with a short expiry time (say 30 mins). You can get cookie servers for this, but since you have Oracle available, you could use that. (but then we're back to database connections again).

A request is a single http request, and stores no persistent state.

A child process is a forked parent, essentially a thread.

I'm currently Java programming (poor old me!) and the way J2ee gets round the connection problem is by connection pooling. Ie. it (or you) creates a group of connections (say 10) then each request uses connections from the pool until they're all used. When requests finish, connections are freed.  Basically what this gets you is a limit on simultaneous threads, and a reduction in connection overhead.
Haven't seen a pool for perl yet. Ideas?

I hope this helps a bit. Perghaps I should have kept my mouth shut given my lack of mod_perl, but there do seem to be similarities between it and servlets.

HTH

B

Posted by mundo (mundo), 31 October 2003
Thanks Custard. Sounds like the tie:handler is the way to go. Anyone know if one comes bundled or if not of a good one to use?

Posted by Custard (Custard), 1 November 2003
Hi,

Tied variables are a standard feature of perl5.

If you're using berkely db through DB_File, them that is also in the perl5 distro. (Unless you're using Solaris 8!)

a basic tied hash to a dbm is

Code:
my %hash;
tie( %hash, "DB_File", "mydata.db" );


Then you just use the hash as normal.

Getting cleverer, you can create an object to tie to the hash.

Code:
package myspecialhash;

sub TIEHASH {
  my $class)=shift;
  my $this={};
  bless $this, $class;
  return $this;
}

sub STORE {
  my $this=shift;
  my $key=shift;
  my $value=shift;

  DO THE STORE WORK HERE
}

sub FETCH {
  my $this=shift;
  my $key=shift;
  my $value;

  DO THE FETCH WORK HERE

  return $value;
}

sub EXISTS {
  my $this=shift;
  my $key=shift;
  my $exists=0;

  SET $EXISTS HERE

  return $exists;
}

sub DEFINED {
  my $this=shift;
  my $key=shift;
  my $defined=0;

  SET $defined HERE

  return $defined;
}
1;


(I think that's all you need.)

Then to tie the object...

Code:
tie %hash, "myspecialhash";


Then magically, the appropriate methods get called when you use the hash. Clever huh?

Means you can implement some really confusing things in perl.
You can also tie arrays and scalars. If you're a masochist you can create scalars that set as one thing and return another.

Of course if your tied object was called something like:-
TIE:racleAuthHash
You will give yourself a clue next time you have to work on the app.

HTH

B

PS, I haven't got the hang of yabb yet, so you'll have to forgive the CODE tags.. Edit by Graham - I have fixed the tags 4 U; replaced < by [ and > by ] ...

Posted by Custard (Custard), 1 November 2003
Oh, yes, I nearly forgot...
There are some standard Tie:: classes that come with perl that make this a bit easier.
Have a look for Tie::Hash in perldoc.

Tied variables can be a bit of a big and confusing subject, but (I think) an exciting and interesting one,

B

Posted by mundo (mundo), 3 November 2003
Thanks again Custard. I think I'm beginning to understand, now. So if I wanted my tied hash to be read only, using the object model, I would have the STORE, DELETE and CLEAR methods overridden with methods that do nothing (except maybe raise a warning message), and include code in my FETCH, EXISTS and DEFINED methods to act appropriate to my database: a file read for a file db and a single DBI thread for an Oracle db? Have I got this right? Then to make the hash global to all children, do I have to create an init method which gets called from a startup script (startup.pl)?

I'm gonna start with a simple data file and see how that goes. How should the key/value pair be delimited within the data file?

(more questions...   )

Posted by Custard (Custard), 3 November 2003
Basically yes.  (Although I think if yo don't implement (say) STORE then it will ignore it, ie effectively read only)

Apart from the last bit of the first paragraph..

You don't need an init. The TIEHASH method in the tied hash class is it's constructor and gets called by tie() for you. You can connect to the Oracle here.
Not sure what the destructor is called for a tied hash, but in it you would disconnect.

A simple test script is a good idea and should give you a clearer idea of how this hangs together.

Let me know how you get on..

HTH.

Posted by mundo (mundo), 3 November 2003
Am having some problems with the TIEHASH method:

Code:
package FileAuthHash;

sub TIEHASH {
  my $class = shift;
  my $this={};
  bless $this, $class;
  return $this;
}


I'm getting the following error: Quote:
Can't locate object method "TIEHASH" via package "TIE::FileAuthHash"


To be honest I'm not sure what this method is doing. Why does it return a scalar? And what does the Quote:
my $this={};
line do?


Posted by Custard (Custard), 3 November 2003
Ok you'll have to give me a few minutes to try it out.

The $this={}; assigns an anonymous hash reference to $this.
Then the reference gets returned.

I'll get back to you in a few minutes.

...time passes...

Ok.
Have you got the file containing the class FileAuthHash on the path (current directory) and called FileAuthHash.pm. ?

I could get the same error if I changed the class name.

Oh, and also the destructor is called DESTROY so it would look something like:-

Code:
sub DESTROY {
 my $this=shift;
 closeYourDataBaseHere();
}


(i will get the hang of yabb one day)

Posted by Custard (Custard), 3 November 2003
Oh, and also, did you use FileAuthHash ?

Here's my tie:-
Code:
#!/usr/bin/perl

use FileAuthClass;

tie %poop, 'FileAuthClass';


And here's my class:-
Code:
#!/usr/bin/perl

package FileAuthClass;

sub TIEHASH {
       my $class = shift;
       my $this={};
       bless $this, $class;
       return $this;
}

sub STORE {
my $this=shift;
my $key=shift;
my $value=shift;

}

sub FETCH {
my $this=shift;
my $key=shift;
my $value;

$value="fetched";

return $value;
}

sub EXISTS {
my $this=shift;
my $key=shift;
my $exists=0;

$exists=1;

return $exists;
}

sub DEFINED {
my $this=shift;
my $key=shift;
my $defined=0;

$defined=1;

return $defined;
}

1;


B


Posted by mundo (mundo), 4 November 2003
Hmmm. Ok here is my tie:
Code:
use strict;

use lib qw(/home/wgsnadm/perl/lib /home/zeus/Apache-AuthCookie-3.04/blib/lib /home/zeus/Apache-AuthCookie-3.04/t/lib);
$ENV{MOD_PERL} or die "not running under mod_perl!";

use Apache::Registry ();
use LWP::UserAgent ();
use Apache::Log ();
use Apache::Cookie ();
use Crypt::CBC ();
use MIME::Base64 ();
use vars qw(%allusers);
use TIE::FileAuthHash;

tie %allusers, 'TIE::FileAuthHash';

1;

... and here is my class:
Code:
package FileAuthHash;

sub TIEHASH {
  my $class = shift;
  my $this={};
  bless $this, $class;
  return $this;
}

sub STORE {}

sub FETCH {
  my $this=shift;
  my $key=shift;
  my $value;

  # DO THE FETCH WORK HERE
  $value = 'fetched';

  return $value;
}

sub EXISTS {
  my $this=shift;
  my $key=shift;
  my $exists=0;

  # SET $EXISTS HERE
  $exists = 1;

  return $exists;
}

sub DEFINED {
  my $this=shift;
  my $key=shift;
  my $defined=0;

  # SET $defined HERE
  $defined = 1;

  return $defined;
}
1;



The tie bit is called from /home/wgsnadm/perl/lib/startup.pl which is called by the follwing line in my httpd.conf for Apache: PerlRequire /home/wgsnadm/perl/lib/startup.pl . The class is found at /home/wgsnadm/perl/lib/TIE/FileAuthHash.pm . Finally, the full error message I'm getting when I try to start Apache is as follows: Quote:
[Tue Nov  4 10:43:17 2003] [error] Can't locate object method "TIEHASH" via package "TIE::FileAuthHash" at /home/wgsnadm/perl/lib/startup.pl line 15.
Compilation failed in require at (eval 2) line 1.

Syntax error on line 38 of /opt/apache/conf/test-httpd.conf:
Can't locate object method "TIEHASH" via package "TIE::FileAuthHash" at /home/wgsnadm/perl/lib/startup.pl line 15.
Compilation failed in require at (eval 2) line 1.


Phew!! I'm sure I'm doing something very obviously wrong, but can't see what it is.

Posted by Custard (Custard), 4 November 2003
I think I can..

Change your

Code:
use TIE::FileAuthHash;


To

Code:
use FileAuthHash;


And that should fix it.

The name in use should be the same as the name in package.

HTH


Posted by mundo (mundo), 4 November 2003
When I tried that the error changed to:
Quote:
[Tue Nov  4 10:57:12 2003] [error] Can't locate FileAuthHash.pm in @INC .........


which kinda suggests that with Code:
use TIE::FileAuthHash;
it does at least find the class, but for some reason not the TIEHASH method...?

Posted by Custard (Custard), 4 November 2003
Hi,

Still sounds like the references to the class are not consistent.

Try:-
Code:
use FileAuthHash;

tie %auth, 'FileAuthHash';


In startup.pl (or whatever its called)

Code:
package FileAuthHash;


In  FileAuthHash.pm.

and make sure that it's on one of the paths in @INC

If you reference a class like..

Code:
use TIE::FileAuthHash;


it expects to find a file called FileAuthHash.pm under a directory called TIE somewhere on the @INC path.

hth

B




Posted by mundo (mundo), 4 November 2003
Hurrah! That got Apache started. Now the difficult bit....  

Posted by mundo (mundo), 4 November 2003
Ok, now for the nitty gritty  

Can I use a multi-dimensional (two) hash?

How do I refer to the hash from my CookieHandler module? I'm getting Quote:
Global symbol "%allusers" requires explicit package name
errors at the moment.

Posted by Custard (Custard), 4 November 2003
Hmmm.

Not sure what you mean by multidimensional, If you mean a hash of hashes or hash of arrays, then no. The hash referenced by the main hash would be a reference and have no meaning when stored in the database. If you need 'fields' in your retrieved record you'll have to use join and split and decide on an appropriate delimiter.  Or you could have several tied hashes.

If you're getting that error then you need to tell strict to ignore it
(from memory)

use strict( %hash );

I'm a bit hazy about that, because I try very hard usually never to have to use globals to the extent that I have almost forgotten how to.

(Graham?)

Posted by mundo (mundo), 6 November 2003
OK I've managed to get it working with the retrieved record being split into fields as you suggested, Custard. But having looked at the way I'm accessing records from the file, I'm thinking this is causing a lot of read/write from the file system (once for every access to a hash value). E.g. :
Code:
sub FETCH {
  my $this = shift;
  my $key = shift;
  my $value = '';

  # DO THE FETCH WORK HERE
  my $LA_DATA = "/path/to/my/data/file";

  # Create big hash of all users
  open (USERDATA, "$LA_DATA") || die "Couldn't open user data file: $!\n";

  # Read the whole file in one go:
  my ($line, $a_uid) = '';
  while ($line = <USERDATA>) {
     next if $line =~ /^\#/;
     chomp $line;
     if ($line =~ m#^$key:(.*)$#) {
        $value = $1;
     }
  }
  close USERDATA;

  return $value;
}


Have I got completely the wrong end of the stick here? I hope I've implemented this whole tied hash thing wrong, otherwise I can't see the advantage in doing it.


Posted by admin (Graham Ellis), 7 November 2003
I'm going to have a whole more detailed look at this thread over the weekend. Quick thoughts - the principle of tying a hash to a database is great, and it's probably what I would use.   For sure, dummy out the write methods if it's to be read only.   I don't think it will cause a huge disc access / read traffic as you suspect, because your database daemon should be caching .... or if you wish to be sure and cut out socket traffic, you could cache data within the individual apache threads; since it's read only, it won't change.

I'm noting the thread has moved on to using files not databases in the later examples;   in such a situation, a cache would make a major difference.  If you're going to be writing back, though, you'll need to have some sort of flush cache capability;  perhaps we're going to get into having the threads communicating via signals and I'm thinking there has to be an easier way / surely it's been done before or is there in some module or other  

More investigation to follow ....

Posted by mundo (mundo), 7 November 2003
All this signals and sockets talk is beginning to lose me, I'm afraid   .  I'm only using a file for the data at the moment because I know how to access and read a file, but am unfamiliar with how to communicate with a database (except with DBI and CGI), so was hoping to get the thing going using a file and then start trying to use a direct connection to the database instead. I can see how this would be faster, but at the moment am not sure how to set up a daemon. Presumably there's a module to do this. Anybody know where I should be looking?

Caching of data sounds fine so long as when data is changed at the back end, the changes are reflected in whatever is cached. Although I'm not expecting my Web server to make any changes to the data, I *am* expecting the data to change.

Posted by admin (Graham Ellis), 7 November 2003
OK ... get it working without cache, and using a plain file, and don't worry about the efficiency.   You won't need signals, etc, in this case, nor will you need to set up any special daemons.   You might well find that the resultant code runs quite fast enought anyhow .... there are time that I've seen people write a prototype (with plans to then upgrade it), but then find the prototype is AOK for live use.

Posted by mundo (mundo), 12 November 2003
OK, Grahame, I'm going to try it as you suggest with a flat file and without cache, etc. but what is confusing me is that it seems that I would be opening and reading the whole file and then closing again, for every access to the hash.

What I originally tried to do, before I went down the tied hash route, was to create a hash from the flat file and only update the hash (i.e. open, read, close the file) every now and then (whenever a user logs in, say) rather than for every request. That way, I thought, I would get quick access to the data (from the hash) for each request, but regular updating of the hash from the file at critical moments only (when logging in). The problem I had, however, was that I wasn't able to get the hash to be seen globally by all methods and children. Would the tied hash solution above really be quicker than my original solution?

Confused...

Posted by Custard (Custard), 12 November 2003
You would only read the file once in the constructor, then use the store and fetch methods to access the loaded data.  This isn't much different to loading a global hash.

Makes more sense when using a database, because you would connect in the constructor, access the db in the store and fetch methods, then close the connection in the constructor. Because it would be a global tied hash, you would keep that connection open. (at least until it timed out.. another possibility to cope with)

hth.

b

Posted by mundo (mundo), 12 November 2003
Aaah I see.   By the constructor I assume you mean the TIEHASH method? While we're on the subject of object methods, what does the "FIRSTKEY" method do? I got an error recently saying: Quote:
Can't locate object method "FIRSTKEY" via package "FileAuthHash"....
.

BTW, does loading the hash in the constructor mean that the hash only ever gets updated at start-up?

Posted by Custard (Custard), 12 November 2003
You got it

I don't know exactly what firstkey does, I don't remember seeing it in my book. (Which was the first blue camel book btw).

I assume it fetches the first key in the hash for use in functions like keys or each.

Not sure what you'd return though.

I'm guessing here that there may also be a NEXTKEY method too.
Perhaps you could implement the methods, but leave the bodies blank.  Or if you're using a hash, call each on that to do the work.

I'm a bit out of my depth on that, because I haven't used them myself..  soz.

B

Posted by mundo (mundo), 12 November 2003
That's working a treat now. Thanks for all your help, mainly Custard, but also Grahame. I got around the location of FIRSTKEY method error by inheriting from Tie::StdHash  

Now to see what happens if I change a record in the file mid session........

Posted by Custard (Custard), 12 November 2003
Glad to hear that.

Last time I worked with a tied hash, I was not aware of of the TIE:: classes, so this has been a bit of a learning experience for me also.

B



This page is a thread posted to the opentalk forum at www.opentalk.org.uk and archived here for reference. To jump to the archive index please follow this link.

You can Add a comment or ranking to this page

© WELL HOUSE CONSULTANTS LTD., 2024: Well House Manor • 48 Spa Road • Melksham, Wiltshire • United Kingdom • SN12 7NY
PH: 01144 1225 708225 • FAX: 01144 1225 793803 • EMAIL: info@wellho.net • WEB: http://www.wellho.net • SKYPE: wellho