DomainHandler.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <?php
  2. # $Id$
  3. /**
  4. * Handlers User level alias actions - e.g. add alias, get aliases, update etc.
  5. */
  6. class DomainHandler extends PFAHandler {
  7. protected $db_table = 'domain';
  8. protected $id_field = 'domain';
  9. protected $domain_field = 'domain';
  10. protected function validate_new_id() {
  11. $domain_check = check_domain($this->id);
  12. if ($domain_check != '') {
  13. $this->errormsg[$this->id_field] = $domain_check;
  14. return false;
  15. }
  16. if (Config::read('vacation_domain') == $this->id) {
  17. $this->errormsg[$this->id_field] = Config::Lang('domain_conflict_vacation_domain');
  18. return false;
  19. }
  20. # still here? good.
  21. return true;
  22. }
  23. protected function initStruct() {
  24. # TODO: shorter PALANG labels ;-)
  25. $super = $this->is_superadmin;
  26. $transp = min($super, Config::intbool('transport'));
  27. $editquota = min($super, Config::intbool('quota'));
  28. $quota = Config::intbool('quota');
  29. $edit_dom_q = min($super, Config::intbool('domain_quota'), $quota);
  30. $dom_q = min(Config::intbool('domain_quota'), $quota);
  31. $pwexp = min($super, Config::intbool('password_expiration'));
  32. $query_used_domainquota = 'round(coalesce(__total_quota/' . intval(Config::read('quota_multiplier')) . ',0))';
  33. # NOTE: There are dependencies between alias_count, mailbox_count and total_quota.
  34. # NOTE: If you disable "display in list" for one of them, the SQL query for the others might break.
  35. # NOTE: (Disabling all of them shouldn't be a problem.)
  36. #
  37. // https://github.com/postfixadmin/postfixadmin/issues/299
  38. $domain_quota_default = Config::read('domain_quota_default');
  39. if ($domain_quota_default === null) {
  40. $domain_quota_default = -1;
  41. }
  42. $this->struct=array(
  43. # field name allow display in... type $PALANG label $PALANG description default / options / ...
  44. # editing? form list
  45. 'domain' => pacol($this->new, 1, 1, 'text', 'domain' , '' , '', array(),
  46. array('linkto' => 'list-virtual.php?domain=%s') ),
  47. 'description' => pacol($super, $super, $super, 'text', 'description' , '' ),
  48. # Aliases
  49. 'aliases' => pacol($super, $super, 0, 'num' , 'aliases' , 'pAdminEdit_domain_aliases_text' , Config::read('aliases') ),
  50. 'alias_count' => pacol(0, 0, 1, 'vnum', '' , '' , '', array(),
  51. /*not_in_db*/ 0,
  52. /*dont_write_to_db*/ 1,
  53. /*select*/ 'coalesce(__alias_count,0) - coalesce(__mailbox_count,0) as alias_count',
  54. /*extrafrom*/ 'left join ( select count(*) as __alias_count, domain as __alias_domain from ' . table_by_key('alias') .
  55. ' group by domain) as __alias on domain = __alias_domain'),
  56. 'aliases_quot' => pacol(0, 0, 1, 'quot', 'aliases' , '' , 0, array(),
  57. array('select' => db_quota_text( '__alias_count - coalesce(__mailbox_count,0)', 'aliases', 'aliases_quot')) ),
  58. '_aliases_quot_percent' => pacol( 0, 0, 1, 'vnum', '' ,'' , 0, array(),
  59. array('select' => db_quota_percent('__alias_count - coalesce(__mailbox_count,0)', 'aliases', '_aliases_quot_percent')) ),
  60. # Mailboxes
  61. 'mailboxes' => pacol($super, $super, 0, 'num' , 'mailboxes' , 'pAdminEdit_domain_aliases_text' , Config::read('mailboxes') ),
  62. 'mailbox_count' => pacol(0, 0, 1, 'vnum', '' , '' , '', array(),
  63. /*not_in_db*/ 0,
  64. /*dont_write_to_db*/ 1,
  65. /*select*/ 'coalesce(__mailbox_count,0) as mailbox_count',
  66. /*extrafrom*/ 'left join ( select count(*) as __mailbox_count, sum(quota) as __total_quota, domain as __mailbox_domain from ' . table_by_key('mailbox') .
  67. ' group by domain) as __mailbox on domain = __mailbox_domain'),
  68. 'mailboxes_quot' => pacol(0, 0, 1, 'quot', 'mailboxes' , '' , 0, array(),
  69. array('select' => db_quota_text( '__mailbox_count', 'mailboxes', 'mailboxes_quot')) ),
  70. '_mailboxes_quot_percent' => pacol( 0, 0, 1, 'vnum', '' , '' , 0, array(),
  71. array('select' => db_quota_percent('__mailbox_count', 'mailboxes', '_mailboxes_quot_percent')) ),
  72. 'maxquota' => pacol($editquota,$editquota,$quota, 'num', 'pOverview_get_quota' , 'pAdminEdit_domain_maxquota_text' , Config::read('maxquota') ),
  73. # Domain quota
  74. 'quota' => pacol($edit_dom_q,$edit_dom_q, 0, 'num', 'pAdminEdit_domain_quota' , 'pAdminEdit_domain_maxquota_text' , $domain_quota_default ),
  75. 'total_quota' => pacol(0, 0, 1, 'vnum', '' , '' , '', array(),
  76. array('select' => "$query_used_domainquota AS total_quota") /*extrafrom*//* already in mailbox_count */ ),
  77. 'total_quot' => pacol( 0, 0, $dom_q, 'quot', 'pAdminEdit_domain_quota' , '' , 0, array(),
  78. array('select' => db_quota_text( $query_used_domainquota, 'quota', 'total_quot')) ),
  79. '_total_quot_percent'=> pacol( 0, 0, $dom_q, 'vnum', '' , '' , 0, array(),
  80. array('select' => db_quota_percent($query_used_domainquota, 'quota', '_total_quot_percent')) ),
  81. 'transport' => pacol($transp, $transp,$transp,'enum', 'transport' , 'pAdminEdit_domain_transport_text' , Config::read('transport_default') ,
  82. /*options*/ Config::read_array('transport_options') ),
  83. 'backupmx' => pacol($super, $super, 1, 'bool', 'pAdminEdit_domain_backupmx' , '' , 0),
  84. 'active' => pacol($super, $super, 1, 'bool', 'active' , '' , 1 ),
  85. 'default_aliases' => pacol($this->new, $this->new, 0, 'bool', 'pAdminCreate_domain_defaultaliases', '' , 1,array(), /*not in db*/ 1 ),
  86. 'created' => pacol(0, 0, 0, 'ts', 'created' , '' ),
  87. 'modified' => pacol(0, 0, $super, 'ts', 'last_modified' , '' ),
  88. 'password_expiry' => pacol($super, $pwexp, $pwexp, 'num', 'password_expiration' , 'password_expiration_desc' , 365),
  89. '_can_edit' => pacol(0, 0, 1, 'int', '' , '' , 0 ,
  90. /*options*/ array(),
  91. /*not_in_db*/ 0,
  92. /*dont_write_to_db*/ 1,
  93. /*select*/ $this->is_superadmin . ' as _can_edit' ),
  94. '_can_delete' => pacol(0, 0, 1, 'int', '' , '' , 0 ,
  95. /*options*/ array(),
  96. /*not_in_db*/ 0,
  97. /*dont_write_to_db*/ 1,
  98. /*select*/ $this->is_superadmin . ' as _can_delete' ),
  99. );
  100. }
  101. protected function initMsg() {
  102. $this->msg['error_already_exists'] = 'pAdminCreate_domain_domain_text_error';
  103. $this->msg['error_does_not_exist'] = 'domain_does_not_exist';
  104. $this->msg['confirm_delete'] = 'confirm_delete_domain';
  105. if ($this->new) {
  106. $this->msg['logname'] = 'create_domain';
  107. $this->msg['store_error'] = 'pAdminCreate_domain_result_error';
  108. $this->msg['successmessage'] = 'pAdminCreate_domain_result_success';
  109. } else {
  110. $this->msg['logname'] = 'edit_domain';
  111. $this->msg['store_error'] = 'pAdminEdit_domain_result_error';
  112. $this->msg['successmessage'] = 'domain_updated';
  113. }
  114. $this->msg['can_create'] = $this->is_superadmin;
  115. }
  116. public function webformConfig() {
  117. return array(
  118. # $PALANG labels
  119. 'formtitle_create' => 'pAdminCreate_domain_welcome',
  120. 'formtitle_edit' => 'pAdminEdit_domain_welcome',
  121. 'create_button' => 'pAdminCreate_domain_button',
  122. # various settings
  123. 'required_role' => 'admin',
  124. 'listview' => 'list.php?table=domain',
  125. 'early_init' => 0,
  126. );
  127. }
  128. protected function preSave() : bool {
  129. # TODO: is this function superfluous? _can_edit should already cover this
  130. if ($this->is_superadmin) {
  131. return true;
  132. }
  133. $this->errormsg[] = Config::Lang_f('edit_not_allowed', $this->id);
  134. return false;
  135. }
  136. /**
  137. * called by $this->store() after storing $this->values in the database
  138. * can be used to update additional tables, call scripts etc.
  139. */
  140. protected function postSave() : bool {
  141. if ($this->new && $this->values['default_aliases']) {
  142. foreach (Config::read_array('default_aliases') as $address=>$goto) {
  143. $address = $address . "@" . $this->id;
  144. # if $goto doesn't contain @, let the alias point to the same domain
  145. if (!strstr($goto, '@')) {
  146. $goto = $goto . "@" . $this->id;
  147. }
  148. # TODO: use AliasHandler->add instead of writing directly to the alias table
  149. $arr = array(
  150. 'address' => $address,
  151. 'goto' => $goto,
  152. 'domain' => $this->id,
  153. );
  154. $result = db_insert('alias', $arr);
  155. # TODO: error checking
  156. }
  157. }
  158. if ($this->new) {
  159. if (!$this->domain_postcreation()) {
  160. $this->errormsg[] = Config::lang('domain_postcreate_failed');
  161. }
  162. } else {
  163. # we don't have domain_postedit()
  164. }
  165. return true; # TODO: don't hardcode
  166. }
  167. /**
  168. * @return bool
  169. */
  170. public function delete() {
  171. # TODO: check for _can_delete instead
  172. if (! $this->is_superadmin) {
  173. $this->errormsg[] = Config::Lang_f('no_delete_permissions', $this->id);
  174. return false;
  175. }
  176. if (! $this->view()) {
  177. $this->errormsg[] = Config::Lang('domain_does_not_exist'); # TODO: can users hit this message at all? init() should already fail...
  178. return false;
  179. }
  180. if (Config::bool('alias_domain')) {
  181. # check if this domain is an alias domain target - if yes, do not allow to delete it
  182. $handler = new AliasdomainHandler(0, $this->admin_username);
  183. $handler->getList("target_domain = '" . escape_string($this->id) . "'");
  184. $aliasdomains = $handler->result();
  185. if (count($aliasdomains) > 0) {
  186. $this->errormsg[] = Config::Lang_f('delete_domain_aliasdomain_target', $this->id);
  187. return false;
  188. }
  189. }
  190. # the correct way would be to recursively delete mailboxes, aliases, alias_domains, fetchmail entries
  191. # with *Handler before deleting the domain, but this would be terribly slow on domains with many aliases etc.,
  192. # so we do it the fast way on the database level
  193. # cleaning up all tables doesn't hurt, even if vacation or displaying the quota is disabled
  194. # some tables don't have a domain field, so we need a workaround
  195. $like_domain = "LIKE '" . escape_string('%@' . $this->id) . "'";
  196. db_delete('domain_admins', 'domain', $this->id);
  197. db_delete('alias', 'domain', $this->id);
  198. db_delete('mailbox', 'domain', $this->id);
  199. db_delete('alias_domain', 'alias_domain', $this->id);
  200. db_delete('vacation', 'domain', $this->id);
  201. db_delete('vacation_notification', 'on_vacation', $this->id, "OR on_vacation $like_domain");
  202. db_delete('quota', 'username', $this->id, "OR username $like_domain");
  203. db_delete('quota2', 'username', $this->id, "OR username $like_domain");
  204. db_delete('fetchmail', 'mailbox', $this->id, "OR mailbox $like_domain");
  205. db_delete('log', 'domain', $this->id); # TODO: should we really delete the log?
  206. # finally delete the domain
  207. db_delete($this->db_table, $this->id_field, $this->id);
  208. if (!$this->domain_postdeletion()) {
  209. $this->errormsg[] = Config::Lang('domain_postdel_failed');
  210. }
  211. db_log($this->id, 'delete_domain', $this->id); # TODO delete_domain is not a valid db_log keyword yet
  212. $this->infomsg[] = Config::Lang_f('pDelete_delete_success', $this->id);
  213. return true;
  214. }
  215. /**
  216. * get formatted version of fields
  217. *
  218. * @param array values of current item
  219. */
  220. public function _formatted_aliases($item) {
  221. return $item['alias_count'] . ' / ' . $item['aliases'] ;
  222. }
  223. public function _formatted_mailboxes($item) {
  224. return $item['mailbox_count'] . ' / ' . $item['mailboxes'];
  225. }
  226. public function _formatted_quota($item) {
  227. return $item['total_quota'] . ' / ' . $item['quota'] ;
  228. }
  229. /**
  230. * Called after a domain has been added
  231. *
  232. * @return boolean
  233. */
  234. protected function domain_postcreation() {
  235. $script=Config::read_string('domain_postcreation_script');
  236. if (empty($script)) {
  237. return true;
  238. }
  239. if (empty($this->id)) {
  240. $this->errormsg[] = 'Empty domain parameter in domain_postcreation';
  241. return false;
  242. }
  243. $cmdarg1=escapeshellarg($this->id);
  244. $command= "$script $cmdarg1";
  245. $retval=0;
  246. $output=array();
  247. $firstline='';
  248. $firstline=exec($command, $output, $retval);
  249. if (0!=$retval) {
  250. error_log("Running $command yielded return value=$retval, first line of output=$firstline");
  251. $this->errormsg[] = 'Problems running domain postcreation script!';
  252. return false;
  253. }
  254. return true;
  255. }
  256. /**
  257. * Called after a domain has been deleted
  258. *
  259. * @return boolean
  260. */
  261. protected function domain_postdeletion() {
  262. $script=Config::read_string('domain_postdeletion_script');
  263. if (empty($script)) {
  264. return true;
  265. }
  266. if (empty($this->id)) {
  267. $this->errormsg[] = 'Empty domain parameter in domain_postdeletion';
  268. return false;
  269. }
  270. $cmdarg1=escapeshellarg($this->id);
  271. $command= "$script $cmdarg1";
  272. $retval=0;
  273. $output=array();
  274. $firstline='';
  275. $firstline=exec($command, $output, $retval);
  276. if (0!=$retval) {
  277. error_log("Running $command yielded return value=$retval, first line of output=$firstline");
  278. $this->errormsg[] = 'Problems running domain postdeletion script!';
  279. return false;
  280. }
  281. return true;
  282. }
  283. }
  284. /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */