Leosac  0.8.0
Open Source Access Control
SMTPModule.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014-2016 Leosac
3 
4  This file is part of Leosac.
5 
6  Leosac is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Affero General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Leosac is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Affero General Public License for more details.
15 
16  You should have received a copy of the GNU Affero General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "SMTPModule.hpp"
21 #include "SMTPAuditSerializer.hpp"
23 #include "SMTPServiceImpl.hpp"
24 #include "core/CoreUtils.hpp"
29 #include "core/auth/Auth.hpp"
30 #include "modules/smtp/SMTPAudit_odb.h"
31 #include "modules/smtp/SMTPConfig_odb.h"
35 #include "tools/MyTime.hpp"
37 #include <boost/asio.hpp>
38 
39 using namespace Leosac;
40 using namespace Leosac::Module;
41 using namespace Leosac::Module::SMTP;
42 
43 SMTPModule::SMTPModule(zmqpp::context &ctx, zmqpp::socket *pipe,
44  const boost::property_tree::ptree &cfg, CoreUtilsPtr utils)
45  : AsioModule(ctx, pipe, cfg, utils)
46 {
47  int ret;
48  long flags = 0;
49 
50  if (config_.get<bool>("module_config.want_ssl", true))
51  flags |= CURL_GLOBAL_SSL;
52  if ((ret = curl_global_init(flags)) != 0)
53  {
54  throw std::runtime_error("Failed to initialize curl: return code: " +
55  std::to_string(ret));
56  }
58 
59  auto audit_serializer_service =
60  utils_->service_registry().get_service<Audit::Serializer::JSONService>();
61  ASSERT_LOG(audit_serializer_service,
62  "Cannot retrieve Audit::Serializer::JSONService.");
63 
64  audit_serializer_service->register_serializer<SMTPAudit>(
65  [](const SMTPAudit &audit, const SecurityContext &sc) -> json {
66  return SMTPAuditSerializer::serialize(audit, sc);
67  });
68 
69  // We're now mostly constructed, time to advertise our services.
71  std::make_unique<SMTPServiceImpl>(*this));
72 }
73 
75 {
76  curl_global_cleanup();
77  auto audit_serializer_service =
78  utils_->service_registry().get_service<Audit::Serializer::JSONService>();
79  ASSERT_LOG(audit_serializer_service,
80  "Cannot retrieve Audit::Serializer::JSONService.");
81  audit_serializer_service->unregister_serializer<SMTPAudit>();
82 
83  // Make sure we properly unregister the service.
85  ;
86 }
87 
89 {
90  if ((use_database_ = config_.get<bool>("module_config.use_database", false)))
91  {
92  try
93  {
95  // Load config from database.
96  int count = 0;
97  odb::transaction t(utils_->database()->begin());
98  odb::result<SMTPConfig> result(utils_->database()->query<SMTPConfig>());
99  for (const auto &cfg : result)
100  {
101  smtp_config_ = std::make_unique<SMTPConfig>(cfg);
102  count++;
103  }
104  t.commit();
105  ASSERT_LOG(count == 0 || count == 1,
106  "We have more than one SMTPConfig entry in the database.");
107  INFO("SMTP module using SQL database for configuration.");
108  }
109  catch (const odb::exception &e)
110  {
111  WARN("SMTP module failed to load database configure. Using default "
112  "config.");
113  }
114  if (!smtp_config_)
115  smtp_config_ = std::make_unique<SMTPConfig>();
116  }
117  else
118  {
119  smtp_config_ = std::make_unique<SMTPConfig>();
120  for (auto &&itr : config_.get_child("module_config.servers"))
121  {
122  SMTPServerInfo server;
123  server.url = itr.second.get<std::string>("url");
124  server.from = itr.second.get<std::string>("from", "leosac@leosac.com");
125  server.verify_host = itr.second.get<bool>("verify_host", true);
126  server.verify_peer = itr.second.get<bool>("verify_peer", true);
127  server.CA_info_file_ = itr.second.get<std::string>("ca_file", "");
128 
129  INFO("SMTP module server: "
130  << Colorize::green(server.url)
131  << ", verify_host: " << Colorize::green(server.verify_host)
132  << ", verify_peer: " << Colorize::green(server.verify_peer)
133  << ", ca_info: " << Colorize::green(server.CA_info_file_) << ")");
134  smtp_config_->server_add(server);
135  }
136  }
137 
138  if (use_database_)
140 }
141 
143 {
144  if (smtp_config_->servers().size() == 0)
145  WARN("Cannot send mail titled " << Colorize::cyan(mail.title)
146  << ". No SMTP server configured.");
147  if (mail.to.size() == 0)
148  {
149  WARN("No recipient for mail titled " << Colorize::cyan(mail.title) << '.');
150  return false;
151  }
152 
153  for (const auto &target : smtp_config_->servers())
154  {
155  if (!target.enabled)
156  continue;
157 
158  auto curl = curl_easy_init();
159  if (curl)
160  {
161  if (!target.CA_info_file_.empty())
162  curl_easy_setopt(curl, CURLOPT_CAINFO, target.CA_info_file_.c_str());
163  if (!target.verify_host)
164  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
165  if (!target.verify_peer)
166  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
167  if (target.username.size())
168  curl_easy_setopt(curl, CURLOPT_USERNAME, target.username.c_str());
169  if (target.password.size())
170  curl_easy_setopt(curl, CURLOPT_PASSWORD, target.password.c_str());
171  if (target.from.size())
172  curl_easy_setopt(curl, CURLOPT_MAIL_FROM, target.from.c_str());
173 
174  ASSERT_LOG(target.url.size(), "No mail server url.");
175  curl_easy_setopt(curl, CURLOPT_URL, target.url.c_str());
176 
177  curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, target.ms_timeout);
178 
179  bool sent = send_mail(curl, mail);
180  curl_easy_cleanup(curl);
181  if (sent)
182  return true;
183  }
184  else
185  {
186  ERROR("Cannot initialize curl_easy.");
187  }
188  }
189  return false;
190 }
191 
193 {
194  const MailInfo &mail;
195  int counter;
196 };
197 
201 static std::string build_mail_str(const MailInfo &mail)
202 {
203  std::stringstream ss;
204 
205  ASSERT_LOG(mail.to.size(), "No recipients for mail.");
206  ss << "Date: " << to_local_rfc2822(std::chrono::system_clock::now()) << "\r\n";
207  ss << "To: " << mail.to.at(0) << "\r\n";
208 
209  ss << "Subject: " << mail.title << "\r\n";
210  ss << "\r\n"; // empty line to divide headers from body, see RFC5322
211  ss << mail.body << "\r\n\r\n";
212 
213  return ss.str();
214 }
215 
221 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
222 {
223  UploadStatus *st = static_cast<UploadStatus *>(userp);
224  ASSERT_LOG(st, "UploadStatus is null.");
225 
226  std::string content = build_mail_str(st->mail);
227  auto wanted = size * nmemb;
228  auto available = content.size() - st->counter;
229  auto to_transfer = std::min(available, wanted);
230 
231  std::memset(ptr, 0, wanted);
232  std::memcpy(ptr, &content[0] + st->counter, to_transfer);
233  st->counter += to_transfer;
234 
235  return to_transfer;
236 }
237 
238 bool SMTPModule::send_mail(CURL *curl, const MailInfo &mail)
239 {
240  ASSERT_LOG(curl, "CURL pointer is null.");
241 
242  struct curl_slist *recipients = NULL;
243  for (const auto &recipient : mail.to)
244  recipients = curl_slist_append(recipients, recipient.c_str());
245  curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
246 
247  UploadStatus status{.mail = mail, .counter = 0};
248 
249  curl_easy_setopt(curl, CURLOPT_READFUNCTION, &read_callback);
250  curl_easy_setopt(curl, CURLOPT_READDATA, &status);
251  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
252  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
253 
254  auto res = curl_easy_perform(curl);
255  curl_slist_free_all(recipients);
256  if (res != CURLE_OK)
257  {
258  WARN("curl_easy_perform() failed: " << curl_easy_strerror(res));
259  return false;
260  }
261  else
262  {
263  INFO("Mail titled " << Colorize::cyan(mail.title) << " has been sent.");
264  return true;
265  }
266 }
267 
269 {
270  using namespace odb;
271  using namespace odb::core;
272  auto db = utils_->database();
273  schema_version v = db->schema_version("module_smtp");
274  schema_version cv(schema_catalog::current_version(*db, "module_smtp"));
275  if (v == 0)
276  {
277  transaction t(db->begin());
278  schema_catalog::create_schema(*db, "module_smtp");
279 
280  SMTPConfig cfg;
281  SMTPServerInfo srv;
282  srv.url = "smtp://mail.leosac.com";
283  srv.username = "leosac-mail";
284  srv.from = "leosac@leosac.com";
285  cfg.server_add(srv);
286  db->persist(cfg);
287 
288  t.commit();
289  }
290  else if (v < cv)
291  {
292  INFO("SMTP Module performing database migration. Going from version "
293  << v << " to version " << cv);
294  transaction t(db->begin());
295  schema_catalog::migrate(*db, cv, "module_smtp");
296  t.commit();
297  }
298 }
299 
301  const json &)
302 {
303  json ret = json::array();
304  for (const auto &server_cfg : smtp_config_->servers())
305  {
306  ret.push_back(SMTPServerInfoJSONSerializer::serialize(server_cfg,
307  req_ctx.security_ctx));
308  }
309  return ret;
310 }
311 
313  const json &req)
314 {
315  SMTPConfigUPtr cfg = std::make_unique<SMTPConfig>();
316  for (const auto &server : req.at("servers"))
317  {
318  SMTPServerInfo srv_info;
320  req_ctx.security_ctx);
321  cfg->server_add(srv_info);
322  }
323 
324  {
325  auto db = utils_->database();
326  odb::transaction t(db->begin());
327  auto smtp_audit = SMTPAudit::create(db, req_ctx.audit);
328 
329  if (smtp_config_->id()) // Maybe we don't have a persisted configuration yet
330  db->erase<SMTPConfig>(smtp_config_->id());
331 
332  db->persist(*cfg);
333  smtp_audit->finalize();
334  t.commit();
335  smtp_config_ = std::move(cfg);
336  }
337 
338  return {};
339 }
340 
342  const json &req)
343 {
344  MailInfo mail;
345 
346  mail.body = req.at("body");
347  mail.title = req.at("subject");
348  for (const auto &recipient : req.at("to"))
349  mail.to.push_back(recipient);
350 
351  return {{"sent", (prepare_curl(mail))}};
352 }
353 
355 {
356  // We are interested in WebSockAPI::Service registration so we can
357  // register our websocket handler against it.
358  if (e.type() == service_event::REGISTERED)
359  {
360  auto &service_registered_event =
361  assert_cast<const service_event::ServiceRegistered &>(e);
362  if (service_registered_event.interface_type() ==
363  boost::typeindex::type_id<WebSockAPI::Service>())
364  {
365  // The service-event handler is invoked in the Websocket thread.
366  // We want the registration to happen from our own thread, otherwise
367  // it will deadlock (because the ws thread event loop is blocked
368  // invoking this handler, that in turn would rely on a callable being
369  // invoked from the ws thread event loop.
370  io_service_.post([this]() {
371  // We don't care about registering WS handler if we don't use the
372  // database.
373  if (!use_database_)
374  return;
376  });
377  }
378  }
379 }
380 
382 {
384  if (ws_srv)
385  {
386  // GetConfig
387 
388  bool ret = ws_srv->register_asio_handler_permission(
389  [this](const WebSockAPI::RequestContext &req_ctx) {
390  return handle_ws_smtp_getconfig(req_ctx,
391  req_ctx.original_msg.content);
392  },
394  io_service_);
395  if (!ret)
396  WARN("Cannot register SMTP WS handler: " << wshandler_getconfig);
397 
398  // SetConfig
399  ret = ws_srv->register_asio_handler_permission(
400  [this](const WebSockAPI::RequestContext &req_ctx) {
401  return handle_ws_smtp_setconfig(req_ctx,
402  req_ctx.original_msg.content);
403  },
405  io_service_);
406  if (!ret)
407  WARN("Cannot register SMTP WS handler: " << wshandler_setconfig);
408 
409  // SendMail
410  ret = ws_srv->register_asio_handler_permission(
411  [this](const WebSockAPI::RequestContext &req_ctx) {
412  return handle_ws_smtp_sendmail(req_ctx,
413  req_ctx.original_msg.content);
414  },
416  if (!ret)
417  WARN("Cannot register SMTP WS handler: " << wshandler_sendmail);
418  }
419  else
420  {
421  INFO("Cannot register Websocket handlers. Service is not (yet) available.");
422  }
423 }
424 
426 {
427  io_service_.post([this, mail]() { prepare_curl(mail); });
428 }
Leosac::MailInfo::to
std::vector< std::string > to
Definition: Mail.hpp:32
Leosac::Module::SMTP::SMTPModule::send_mail
bool send_mail(CURL *curl, const MailInfo &mail)
Definition: SMTPModule.cpp:238
Leosac::ServiceRegistry::unregister_service
bool unregister_service(RegistrationHandle h)
Unregister a service using the RegistrationHandle that was returned from the register_service() call.
Definition: ServiceRegistry.hpp:204
Leosac::Module::SMTP::SMTPModule::setup_database
void setup_database()
Definition: SMTPModule.cpp:268
Leosac::SMTPService
Reference interface for SMTP module.
Definition: Mail.hpp:45
GlobalRegistry.hpp
Leosac::Module::SMTP::SMTPModule::~SMTPModule
~SMTPModule()
Definition: SMTPModule.cpp:74
WARN
@ WARN
Definition: log.hpp:33
Exceptions.hpp
Leosac::Module::SMTP::SMTPModule::handle_ws_smtp_setconfig
json handle_ws_smtp_setconfig(const WebSockAPI::RequestContext &, const json &)
Process the websocket request "smtp.setconfig".
Definition: SMTPModule.cpp:312
Leosac::Module::SMTP::SMTPModule::on_service_event
virtual void on_service_event(const service_event::Event &) override
Function invoked when a service event is triggered.
Definition: SMTPModule.cpp:354
Leosac::Module::SMTP::SMTPModule::handle_ws_smtp_sendmail
json handle_ws_smtp_sendmail(const WebSockAPI::RequestContext &, const json &)
Process thesocket request "smtp.sendmail".
Definition: SMTPModule.cpp:341
Leosac::service_event::Event::type
EventType type() const
Definition: ServiceRegistry.hpp:45
Leosac::Module::SMTP::SMTPServerInfo::url
std::string url
Definition: SMTPConfig.hpp:46
ERROR
@ ERROR
Definition: log.hpp:32
Auth.hpp
Leosac::get_service_registry
ServiceRegistry & get_service_registry()
A function to retrieve the ServiceRegistry from pretty much anywhere.
Definition: GetServiceRegistry.cpp:25
Leosac::Module::SMTP
Definition: SMTPAudit.cpp:32
Leosac::Module::SMTP::SMTPServerInfo::verify_peer
bool verify_peer
Definition: SMTPConfig.hpp:58
ASSERT_LOG
#define ASSERT_LOG(cond, msg)
Definition: log.hpp:190
Leosac::Module::SMTP::SMTPServerInfo::CA_info_file_
std::string CA_info_file_
Definition: SMTPConfig.hpp:59
Leosac::Module::SMTP::SMTPModule::handle_ws_smtp_getconfig
json handle_ws_smtp_getconfig(const WebSockAPI::RequestContext &, const json &)
Process the websocket request "smtp.getconfig".
Definition: SMTPModule.cpp:300
INFO
@ INFO
Definition: log.hpp:34
odb
Provide ODB magic to be able to store an Leosac::Audit::EventType (FlagSet) object.
Definition: AuditEventMaskODB.hpp:31
Leosac::Module::SMTP::SMTPConfig
Wrapper around the SMTP module configuration.
Definition: SMTPConfig.hpp:73
Leosac::Module::SMTP::SMTPServerInfo::verify_host
bool verify_host
Definition: SMTPConfig.hpp:57
Leosac::Module
All modules that provides features to Leosac shall be in this namespace.
Leosac::SecurityContext::Action::SMTP_GETCONFIG
@ SMTP_GETCONFIG
Retrieve SMTP configuration.
Leosac::Module::SMTP::SMTPAuditSerializer::serialize
static json serialize(const SMTPAudit &in, const SecurityContext &sc)
Definition: SMTPAuditSerializer.cpp:30
Leosac::Module::SMTP::SMTPModule::register_ws_handlers
void register_ws_handlers()
Attempt to register websocket handlers against the websocket service, if available.
Definition: SMTPModule.cpp:381
Leosac::Module::SMTP::SMTPServerInfo::username
std::string username
Definition: SMTPConfig.hpp:48
Leosac::MailInfo
Definition: Mail.hpp:30
Leosac::Module::SMTP::SMTPAudit
Keeps track of SMTP event.
Definition: SMTPAudit.hpp:39
Leosac::Module::WebSockAPI::ClientMessage::content
json content
Definition: Messages.hpp:58
Leosac
This is the header file for a generated source file, GitSHA1.cpp.
Definition: APIStatusCode.hpp:22
Leosac::Module::WebSockAPI::RequestContext::original_msg
const ClientMessage & original_msg
The original, complete, client message object.
Definition: RequestContext.hpp:45
GetServiceRegistry.hpp
Leosac::Module::WebSockAPI::Service
A service object provided by the Websocket module.
Definition: Service.hpp:45
SMTPModule.hpp
Leosac::to_local_rfc2822
std::string to_local_rfc2822(const std::chrono::system_clock::time_point &tp)
Convert a timepoint to an RFC2822 (SMTP) date.
Definition: MyTime.cpp:45
UploadStatus
Definition: SMTPModule.cpp:192
ExceptionConverter.hpp
Leosac::Module::BaseModule::config_
boost::property_tree::ptree config_
The configuration tree passed to the start_module function.
Definition: BaseModule.hpp:193
Leosac::Module::AsioModule
This is a base class for boost::asio 'aware' module.
Definition: AsioModule.hpp:40
SMTPServerInfoSerializer.hpp
JSONService.hpp
Leosac::Module::SMTP::SMTPConfigUPtr
std::unique_ptr< SMTPConfig > SMTPConfigUPtr
Definition: SMTPFwd.hpp:34
Leosac::Module::SMTP::SMTPModule::async_send_mail
void async_send_mail(const MailInfo &mail)
Asynchronously and thread-safely send an email.
Definition: SMTPModule.cpp:425
Leosac::Module::SMTP::SMTPModule::process_config
void process_config()
Process the configuration file.
Definition: SMTPModule.cpp:88
Leosac::Module::BaseModule::utils_
CoreUtilsPtr utils_
Pointer to the core utils, which gives access to scheduler and others.
Definition: BaseModule.hpp:198
Leosac::SecurityContext::Action::SMTP_SETCONFIG
@ SMTP_SETCONFIG
Edit the SMTP configuration.
Leosac::Module::SMTP::SMTPServerInfo
Definition: SMTPConfig.hpp:36
Leosac::MailInfo::body
std::string body
Definition: Mail.hpp:34
Leosac::Module::SMTP::json
nlohmann::json json
Definition: SMTPModule.hpp:41
Leosac::Colorize::green
std::string green(const T &in)
Definition: Colorize.hpp:82
SMTPAuditSerializer.hpp
UploadStatus::counter
int counter
Definition: SMTPModule.cpp:195
Leosac::Module::SMTP::SMTPConfig::server_add
void server_add(SMTPServerInfo)
Definition: SMTPConfig.cpp:30
Leosac::Module::WebSockAPI::RequestContext::security_ctx
SecurityContext & security_ctx
Definition: RequestContext.hpp:47
Service.hpp
Leosac::Module::SMTP::SMTPServerInfoJSONSerializer::unserialize
static void unserialize(SMTPServerInfo &out, const json &in, const SecurityContext &sc)
Definition: SMTPServerInfoSerializer.cpp:45
Leosac::Module::SMTP::SMTPModule::prepare_curl
bool prepare_curl(const MailInfo &mail)
Definition: SMTPModule.cpp:142
Leosac::Module::SMTP::SMTPModule::SMTPModule
SMTPModule(zmqpp::context &ctx, zmqpp::socket *pipe, const boost::property_tree::ptree &cfg, CoreUtilsPtr utils)
Definition: SMTPModule.cpp:43
Leosac::Module::SMTP::SMTPAudit::create
static SMTPAuditPtr create(const DBPtr &database, Audit::IAuditEntryPtr parent)
Factory function, similar to those found in Audit::Factory.
Definition: SMTPAudit.cpp:44
Leosac::ServiceRegistry::register_service
RegistrationHandle register_service(std::unique_ptr< ServiceInterface > srv)
Register a service by passing an unique_ptr to it.
Definition: ServiceRegistry.hpp:146
Leosac::Module::WebSockAPI::RequestContext::audit
Audit::IAuditEntryPtr audit
The initial audit trail for the request.
Definition: RequestContext.hpp:55
Leosac::ServiceRegistry::get_service
std::shared_ptr< ServiceInterface > get_service() const
Retrieve the service instance implementing the ServiceInterface, or nullptr if no such service was re...
Definition: ServiceRegistry.hpp:290
Leosac::Module::SMTP::SMTPModule::wshandler_setconfig
static constexpr const char * wshandler_setconfig
Definition: SMTPModule.hpp:93
Leosac::Colorize::cyan
std::string cyan(const T &in)
Definition: Colorize.hpp:94
CoreUtils.hpp
IWSAPICall.hpp
Leosac::service_event::Event
Definition: ServiceRegistry.hpp:37
Leosac::Module::WebSockAPI::RequestContext
Holds valuable pointer to provide context to a request.
Definition: RequestContext.hpp:36
Leosac::Module::SMTP::SMTPModule::use_database_
bool use_database_
Definition: SMTPModule.hpp:80
Leosac::service_event::REGISTERED
@ REGISTERED
Definition: ServiceRegistry.hpp:34
Leosac::Module::SMTP::SMTPModule::wshandler_sendmail
static constexpr const char * wshandler_sendmail
Definition: SMTPModule.hpp:94
Leosac::Module::SMTP::SMTPServerInfo::from
std::string from
Definition: SMTPConfig.hpp:47
Leosac::CoreUtilsPtr
std::shared_ptr< CoreUtils > CoreUtilsPtr
Definition: LeosacFwd.hpp:35
Leosac::MailInfo::title
std::string title
Definition: Mail.hpp:33
UserSecurityContext.hpp
SMTPServiceImpl.hpp
Leosac::SecurityContext
A SecurityContext is used to query permission while doing an operation.
Definition: SecurityContext.hpp:40
UploadStatus::mail
const MailInfo & mail
Definition: SMTPModule.cpp:194
Leosac::Module::SMTP::SMTPModule::smtp_config_
SMTPConfigUPtr smtp_config_
Configuration: either load from XML or database.
Definition: SMTPModule.hpp:85
Leosac::Module::WebSockAPI::Service::register_asio_handler_permission
bool register_asio_handler_permission(HandlerT &&handler, const std::string &type, ActionActionParam permission, boost::asio::io_service &io)
Definition: Service.hpp:112
Leosac::Module::SMTP::SMTPModule::wshandler_getconfig
static constexpr const char * wshandler_getconfig
Definition: SMTPModule.hpp:92
Leosac::Module::AsioModule::io_service_
boost::asio::io_service io_service_
Definition: AsioModule.hpp:64
Leosac::Audit::Serializer::JSONService
This service manages runtime registered serializer that target AuditEntry object.
Definition: JSONService.hpp:42
Leosac::Module::SMTP::SMTPServerInfoJSONSerializer::serialize
static json serialize(const SMTPServerInfo &in, const SecurityContext &sc)
Definition: SMTPServerInfoSerializer.cpp:28
MyTime.hpp
Leosac::SecurityContext::Action::SMTP_SENDMAIL
@ SMTP_SENDMAIL