diff --git a/README.md b/README.md
index a4373e1..1201eb1 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,50 @@
pureftpd-auth
=============
-ortiche pure_authd authenticator script written in perl
\ No newline at end of file
+Ortiche pure_authd authenticator script written in perl. Based on ziokernel code.
+
+Dependencies
+---
+
+apt-get install pure-ftpd \
+ libcrypt-eksblowfish-perl \
+ libclass-mix-perl \
+ libparams-classify-perl \
+ liblog-log4perl-perl \
+ dh-make-perl
+
+dh-make-perl --build --cpan Crypt::Passwd::XS
+dpkg -i libcrypt-passwd-xs-perl_0.601-1_amd64.deb
+
+Config
+---
+
+echo "/usr/local/ortiche/pureftpd-auth.pl" > /etc/pure-ftpd/conf/ExtAuth
+echo "yes" > /etc/pure-ftpd/conf/CreateHomeDir
+rm -fr /etc/pure-ftpd/auth/
+ln -s /etc/pure-ftpd/conf/ExtAuth /etc/pure-ftpd/auth/ExtAuth
+
+Auth
+---
+mkdir /usr/local/ortiche/auth
+vi /usr/local/ortiche/auth/pureftpd.pl
+
+
+#!/usr/bin/perl
+
+$DB_DB="ftp_db";
+$DB_HOST="localhost";
+$DB_USER="ftp_user_ro";
+$DB_PASS="your_password";
+$DB_TABLE="ftp_table";
+
+
+Running
+---
+/usr/sbin/pure-ftpd -d -A -B -4 -Z -p 50000:50601 -H -Y 1 -E -k 98 -C 5 -c 30 -lextauth:/var/run/pure-ftpd/extauth.sock
+
+/usr/sbin/pure-authd -s /var/run/pure-ftpd/extauth.sock -r /usr/local/ortiche/pureftpd-auth.pl &
+
+
+
+
diff --git a/pureftpd-auth.pl b/pureftpd-auth.pl
new file mode 100755
index 0000000..fede40e
--- /dev/null
+++ b/pureftpd-auth.pl
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+
+### v.2.0.2 by zio 06/4/2012
+### v.3.0.0 by gine 11/10/2015
+### v.3.0.1 by gine 22/10/2015
+
+
+### uses
+#
+use DBI;
+#use Crypt::Random;
+#use Crypt::Passwd::XS qw(unix_sha256_crypt);
+#use Crypt::Passwd::XS qw(unix_md5_crypt);
+use Crypt::Passwd::XS; ### The blowfish crypt() scheme is currently unsupported by Crypt::Passwd::XS::crypt() ,
+use Crypt::Eksblowfish::Bcrypt qw(bcrypt); ### so additional Crypt::Eksblowfish::Bcrypt::bcrypt() is being used
+use Digest::SHA qw(sha256_hex);
+use Log::Log4perl qw(get_logger :levels);
+
+### conf
+#
+$SCRIPT_DIR="/usr/local/ortiche";
+$AUTH_DIR="$SCRIPT_DIR/auth";
+require "$AUTH_DIR/pureftpd.pl";
+
+$AUTH_LOG="/var/log/pure-ftpd/auth.log"; ### log kept for debug, to remove in future because of privacy !!!
+
+$SHA256_DEFAULT_ROUNDS = 5000;
+
+### variables read from environment ( pure-authd )
+#
+$REQ_USER=$ENV{AUTHD_ACCOUNT}; ### TODO: sanitize input ?
+$REQ_PASS=$ENV{AUTHD_PASSWORD}; #
+$SERVER_IP=$ENV{AUTHD_LOCAL_IP};
+#AUTHD_LOCAL_PORT
+#AUTHD_REMOTE_IP
+#AUTHD_ENCRYPTED
+
+@numbers = split(/\./, $SERVER_IP);
+$ip_number = pack("C4", @numbers);
+($server_name) = (gethostbyaddr($ip_number, 2))[0];
+if (not $server_name)
+{
+ $server_name=$SERVER_IP;
+}
+
+### variables shared with subs
+#
+my $DB_conn;
+my $auth_logger;
+
+### always returns with sub auth_return() ...
+#
+sub auth_return
+{
+ my ( $RET_AUTH, $RET_DIR ) = @_;
+
+ $auth_logger->info("auth_ok:", $RET_AUTH, " , reason: auth terminated, user: ", $REQ_USER);
+
+ if ( $RET_AUTH == 1 ) ### add extra checks on fields...
+ {
+ print "auth_ok:1\n";
+# print "uid:", $RET_UID, "\n"; #### TODO: fix DB and user creation scripts instead!!!
+# print "gid:", $RET_GID, "\n"; ##
+ print "uid:33\n"; ##
+ print "gid:33\n"; ##
+ print "dir:", $RET_DIR, "\n";
+ print "end\n";
+ exit 0;
+ }
+ else
+ {
+ print "auth_ok:", $RET_AUTH, "\n";
+ print "end\n";
+ exit 1;
+ }
+}
+
+
+### logging subs
+#
+sub auth_log_init
+{
+# log4perl levels:
+# DEBUG INFO WARN ERROR FATAL
+ Log::Log4perl->easy_init({
+ file => ">> $AUTH_LOG",
+ level => $INFO,
+ layout => "[3.0.1] [%d{yy/MM/dd-HH:mm}] [%p] (%F{2} line %L) %m%n",
+ },{
+ file => "STDERR",
+ level => $DEBUG,
+ layout => "[3.0.1] [%d{yy/MM/dd-HH:mm}] [%p] (%F{2} line %L) %m%n",
+ });
+ $auth_logger = Log::Log4perl->get_logger();
+}
+
+
+### digests, salts and crypt() subs
+#
+sub check_crypt {
+ my ( $plain_password, $hashed_password ) = @_;
+ my $encrypted;
+ if ( $hashed_password =~ /(\$2a\$[0-9]{2}\$[A-Za-z0-9.\/]{22})/ ) {
+ $encrypted = Crypt::Eksblowfish::Bcrypt::bcrypt($plain_password, $hashed_password); ### Blowfish scheme
+ } else {
+ $encrypted = Crypt::Passwd::XS::crypt($plain_password, $hashed_password); ### MD5, SHA256 schemes
+ }
+
+ return ( $encrypted eq $hashed_password );
+}
+
+sub sha256_salt {
+# my $salt = en_base64(Crypt::Random::makerandom_octet(Length=>16));
+ my @letters = ('A' .. 'Z', 'a' .. 'z', '0' .. '9', '/', '.');
+ my $salt;
+ for (1 .. 16) { $salt .= $letters[rand@letters]; }
+ return '$5$'.'rounds='.$SHA256_DEFAULT_ROUNDS.'$'.$salt;
+}
+
+#sub migra_update_pw {
+# my ( $req_user, $req_pass ) = @_;
+# my $p_sha256 = sha256_hex($req_pass);
+# my $p_sha256_crypt_salt = Crypt::Passwd::XS::crypt($req_pass, sha256_salt());
+# my $UPD_QUERY = "UPDATE $DB_TABLE SET password = NULL, password_sha256 = '$p_sha256', password_crypt = '$p_sha256_crypt_salt' WHERE username = '$req_user'";
+# $auth_logger->info("updated password for hrd style: ".$REQ_USER);
+# return $DB_conn->do($UPD_QUERY);
+#}
+
+#deprecated
+sub migra_pw {
+ my ( $req_user, $req_pass ) = @_;
+ my $p_sha256_crypt_salt = Crypt::Passwd::XS::crypt($req_pass, sha256_salt());
+ $auth_logger->info("going to run password migration, user: ".$REQ_USER);
+ my $UPD_QUERY = "UPDATE $DB_TABLE SET password = NULL, password_crypt = '$p_sha256_crypt_salt' WHERE username = '$req_user'";
+ return $DB_conn->do($UPD_QUERY);
+}
+
+sub update_hrd_pw {
+ my ( $req_user, $req_pass ) = @_;
+ my $p_sha256 = sha256_hex($req_pass);
+ my $UPD_QUERY = "UPDATE $DB_TABLE SET password_sha256 = '$p_sha256' WHERE username = '$req_user'";
+ $auth_logger->info("updated password for hrd style, user: ".$REQ_USER);
+ return $DB_conn->do($UPD_QUERY);
+}
+
+### end of subs
+
+### begins setting up log
+#
+auth_log_init();
+$auth_logger->info("reason: auth begun , user: ", $REQ_USER);
+$server_name='web00.ortiche.net';
+#$auth_logger->debug("server ip: '$SERVER_IP'\n");
+#$auth_logger->debug("server name: '$server_name'\n");
+
+if ( $REQ_USER eq "" )
+{
+ $auth_logger->fatal("reason: empty user");
+ auth_return(-1, "");
+}
+
+$DB_conn = DBI->connect("DBI:mysql:database=$DB_DB;host=$DB_HOST", $DB_USER, $DB_PASS)
+ || $auth_logger->fatal("fatal: connect() to DB")
+ && auth_return(-1, "");
+
+### runs query and checks
+#
+my $QUERY_pureftpd = $DB_conn->prepare("SELECT path, password_sha256, password_crypt FROM $DB_TABLE JOIN directories USING (dir_id) JOIN hosts_urls USING (url_id) JOIN hosts USING (host_id) WHERE username='$REQ_USER' AND ftp = 'Y' AND hostname = '$server_name'")
+ || $auth_logger->fatal("prepare() query, reason: no user? no ftp=Y? wrong \$server_name ?, user: ".$REQ_USER)
+ && auth_return(-1, "");
+
+$QUERY_pureftpd->execute()
+ || $auth_logger->fatal("execute() query, reason: no user? no ftp=Y? wrong \$server_name ?, user: ".$REQ_USER)
+ && auth_return(-1, "");
+
+my ( $READ_path, $READ_hrd_sha256, $READ_new_crypt ) = $QUERY_pureftpd->fetchrow_array();
+
+### TODO: place here separated checks on ftp = 'Y' AND hostname = '$server_name' ; remove clause from above SELECT
+###
+
+if ( $READ_path eq "" ) { $auth_logger->warn("warning: empty field PATH"); }
+if ( $READ_hrd_sha256 eq "" ) { $auth_logger->warn("warning: empty field HRD_SHA256"); }
+if ( $READ_new_crypt eq "" ) { $auth_logger->warn("warning: empty field NEW_CRYPT"); }
+
+if ( $QUERY_pureftpd->rows == 0 ) {
+ $auth_logger->error("user not found, reason: no user? no ftp=Y ? wrong \$server_name ?, user: ".$REQ_USER);
+ auth_return(0, "");
+} else {
+ $auth_logger->info("User: ".$REQ_USER);
+ if (check_crypt($REQ_PASS, $READ_new_crypt)) {
+ $auth_logger->info("auth_ok:1, GOOD pwd match!, user: ".$REQ_USER);
+ auth_return(1, $READ_path);
+ } else {
+ $auth_logger->info("auth_ok:-1, reason: wrong password from check_crypt(), user: ".$REQ_USER);
+ auth_return(-1, "");
+ }
+}