IBR-DTN  1.0.0
EMailSmtpService.cpp
Go to the documentation of this file.
1 /*
2  * EMailSmtpService.cpp
3  *
4  * Copyright (C) 2013 IBR, TU Braunschweig
5  *
6  * Written-by: Björn Gernert <mail@bjoern-gernert.de>
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  */
21 
22 #include "net/EMailSmtpService.h"
23 #include "net/EMailImapService.h"
24 
25 #include "Configuration.h"
26 #include "core/BundleCore.h"
27 #include "core/BundleEvent.h"
30 #include "storage/BundleStorage.h"
31 
32 #include <ibrcommon/Logger.h>
33 #include <vmime/platforms/posix/posixHandler.hpp>
34 
35 namespace dtn
36 {
37  namespace net
38  {
39  bool EMailSmtpService::_run(false);
40 
42  {
43  static EMailSmtpService instance;
44  return instance;
45  }
46 
47  EMailSmtpService::EMailSmtpService()
48  : _config(daemon::Configuration::getInstance().getEMail()),
49  _storage(dtn::core::BundleCore::getInstance().getStorage()),
50  _certificateVerifier(vmime::create<vmime::security::cert::defaultCertificateVerifier>())
51  {
52  // Load certificates
53  loadCerificates();
54 
55  try {
56  // Initialize VMime engine
57  vmime::platform::setHandler<vmime::platforms::posix::posixHandler>();
58 
59  // Set SMTP server for outgoing mails (use SSL or not)
60  std::string url;
61  if(_config.smtpUseSSL())
62  {
63  url = "smtps://" + _config.getSmtpServer();
64  }else{
65  url = "smtp://" + _config.getSmtpServer();
66  }
67  vmime::ref<vmime::net::session> session =
68  vmime::create<vmime::net::session>();
69 
70  // Create an instance of the transport service
71  _transport = session->getTransport(vmime::utility::url(url));
72 
73  // Set username, password and port
74  _transport->setProperty("options.need-authentication",
75  _config.smtpAuthenticationNeeded());
76  _transport->setProperty("auth.username", _config.getSmtpUsername());
77  _transport->setProperty("auth.password", _config.getSmtpPassword());
78  _transport->setProperty("server.port", _config.getSmtpPort());
79 
80  // Enable TLS
81  if(_config.smtpUseTLS())
82  {
83  _transport->setProperty("connection.tls", true);
84  _transport->setProperty("connection.tls.required", true);
85  }
86 
87  // Handle timeouts
88  _transport->setTimeoutHandlerFactory(vmime::create<TimeoutHandlerFactory>());
89 
90  // Add certificate verifier
91  if(_config.imapUseTLS() || _config.imapUseSSL())
92  {
93  _transport->setCertificateVerifier(_certificateVerifier);
94  }
95  } catch (vmime::exception &e) {
96  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: Unable to create SMTP service: " << e.what() << IBRCOMMON_LOGGER_ENDL;
97  }
98 
99  // Lock mutex
100  _threadMutex.enter();
101 
102  // Start the thread
103  _run = true;
104  this->start();
105  }
106 
107  EMailSmtpService::~EMailSmtpService()
108  {
109  this->stop();
110  this->join();
111 
112  // Delete remaining tasks
113  _queue.reset();
114  while(!_queue.empty()) {
115  Task *t = _queue.front();
116  _queue.pop();
117  delete t;
118  t = NULL;
119  }
120  _queue.abort();
121  }
122 
123  void EMailSmtpService::run() throw ()
124  {
125  while(true)
126  {
127  _threadMutex.enter();
128 
129  if(!_run)
130  break;
131 
132  try {
133  // Get Task
134  Task *t = _queue.poll(_config.getSmtpKeepAliveTimeout());
135 
136  //Check if bundle is still in the storage
137  if ( ! _storage.contains(t->getJob().getBundle()) )
138  {
139  // Destroy Task
140  delete t;
141  t = NULL;
142  continue;
143  }
144 
145  try {
146  // Submit Task
147  submit(t);
149  } catch(std::exception &e) {
150  if(!_run)
151  break;
152  if(dynamic_cast<vmime::exceptions::authentication_error*>(&e) != NULL)
153  {
155  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: SMTP authentication error (username/password correct?)" << IBRCOMMON_LOGGER_ENDL;
156  }
157  else if(dynamic_cast<vmime::exceptions::connection_error*>(&e) != NULL)
158  {
160  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: Unable to connect to the SMTP server" << IBRCOMMON_LOGGER_ENDL;
161  }
162  else
163  {
165  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: SMTP error: " << e.what() << IBRCOMMON_LOGGER_ENDL;
166  }
167  // Delete task
168  delete t;
169  t = NULL;
170 
171  _threadMutex.leave();
172  continue;
173  }
174  } catch(ibrcommon::QueueUnblockedException&) {
175  // Disconnect from server
176  disconnect();
177  }
178  }
179  }
180 
182  {
183  _run = false;
184  disconnect();
185  _queue.abort();
186  _threadMutex.leave();
187  }
188 
189  void EMailSmtpService::connect()
190  {
191  if(!isConnected())
192  _transport->connect();
193  }
194 
195  bool EMailSmtpService::isConnected()
196  {
197  return _transport->isConnected();
198  }
199 
200  void EMailSmtpService::disconnect()
201  {
202  if(isConnected())
203  _transport->disconnect();
204  }
205 
206  void EMailSmtpService::loadCerificates()
207  {
208  std::vector<std::string> certPath;
209  // Load CA certificates
210  std::vector<vmime::ref <vmime::security::cert::X509Certificate> > ca;
211  certPath = _config.getTlsCACerts();
212  if(!certPath.empty()) {
213  for(std::vector<std::string>::iterator it = certPath.begin() ; it != certPath.end(); ++it)
214  {
215  try {
216  ca.push_back(loadCertificateFromFile((*it)));
217  }catch(InvalidCertificate &e) {
218  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: Certificate load error: " << e.what() << IBRCOMMON_LOGGER_ENDL;
219  }
220  }
221  _certificateVerifier->setX509RootCAs(ca);
222  }
223 
224  // Load user certificates
225  std::vector<vmime::ref <vmime::security::cert::X509Certificate> > user;
226  certPath = _config.getTlsUserCerts();
227  if(!certPath.empty()) {
228  for(std::vector<std::string>::iterator it = certPath.begin() ; it != certPath.end(); ++it)
229  {
230  try {
231  user.push_back(loadCertificateFromFile((*it)));
232  }catch(InvalidCertificate &e) {
233  IBRCOMMON_LOGGER(error) << "EMail Convergence Layer: Certificate load error: " << e.what() << IBRCOMMON_LOGGER_ENDL;
234  }
235  }
236  _certificateVerifier->setX509TrustedCerts(user);
237  }
238  }
239 
240  vmime::ref<vmime::security::cert::X509Certificate> EMailSmtpService::loadCertificateFromFile(const std::string &path)
241  {
242  std::ifstream certFile;
243  certFile.open(path.c_str(), std::ios::in | std::ios::binary);
244  if(!certFile)
245  {
246  throw(InvalidCertificate("Unable to find certificate at \"" + path + "\""));
247  return NULL;
248  }
249 
250  vmime::utility::inputStreamAdapter is(certFile);
251  vmime::ref<vmime::security::cert::X509Certificate> cert;
252  cert = vmime::security::cert::X509Certificate::import(is);
253  if(cert != NULL)
254  {
255  return cert;
256  }else{
257  throw(InvalidCertificate("The certificate at \"" + path + "\" does not seem to be PEM or DER encoded"));
258  return NULL;
259  }
260  }
261 
263  {
264  _queue.push(t);
265  }
266 
268  {
269  _queue.push(t);
270  _threadMutex.leave();
271  }
272 
274  {
275  _threadMutex.leave();
276  }
277 
278  void EMailSmtpService::submit(Task *t)
279  {
280  // Check connection
281  if(!isConnected())
282  connect();
283 
284  // create a filter context
285  dtn::core::FilterContext context;
286  context.setPeer(t->getNode().getEID());
288 
289  dtn::data::Bundle bundle = _storage.get(t->getJob().getBundle());
291 
292  // push bundle through the filter routines
293  context.setBundle(bundle);
295 
296  if (ret != BundleFilter::ACCEPT)
297  {
298  IBRCOMMON_LOGGER_DEBUG(1) << "BPTables: OUTPUT table prohibits sending" << IBRCOMMON_LOGGER_ENDL;
299  BundleTransfer job = t->getJob();
301  return;
302  }
303 
304  // Create new email
305  vmime::ref<vmime::message> msg = vmime::create <vmime::message>();
306  vmime::ref<vmime::header> header = msg->getHeader();
307  vmime::ref<vmime::body> body = msg->getBody();
308 
309  // Create header
310  vmime::headerFieldFactory* hfFactory =
311  vmime::headerFieldFactory::getInstance();
312 
313  // From
314  header->appendField(hfFactory->create(vmime::fields::FROM,
315  _config.getOwnAddress()));
316  // To
317  header->appendField(hfFactory->create(vmime::fields::TO,
318  t->getRecipient()));
319  // Subject
320  header->appendField(hfFactory->create(vmime::fields::SUBJECT,
321  "Bundle for " + t->getNode().getEID().getString()));
322 
323  // Insert header for primary block
324  header->appendField(hfFactory->create("Bundle-EMailCL-Version", "1"));
325  header->appendField(hfFactory->create("Bundle-Flags",
326  bundle.procflags.toString()));
327  header->appendField(hfFactory->create("Bundle-Destination",
328  bundle.destination.getString()));
329  header->appendField(hfFactory->create("Bundle-Source",
330  bundle.source.getString()));
331  header->appendField(hfFactory->create("Bundle-Report-To",
332  bundle.reportto.getString()));
333  header->appendField(hfFactory->create("Bundle-Custodian",
334  bundle.custodian.getString()));
335  header->appendField(hfFactory->create("Bundle-Creation-Time",
336  bundle.timestamp.toString()));
337  header->appendField(hfFactory->create("Bundle-Sequence-Number",
338  bundle.sequencenumber.toString()));
339  header->appendField(hfFactory->create("Bundle-Lifetime",
340  bundle.lifetime.toString()));
341  if(bundle.get(dtn::data::Bundle::FRAGMENT))
342  {
343  header->appendField(hfFactory->create("Bundle-Fragment-Offset",
344  bundle.fragmentoffset.toString()));
345  header->appendField(hfFactory->create("Bundle-Total-Application-Data-Unit-Length",
346  bundle.appdatalength.toString()));
347  }
348 
349  // Count additional blocks
350  unsigned int additionalBlocks = 0;
351 
352  // Set MIME-Header if we we need attachments
353  if(bundle.size() > 0)
354  header->appendField(hfFactory->create("MIME-Version","1.0"));
355 
356  for (dtn::data::Bundle::const_iterator iter = bundle.begin(); iter != bundle.end(); ++iter) {
357  // Payload Block
358  try {
359  const dtn::data::PayloadBlock &payload =
360  dynamic_cast<const dtn::data::PayloadBlock&>(**iter);
361 
362  header->appendField(hfFactory->create("Bundle-Payload-Flags",
363  toString(getProcFlags(payload))));
364  header->appendField(hfFactory->create("Bundle-Payload-Block-Length",
365  toString(payload.getLength())));
366  header->appendField(hfFactory->create("Bundle-Payload-Data-Name",
367  "payload.data"));
368 
369  // Create payload attachment
370  ibrcommon::BLOB::iostream data = payload.getBLOB().iostream();
371  std::stringstream ss;
372  ss << data->rdbuf();
373 
374  vmime::ref<vmime::utility::inputStream> dataStream =
375  vmime::create<vmime::utility::inputStreamStringAdapter>(ss.str());
376 
377  vmime::ref<vmime::attachment> payloadAttachement =
378  vmime::create<vmime::fileAttachment>(
379  dataStream,
380  vmime::word("payload.data"),
381  vmime::mediaType("application/octet-stream")
382  );
383  vmime::attachmentHelper::addAttachment(msg, payloadAttachement);
384  continue;
385  } catch(const std::bad_cast&) {};
386 
387  // Add extension blocks
388  try {
389  const dtn::data::Block &block = (**iter);
390 
391  // Create extension attachment
392  std::stringstream attachment;
393  size_t length = 0;
394  (**iter).serialize(attachment, length);
395 
396  vmime::ref<vmime::utility::inputStream> dataStream =
397  vmime::create<vmime::utility::inputStreamStringAdapter>(attachment.str());
398 
399  vmime::ref<vmime::attachment> extensionAttachement =
400  vmime::create<vmime::fileAttachment>(
401  dataStream,
402  vmime::word("block-" + toString(additionalBlocks) + ".data"),
403  vmime::mediaType("application/octet-stream")
404  );
405 
406  vmime::attachmentHelper::addAttachment(msg, extensionAttachement);
407 
408  // Get header of attachment
409  vmime::ref<vmime::header> extensionHeader = msg->getBody()->getPartAt(msg->getBody()->getPartCount()-1)->getHeader();
410  std::stringstream ss;
411  ss << (int)block.getType();
412 
413  extensionHeader->appendField(hfFactory->create("Block-Type", ss.str()));
414 
415  extensionHeader->appendField(hfFactory->create("Block-Processing-Flags",
416  toString(getProcFlags(block))));
417 
418  // Get EIDs
420  {
421  const Block::eid_list &eids = block.getEIDList();
422  for(Block::eid_list::const_iterator eidIt = eids.begin(); eidIt != eids.end(); eidIt++)
423  {
424  extensionHeader->appendField(hfFactory->create("Block-EID-Reference",
425  (*eidIt).getString()));
426  }
427  }
428 
429  // Add filename to header
430  header->appendField(hfFactory->create("Bundle-Additional-Block",
431  "block-" + toString(additionalBlocks++) + ".data"));
432  continue;
433  } catch(const std::bad_cast&) {};
434 
435  }
436  _transport->send(msg);
437  dtn::net::TransferCompletedEvent::raise(t->getNode().getEID(), meta);
439  IBRCOMMON_LOGGER(info) << "EMail Convergence Layer: Bundle " << t->getJob().getBundle().toString() << " for node " << t->getNode().getEID().getString() << " submitted via smtp" << IBRCOMMON_LOGGER_ENDL;
440  }
441 
442  unsigned int EMailSmtpService::getProcFlags(const dtn::data::Block &block)
443  {
444  unsigned int out = 0;
445 
460 
461  return out;
462  }
463 
464  std::string EMailSmtpService::toString(int i)
465  {
466  std::stringstream ss;
467  ss << i;
468  return ss.str();
469  }
470 
471  EMailSmtpService::Task::Task(const dtn::core::Node &node, const dtn::net::BundleTransfer &job, std::string recipient)
472  : _node(node), _job(job), _recipient(recipient), _timesChecked(0) {}
473 
475 
477  {
478  return _node;
479  }
480 
482  {
483  return _job;
484  }
485 
487  {
488  return _recipient;
489  }
490 
492  {
493  return (!++_timesChecked > daemon::Configuration::getInstance().getEMail().getReturningMailChecks());
494  }
495 
496  bool EMailSmtpService::TimeoutHandler::isTimeOut()
497  {
498  if(!_run)
499  return true;
500 
501  return (getTime() >= last +
502  daemon::Configuration::getInstance().getEMail().getSmtpConnectionTimeout());
503  }
504 
505  void EMailSmtpService::TimeoutHandler::resetTimeOut()
506  {
507  last = getTime();
508  }
509 
510  bool EMailSmtpService::TimeoutHandler::handleTimeOut()
511  {
512  // true = continue, false = cancel
513  return false;
514  }
515 
516  }
517 }
static Configuration & getInstance(bool reset=false)
Task(const dtn::core::Node &node, const dtn::net::BundleTransfer &job, std::string recipient)
Bitset< FLAGS > procflags
Definition: PrimaryBlock.h:118
bool get(ProcFlags flag) const
Definition: Block.cpp:82
static EMailSmtpService & getInstance()
void setBundle(const dtn::data::Bundle &data)
dtn::data::Timestamp timestamp
Definition: BundleID.h:54
static void raise(const dtn::data::EID &peer, const dtn::data::BundleID &id, const AbortReason reason=REASON_UNDEFINED)
virtual bool contains(const dtn::data::BundleID &id)=0
static void raise(const dtn::data::EID peer, const dtn::data::MetaBundle &bundle)
iterator begin()
Definition: Bundle.cpp:49
virtual std::ostream & serialize(std::ostream &stream, Length &length) const =0
BundleFilter::ACTION filter(BundleFilter::TABLE table, const FilterContext &context, dtn::data::Bundle &bundle) const
Definition: BundleCore.cpp:598
std::list< dtn::data::EID > eid_list
Definition: Block.h:55
std::string getSmtpUsername() const
void setProtocol(const dtn::core::Node::Protocol &protocol)
dtn::data::Number sequencenumber
Definition: BundleID.h:55
bool get(FLAGS flag) const
void setPeer(const dtn::data::EID &endpoint)
std::string toString() const
Definition: SDNV.h:414
block_list::const_iterator const_iterator
Definition: Bundle.h:77
virtual Length getLength() const
virtual const eid_list & getEIDList() const
Definition: Block.cpp:72
std::string getSmtpPassword() const
virtual dtn::data::Bundle get(const dtn::data::BundleID &id)=0
Size size() const
Definition: Bundle.cpp:258
const dtn::net::BundleTransfer getJob()
std::vector< std::string > getTlsCACerts() const
void abort(const TransferAbortedEvent::AbortReason reason)
const dtn::data::MetaBundle & getBundle() const
std::string getString() const
Definition: EID.cpp:374
static void raise(const dtn::data::MetaBundle &bundle, EventBundleAction action, dtn::data::StatusReportBlock::REASON_CODE reason=dtn::data::StatusReportBlock::NO_ADDITIONAL_INFORMATION)
Definition: BundleEvent.cpp:78
static MetaBundle create(const dtn::data::BundleID &id)
Definition: MetaBundle.cpp:34
ibrcommon::BLOB::Reference getBLOB() const
const dtn::data::EID & getEID() const
Definition: Node.cpp:406
const block_t & getType() const
Definition: Block.h:73
iterator end()
Definition: Bundle.cpp:54
dtn::data::Number fragmentoffset
Definition: BundleID.h:57
void storeProcessedTask(EMailSmtpService::Task *task)
dtn::data::EID source
Definition: BundleID.h:53
std::vector< std::string > getTlsUserCerts() const
static EMailImapService & getInstance()
static BundleCore & getInstance()
Definition: BundleCore.cpp:82