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.

324 lines
9.4 KiB

3 years ago
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Arne Hamann <kontakt+github@arne.email>
  7. * @author Branko Kokanovic <branko@kokanovic.org>
  8. * @author Carsten Wiedmann <carsten_sttgt@gmx.de>
  9. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  10. * @author Jared Boone <jared.boone@gmail.com>
  11. * @author Joas Schilling <coding@schilljs.com>
  12. * @author Julius Härtl <jus@bitgrid.net>
  13. * @author Lukas Reschke <lukas@statuscode.ch>
  14. * @author Morris Jobke <hey@morrisjobke.de>
  15. * @author Roeland Jago Douma <roeland@famdouma.nl>
  16. * @author Tekhnee <info@tekhnee.org>
  17. *
  18. * @license AGPL-3.0
  19. *
  20. * This code is free software: you can redistribute it and/or modify
  21. * it under the terms of the GNU Affero General Public License, version 3,
  22. * as published by the Free Software Foundation.
  23. *
  24. * This program is distributed in the hope that it will be useful,
  25. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. * GNU Affero General Public License for more details.
  28. *
  29. * You should have received a copy of the GNU Affero General Public License, version 3,
  30. * along with this program. If not, see <http://www.gnu.org/licenses/>
  31. *
  32. */
  33. namespace OC\Mail;
  34. use Egulias\EmailValidator\EmailValidator;
  35. use Egulias\EmailValidator\Validation\RFCValidation;
  36. use OCP\Defaults;
  37. use OCP\EventDispatcher\IEventDispatcher;
  38. use OCP\IConfig;
  39. use OCP\IL10N;
  40. use OCP\ILogger;
  41. use OCP\IURLGenerator;
  42. use OCP\L10N\IFactory;
  43. use OCP\Mail\IAttachment;
  44. use OCP\Mail\IEMailTemplate;
  45. use OCP\Mail\IMailer;
  46. use OCP\Mail\IMessage;
  47. use OCP\Mail\Events\BeforeMessageSent;
  48. /**
  49. * Class Mailer provides some basic functions to create a mail message that can be used in combination with
  50. * \OC\Mail\Message.
  51. *
  52. * Example usage:
  53. *
  54. * $mailer = \OC::$server->getMailer();
  55. * $message = $mailer->createMessage();
  56. * $message->setSubject('Your Subject');
  57. * $message->setFrom(array('cloud@domain.org' => 'ownCloud Notifier');
  58. * $message->setTo(array('recipient@domain.org' => 'Recipient');
  59. * $message->setBody('The message text');
  60. * $mailer->send($message);
  61. *
  62. * This message can then be passed to send() of \OC\Mail\Mailer
  63. *
  64. * @package OC\Mail
  65. */
  66. class Mailer implements IMailer {
  67. /** @var \Swift_Mailer Cached mailer */
  68. private $instance = null;
  69. /** @var IConfig */
  70. private $config;
  71. /** @var ILogger */
  72. private $logger;
  73. /** @var Defaults */
  74. private $defaults;
  75. /** @var IURLGenerator */
  76. private $urlGenerator;
  77. /** @var IL10N */
  78. private $l10n;
  79. /** @var IEventDispatcher */
  80. private $dispatcher;
  81. /** @var IFactory */
  82. private $l10nFactory;
  83. /**
  84. * @param IConfig $config
  85. * @param ILogger $logger
  86. * @param Defaults $defaults
  87. * @param IURLGenerator $urlGenerator
  88. * @param IL10N $l10n
  89. * @param IEventDispatcher $dispatcher
  90. */
  91. public function __construct(IConfig $config,
  92. ILogger $logger,
  93. Defaults $defaults,
  94. IURLGenerator $urlGenerator,
  95. IL10N $l10n,
  96. IEventDispatcher $dispatcher,
  97. IFactory $l10nFactory) {
  98. $this->config = $config;
  99. $this->logger = $logger;
  100. $this->defaults = $defaults;
  101. $this->urlGenerator = $urlGenerator;
  102. $this->l10n = $l10n;
  103. $this->dispatcher = $dispatcher;
  104. $this->l10nFactory = $l10nFactory;
  105. }
  106. /**
  107. * Creates a new message object that can be passed to send()
  108. *
  109. * @return IMessage
  110. */
  111. public function createMessage(): IMessage {
  112. $plainTextOnly = $this->config->getSystemValue('mail_send_plaintext_only', false);
  113. return new Message(new \Swift_Message(), $plainTextOnly);
  114. }
  115. /**
  116. * @param string|null $data
  117. * @param string|null $filename
  118. * @param string|null $contentType
  119. * @return IAttachment
  120. * @since 13.0.0
  121. */
  122. public function createAttachment($data = null, $filename = null, $contentType = null): IAttachment {
  123. return new Attachment(new \Swift_Attachment($data, $filename, $contentType));
  124. }
  125. /**
  126. * @param string $path
  127. * @param string|null $contentType
  128. * @return IAttachment
  129. * @since 13.0.0
  130. */
  131. public function createAttachmentFromPath(string $path, $contentType = null): IAttachment {
  132. return new Attachment(\Swift_Attachment::fromPath($path, $contentType));
  133. }
  134. /**
  135. * Creates a new email template object
  136. *
  137. * @param string $emailId
  138. * @param array $data
  139. * @return IEMailTemplate
  140. * @since 12.0.0
  141. */
  142. public function createEMailTemplate(string $emailId, array $data = []): IEMailTemplate {
  143. $class = $this->config->getSystemValue('mail_template_class', '');
  144. if ($class !== '' && class_exists($class) && is_a($class, EMailTemplate::class, true)) {
  145. return new $class(
  146. $this->defaults,
  147. $this->urlGenerator,
  148. $this->l10nFactory,
  149. $emailId,
  150. $data
  151. );
  152. }
  153. return new EMailTemplate(
  154. $this->defaults,
  155. $this->urlGenerator,
  156. $this->l10nFactory,
  157. $emailId,
  158. $data
  159. );
  160. }
  161. /**
  162. * Send the specified message. Also sets the from address to the value defined in config.php
  163. * if no-one has been passed.
  164. *
  165. * @param IMessage|Message $message Message to send
  166. * @return string[] Array with failed recipients. Be aware that this depends on the used mail backend and
  167. * therefore should be considered
  168. * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address
  169. * has been supplied.)
  170. */
  171. public function send(IMessage $message): array {
  172. $debugMode = $this->config->getSystemValue('mail_smtpdebug', false);
  173. if (empty($message->getFrom())) {
  174. $message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]);
  175. }
  176. $failedRecipients = [];
  177. $mailer = $this->getInstance();
  178. // Enable logger if debug mode is enabled
  179. if ($debugMode) {
  180. $mailLogger = new \Swift_Plugins_Loggers_ArrayLogger();
  181. $mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger));
  182. }
  183. $this->dispatcher->dispatchTyped(new BeforeMessageSent($message));
  184. $mailer->send($message->getSwiftMessage(), $failedRecipients);
  185. // Debugging logging
  186. $logMessage = sprintf('Sent mail to "%s" with subject "%s"', print_r($message->getTo(), true), $message->getSubject());
  187. $this->logger->debug($logMessage, ['app' => 'core']);
  188. if ($debugMode && isset($mailLogger)) {
  189. $this->logger->debug($mailLogger->dump(), ['app' => 'core']);
  190. }
  191. return $failedRecipients;
  192. }
  193. /**
  194. * Checks if an e-mail address is valid
  195. *
  196. * @param string $email Email address to be validated
  197. * @return bool True if the mail address is valid, false otherwise
  198. */
  199. public function validateMailAddress(string $email): bool {
  200. $validator = new EmailValidator();
  201. $validation = new RFCValidation();
  202. return $validator->isValid($this->convertEmail($email), $validation);
  203. }
  204. /**
  205. * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains
  206. *
  207. * FIXME: Remove this once SwiftMailer supports IDN
  208. *
  209. * @param string $email
  210. * @return string Converted mail address if `idn_to_ascii` exists
  211. */
  212. protected function convertEmail(string $email): string {
  213. if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46') || strpos($email, '@') === false) {
  214. return $email;
  215. }
  216. list($name, $domain) = explode('@', $email, 2);
  217. $domain = idn_to_ascii($domain, 0,INTL_IDNA_VARIANT_UTS46);
  218. return $name.'@'.$domain;
  219. }
  220. protected function getInstance(): \Swift_Mailer {
  221. if (!is_null($this->instance)) {
  222. return $this->instance;
  223. }
  224. $transport = null;
  225. switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) {
  226. case 'sendmail':
  227. $transport = $this->getSendMailInstance();
  228. break;
  229. case 'smtp':
  230. default:
  231. $transport = $this->getSmtpInstance();
  232. break;
  233. }
  234. return new \Swift_Mailer($transport);
  235. }
  236. /**
  237. * Returns the SMTP transport
  238. *
  239. * @return \Swift_SmtpTransport
  240. */
  241. protected function getSmtpInstance(): \Swift_SmtpTransport {
  242. $transport = new \Swift_SmtpTransport();
  243. $transport->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10));
  244. $transport->setHost($this->config->getSystemValue('mail_smtphost', '127.0.0.1'));
  245. $transport->setPort($this->config->getSystemValue('mail_smtpport', 25));
  246. if ($this->config->getSystemValue('mail_smtpauth', false)) {
  247. $transport->setUsername($this->config->getSystemValue('mail_smtpname', ''));
  248. $transport->setPassword($this->config->getSystemValue('mail_smtppassword', ''));
  249. $transport->setAuthMode($this->config->getSystemValue('mail_smtpauthtype', 'LOGIN'));
  250. }
  251. $smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', '');
  252. if (!empty($smtpSecurity)) {
  253. $transport->setEncryption($smtpSecurity);
  254. }
  255. $streamingOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []);
  256. if (is_array($streamingOptions) && !empty($streamingOptions)) {
  257. $transport->setStreamOptions($streamingOptions);
  258. }
  259. return $transport;
  260. }
  261. /**
  262. * Returns the sendmail transport
  263. *
  264. * @return \Swift_SendmailTransport
  265. */
  266. protected function getSendMailInstance(): \Swift_SendmailTransport {
  267. switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) {
  268. case 'qmail':
  269. $binaryPath = '/var/qmail/bin/sendmail';
  270. break;
  271. default:
  272. $sendmail = \OC_Helper::findBinaryPath('sendmail');
  273. if ($sendmail === null) {
  274. $sendmail = '/usr/sbin/sendmail';
  275. }
  276. $binaryPath = $sendmail;
  277. break;
  278. }
  279. switch ($this->config->getSystemValue('mail_sendmailmode', 'smtp')) {
  280. case 'pipe':
  281. $binaryParam = ' -t';
  282. break;
  283. default:
  284. $binaryParam = ' -bs';
  285. break;
  286. }
  287. return new \Swift_SendmailTransport($binaryPath . $binaryParam);
  288. }
  289. }