pureftpd-auth.pl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/perl
  2. ### v.2.0.2 by zio 06/4/2012
  3. ### v.3.0.0 by gine 11/10/2015
  4. ### v.3.0.1 by gine 22/10/2015
  5. ### uses
  6. #
  7. use DBI;
  8. #use Crypt::Random;
  9. #use Crypt::Passwd::XS qw(unix_sha256_crypt);
  10. #use Crypt::Passwd::XS qw(unix_md5_crypt);
  11. use Crypt::Passwd::XS; ### The blowfish crypt() scheme is currently unsupported by Crypt::Passwd::XS::crypt() ,
  12. use Crypt::Eksblowfish::Bcrypt qw(bcrypt); ### so additional Crypt::Eksblowfish::Bcrypt::bcrypt() is being used
  13. use Digest::SHA qw(sha256_hex);
  14. use Log::Log4perl qw(get_logger :levels);
  15. ### conf
  16. #
  17. $SCRIPT_DIR="/usr/local/ortiche/pureftpd-auth";
  18. $AUTH_DIR="$SCRIPT_DIR/auth";
  19. require "$AUTH_DIR/pureftpd.pl";
  20. $AUTH_LOG="/var/log/pure-ftpd/auth.log"; ### log kept for debug, to remove in future because of privacy !!!
  21. $SHA256_DEFAULT_ROUNDS = 5000;
  22. ### variables read from environment ( pure-authd )
  23. #
  24. $REQ_USER=$ENV{AUTHD_ACCOUNT}; ### TODO: sanitize input ?
  25. $REQ_PASS=$ENV{AUTHD_PASSWORD}; #
  26. $SERVER_IP=$ENV{AUTHD_LOCAL_IP};
  27. #AUTHD_LOCAL_PORT
  28. #AUTHD_REMOTE_IP
  29. #AUTHD_ENCRYPTED
  30. @numbers = split(/\./, $SERVER_IP);
  31. $ip_number = pack("C4", @numbers);
  32. ($server_name) = (gethostbyaddr($ip_number, 2))[0];
  33. if (not $server_name)
  34. {
  35. $server_name=$SERVER_IP;
  36. }
  37. ### variables shared with subs
  38. #
  39. my $DB_conn;
  40. my $auth_logger;
  41. ### always returns with sub auth_return() ...
  42. #
  43. sub auth_return
  44. {
  45. my ( $RET_AUTH, $RET_DIR ) = @_;
  46. $auth_logger->info("auth_ok:", $RET_AUTH, " , reason: auth terminated, user: ", $REQ_USER);
  47. if ( $RET_AUTH == 1 ) ### add extra checks on fields...
  48. {
  49. print "auth_ok:1\n";
  50. # print "uid:", $RET_UID, "\n"; #### TODO: fix DB and user creation scripts instead!!!
  51. # print "gid:", $RET_GID, "\n"; ##
  52. print "uid:33\n"; ##
  53. print "gid:33\n"; ##
  54. print "dir:", $RET_DIR, "\n";
  55. print "end\n";
  56. exit 0;
  57. }
  58. else
  59. {
  60. print "auth_ok:", $RET_AUTH, "\n";
  61. print "end\n";
  62. exit 1;
  63. }
  64. }
  65. ### logging subs
  66. #
  67. sub auth_log_init
  68. {
  69. # log4perl levels:
  70. # DEBUG INFO WARN ERROR FATAL
  71. Log::Log4perl->easy_init({
  72. file => ">> $AUTH_LOG",
  73. level => $INFO,
  74. layout => "[3.0.1] [%d{yy/MM/dd-HH:mm}] [%p] (%F{2} line %L) %m%n",
  75. },{
  76. file => "STDERR",
  77. level => $DEBUG,
  78. layout => "[3.0.1] [%d{yy/MM/dd-HH:mm}] [%p] (%F{2} line %L) %m%n",
  79. });
  80. $auth_logger = Log::Log4perl->get_logger();
  81. }
  82. ### digests, salts and crypt() subs
  83. #
  84. sub check_crypt {
  85. my ( $plain_password, $hashed_password ) = @_;
  86. my $encrypted;
  87. if ( $hashed_password =~ /(\$2a\$[0-9]{2}\$[A-Za-z0-9.\/]{22})/ ) {
  88. $encrypted = Crypt::Eksblowfish::Bcrypt::bcrypt($plain_password, $hashed_password); ### Blowfish scheme
  89. } else {
  90. $encrypted = Crypt::Passwd::XS::crypt($plain_password, $hashed_password); ### MD5, SHA256 schemes
  91. }
  92. return ( $encrypted eq $hashed_password );
  93. }
  94. sub sha256_salt {
  95. # my $salt = en_base64(Crypt::Random::makerandom_octet(Length=>16));
  96. my @letters = ('A' .. 'Z', 'a' .. 'z', '0' .. '9', '/', '.');
  97. my $salt;
  98. for (1 .. 16) { $salt .= $letters[rand@letters]; }
  99. return '$5$'.'rounds='.$SHA256_DEFAULT_ROUNDS.'$'.$salt;
  100. }
  101. #sub migra_update_pw {
  102. # my ( $req_user, $req_pass ) = @_;
  103. # my $p_sha256 = sha256_hex($req_pass);
  104. # my $p_sha256_crypt_salt = Crypt::Passwd::XS::crypt($req_pass, sha256_salt());
  105. # my $UPD_QUERY = "UPDATE $DB_TABLE SET password = NULL, password_sha256 = '$p_sha256', password_crypt = '$p_sha256_crypt_salt' WHERE username = '$req_user'";
  106. # $auth_logger->info("updated password for hrd style: ".$REQ_USER);
  107. # return $DB_conn->do($UPD_QUERY);
  108. #}
  109. #deprecated
  110. sub migra_pw {
  111. my ( $req_user, $req_pass ) = @_;
  112. my $p_sha256_crypt_salt = Crypt::Passwd::XS::crypt($req_pass, sha256_salt());
  113. $auth_logger->info("going to run password migration, user: ".$REQ_USER);
  114. my $UPD_QUERY = "UPDATE $DB_TABLE SET password = NULL, password_crypt = '$p_sha256_crypt_salt' WHERE username = '$req_user'";
  115. return $DB_conn->do($UPD_QUERY);
  116. }
  117. sub update_hrd_pw {
  118. my ( $req_user, $req_pass ) = @_;
  119. my $p_sha256 = sha256_hex($req_pass);
  120. my $UPD_QUERY = "UPDATE $DB_TABLE SET password_sha256 = '$p_sha256' WHERE username = '$req_user'";
  121. $auth_logger->info("updated password for hrd style, user: ".$REQ_USER);
  122. return $DB_conn->do($UPD_QUERY);
  123. }
  124. ### end of subs
  125. ### begins setting up log
  126. #
  127. auth_log_init();
  128. $auth_logger->info("reason: auth begun , user: ", $REQ_USER);
  129. $server_name='web01.indivia.net';
  130. #$auth_logger->debug("server ip: '$SERVER_IP'\n");
  131. #$auth_logger->debug("server name: '$server_name'\n");
  132. if ( $REQ_USER eq "" )
  133. {
  134. $auth_logger->fatal("reason: empty user");
  135. auth_return(-1, "");
  136. }
  137. $DB_conn = DBI->connect("DBI:mysql:database=$DB_DB;host=$DB_HOST", $DB_USER, $DB_PASS)
  138. || $auth_logger->fatal("fatal: connect() to DB")
  139. && auth_return(-1, "");
  140. ### runs query and checks
  141. #
  142. 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'")
  143. || $auth_logger->fatal("prepare() query, reason: no user? no ftp=Y? wrong \$server_name ?, user: ".$REQ_USER)
  144. && auth_return(-1, "");
  145. $QUERY_pureftpd->execute()
  146. || $auth_logger->fatal("execute() query, reason: no user? no ftp=Y? wrong \$server_name ?, user: ".$REQ_USER)
  147. && auth_return(-1, "");
  148. my ( $READ_path, $READ_hrd_sha256, $READ_new_crypt ) = $QUERY_pureftpd->fetchrow_array();
  149. ### TODO: place here separated checks on ftp = 'Y' AND hostname = '$server_name' ; remove clause from above SELECT
  150. ###
  151. if ( $READ_path eq "" ) { $auth_logger->warn("warning: empty field PATH"); }
  152. if ( $READ_hrd_sha256 eq "" ) { $auth_logger->warn("warning: empty field HRD_SHA256"); }
  153. if ( $READ_new_crypt eq "" ) { $auth_logger->warn("warning: empty field NEW_CRYPT"); }
  154. if ( $QUERY_pureftpd->rows == 0 ) {
  155. $auth_logger->error("user not found, reason: no user? no ftp=Y ? wrong \$server_name ?, user: ".$REQ_USER);
  156. auth_return(0, "");
  157. } else {
  158. $auth_logger->info("User: ".$REQ_USER);
  159. if (check_crypt($REQ_PASS, $READ_new_crypt)) {
  160. $auth_logger->info("auth_ok:1, GOOD pwd match!, user: ".$REQ_USER);
  161. auth_return(1, $READ_path);
  162. } else {
  163. $auth_logger->info("auth_ok:-1, reason: wrong password from check_crypt(), user: ".$REQ_USER);
  164. auth_return(-1, "");
  165. }
  166. }