PostfixAdmin - web based virtual user administration interface for Postfix mail servers https://postfixadmin.github.io/postfixadmin/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
12 KiB

  1. <?php
  2. # $Id$
  3. /**
  4. * Simple class to represent a user.
  5. */
  6. class UserHandler {
  7. protected $username = null;
  8. public $errormsg = array();
  9. public function __construct($username) {
  10. $this->username = strtolower($username);
  11. }
  12. public function change_pass($old_password, $new_password) {
  13. error_log('UserHandler->change_pass is deprecated. Please use UserHandler->change_pw!');
  14. return $this->change_pw($new_password, $old_password);
  15. }
  16. /**
  17. * @return boolean true on success; false on failure
  18. * @param string $old_password
  19. * @param string $new_passwords
  20. * @param bool $match = true
  21. *
  22. * All passwords need to be plain text; they'll be hashed appropriately
  23. * as per the configuration in config.inc.php
  24. */
  25. public function change_pw($new_password, $old_password, $match = true) {
  26. list(/*NULL*/,$domain) = explode('@', $username);
  27. $E_username = escape_string($this->username);
  28. $table_mailbox = table_by_key('mailbox');
  29. if ($match == true) {
  30. $active = db_get_boolean(True);
  31. $result = db_query("SELECT password FROM $table_mailbox WHERE username='$E_username' AND active='$active'");
  32. $result = db_assoc($result['result']);
  33. if (pacrypt($old_password, $result['password']) != $result['password']) {
  34. db_log ($domain, 'edit_password', "MATCH FAILURE: " . $this->username);
  35. $this->errormsg[] = 'Passwords do not match'; # TODO: make translatable
  36. return false;
  37. }
  38. }
  39. $set = array(
  40. 'password' => pacrypt($new_password) ,
  41. );
  42. $result = db_update('mailbox', 'username', $this->username, $set );
  43. if ($result != 1) {
  44. db_log ($domain, 'edit_password', "FAILURE: " . $this->username);
  45. $this->errormsg[] = Lang::read('pEdit_mailbox_result_error');
  46. return false;
  47. }
  48. db_log ($domain, 'edit_password', $this->username);
  49. return true;
  50. }
  51. /**
  52. * Attempt to log a user in.
  53. * @param string $username
  54. * @param string $password
  55. * @return boolean true on successful login (i.e. password matches etc)
  56. */
  57. public static function login($username, $password) {
  58. $username = escape_string($username);
  59. $table_mailbox = table_by_key('mailbox');
  60. $active = db_get_boolean(True);
  61. $query = "SELECT password FROM $table_mailbox WHERE username='$username' AND active='$active'";
  62. $result = db_query ($query);
  63. if ($result['rows'] == 1)
  64. {
  65. $row = db_array ($result['result']);
  66. $crypt_password = pacrypt ($password, $row['password']);
  67. if($row['password'] == $crypt_password) {
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. /**
  74. * Add mailbox
  75. * @param password string password of account
  76. * @param gen boolean
  77. * @param name string
  78. *
  79. */
  80. public function add($password, $name = '', $quota = -999, $active = true, $mail = true ) {
  81. # FIXME: default value of $quota (-999) is intentionally invalid. Add fallback to default quota.
  82. # Solution: Invent an sub config class with additional informations about domain based configs like default qouta.
  83. # FIXME: Should the parameters be optional at all?
  84. # TODO: check if parameters are valid/allowed (quota?).
  85. # TODO: most code should live in a separate function that can be used by add and edit.
  86. # TODO: On the longer term, the web interface should also use this class.
  87. # TODO: copy/move all checks and validations from create-mailbox.php here
  88. $username = $this->username;
  89. list($local_part,$domain) = explode ('@', $username);
  90. #TODO: more self explaining language strings!
  91. if(!check_mailbox ($domain)) {
  92. $this->errormsg[] = Lang::read('pCreate_mailbox_username_text_error3');
  93. return false;
  94. }
  95. # check if an alias with this name already exists
  96. $result = db_query ("SELECT * FROM " . table_by_key('alias') . " WHERE address='" . escape_string($username) . "'");
  97. if ($result['rows'] == 1){
  98. $this->errormsg[] = Lang::read('pCreate_mailbox_username_text_error2');
  99. return false;
  100. }
  101. $plain = $password;
  102. $password = pacrypt ($password);
  103. # TODO: if we want to have the encryption method in the encrypted password string, it should be done in pacrypt(). No special handling here!
  104. # if ( preg_match("/^dovecot:/", Config::read('encrypt')) ) {
  105. # $split_method = preg_split ('/:/', Config::read('encrypt'));
  106. # $method = strtoupper($split_method[1]);
  107. # $password = '{' . $method . '}' . $password;
  108. # }
  109. #TODO: 2nd clause should be the first for self explaining code.
  110. #TODO: When calling config::Read with parameter we sould be right that read return false if the parameter isn't in our config file.
  111. if(Config::read('maildir_name_hook') != 'NO' && function_exists(Config::read('maildir_name_hook')) ) {
  112. $hook_func = $CONF['maildir_name_hook'];
  113. $maildir = $hook_func ($fDomain, $fUsername);
  114. }
  115. elseif (Config::read('domain_path') == "YES")
  116. {
  117. if (Config::read('domain_in_mailbox') == "YES")
  118. {
  119. $maildir = $domain . "/" . $username . "/";
  120. }
  121. else
  122. {
  123. $maildir = $domain . "/" . $local_part . "/";
  124. }
  125. }
  126. else
  127. {
  128. $maildir = $username . "/";
  129. }
  130. db_begin();
  131. $active = db_get_boolean($active);
  132. $quota = multiply_quota ($quota);
  133. $alias_data = array(
  134. 'address' => $username,
  135. 'goto' => $username,
  136. 'domain' => $domain,
  137. 'active' => $active,
  138. );
  139. $result = db_insert('alias', $alias_data);
  140. #MARK: db_insert returns true/false??
  141. if ($result != 1)
  142. {
  143. $this->errormsg[] = Lang::read('pAlias_result_error') . "\n($username -> $username)\n";
  144. return false;
  145. }
  146. $mailbox_data = array(
  147. 'username' => $username,
  148. 'password' => $password,
  149. 'name' => $name,
  150. 'maildir' => $maildir,
  151. 'local_part' => $local_part,
  152. 'quota' => $quota,
  153. 'domain' => $domain,
  154. 'active' => $active,
  155. );
  156. $result = db_insert('mailbox', $mailbox_data);
  157. #MARK: Same here!
  158. if ($result != 1 || !mailbox_postcreation($username,$domain,$maildir, $quota)) {
  159. $this->errormsg[] = Lang::read('pCreate_mailbox_result_error') . "\n($username)\n";
  160. db_rollback();
  161. return false;
  162. } else {
  163. db_commit();
  164. db_log ($domain, 'create_mailbox', $username);
  165. if ($mail == true)
  166. {
  167. # TODO: move "send the mail" to a function
  168. $fTo = $username;
  169. $fFrom = Config::read('admin_email');
  170. $fSubject = Lang::read('pSendmail_subject_text');
  171. $fBody = Config::read('welcome_text');
  172. if (!smtp_mail ($fTo, $fFrom, $fSubject, $fBody))
  173. {
  174. $this->errormsg[] = Lang::read('pSendmail_result_error');
  175. return false;
  176. }
  177. }
  178. create_mailbox_subfolders($username,$plain);
  179. }
  180. return true;
  181. }
  182. public function view() {
  183. $username = $this->username;
  184. $table_mailbox = table_by_key('mailbox');
  185. # TODO: check if DATE_FORMAT works in MySQL and PostgreSQL
  186. # TODO: maybe a more fine-grained date format would be better for non-CLI usage
  187. $result = db_query("SELECT username, name, maildir, quota, local_part, domain, DATE_FORMAT(created, '%d.%m.%y') AS created, DATE_FORMAT(modified, '%d.%m.%y') AS modified, active FROM $table_mailbox WHERE username='$username'");
  188. if ($result['rows'] != 0) {
  189. $this->return = db_array($result['result']);
  190. return true;
  191. }
  192. $this->errormsg = $result['error'];
  193. return false;
  194. }
  195. public function delete() {
  196. $username = $this->username;
  197. list(/*$local_part*/,$domain) = explode ('@', $username);
  198. $E_username = escape_string($username);
  199. $E_domain = escape_string($domain);
  200. #TODO: At this level of table by key calls we should think about a solution in our query function and drupal like {mailbox} {alias}.
  201. # Pseudocode for db_query etc.
  202. # if {} in query then
  203. # table_by_key( content between { and } )
  204. # else error
  205. $table_mailbox = table_by_key('mailbox');
  206. $table_alias = table_by_key('alias');
  207. $table_vacation = table_by_key('vacation');
  208. $table_vacation_notification = table_by_key('vacation_notification');
  209. db_begin();
  210. #TODO: ture/false replacement!
  211. $error = 0;
  212. $result = db_query("SELECT * FROM $table_alias WHERE address = '$E_username' AND domain = '$domain'");
  213. if($result['rows'] == 1) {
  214. $result = db_delete('alias', 'address', $username);
  215. db_log ($domain, 'delete_alias', $username);
  216. } else {
  217. $this->errormsg[] = "no alias $username"; # todo: better message, make translatable
  218. $error = 1;
  219. }
  220. /* is there a mailbox? if do delete it from orbit; it's the only way to be sure */
  221. $result = db_query ("SELECT * FROM $table_mailbox WHERE username='$E_username' AND domain='$domain'");
  222. if ($result['rows'] == 1)
  223. {
  224. $result = db_delete('mailbox', 'username', $username);
  225. $postdel_res=mailbox_postdeletion($username,$domain);
  226. if ($result != 1 || !$postdel_res)
  227. {
  228. $tMessage = Lang::read('pDelete_delete_error') . "$username (";
  229. if ($result['rows']!=1) # TODO: invalid test, $result is from db_delete and only contains the number of deleted rows
  230. {
  231. $tMessage.='mailbox';
  232. if (!$postdel_res) $tMessage.=', ';
  233. $this->errormsg[] = "no mailbox $username"; # todo: better message, make translatable
  234. $error = 1;
  235. }
  236. if (!$postdel_res)
  237. {
  238. $tMessage.='post-deletion';
  239. $this->errormsg[] = "post-deletion script failed"; # todo: better message, make translatable
  240. $error = 1;
  241. }
  242. $this->errormsg[] = $tMessage.')';
  243. # TODO: does db_rollback(); make sense? Not sure because mailbox_postdeletion was already called (move the call checking the db_delete result?)
  244. # TODO: maybe mailbox_postdeletion should be run after all queries, just before commit/rollback
  245. $error = 1;
  246. # return false; # TODO: does this make sense? Or should we still cleanup vacation and vacation_notification?
  247. }
  248. db_log ($domain, 'delete_mailbox', $username);
  249. } else {
  250. $this->errormsg[] = "no mailbox $username"; # TODO: better message, make translatable
  251. $error = 1;
  252. }
  253. $result = db_query("SELECT * FROM $table_vacation WHERE email = '$E_username' AND domain = '$domain'");
  254. if($result['rows'] == 1) {
  255. db_delete('vacation', 'email', $username);
  256. db_delete('vacation_notification', 'on_vacation', $username); # TODO: delete vacation_notification independent of vacation? (in case of "forgotten" vacation_notification entries)
  257. }
  258. db_commit();
  259. if ($error != 0) return false;
  260. return true;
  261. }
  262. }
  263. /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */