summaryrefslogtreecommitdiff
path: root/LedgerSMB/Session/DB.pm
blob: 145e1476612955d7e25219d7ab3e498313b2d6e6 (plain)
  1. #=====================================================================
  2. # LedgerSMB
  3. # Small Medium Business Accounting software
  4. # http://www.ledgersmb.org/
  5. #
  6. #
  7. # Copyright (C) 2006
  8. # This work contains copyrighted information from a number of sources all used
  9. # with permission. It is released under the GNU General Public License
  10. # Version 2 or, at your option, any later version. See COPYRIGHT file for
  11. # details.
  12. #
  13. #
  14. #======================================================================
  15. #
  16. # This file has undergone whitespace cleanup.
  17. #
  18. #======================================================================
  19. # This package contains session related functions:
  20. #
  21. # check - checks validity of session based on the user's cookie and login
  22. #
  23. # create - creates a new session, writes cookie upon success
  24. #
  25. # destroy - destroys session
  26. #
  27. # password_check - compares the password with the stored cryted password
  28. # (ver. < 1.2) and the md5 one (ver. >= 1.2)
  29. #====================================================================
  30. package Session;
  31. use MIME::Base64;
  32. use strict;
  33. sub session_check {
  34. use Time::HiRes qw(gettimeofday);
  35. my ( $cookie, $form ) = @_;
  36. if ($cookie eq 'Login'){
  37. return session_create($form);
  38. }
  39. my $timeout;
  40. my $dbh = $form->{dbh};
  41. my $checkQuery = $dbh->prepare(
  42. "SELECT u.username, s.transaction_id
  43. FROM session as s, users as u
  44. WHERE s.session_id = ?
  45. AND s.users_id = u.id
  46. AND s.last_used > now() - ?::interval"
  47. );
  48. my $updateAge = $dbh->prepare(
  49. "UPDATE session
  50. SET last_used = now(),
  51. transaction_id = ?
  52. WHERE session_id = ?;"
  53. );
  54. my ($sessionID, $transactionID, $company) = split(/:/, $cookie);
  55. $form->{company} ||= $company;
  56. #must be an integer
  57. $sessionID =~ s/[^0-9]//g;
  58. $sessionID = int $sessionID;
  59. $transactionID =~ s/[^0-9]//g;
  60. $transactionID = int $transactionID;
  61. if ( !$form->{timeout} ) {
  62. $timeout = "1 day";
  63. }
  64. else {
  65. $timeout = "$form->{timeout} seconds";
  66. }
  67. $checkQuery->execute( $sessionID, $timeout )
  68. || $form->dberror(
  69. __FILE__ . ':' . __LINE__ . ': Looking for session: ' );
  70. my $sessionValid = $checkQuery->rows;
  71. if ($sessionValid) {
  72. #user has a valid session cookie, now check the user
  73. my ( $sessionLogin, $sessionTransaction ) = $checkQuery->fetchrow_array;
  74. my $login = $form->{login};
  75. $login =~ s/[^a-zA-Z0-9._+\@'-]//g;
  76. if ( ( $sessionLogin eq $login )
  77. and ( $sessionTransaction eq $transactionID ) )
  78. {
  79. #microseconds are more than random enough for transaction_id
  80. my ( $ignore, $newTransactionID ) = gettimeofday();
  81. $newTransactionID = int $newTransactionID;
  82. $updateAge->execute( $newTransactionID, $sessionID )
  83. || $form->dberror(
  84. __FILE__ . ':' . __LINE__ . ': Updating session age: ' );
  85. my $newCookieValue =
  86. $sessionID . ':' . $newTransactionID . ':' . $form->{company};
  87. #now update the cookie in the browser
  88. print qq|Set-Cookie: LedgerSMB=$newCookieValue; path=/;\n|;
  89. return 1;
  90. }
  91. else {
  92. #something's wrong, they have the cookie, but wrong user or the wrong transaction id. Hijack attempt?
  93. #destroy the session
  94. my $sessionDestroy = $dbh->prepare("");
  95. #delete the cookie in the browser
  96. print qq|Set-Cookie: LedgerSMB=; path=/;\n|;
  97. return 0;
  98. }
  99. }
  100. else {
  101. #cookie is not valid
  102. #delete the cookie in the browser
  103. print qq|Set-Cookie: LedgerSMB=; path=/;\n|;
  104. return 0;
  105. }
  106. }
  107. sub session_create {
  108. my ($lsmb) = @_;
  109. my $lsmb;
  110. use Time::HiRes qw(gettimeofday);
  111. #microseconds are more than random enough for transaction_id
  112. my ( $ignore, $newTransactionID ) = gettimeofday();
  113. $newTransactionID = int $newTransactionID;
  114. if ( !$ENV{HTTP_HOST} ) {
  115. #don't create cookies or sessions for CLI use
  116. return 1;
  117. }
  118. # use the central database handle
  119. my $dbh = $lsmb->{dbh};
  120. # TODO Change this to use %myconfig
  121. my $deleteExisting = $dbh->prepare(
  122. "DELETE
  123. FROM session
  124. WHERE session.users_id = (select id from users where username = ?)
  125. AND age(last_used) > ?::interval"
  126. );
  127. my $seedRandom = $dbh->prepare("SELECT setseed(?);");
  128. my $fetchSequence =
  129. $dbh->prepare("SELECT nextval('session_session_id_seq'), md5(random());");
  130. my $createNew = $dbh->prepare(
  131. "INSERT INTO session (session_id, users_id, token, transaction_id)
  132. VALUES(?, (SELECT id
  133. FROM users
  134. WHERE username = ?), ?, ?);"
  135. );
  136. # this is assuming that the login is safe, which might be a bad assumption
  137. # so, I'm going to remove some chars, which might make previously valid
  138. # logins invalid --CM
  139. # I am changing this to use HTTP Basic Auth credentials for now. -- CT
  140. my $auth = $ENV{HTTP_AUTHORIZATION};
  141. $auth =~ s/^Basic //i;
  142. my ($login, undef) = split(/:/, MIME::Base64::decode($auth));
  143. $login =~ s/[^a-zA-Z0-9._+\@'-]//g;
  144. #delete any existing stale sessions with this login if they exist
  145. if ( $lsmb->{timeout} ) {
  146. $lsmb->{timeout} = 86400;
  147. }
  148. $deleteExisting->execute( $login, "$lsmb->{timeout} seconds" )
  149. || $lsmb->dberror(
  150. __FILE__ . ':' . __LINE__ . ': Delete from session: ' );
  151. #doing the random stuff in the db so that LedgerSMB won't
  152. #require a good random generator - maybe this should be reviewed,
  153. #pgsql's isn't great either -CM
  154. #
  155. #I think we should be OK. The random number generator is only a small part
  156. #of the credentials in 1.3.x, and for people that need greater security, there
  157. #is always Kerberos.... -- CT
  158. $fetchSequence->execute()
  159. || $lsmb->dberror( __FILE__ . ':' . __LINE__ . ': Fetch sequence id: ' );
  160. my ( $newSessionID, $newToken ) = $fetchSequence->fetchrow_array;
  161. #create a new session
  162. $createNew->execute( $newSessionID, $login, $newToken, $newTransactionID )
  163. || $lsmb->dberror( __FILE__ . ':' . __LINE__ . ': Create new session: ' );
  164. #reseed the random number generator
  165. my $randomSeed = 1.0 * ( '0.' . ( time() ^ ( $$ + ( $$ << 15 ) ) ) );
  166. $seedRandom->execute($randomSeed)
  167. || $lsmb->dberror(
  168. __FILE__ . ':' . __LINE__ . ': Reseed random generator: ' );
  169. my $newCookieValue = $newSessionID . ':' . $newTransactionID . ':'
  170. . $lsmb->{company};
  171. #now set the cookie in the browser
  172. #TODO set domain from ENV, also set path to install path
  173. print qq|Set-Cookie: LedgerSMB=$newCookieValue; path=/;\n|;
  174. $lsmb->{LedgerSMB} = $newCookieValue;
  175. }
  176. sub session_destroy {
  177. my ($form) = @_;
  178. my $login = $form->{login};
  179. $login =~ s/[^a-zA-Z0-9._+\@'-]//g;
  180. # use the central database handle
  181. my $dbh = ${LedgerSMB::Sysconfig::GLOBALDBH};
  182. my $deleteExisting = $dbh->prepare( "
  183. DELETE FROM session
  184. WHERE users_id = (select id from users where username = ?)
  185. " );
  186. $deleteExisting->execute($login)
  187. || $form->dberror(
  188. __FILE__ . ':' . __LINE__ . ': Delete from session: ' );
  189. #delete the cookie in the browser
  190. print qq|Set-Cookie: LedgerSMB=; path=/;\n|;
  191. }
  192. sub password_check {
  193. use Digest::MD5;
  194. my ( $form, $username, $password ) = @_;
  195. $username =~ s/[^a-zA-Z0-9._+\@'-]//g;
  196. # use the central database handle
  197. my $dbh = ${LedgerSMB::Sysconfig::GLOBALDBH};
  198. my $fetchPassword = $dbh->prepare(
  199. "SELECT u.username, uc.password, uc.crypted_password
  200. FROM users as u, users_conf as uc
  201. WHERE u.username = ?
  202. AND u.id = uc.id;"
  203. );
  204. $fetchPassword->execute($username)
  205. || $form->dberror( __FILE__ . ':' . __LINE__ . ': Fetching password : ' );
  206. my ( $dbusername, $md5Password, $cryptPassword ) =
  207. $fetchPassword->fetchrow_array;
  208. if ( $dbusername ne $username ) {
  209. # User data retrieved from db not for the requested user
  210. return 0;
  211. }
  212. elsif ($cryptPassword) {
  213. #First time login from old system, check crypted password
  214. if ( ( crypt $password, substr( $username, 0, 2 ) ) eq $cryptPassword )
  215. {
  216. #password was good, convert to md5 password and null crypted
  217. my $updatePassword = $dbh->prepare(
  218. "UPDATE users_conf
  219. SET password = md5(?),
  220. crypted_password = null
  221. FROM users
  222. WHERE users_conf.id = users.id
  223. AND users.username = ?;"
  224. );
  225. $updatePassword->execute( $password, $username )
  226. || $form->dberror(
  227. __FILE__ . ':' . __LINE__ . ': Converting password : ' );
  228. return 1;
  229. }
  230. else {
  231. return 0; #password failed
  232. }
  233. }
  234. elsif ($md5Password) {
  235. if ( $md5Password ne ( Digest::MD5::md5_hex $password) ) {
  236. return 0;
  237. }
  238. else {
  239. return 1;
  240. }
  241. }
  242. else {
  243. #both the md5Password and cryptPasswords were blank
  244. return 0;
  245. }
  246. }
  247. 1;