2 This file is part of GNUnet
3 Copyright (C) 2003-2013 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file transport/plugin_transport_smtp.c
23 * @brief Implementation of the SMTP transport service
24 * @author Christian Grothoff
25 * @author Renaldo Ferreira
29 #include "gnunet_util.h"
30 #include "gnunet_constants.h"
31 #include "gnunet_protocols.h"
32 #include "gnunet_transport.h"
33 #include "gnunet_stats_service.h"
39 * The default maximum size of each outbound SMTP message.
41 #define SMTP_MESSAGE_SIZE 65528
43 #define DEBUG_SMTP GNUNET_EXTRA_LOGGING
45 #define FILTER_STRING_SIZE 64
47 /* how long can a line in base64 encoded
48 mime text be? (in characters, excluding "\n") */
49 #define MAX_CHAR_PER_LINE 76
54 * Host-Address in a SMTP network.
59 * Filter line that every sender must include in the E-mails such
60 * that the receiver can effectively filter out the GNUnet traffic
63 char filter[FILTER_STRING_SIZE];
66 * Claimed E-mail address of the sender.
67 * Format is "foo@bar.com" with null termination, padded to be
68 * of a multiple of 8 bytes long.
70 char senderAddress[0];
73 GNUNET_NETWORK_STRUCT_BEGIN
76 * Encapsulation of a GNUnet message in the SMTP mail body (before
81 GNUNET_MessageHeader header;
84 * What is the identity of the sender (GNUNET_hash of public key)
86 GNUNET_PeerIdentity sender;
88 GNUNET_NETWORK_STRUCT_END
90 /* *********** globals ************* */
93 * apis (our advertised API and the core api )
95 static GNUNET_CoreAPIForTransport *core_api;
97 static struct GNUNET_GE_Context *ectx;
100 * Thread that listens for inbound messages
102 static struct GNUNET_ThreadHandle *dispatchThread;
105 * Flag to indicate that server has been shut down.
107 static int smtp_shutdown = GNUNET_YES;
110 * Set to the SMTP server hostname (and port) for outgoing messages.
112 static char *smtp_server_name;
114 static char *pipename;
117 * Lock for uses of libesmtp (not thread-safe).
119 static struct GNUNET_Mutex *lock;
122 * Old handler for SIGPIPE (kept to be able to restore).
124 static struct sigaction old_handler;
128 static GNUNET_TransportAPI smtpAPI;
130 static GNUNET_Stats_ServiceAPI *stats;
132 static int stat_bytesReceived;
134 static int stat_bytesSent;
136 static int stat_bytesDropped;
139 * How many e-mails are we allowed to send per hour?
141 static unsigned long long rate_limit;
143 static GNUNET_CronTime last_transmission;
146 /* ********************* the real stuff ******************* */
148 #define strAUTOncmp(a, b) strncmp (a, b, strlen (b))
151 * Listen to the pipe, decode messages and send to core.
154 listenAndDistribute (void *unused)
157 unsigned int linesize;
163 GNUNET_TransportPacket *coreMP;
167 linesize = ((GNUNET_MAX_BUFFER_SIZE * 4 / 3) + 8) * (MAX_CHAR_PER_LINE + 2)
168 / MAX_CHAR_PER_LINE; /* maximum size of a line supported */
169 line = GNUNET_malloc (linesize + 2); /* 2 bytes for off-by-one errors, just to be safe... */
171 #define READLINE(l, limit) \
172 do { retl = fgets (l, (limit), fdes); \
173 if ((retl == NULL) || (smtp_shutdown == GNUNET_YES)) { \
176 if (core_api->load_monitor != NULL) \
177 GNUNET_network_monitor_notify_transmission (core_api->load_monitor, \
178 GNUNET_ND_DOWNLOAD, \
183 while (smtp_shutdown == GNUNET_NO)
185 fd = OPEN (pipename, O_RDONLY | O_ASYNC);
188 if (smtp_shutdown == GNUNET_NO)
189 GNUNET_thread_sleep (5 * GNUNET_CRON_SECONDS);
192 fdes = fdopen (fd, "r");
193 while (smtp_shutdown == GNUNET_NO)
195 /* skip until end of header */
198 READLINE (line, linesize);
200 while ((line[0] != '\r') && (line[0] != '\n')); /* expect newline */
201 READLINE (line, linesize); /* read base64 encoded message; decode, process */
205 pos = strlen (line) - 1; /* ignore new line */
206 READLINE (&line[pos], linesize - pos); /* read base64 encoded message; decode, process */
207 if ((line[pos] == '\r') || (line[pos] == '\n'))
208 break; /* empty line => end of message! */
210 size = GNUNET_STRINGS_base64_decode (line, pos, &out);
211 if (size < sizeof(SMTPMessage))
213 GNUNET_GE_BREAK (ectx, 0);
218 mp = (SMTPMessage *) &out[size - sizeof(SMTPMessage)];
219 if (ntohs (mp->header.size) != size)
222 GNUNET_GE_WARNING | GNUNET_GE_BULK | GNUNET_GE_USER,
223 _ ("Received malformed message via %s. Ignored.\n"),
227 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
228 "Size returned by base64=%d, in the msg=%d.\n", size,
235 stats->change (stat_bytesReceived, size);
236 coreMP = GNUNET_new (GNUNET_TransportPacket);
238 coreMP->size = size - sizeof(SMTPMessage);
239 coreMP->tsession = NULL;
240 coreMP->sender = mp->sender;
242 GNUNET_GE_LOG (ectx, GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
243 "SMTP message passed to the core.\n");
246 core_api->receive (coreMP);
250 GNUNET_GE_LOG (ectx, GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
251 "SMTP message processed.\n");
260 /* *************** API implementation *************** */
263 * Verify that a hello-Message is correct (a node is reachable at that
264 * address). Since the reply will be asynchronous, a method must be
267 * @param hello the hello message to verify
268 * (the signature/crc have been verified before)
269 * @return GNUNET_OK on success, GNUNET_SYSERR on error
272 api_verify_hello (const GNUNET_MessageHello *hello)
274 const EmailAddress *maddr;
276 maddr = (const EmailAddress *) &hello[1];
277 if ((ntohs (hello->header.size) !=
278 sizeof(GNUNET_MessageHello) + ntohs (hello->senderAddressSize)) ||
279 (maddr->senderAddress
280 [ntohs (hello->senderAddressSize) - 1 - FILTER_STRING_SIZE] != '\0'))
282 GNUNET_GE_BREAK (ectx, 0);
283 return GNUNET_SYSERR; /* obviously invalid */
285 if (NULL == strstr (maddr->filter, ": "))
286 return GNUNET_SYSERR;
291 * Create a hello-Message for the current node. The hello is created
292 * without signature and without a timestamp. The GNUnet core will
293 * GNUNET_RSA_sign the message and add an expiration time.
295 * @return hello on success, NULL on error
297 static GNUNET_MessageHello *
300 GNUNET_MessageHello *msg;
305 GNUNET_GC_get_configuration_value_string (core_api->cfg, "SMTP", "FILTER",
306 "X-mailer: GNUnet", &filter);
307 if (NULL == strstr (filter, ": "))
309 GNUNET_GE_LOG (ectx, GNUNET_GE_WARNING | GNUNET_GE_BULK | GNUNET_GE_USER,
310 _ ("SMTP filter string to invalid, lacks ': '\n"));
311 GNUNET_free (filter);
315 if (strlen (filter) > FILTER_STRING_SIZE)
317 filter[FILTER_STRING_SIZE] = '\0';
318 GNUNET_GE_LOG (ectx, GNUNET_GE_WARNING | GNUNET_GE_BULK | GNUNET_GE_USER,
319 _ ("SMTP filter string to long, capped to `%s'\n"), filter);
321 i = (strlen (email) + 8) & (~7); /* make multiple of 8 */
323 GNUNET_malloc (sizeof(GNUNET_MessageHello) + sizeof(EmailAddress) + i);
324 memset (msg, 0, sizeof(GNUNET_MessageHello) + sizeof(EmailAddress) + i);
325 haddr = (EmailAddress *) &msg[1];
326 memset (&haddr->filter[0], 0, FILTER_STRING_SIZE);
327 strcpy (&haddr->filter[0], filter);
328 GNUNET_memcpy (&haddr->senderAddress[0], email, strlen (email) + 1);
329 msg->senderAddressSize = htons (strlen (email) + 1 + sizeof(EmailAddress));
330 msg->protocol = htons (GNUNET_TRANSPORT_PROTOCOL_NUMBER_SMTP);
331 msg->MTU = htonl (smtpAPI.mtu);
332 msg->header.size = htons (GNUNET_sizeof_hello (msg));
333 if (api_verify_hello (msg) == GNUNET_SYSERR)
334 GNUNET_GE_ASSERT (ectx, 0);
335 GNUNET_free (filter);
339 struct GetMessageClosure
347 get_message (void **buf, int *len, void *cls)
349 struct GetMessageClosure *gmc = cls;
357 if (gmc->pos == gmc->esize)
358 return NULL; /* done */
360 gmc->pos = gmc->esize;
365 * Send a message to the specified remote node.
367 * @param tsession the GNUNET_MessageHello identifying the remote node
368 * @param msg what to send
369 * @param size the size of the message
370 * @param important is this message important enough to override typical limits?
371 * @return GNUNET_SYSERR on error, GNUNET_OK on success
374 api_send (GNUNET_TSession *tsession, const void *msg, const unsigned int size,
377 const GNUNET_MessageHello *hello;
378 const EmailAddress *haddr;
383 struct GetMessageClosure gm_cls;
384 smtp_session_t session;
385 smtp_message_t message;
386 smtp_recipient_t recipient;
392 if (smtp_shutdown == GNUNET_YES)
393 return GNUNET_SYSERR;
394 if ((size == 0) || (size > smtpAPI.mtu))
396 GNUNET_GE_BREAK (ectx, 0);
397 return GNUNET_SYSERR;
399 now = GNUNET_get_time ();
400 if ((important != GNUNET_YES) &&
401 ( ((now - last_transmission) * rate_limit) < GNUNET_CRON_HOURS) )
402 return GNUNET_NO; /* rate too high */
403 last_transmission = now;
405 hello = (const GNUNET_MessageHello *) tsession->internal;
407 return GNUNET_SYSERR;
408 GNUNET_mutex_lock (lock);
409 session = smtp_create_session ();
413 GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER
414 | GNUNET_GE_IMMEDIATE, _ ("SMTP: `%s' failed: %s.\n"),
415 "smtp_create_session", smtp_strerror (smtp_errno (), ebuf,
417 GNUNET_mutex_unlock (lock);
418 return GNUNET_SYSERR;
420 if (0 == smtp_set_server (session, smtp_server_name))
423 GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_USER
424 | GNUNET_GE_IMMEDIATE, _ ("SMTP: `%s' failed: %s.\n"),
425 "smtp_set_server", smtp_strerror (smtp_errno (), ebuf,
427 smtp_destroy_session (session);
428 GNUNET_mutex_unlock (lock);
429 return GNUNET_SYSERR;
431 haddr = (const EmailAddress *) &hello[1];
432 message = smtp_add_message (session);
436 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
437 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
438 "smtp_add_message", smtp_strerror (smtp_errno (), ebuf,
440 smtp_destroy_session (session);
441 GNUNET_mutex_unlock (lock);
442 return GNUNET_SYSERR;
444 smtp_set_header (message, "To", NULL, haddr->senderAddress);
445 smtp_set_header (message, "From", NULL, email);
447 filter = GNUNET_strdup (haddr->filter);
448 fvalue = strstr (filter, ": ");
449 GNUNET_GE_ASSERT (NULL, NULL != fvalue);
452 if (0 == smtp_set_header (message, filter, fvalue))
455 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
456 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
457 "smtp_set_header", smtp_strerror (smtp_errno (), ebuf,
459 smtp_destroy_session (session);
460 GNUNET_mutex_unlock (lock);
461 GNUNET_free (filter);
462 return GNUNET_SYSERR;
464 GNUNET_free (filter);
465 m = GNUNET_malloc (size + sizeof(SMTPMessage));
466 GNUNET_memcpy (m, msg, size);
467 mp = (SMTPMessage *) &m[size];
468 mp->header.size = htons (size + sizeof(SMTPMessage));
469 mp->header.type = htons (0);
470 mp->sender = *core_api->my_identity;
473 gm_cls.esize = GNUNET_STRINGS_base64_encode (m, size + sizeof(SMTPMessage),
476 if (0 == smtp_size_set_estimate (message, gm_cls.esize))
479 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
480 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
481 "smtp_size_set_estimate", smtp_strerror (smtp_errno (), ebuf,
484 if (0 == smtp_set_messagecb (message, &get_message, &gm_cls))
487 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
488 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
489 "smtp_set_messagecb", smtp_strerror (smtp_errno (), ebuf,
491 smtp_destroy_session (session);
492 GNUNET_mutex_unlock (lock);
493 GNUNET_free (gm_cls.ebody);
494 return GNUNET_SYSERR;
496 recipient = smtp_add_recipient (message, haddr->senderAddress);
497 if (recipient == NULL)
500 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
501 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
502 "smtp_add_recipient", smtp_strerror (smtp_errno (), ebuf,
504 smtp_destroy_session (session);
505 GNUNET_mutex_unlock (lock);
506 return GNUNET_SYSERR;
508 if (0 == smtp_start_session (session))
511 GNUNET_GE_WARNING | GNUNET_GE_ADMIN | GNUNET_GE_USER
512 | GNUNET_GE_BULK, _ ("SMTP: `%s' failed: %s.\n"),
513 "smtp_start_session", smtp_strerror (smtp_errno (), ebuf,
515 smtp_destroy_session (session);
516 GNUNET_mutex_unlock (lock);
517 GNUNET_free (gm_cls.ebody);
518 return GNUNET_SYSERR;
521 stats->change (stat_bytesSent, size);
522 if (core_api->load_monitor != NULL)
523 GNUNET_network_monitor_notify_transmission (core_api->load_monitor,
524 GNUNET_ND_UPLOAD, gm_cls.esize);
525 smtp_message_reset_status (message); /* this is needed to plug a 28-byte/message memory leak in libesmtp */
526 smtp_destroy_session (session);
527 GNUNET_mutex_unlock (lock);
528 GNUNET_free (gm_cls.ebody);
533 * Establish a connection to a remote node.
534 * @param hello the hello-Message for the target node
535 * @param tsessionPtr the session handle that is to be set
536 * @param may_reuse can we re-use an existing connection?
537 * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed
540 api_connect (const GNUNET_MessageHello *hello, GNUNET_TSession **tsessionPtr,
543 GNUNET_TSession *tsession;
545 tsession = GNUNET_new (GNUNET_TSession);
546 tsession->internal = GNUNET_malloc (GNUNET_sizeof_hello (hello));
547 tsession->peer = hello->senderIdentity;
548 GNUNET_memcpy (tsession->internal, hello, GNUNET_sizeof_hello (hello));
549 tsession->ttype = smtpAPI.protocol_number;
550 (*tsessionPtr) = tsession;
555 * Disconnect from a remote node.
557 * @param tsession the session that is closed
558 * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed
561 api_disconnect (GNUNET_TSession *tsession)
563 if (tsession != NULL)
565 if (tsession->internal != NULL)
566 GNUNET_free (tsession->internal);
567 GNUNET_free (tsession);
573 * Start the server process to receive inbound traffic.
574 * @return GNUNET_OK on success, GNUNET_SYSERR if the operation failed
577 api_start_transport_server ()
579 smtp_shutdown = GNUNET_NO;
580 /* initialize SMTP network */
581 dispatchThread = GNUNET_thread_create (&listenAndDistribute, NULL, 1024 * 4);
582 if (dispatchThread == NULL)
584 GNUNET_GE_DIE_STRERROR (ectx,
585 GNUNET_GE_ADMIN | GNUNET_GE_BULK | GNUNET_GE_FATAL,
587 return GNUNET_SYSERR;
593 * Shutdown the server process (stop receiving inbound traffic). Maybe
597 api_stop_transport_server ()
601 smtp_shutdown = GNUNET_YES;
602 GNUNET_thread_stop_sleep (dispatchThread);
603 GNUNET_thread_join (dispatchThread, &unused);
608 * Convert SMTP hello to an IP address (always fails).
611 api_hello_to_address (const GNUNET_MessageHello *hello, void **sa,
612 unsigned int *sa_len)
614 return GNUNET_SYSERR;
621 api_associate (GNUNET_TSession *tsession)
623 return GNUNET_SYSERR; /* SMTP connections can never be associated */
627 * Always succeeds (for now; we should look at adding
628 * frequency limits to SMTP in the future!).
631 api_test_would_try (GNUNET_TSession *tsession, unsigned int size,
634 return GNUNET_OK; /* we always try... */
638 * The exported method. Makes the core api available via a global and
639 * returns the smtp transport API.
641 GNUNET_TransportAPI *
642 inittransport_smtp (struct GNUNET_CoreAPIForTransport *core)
644 unsigned long long mtu;
649 if (! GNUNET_GC_have_configuration_value (core_api->cfg, "SMTP", "EMAIL"))
651 GNUNET_GE_LOG (ectx, GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
654 "No email-address specified, can not start SMTP transport.\n"));
657 GNUNET_GC_get_configuration_value_number (core_api->cfg, "SMTP", "MTU", 1200,
659 SMTP_MESSAGE_SIZE, &mtu);
660 GNUNET_GC_get_configuration_value_number (core_api->cfg, "SMTP", "RATELIMIT",
661 0, 0, 1024 * 1024, &rate_limit);
662 stats = core_api->service_request ("stats");
666 stats->create (gettext_noop ("# bytes received via SMTP"));
667 stat_bytesSent = stats->create (gettext_noop ("# bytes sent via SMTP"));
669 stats->create (gettext_noop ("# bytes dropped by SMTP (outgoing)"));
671 GNUNET_GC_get_configuration_value_filename (core_api->cfg, "SMTP", "PIPE",
674 if (0 != mkfifo (pipename, S_IWUSR | S_IRUSR | S_IWGRP | S_IWOTH))
676 GNUNET_GE_LOG_STRERROR (ectx,
677 GNUNET_GE_ADMIN | GNUNET_GE_BULK | GNUNET_GE_FATAL,
679 GNUNET_free (pipename);
680 core_api->service_release (stats);
684 /* we need to allow the mailer program to send us messages;
685 * easiest done by giving it write permissions (see Mantis #1142) */
686 if (0 != chmod (pipename, S_IWUSR | S_IRUSR | S_IWGRP | S_IWOTH))
687 GNUNET_GE_LOG_STRERROR (ectx,
688 GNUNET_GE_ADMIN | GNUNET_GE_BULK
689 | GNUNET_GE_WARNING, "chmod");
690 GNUNET_GC_get_configuration_value_string (core_api->cfg, "SMTP", "EMAIL",
693 lock = GNUNET_mutex_create (GNUNET_NO);
694 GNUNET_GC_get_configuration_value_string (core_api->cfg, "SMTP", "SERVER",
695 "localhost:25", &smtp_server_name);
696 sa.sa_handler = SIG_IGN;
697 sigemptyset (&sa.sa_mask);
699 sigaction (SIGPIPE, &sa, &old_handler);
701 smtpAPI.protocol_number = GNUNET_TRANSPORT_PROTOCOL_NUMBER_SMTP;
702 smtpAPI.mtu = mtu - sizeof(SMTPMessage);
704 smtpAPI.hello_verify = &api_verify_hello;
705 smtpAPI.hello_create = &api_create_hello;
706 smtpAPI.connect = &api_connect;
707 smtpAPI.send = &api_send;
708 smtpAPI.associate = &api_associate;
709 smtpAPI.disconnect = &api_disconnect;
710 smtpAPI.server_start = &api_start_transport_server;
711 smtpAPI.server_stop = &api_stop_transport_server;
712 smtpAPI.hello_to_address = &api_hello_to_address;
713 smtpAPI.send_now_test = &api_test_would_try;
718 donetransport_smtp ()
720 sigaction (SIGPIPE, &old_handler, NULL);
721 GNUNET_free (smtp_server_name);
724 core_api->service_release (stats);
727 GNUNET_mutex_destroy (lock);
730 GNUNET_free (pipename);