joiner.pl 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  1. # Joiner - a user registration module for Oddmuse
  2. #
  3. # Copyright (C) 2014 Aki Goto <tyatsumi@gmail.com>
  4. #
  5. # Based on Login Module for Oddmuse (login.pl)
  6. # Copyright (C) 2004 Fletcher T. Penney <fletcher@freeshell.org>
  7. #
  8. # Codes included from questionasker.pl for Oddmuse
  9. # Copyright (C) 2004 Brock Wilcox <awwaiid@thelackthereof.org>
  10. # Copyright (C) 2006, 2007 Alex Schroeder <alex@gnu.org>
  11. #
  12. # Codes included from ReCaptcha Extension for Oddmuse
  13. # Copyleft 2008 by B.w.Curry <http://www.raiazome.com>.
  14. # Copyright 2004, 2008 by Brock Wilcox <awwaiid@thelackthereof.org>.
  15. # Copyright 2006, 2007, 2008 by Alex Schroeder <alex@gnu.org>.
  16. #
  17. # This program is free software: you can redistribute it and/or modify
  18. # it under the terms of the GNU General Public License as published by
  19. # the Free Software Foundation, either version 3 of the License, or
  20. # (at your option) any later version.
  21. #
  22. # This program is distributed in the hope that it will be useful,
  23. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. # GNU General Public License for more details.
  26. #
  27. # You should have received a copy of the GNU General Public License
  28. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  29. use strict;
  30. use v5.10;
  31. AddModuleDescription('joiner.pl', 'Joiner Extension');
  32. our ($q, $Now, %Action, @MyAdminCode, @MyInitVariables, $UserGotoBar, $DataDir, $FullUrl, $SiteName, %CookieParameters, @QuestionaskerQuestions, $QuestionaskerRememberAnswer, $QuestionaskerSecretKey, $ReCaptchaSecretKey, $ReCaptchaRememberAnswer);
  33. =head1 DESCRIPTION
  34. This is a user registration module for Oddmuse based on Fletcher's login.pl.
  35. File locking and some functions are improved.
  36. =head1 MENUS
  37. When not logged in, 'Login' and 'Register' menus are shown on UserGotoBar.
  38. When logged in, 'Logout' and 'Account Settings' menus are shown on UserGotoBar.
  39. In 'Account Settings', you can change password and email address.
  40. 'Forgot Password?' menu is in 'Login' menu.
  41. In Administration menu, 'Account Management' menu is shown.
  42. In this menu, you can ban accounts.
  43. =head1 REGISTRATION
  44. To register account, use 'Register' menu.
  45. You have to confirm the email address entered by visiting the link
  46. on the confirmation email sent to the address.
  47. =head1 CONFIGURATION
  48. You can set configuration variables below.
  49. $JoinerSalt:
  50. To increase security for storing passwords, specify arbitrary string.
  51. Default = ''.
  52. $JoinerGeneratorSalt:
  53. To increase security for auto generated passwords and ticket keys,
  54. specify arbitrary string.
  55. Default = ''.
  56. $JoinerEmailSenderAddress
  57. The sender address of the emails sent by this module.
  58. Default = 'www-data@example.net'.
  59. $JoinerCommentAllowed
  60. If 0, you must loggin to write comments.
  61. Default = 1.
  62. $JoinerMinimumPasswordLength
  63. Default = 6.
  64. $JoinerWait
  65. Retrying the email-sending commands is restricted to certain frequency.
  66. Specify the waiting duration for retry in seconds.
  67. Default = 60 * 10.
  68. $JoinerQuestionModule
  69. Specify cooperative anti-spam extension.
  70. Supported values are 'Questionasker', 'ReCaptcha' and 'GdSecurityImage'.
  71. Corresponding anti-spam extension need to be installed separately.
  72. Default = (auto detect).
  73. $JoinerDataDir
  74. When using with Namespaces Extension, specify original root data directory
  75. to concentrate Joiner data files in it.
  76. Default = $DataDir.
  77. $JoinerEmailCommand
  78. The command used to send email.
  79. Default = '/usr/sbin/sendmail -oi -t'.
  80. $JoinerEmailRegExp
  81. Email address format.
  82. Default = '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+$'.
  83. =head1 DATA STRUCTURE
  84. Account data is stored in $DataDir/joiner directory.
  85. Registration pending email data is stored in $DataDir/joiner_email directory.
  86. Their data format is same as wiki page's.
  87. =cut
  88. our ($JoinerSalt, $JoinerGeneratorSalt, $JoinerEmailSenderAddress,
  89. $JoinerCommentAllowed, $JoinerMinimumPasswordLength, $JoinerWait,
  90. $JoinerQuestionModule, $JoinerDataDir, $JoinerEmailCommand, $JoinerEmailRegExp);
  91. our ($JoinerDir, $JoinerEmailDir, $JoinerMessage, $JoinerLoggedIn);
  92. use Digest::MD5;
  93. $Action{joiner_register} = \&JoinerDoRegister;
  94. $Action{joiner_process_registration} = \&JoinerDoProcessRegistration;
  95. $Action{joiner_confirm_registration} = \&JoinerDoConfirmRegistration;
  96. $Action{joiner_login} = \&JoinerDoLogin;
  97. $Action{joiner_process_login} = \&JoinerDoProcessLogin;
  98. $Action{joiner_ticket} = \&JoinerDoProcessLogin;
  99. $Action{joiner_logout} = \&JoinerDoLogout;
  100. $Action{joiner_account_settings} = \&JoinerDoAccountSettings;
  101. $Action{joiner_change_password} = \&JoinerDoChangePassword;
  102. $Action{joiner_process_change_password} = \&JoinerDoProcessChangePassword;
  103. $Action{joiner_forgot_password} = \&JoinerDoForgotPassword;
  104. $Action{joiner_process_forgot_password} = \&JoinerDoProcessForgotPassword;
  105. $Action{joiner_recover} = \&JoinerDoProcessLogin;
  106. $Action{joiner_change_email} = \&JoinerDoChangeEmail;
  107. $Action{joiner_process_change_email} = \&JoinerDoProcessChangeEmail;
  108. $Action{joiner_confirm_email} = \&JoinerDoConfirmEmail;
  109. $Action{joiner_manage} = \&JoinerDoManage;
  110. $Action{joiner_ban} = \&JoinerDoBan;
  111. $Action{joiner_process_ban} = \&JoinerDoProcessBan;
  112. push(@MyAdminCode, \&JoinerAdminCode);
  113. push(@MyInitVariables, \&JoinerInitVariables);
  114. sub JoinerGetPasswordHash {
  115. my ($raw_password) = @_;
  116. return Digest::MD5::md5_hex($JoinerSalt . $raw_password);
  117. }
  118. sub JoinerRequestLockOrError {
  119. my ($name) = @_;
  120. # 10 tries, 3 second wait, die on error
  121. return RequestLockDir($name, 10, 3, 1);
  122. }
  123. sub JoinerGetEmailFile {
  124. my ($email) = @_;
  125. return "$JoinerEmailDir/$email.email";
  126. }
  127. sub JoinerGetAccountFile {
  128. my ($username) = @_;
  129. return "$JoinerDir/$username.account";
  130. }
  131. # Always call JoinerCreateAccount within a lock.
  132. sub JoinerCreateAccount {
  133. my ($username, $password, $email, $key) = @_;
  134. my ($account_status, $account_data)
  135. = ReadFile(JoinerGetAccountFile($username));
  136. if ($account_status) {
  137. return T('Username:') . ' ' .
  138. Ts('The username %s already exists.', $username);
  139. }
  140. my ($email_status, $email_data) = ReadFile(JoinerGetEmailFile($email));
  141. if ($email_status) {
  142. my $email_page = ParseData($email_data);
  143. if ($email_page->{confirmed}) {
  144. return Ts('The email address %s has already been used.', $email);
  145. }
  146. if ($email_page->{registration_time} + $JoinerWait > $Now) {
  147. my $min = 1 + int(($email_page->{registration_time} + $JoinerWait - $Now) / 60);
  148. return Ts('Wait %s minutes before try again.', $min);
  149. }
  150. }
  151. my %email_page = ();
  152. $email_page{username} = $username;
  153. $email_page{email} = $email;
  154. $email_page{confirmed} = 0;
  155. $email_page{registration_time} = $Now;
  156. CreateDir($JoinerEmailDir);
  157. WriteStringToFile(JoinerGetEmailFile($email), EncodePage(%email_page));
  158. my %page;
  159. $page{username} = $username;
  160. $page{password} = $password;
  161. $page{email} = $email;
  162. $page{key} = $key;
  163. $page{confirmed} = 0;
  164. $page{registration_time} = $Now;
  165. CreateDir($JoinerDir);
  166. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%page));
  167. return '';
  168. }
  169. sub JoinerSendRegistrationConfirmationEmail {
  170. my ($email, $username, $key) = @_;
  171. my $link = "$FullUrl?action=joiner_confirm_registration&joiner_username=" . UrlEncode($username) . "&joiner_key=$key";
  172. open (my $EMAIL, '|', $JoinerEmailCommand);
  173. print $EMAIL "To: $email\n";
  174. print $EMAIL "From: $JoinerEmailSenderAddress\n";
  175. print $EMAIL "Subject: $SiteName " . T('Registration Confirmation') . "\n";
  176. print $EMAIL "\n";
  177. print $EMAIL T('Visit the link below to confirm registration.') . "\n";
  178. print $EMAIL "\n";
  179. print $EMAIL "$link\n";
  180. print $EMAIL "\n";
  181. close $EMAIL;
  182. }
  183. sub JoinerSendRecoverAccountEmail {
  184. my ($email, $username, $key) = @_;
  185. my $link = "$FullUrl?action=joiner_recover&joiner_username=" . UrlEncode($username) . "&joiner_key=$key";
  186. open (my $EMAIL, '|', $JoinerEmailCommand);
  187. print $EMAIL "To: $email\n";
  188. print $EMAIL "From: $JoinerEmailSenderAddress\n";
  189. print $EMAIL "Subject: " . T('Recover Account') . " - $SiteName\n";
  190. print $EMAIL "\n";
  191. print $EMAIL T('You can login by following the link below. Then set new password.') . "\n";
  192. print $EMAIL "\n";
  193. print $EMAIL "$link\n";
  194. print $EMAIL "\n";
  195. close $EMAIL;
  196. }
  197. sub JoinerSendChangeEmailEmail {
  198. my ($email, $username, $key) = @_;
  199. my $link = "$FullUrl?action=joiner_confirm_email&joiner_username=" . UrlEncode($username) . "&joiner_key=$key";
  200. open (my $EMAIL, '|', $JoinerEmailCommand);
  201. print $EMAIL "To: $email\n";
  202. print $EMAIL "From: $JoinerEmailSenderAddress\n";
  203. print $EMAIL "Subject: " . T('Change Email Address') . " - $SiteName\n";
  204. print $EMAIL "\n";
  205. print $EMAIL T('To confirm changing email address, follow the link below.') . "\n";
  206. print $EMAIL "\n";
  207. print $EMAIL "$link\n";
  208. print $EMAIL "\n";
  209. close $EMAIL;
  210. }
  211. sub JoinerQuestionaskerGetQuestion {
  212. my $need_button = shift;
  213. my $button = $need_button ? $q->submit(-value=>T('Go!')) : '';
  214. my $question_number = int(rand(scalar(@QuestionaskerQuestions)));
  215. return $q->div({-class=>'question'},
  216. $q->p(T('To submit this form you must answer this question:')),
  217. $q->blockquote($q->p($QuestionaskerQuestions[$question_number][0]),
  218. $q->p($q->input({-type=>'text', -name=>'answer'}),
  219. $q->input({-type=>'hidden', -name=>'question_num',
  220. -value=>$question_number}),
  221. $button)));
  222. }
  223. sub JoinerQuestionaskerCheck {
  224. my $question_num = GetParam('question_num', undef);
  225. my $answer = GetParam('answer', undef);
  226. unless (UserIsEditor()
  227. or $QuestionaskerRememberAnswer && GetParam($QuestionaskerSecretKey, 0)
  228. or $QuestionaskerQuestions[$question_num][1]($answer)) {
  229. # logging to the error log file of the server
  230. # warn "Q: '$QuestionaskerQuestions[$question_num][0]', A: '$answer'\n";
  231. return 0;
  232. }
  233. # Set the secret key only if a question has in fact been answered
  234. if (not GetParam($QuestionaskerSecretKey, 0)
  235. and $QuestionaskerQuestions[$question_num][1]($answer)) {
  236. SetParam($QuestionaskerSecretKey, 1)
  237. }
  238. return 1;
  239. }
  240. sub JoinerReCaptchaCheck {
  241. my $correct = 0;
  242. unless (UserIsEditor() or UserIsAdmin()
  243. or $ReCaptchaRememberAnswer && GetParam($ReCaptchaSecretKey, 0)
  244. or $correct = ReCaptchaCheckAnswer() # remember this!
  245. ) {
  246. # logging to the error log file of the server
  247. # warn "Q: '$ReCaptchaQuestions[$question_num][0]', A: '$answer'\n";
  248. return 0;
  249. }
  250. if (not GetParam($ReCaptchaSecretKey, 0) and $correct) {
  251. SetParam($ReCaptchaSecretKey, 1);
  252. }
  253. return 1;
  254. }
  255. sub JoinerGetQuestion {
  256. if ($JoinerQuestionModule eq 'Questionasker') {
  257. if (not $QuestionaskerRememberAnswer && GetParam($QuestionaskerSecretKey, 0)
  258. and not UserIsEditor()) {
  259. return JoinerQuestionaskerGetQuestion();
  260. }
  261. } elsif ($JoinerQuestionModule eq 'ReCaptcha') {
  262. if (not $ReCaptchaRememberAnswer && GetParam($ReCaptchaSecretKey, 0)
  263. and not UserIsEditor()) {
  264. return ReCaptchaGetQuestion();
  265. }
  266. } elsif ($JoinerQuestionModule eq 'GdSecurityImage') {
  267. return GdSecurityImageGetHtml();
  268. }
  269. return '';
  270. }
  271. sub JoinerCheckQuestion {
  272. if ($JoinerQuestionModule eq 'Questionasker') {
  273. if (!JoinerQuestionaskerCheck()) {
  274. $JoinerMessage = T('Question:') . ' ' . T('You did not answer correctly.');
  275. return 0;
  276. }
  277. } elsif ($JoinerQuestionModule eq 'ReCaptcha') {
  278. if (!JoinerReCaptchaCheck()) {
  279. $JoinerMessage = T('CAPTCHA:') . ' ' . T('You did not answer correctly.');
  280. return 0;
  281. }
  282. } elsif ($JoinerQuestionModule eq 'GdSecurityImage') {
  283. if (!GdSecurityImageCheck()) {
  284. $JoinerMessage = T('CAPTCHA:') . ' ' . T('Please type the six characters from the anti-spam image');
  285. return 0;
  286. }
  287. }
  288. return 1;
  289. }
  290. sub JoinerDoRegister {
  291. print GetHeader('', T('Registration'), '');
  292. print $q->start_div({-class=>'joiner'});
  293. if ($JoinerMessage) {
  294. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  295. }
  296. print $q->start_p();
  297. print T('The username must be valid page name.');
  298. print $q->end_p();
  299. print $q->start_p();
  300. print T('Confirmation email will be sent to the email address.');
  301. print $q->end_p();
  302. print GetFormStart(undef, undef, undef);
  303. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_registration'});
  304. my $table = '';
  305. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  306. $q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
  307. $table .= $q->Tr($q->td($q->label({-for=>'joiner_password'}, T('Password:'))),
  308. $q->td($q->password_field(-name=>'joiner_password', -id=>'joiner_password')));
  309. $table .= $q->Tr($q->td($q->label({-for=>'joiner_repeated_password'}, T('Repeat Password:'))),
  310. $q->td($q->password_field(-name=>'joiner_repeated_password', -id=>'joiner_repeated_password')));
  311. $table .= $q->Tr($q->td($q->label({-for=>'joiner_email'}, T('Email:'))),
  312. $q->td($q->textfield(-name=>'joiner_email', -id=>'joiner_email')));
  313. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
  314. print $q->table($table);
  315. print JoinerGetQuestion();
  316. print $q->end_form;
  317. print $q->end_div();
  318. PrintFooter();
  319. }
  320. sub JoinerDoProcessRegistration {
  321. my $username = GetParam('joiner_username', '');
  322. my $password = GetParam('joiner_password', '');
  323. my $repeated_password = GetParam('joiner_repeated_password', '');
  324. my $email = GetParam('joiner_email', '');
  325. my $message;
  326. $message = ValidId($username);
  327. if ($message ne '') {
  328. $JoinerMessage = T('Username:') . ' ' . $message;
  329. JoinerDoRegister();
  330. return;
  331. }
  332. if (!($email =~ /$JoinerEmailRegExp/)) {
  333. $JoinerMessage = T('Email:') . ' ' . T('Bad email address format.');
  334. JoinerDoRegister();
  335. return;
  336. }
  337. if (length($password) < $JoinerMinimumPasswordLength) {
  338. $JoinerMessage = T('Password:') . ' ' . Ts('Password needs to have at least %s characters.', $JoinerMinimumPasswordLength);
  339. JoinerDoRegister();
  340. return;
  341. }
  342. if ($repeated_password ne $password) {
  343. $JoinerMessage = T('Password:') . ' ' . T('Repeat Password:') . ' ' . T('Passwords differ.');
  344. JoinerDoRegister();
  345. return;
  346. }
  347. if (!JoinerCheckQuestion()) {
  348. JoinerDoRegister();
  349. return;
  350. }
  351. my $hash = JoinerGetPasswordHash($password);
  352. my $key = Digest::MD5::md5_hex($JoinerGeneratorSalt . rand());
  353. JoinerRequestLockOrError('joiner');
  354. $message = JoinerCreateAccount($username, $hash, $email, $key);
  355. ReleaseLockDir('joiner');
  356. if ($message ne '') {
  357. $JoinerMessage = $message;
  358. JoinerDoRegister();
  359. return;
  360. }
  361. JoinerSendRegistrationConfirmationEmail($email, $username, $key);
  362. print GetHeader('', T('Email Sent'), '');
  363. print $q->start_div({-class=>'joiner'});
  364. print $q->start_p();
  365. print Ts('Confirmation email has been sent to %s. Visit the link on the mail to confirm registration.', $email);
  366. print $q->end_p();
  367. print $q->end_div();
  368. PrintFooter();
  369. }
  370. sub JoinerShowRegistrationConfirmationFailed {
  371. print GetHeader('', T('Failed to Confirm Registration'), '');
  372. print $q->start_div({-class=>'joiner'});
  373. if ($JoinerMessage) {
  374. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  375. }
  376. print $q->end_div();
  377. PrintFooter();
  378. }
  379. sub JoinerDoConfirmRegistration {
  380. my $username = GetParam('joiner_username', '');
  381. my $key = GetParam('joiner_key', '');
  382. my $message = ValidId($username);
  383. if ($message ne '') {
  384. $JoinerMessage = T('Username:') . ' ' . $message;
  385. JoinerShowRegistrationConfirmationFailed();
  386. return;
  387. }
  388. JoinerRequestLockOrError('joiner');
  389. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  390. ReleaseLockDir('joiner');
  391. if (!$status) {
  392. $JoinerMessage = T('Invalid key.');
  393. JoinerShowRegistrationConfirmationFailed();
  394. return;
  395. }
  396. my $page = ParseData($data);
  397. if ($key ne $page->{key}) {
  398. $JoinerMessage = T('Invalid key.');
  399. JoinerShowRegistrationConfirmationFailed();
  400. return;
  401. }
  402. if ($page->{registration_time} + $JoinerWait < $Now) {
  403. $JoinerMessage = T('The key expired.');
  404. JoinerShowRegistrationConfirmationFailed();
  405. return;
  406. }
  407. $page->{key} = '';
  408. $page->{confirmed} = 1;
  409. JoinerRequestLockOrError('joiner');
  410. CreateDir($JoinerDir);
  411. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  412. ReleaseLockDir('joiner');
  413. my $email = $page->{email};
  414. JoinerRequestLockOrError('joiner');
  415. my ($email_status, $email_data) = ReadFile(JoinerGetEmailFile($email));
  416. ReleaseLockDir('joiner');
  417. if ($email_status) {
  418. my $email_page = ParseData($email_data);
  419. $email_page->{confirmed} = 1;
  420. JoinerRequestLockOrError('joiner');
  421. CreateDir($JoinerEmailDir);
  422. WriteStringToFile(JoinerGetEmailFile($email), EncodePage(%$email_page));
  423. ReleaseLockDir('joiner');
  424. }
  425. print GetHeader('', T('Registration Confirmed'), '');
  426. print $q->start_div({-class=>'joiner'});
  427. print $q->start_p();
  428. print T('Now, you can login by using username and password.');
  429. print $q->end_p();
  430. print $q->start_p();
  431. print ScriptLink('action=joiner_login', T('Login'));
  432. print $q->end_p();
  433. print $q->end_div();
  434. PrintFooter();
  435. }
  436. sub JoinerDoLogin {
  437. print GetHeader('', T('Login'), '');
  438. print $q->start_div({-class=>'joiner'});
  439. if ($JoinerMessage) {
  440. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  441. }
  442. print GetFormStart(undef, undef, undef);
  443. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_login'});
  444. my $table = '';
  445. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  446. $q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
  447. $table .= $q->Tr($q->td($q->label({-for=>'joiner_password'}, T('Password:'))),
  448. $q->td($q->password_field(-name=>'joiner_password', -id=>'joiner_password')));
  449. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
  450. print $q->table($table);
  451. print JoinerGetQuestion();
  452. print $q->end_form;
  453. print $q->start_p();
  454. print ScriptLink('action=joiner_forgot_password', T('Forgot your password?'));
  455. print $q->end_p();
  456. print $q->end_div();
  457. PrintFooter();
  458. }
  459. sub JoinerDoProcessLogin {
  460. my $username = GetParam('joiner_username', '');
  461. my $password = GetParam('joiner_password', '');
  462. my $key = GetParam('joiner_key', '');
  463. my $message = ValidId($username);
  464. if ($message ne '') {
  465. $JoinerMessage = T('Username:') . ' ' . $message;
  466. JoinerDoLogin();
  467. return;
  468. }
  469. if (!($key ne '' && $password eq '') && !JoinerCheckQuestion()) {
  470. JoinerDoLogin();
  471. return;
  472. }
  473. JoinerRequestLockOrError('joiner');
  474. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  475. ReleaseLockDir('joiner');
  476. if (!$status) {
  477. $JoinerMessage = T('Login failed.');
  478. JoinerDoLogin();
  479. return;
  480. }
  481. my $page = ParseData($data);
  482. my $hash = JoinerGetPasswordHash($password);
  483. if ($hash eq $page->{password}) {
  484. $page->{recover} = 0;
  485. SetParam('joiner_recover', 0);
  486. } elsif ($key ne '' && $key eq $page->{recover_key}) {
  487. if ($page->{recover_time} + $JoinerWait < $Now) {
  488. $JoinerMessage = T('The key expired.');
  489. JoinerDoLogin();
  490. return;
  491. }
  492. $page->{recover} = 1;
  493. SetParam('joiner_recover', 1);
  494. } else {
  495. $JoinerMessage = T('Login failed.');
  496. JoinerDoLogin();
  497. return;
  498. }
  499. if ($page->{banned}) {
  500. $JoinerMessage = T('You are banned.');
  501. JoinerDoLogin();
  502. return;
  503. }
  504. if (!$page->{confirmed}) {
  505. $JoinerMessage = T('You must confirm email address.');
  506. JoinerDoLogin();
  507. return;
  508. }
  509. my $session = Digest::MD5::md5_hex(rand());
  510. $page->{session} = $session;
  511. JoinerRequestLockOrError('joiner');
  512. CreateDir($JoinerDir);
  513. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  514. ReleaseLockDir('joiner');
  515. SetParam('username', $username);
  516. SetParam('joiner_session', $session);
  517. print GetHeader('', T('Logged in'), '');
  518. print $q->start_div({-class=>'joiner'});
  519. print $q->start_p();
  520. print Ts('%s has logged in.', $username);
  521. print $q->end_p();
  522. if ($page->{recover}) {
  523. print $q->start_p();
  524. print T('You should set new password immediately.');
  525. print $q->end_p();
  526. print $q->start_p();
  527. print ScriptLink('action=joiner_change_password', T('Change Password'));
  528. print $q->end_p();
  529. }
  530. print $q->end_div();
  531. print PrintFooter();
  532. }
  533. sub JoinerDoLogout {
  534. my $username = GetParam('username', '');
  535. SetParam('username', '');
  536. SetParam('joiner_session', '');
  537. print GetHeader('', T('Logged out'), '');
  538. print $q->start_div({-class=>'joiner'});
  539. print $q->start_p();
  540. print Ts('%s has logged out.', $username);
  541. print $q->end_p();
  542. print $q->end_div();
  543. print PrintFooter();
  544. }
  545. sub JoinerDoAccountSettings {
  546. if (!JoinerIsLoggedIn()) {
  547. JoinerDoLogin();
  548. return;
  549. }
  550. my $username = GetParam('username', '');
  551. print GetHeader('', T('Account Settings'), '');
  552. print $q->start_div({-class=>'joiner'});
  553. print $q->start_p();
  554. print T('Username:') . ' ' . $username;
  555. print $q->end_p();
  556. print $q->start_p();
  557. print ScriptLink('action=joiner_logout', T('Logout'));
  558. print $q->end_p();
  559. print $q->start_p();
  560. print ScriptLink('action=joiner_change_password', T('Change Password'));
  561. print $q->end_p();
  562. print $q->start_p();
  563. print ScriptLink('action=joiner_change_email', T('Change Email Address'));
  564. print $q->end_p();
  565. print $q->end_div();
  566. print PrintFooter();
  567. }
  568. sub JoinerDoChangePassword {
  569. if (!JoinerIsLoggedIn()) {
  570. JoinerDoLogin();
  571. return;
  572. }
  573. my $username = GetParam('username', '');
  574. my $recover = GetParam('joiner_recover', '');
  575. print GetHeader('', T('Change Password'), '');
  576. print $q->start_div({-class=>'joiner'});
  577. if ($JoinerMessage) {
  578. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  579. }
  580. print GetFormStart(undef, undef, undef);
  581. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_change_password'});
  582. my $table = '';
  583. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  584. $q->td($username));
  585. if (!$recover) {
  586. $table .= $q->Tr($q->td($q->label({-for=>'joiner_current_password'}, T('Current Password:'))),
  587. $q->td($q->password_field(-name=>'joiner_current_password', -id=>'joiner_current_password')));
  588. }
  589. $table .= $q->Tr($q->td($q->label({-for=>'joiner_new_password'}, T('New Password:'))),
  590. $q->td($q->password_field(-name=>'joiner_new_password', -id=>'joiner_new_password')));
  591. $table .= $q->Tr($q->td($q->label({-for=>'joiner_repeat_new_password'}, T('Repeat New Password:'))),
  592. $q->td($q->password_field(-name=>'joiner_repeat_new_password', -id=>'joiner_repeat_new_password')));
  593. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
  594. print $q->table($table);
  595. print $q->end_form;
  596. print $q->end_div();
  597. PrintFooter();
  598. }
  599. sub JoinerDoProcessChangePassword {
  600. if (!JoinerIsLoggedIn()) {
  601. JoinerDoLogin();
  602. return;
  603. }
  604. my $username = GetParam('username', '');
  605. my $current_password = GetParam('joiner_current_password', '');
  606. my $new_password = GetParam('joiner_new_password', '');
  607. my $repeat_new_password = GetParam('joiner_repeat_new_password', '');
  608. JoinerRequestLockOrError('joiner');
  609. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  610. ReleaseLockDir('joiner');
  611. if (!$status) {
  612. $JoinerMessage = T('Login failed.');
  613. JoinerDoChangePassword();
  614. return;
  615. }
  616. my $page = ParseData($data);
  617. my $hash = JoinerGetPasswordHash($current_password);
  618. if (!$page->{recover} && $hash ne $page->{password}) {
  619. $JoinerMessage = T('Current Password:') . ' ' . T('Password is wrong.');
  620. JoinerDoChangePassword();
  621. return;
  622. }
  623. if (length($new_password) < $JoinerMinimumPasswordLength) {
  624. $JoinerMessage = T('New Password:') . ' ' . Ts('Password needs to have at least %s characters.', $JoinerMinimumPasswordLength);
  625. JoinerDoChangePassword();
  626. return;
  627. }
  628. if ($repeat_new_password ne $new_password) {
  629. $JoinerMessage = T('New Password:') . ' ' . T('Repeat New Password:') . ' ' . T('Passwords differ.');
  630. JoinerDoChangePassword();
  631. return;
  632. }
  633. $page->{password} = JoinerGetPasswordHash($new_password);
  634. $page->{key} = '';
  635. $page->{recover} = '';
  636. JoinerRequestLockOrError('joiner');
  637. CreateDir($JoinerDir);
  638. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  639. ReleaseLockDir('joiner');
  640. SetParam('joiner_recover', 0);
  641. print GetHeader('', T('Password Changed'), '');
  642. print $q->start_div({-class=>'joiner'});
  643. print $q->start_p();
  644. print T('Your password has been changed.');
  645. print $q->end_p();
  646. print $q->end_div();
  647. print PrintFooter();
  648. }
  649. sub JoinerDoForgotPassword {
  650. print GetHeader('', T('Forgot Password'), '');
  651. print $q->start_div({-class=>'joiner'});
  652. if ($JoinerMessage) {
  653. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  654. }
  655. print $q->start_p();
  656. print T('Enter email address, and recovery login ticket will be sent.');
  657. print $q->end_p();
  658. print GetFormStart(undef, undef, undef);
  659. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_forgot_password'});
  660. my $table = '';
  661. $table .= $q->Tr($q->td($q->label({-for=>'joiner_email'}, T('Email:'))),
  662. $q->td($q->textfield(-name=>'joiner_email', -id=>'joiner_email')));
  663. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
  664. print $q->table($table);
  665. print JoinerGetQuestion();
  666. print $q->end_form;
  667. print $q->end_div();
  668. PrintFooter();
  669. }
  670. sub JoinerDoProcessForgotPassword {
  671. my $email = GetParam('joiner_email', '');
  672. if (!($email =~ /$JoinerEmailRegExp/)) {
  673. $JoinerMessage = T('Email:') . ' ' . T('Bad email address format.');
  674. JoinerDoForgotPassword();
  675. return;
  676. }
  677. if (!JoinerCheckQuestion()) {
  678. JoinerDoForgotPassword();
  679. return;
  680. }
  681. JoinerRequestLockOrError('joiner');
  682. my ($email_status, $email_data) = ReadFile(JoinerGetEmailFile($email));
  683. ReleaseLockDir('joiner');
  684. if (!$email_status) {
  685. $JoinerMessage = T('Email:') . ' ' . T('Not found.');
  686. JoinerDoForgotPassword();
  687. return;
  688. }
  689. my $email_page = ParseData($email_data);
  690. my $username = $email_page->{username};
  691. JoinerRequestLockOrError('joiner');
  692. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  693. ReleaseLockDir('joiner');
  694. if (!$status) {
  695. $JoinerMessage = T('Username:') . ' ' . T('Not found.');
  696. JoinerDoForgotPassword();
  697. return;
  698. }
  699. my $page = ParseData($data);
  700. if ($email ne $page->{email}) {
  701. $JoinerMessage = T('The mail address is not valid anymore.');
  702. JoinerDoForgotPassword();
  703. return;
  704. }
  705. if ($page->{recover_time} + $JoinerWait > $Now) {
  706. my $min = 1 + int(($page->{recover_time} + $JoinerWait - $Now) / 60);
  707. $JoinerMessage = Ts('Wait %s minutes before try again.', $min);
  708. JoinerDoForgotPassword();
  709. return;
  710. }
  711. my $key = Digest::MD5::md5_hex($JoinerGeneratorSalt . rand());
  712. $page->{recover_time} = $Now;
  713. $page->{recover_key} = $key;
  714. JoinerRequestLockOrError('joiner');
  715. CreateDir($JoinerDir);
  716. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  717. ReleaseLockDir('joiner');
  718. JoinerSendRecoverAccountEmail($email, $username, $key);
  719. print GetHeader('', T('Email Sent'), '');
  720. print $q->start_div({-class=>'joiner'});
  721. print $q->start_p();
  722. print Ts('An email has been sent to %s with further instructions.', $email);
  723. print $q->end_p();
  724. print $q->end_div();
  725. print PrintFooter();
  726. }
  727. sub JoinerDoChangeEmail {
  728. if (!JoinerIsLoggedIn()) {
  729. JoinerDoLogin();
  730. return;
  731. }
  732. my $username = GetParam('username', '');
  733. print GetHeader('', T('Change Email Address'), '');
  734. print $q->start_div({-class=>'joiner'});
  735. if ($JoinerMessage) {
  736. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  737. }
  738. print GetFormStart(undef, undef, undef);
  739. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_change_email'});
  740. my $table = '';
  741. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  742. $q->td($username));
  743. $table .= $q->Tr($q->td($q->label({-for=>'joiner_email'}, T('New Email Address:'))),
  744. $q->td($q->textfield(-name=>'joiner_email', -id=>'joiner_email')));
  745. $table .= $q->Tr($q->td($q->label({-for=>'joiner_password'}, T('Password:'))),
  746. $q->td($q->password_field(-name=>'joiner_password', -id=>'joiner_password')));
  747. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
  748. print $q->table($table);
  749. print $q->end_form;
  750. print $q->end_div();
  751. PrintFooter();
  752. }
  753. sub JoinerDoProcessChangeEmail {
  754. if (!JoinerIsLoggedIn()) {
  755. JoinerDoLogin();
  756. return;
  757. }
  758. my $username = GetParam('username', '');
  759. my $email = GetParam('joiner_email', '');
  760. my $password = GetParam('joiner_password', '');
  761. if (!($email =~ /$JoinerEmailRegExp/)) {
  762. $JoinerMessage = T('Email:') . ' ' . T('Bad email address format.');
  763. JoinerDoChangeEmail();
  764. return;
  765. }
  766. JoinerRequestLockOrError('joiner');
  767. my ($email_status, $email_data) = ReadFile(JoinerGetEmailFile($email));
  768. ReleaseLockDir('joiner');
  769. if ($email_status) {
  770. my $email_page = ParseData($email_data);
  771. if ($email_page->{confirmed} && $email_page->{username} ne $username) {
  772. $JoinerMessage = T('Email:') . ' ' .
  773. Ts('The email address %s has already been used.', $email);
  774. JoinerDoChangeEmail();
  775. return;
  776. }
  777. }
  778. JoinerRequestLockOrError('joiner');
  779. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  780. ReleaseLockDir('joiner');
  781. if (!$status) {
  782. $JoinerMessage = T('Failed to load account.');
  783. JoinerDoChangeEmail();
  784. return;
  785. }
  786. my $page = ParseData($data);
  787. if ($page->{change_email_time} + $JoinerWait > $Now) {
  788. my $min = 1 + int(($page->{change_email_time} + $JoinerWait - $Now) / 60);
  789. $JoinerMessage = Ts('Wait %s minutes before try again.', $min);
  790. JoinerDoChangeEmail();
  791. return;
  792. }
  793. my $hash = JoinerGetPasswordHash($password);
  794. if ($hash ne $page->{password}) {
  795. $JoinerMessage = T('Password:') . ' ' . T('Password is wrong.');
  796. JoinerDoChangeEmail();
  797. return;
  798. }
  799. my $key = Digest::MD5::md5_hex(rand());
  800. $page->{change_email} = $email;
  801. $page->{change_email_key} = $key;
  802. $page->{change_email_time} = $Now;
  803. JoinerRequestLockOrError('joiner');
  804. CreateDir($JoinerDir);
  805. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  806. ReleaseLockDir('joiner');
  807. JoinerSendChangeEmailEmail($email, $username, $key);
  808. print GetHeader('', T('Email Sent'), '');
  809. print $q->start_div({-class=>'joiner'});
  810. print $q->start_p();
  811. print Ts('An email has been sent to %s with a login ticket.', $email);
  812. print $q->end_p();
  813. print $q->end_div();
  814. print PrintFooter();
  815. }
  816. sub JoinerShowConfirmEmailFailed {
  817. print GetHeader('', T('Confirmation Failed'), '');
  818. print $q->start_div({-class=>'joiner'});
  819. if ($JoinerMessage) {
  820. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  821. }
  822. print $q->start_p();
  823. print T('Failed to confirm.');
  824. print $q->end_p();
  825. print $q->end_div();
  826. print PrintFooter();
  827. }
  828. sub JoinerDoConfirmEmail {
  829. my $username = GetParam('joiner_username', '');
  830. my $key = GetParam('joiner_key', '');
  831. my $message = ValidId($username);
  832. if ($message ne '') {
  833. $JoinerMessage = $message;
  834. JoinerShowConfirmEmailFailed();
  835. return;
  836. }
  837. JoinerRequestLockOrError('joiner');
  838. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  839. ReleaseLockDir('joiner');
  840. if (!$status) {
  841. $JoinerMessage = T('Failed to load account.');
  842. JoinerShowConfirmEmailFailed();
  843. return;
  844. }
  845. my $page = ParseData($data);
  846. if ($key ne $page->{change_email_key}) {
  847. $JoinerMessage = T('Invalid key.');
  848. JoinerShowConfirmEmailFailed();
  849. return;
  850. }
  851. my $new_email = $page->{change_email};
  852. $page->{email} = $new_email;
  853. $page->{change_email} = '';
  854. $page->{change_email_key} = '';
  855. $page->{change_email_time} = '';
  856. JoinerRequestLockOrError('joiner');
  857. CreateDir($JoinerDir);
  858. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  859. ReleaseLockDir('joiner');
  860. my %email_page = ();
  861. $email_page{username} = $username;
  862. $email_page{email} = $new_email;
  863. $email_page{confirmed} = 1;
  864. $email_page{registration_time} = $Now;
  865. JoinerRequestLockOrError('joiner');
  866. CreateDir($JoinerEmailDir);
  867. WriteStringToFile(JoinerGetEmailFile($new_email), EncodePage(%email_page));
  868. ReleaseLockDir('joiner');
  869. print GetHeader('', T('Email Address Changed'), '');
  870. print $q->start_div({-class=>'joiner'});
  871. print $q->start_p();
  872. print Tss('Email address for %1 has been changed to %2.', $username, $new_email);
  873. print $q->end_p();
  874. print $q->end_div();
  875. print PrintFooter();
  876. }
  877. sub JoinerDoManage {
  878. UserIsAdminOrError();
  879. print GetHeader('', T('Account Management'), '');
  880. print $q->start_div({-class=>'joiner'});
  881. print $q->start_p();
  882. print ScriptLink('action=joiner_ban', T('Ban Account'));
  883. print $q->end_p();
  884. print $q->end_div();
  885. print PrintFooter();
  886. }
  887. sub JoinerDoBan {
  888. UserIsAdminOrError();
  889. print GetHeader('', T('Ban Account'), '');
  890. print $q->start_div({-class=>'joiner'});
  891. if ($JoinerMessage) {
  892. print $q->start_p() . $q->b($JoinerMessage) . $q->end_p();
  893. }
  894. print $q->start_p();
  895. print T('Enter username of the account to ban:');
  896. print $q->end_p();
  897. print GetFormStart(undef, undef, undef);
  898. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_ban'});
  899. print $q->input({-type=>'hidden', -name=>'joiner_ban', -value=>'1'});
  900. my $table = '';
  901. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  902. $q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
  903. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Ban', -value=>T('Ban'))));
  904. print $q->table($table);
  905. print $q->end_form;
  906. print $q->start_p();
  907. print T('Enter username of the account to unban:');
  908. print $q->end_p();
  909. print GetFormStart(undef, undef, undef);
  910. print $q->input({-type=>'hidden', -name=>'action', -value=>'joiner_process_ban'});
  911. print $q->input({-type=>'hidden', -name=>'joiner_ban', -value=>'0'});
  912. $table = '';
  913. $table .= $q->Tr($q->td($q->label({-for=>'joiner_username'}, T('Username:'))),
  914. $q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
  915. $table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Unban', -value=>T('Unban'))));
  916. print $q->table($table);
  917. print $q->end_form;
  918. print $q->end_div();
  919. PrintFooter();
  920. }
  921. sub JoinerDoProcessBan {
  922. UserIsAdminOrError();
  923. my $username = GetParam('joiner_username', '');
  924. my $ban = GetParam('joiner_ban', '');
  925. my $message = ValidId($username);
  926. if ($message ne '') {
  927. $JoinerMessage = $message;
  928. JoinerDoBan();
  929. return;
  930. }
  931. JoinerRequestLockOrError('joiner');
  932. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  933. ReleaseLockDir('joiner');
  934. if (!$status) {
  935. $JoinerMessage = T('Failed to load account.');
  936. JoinerDoBan();
  937. return;
  938. }
  939. my $page = ParseData($data);
  940. if ($ban) {
  941. if ($page->{banned}) {
  942. $JoinerMessage = Ts('%s is already banned.', $username);
  943. JoinerDoBan();
  944. return;
  945. }
  946. $page->{banned} = 1;
  947. $page->{session} = '';
  948. $JoinerMessage = Ts('%s has been banned.', $username);
  949. } else {
  950. if (!$page->{banned}) {
  951. $JoinerMessage = Ts('%s is not banned.', $username);
  952. JoinerDoBan();
  953. return;
  954. }
  955. $page->{banned} = 0;
  956. $JoinerMessage = Ts('%s has been unbanned.', $username);
  957. }
  958. JoinerRequestLockOrError('joiner');
  959. CreateDir($JoinerDir);
  960. WriteStringToFile(JoinerGetAccountFile($username), EncodePage(%$page));
  961. ReleaseLockDir('joiner');
  962. JoinerDoBan();
  963. }
  964. sub JoinerIsLoggedIn {
  965. if ($JoinerLoggedIn ne '') {
  966. return $JoinerLoggedIn;
  967. }
  968. my $username = GetParam('username', '');
  969. my $session = GetParam('joiner_session', '');
  970. my $message = ValidId($username);
  971. if ($message ne '') {
  972. $JoinerLoggedIn = 0;
  973. return $JoinerLoggedIn;
  974. }
  975. JoinerRequestLockOrError('joiner');
  976. my ($status, $data) = ReadFile(JoinerGetAccountFile($username));
  977. ReleaseLockDir('joiner');
  978. if (!$status) {
  979. $JoinerLoggedIn = 0;
  980. return $JoinerLoggedIn;
  981. }
  982. my $page = ParseData($data);
  983. if (!$page->{confirmed}) {
  984. $JoinerLoggedIn = 0;
  985. return $JoinerLoggedIn;
  986. }
  987. if ($session ne $page->{session}) {
  988. $JoinerLoggedIn = 0;
  989. return $JoinerLoggedIn;
  990. }
  991. if ($page->{banned}) {
  992. $JoinerLoggedIn = 0;
  993. return $JoinerLoggedIn;
  994. }
  995. $JoinerLoggedIn = 1;
  996. return $JoinerLoggedIn;
  997. }
  998. *OldJoinerUserCanEdit = \&UserCanEdit;
  999. *UserCanEdit = \&NewJoinerUserCanEdit;
  1000. sub NewJoinerUserCanEdit {
  1001. my ($id, $editing, $comment) = @_;
  1002. if (!OldJoinerUserCanEdit($id, $editing, $comment)) {
  1003. return 0;
  1004. }
  1005. return 1 if UserIsAdmin();
  1006. return 1 if UserIsEditor();
  1007. return 1 if $JoinerCommentAllowed and ($comment or (GetParam('aftertext', '') and not GetParam('text', '')));
  1008. return JoinerIsLoggedIn();
  1009. }
  1010. *OldJoinerGetHeader = \&GetHeader;
  1011. *GetHeader = \&NewJoinerGetHeader;
  1012. sub NewJoinerGetHeader {
  1013. if (JoinerIsLoggedIn()) {
  1014. $UserGotoBar = ScriptLink('action=joiner_logout', T('Logout')) . ' ' .
  1015. ScriptLink('action=joiner_account_settings', T('Account Settings')) . ' ' .
  1016. $UserGotoBar;
  1017. } else {
  1018. $UserGotoBar = ScriptLink('action=joiner_login', T('Login')) . ' ' .
  1019. ScriptLink('action=joiner_register', T('Register')) . ' ' .
  1020. $UserGotoBar;
  1021. }
  1022. my ($id, $title, $oldId, $nocache, $status) = @_;
  1023. return OldJoinerGetHeader($id, $title, $oldId, $nocache, $status);
  1024. }
  1025. sub JoinerAdminCode {
  1026. my ($id, $menuref, $restref) = @_;
  1027. push(@$menuref, ScriptLink('action=joiner_manage', T('Account Management')));
  1028. }
  1029. sub JoinerInitVariables {
  1030. $JoinerSalt = '' unless defined $JoinerSalt;
  1031. $JoinerGeneratorSalt = '' unless defined $JoinerGeneratorSalt;
  1032. $JoinerEmailSenderAddress = 'www-data@example.net' unless defined $JoinerEmailSenderAddress;
  1033. $JoinerCommentAllowed = 1 unless defined $JoinerCommentAllowed;
  1034. $JoinerMinimumPasswordLength = 6 unless defined $JoinerMinimumPasswordLength;
  1035. $JoinerWait = 60 * 10 unless defined $JoinerWait;
  1036. if (!defined($JoinerQuestionModule)) {
  1037. if (defined &QuestionaskerInit) {
  1038. $JoinerQuestionModule = 'Questionasker';
  1039. } elsif (defined &ReCaptchaInit) {
  1040. $JoinerQuestionModule = 'ReCaptcha';
  1041. } elsif (defined &GdSecurityImageInitVariables) {
  1042. $JoinerQuestionModule = 'GdSecurityImage';
  1043. } else {
  1044. $JoinerQuestionModule = '';
  1045. }
  1046. }
  1047. $JoinerDataDir = $DataDir unless defined $JoinerDataDir;
  1048. $JoinerEmailCommand = '/usr/sbin/sendmail -oi -t' unless defined $JoinerEmailCommand;
  1049. $JoinerEmailRegExp = '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+$' unless defined $JoinerEmailRegExp;
  1050. $JoinerDir = "$JoinerDataDir/joiner";
  1051. $JoinerEmailDir = "$JoinerDataDir/joiner_email";
  1052. $JoinerLoggedIn = '';
  1053. $CookieParameters{'joiner_session'} = '';
  1054. $CookieParameters{'joiner_recover'} = '';
  1055. }