#ifndef SYNCSM_C
#define SYNCSM_C
/*
* this code is protected by the GNU affero GPLv3 license
* author:Sylvain BERTRAND
*/
/* compiler stuff */
#include <stdbool.h>
#include <stdarg.h>
/*----------------------------------------------------------------------------*/
#include "config.h"
/*----------------------------------------------------------------------------*/
#include "ulinux.h"
/*----------------------------------------------------------------------------*/
#include "smtp/rfc.h"
/*----------------------------------------------------------------------------*/
#include "perr.h"
#include "syncsm.h"
#include "dns.h"
#include "smtp.h"
/*============================================================================*/
#include "namespace/ulinux.h"
#include "namespace/syncsm.h"
#include "namespace/dns.h"
#include "namespace/smtp.h"
#include "namespace/syncsm.c"
/*----------------------------------------------------------------------------*/
/* array of pointers, on 0 terminated recipient string */
static u8 **rcpts;
/*----------------------------------------------------------------------------*/
/* XXX: quoted-string is not supported */
static void rcpt_slices(u8 *rcpt, struct str_slice_t *local_part,
struct str_slice_t *dn_al_slice)
{
u8 *c;
memset(local_part, 0, sizeof(*local_part));
memset(dn_al_slice, 0, sizeof(*dn_al_slice));
c = rcpt;
local_part->s = c;
loop {
if (*c == 0) {
dn_al_slice->l = c - 1;
break;
}
if (*c == '@') {
local_part->l = c - 1;
dn_al_slice->s = c + 1;
}
++c;
}
if (((local_part->l - local_part->s) < 0)
|| ((dn_al_slice->l - dn_al_slice->s) < 0)) {
PERR("SYNCSM:RECIPIENT:ERROR:wrong local/(domain name | address literal) parts for \"%s\"\n", rcpt);
exit(1);
}
PERR("SYNCSM:RECIPIENT:local part is \"%.*s\"", local_part->l - local_part->s + 1, local_part->s);
PERR(", (domain name | address literal) is \"%.*s\"\n", dn_al_slice->l - dn_al_slice->s + 1, dn_al_slice->s);
}
/* domain part in an smtp mailbox does not have a terminating '.' */
static bool slice_eq(struct str_slice_t *a, struct str_slice_t *b)
{
u64 a_len;
u64 b_len;
a_len = a->l - a->s + 1;
b_len = b->l - b->s + 1;
if (a_len != b_len)
return false;
if (!memcmp(a->s, b->s, a_len))
return false;
return true;
}
static bool ip_eq(struct ip_t *a, struct ip_t *b)
{
#if defined CONFIG_SMTP_IPV4 || defined CONFIG_DNS_IPV4
if (a->type != b->type)
return false;
switch (a->type) {
case ipv4:
if (a->ipv4_net == b->ipv4_net)
return true;
break;
case ipv6:
#endif
if (memcmp(a->ipv6_net, b->ipv6_net, 16))
return true;
#if defined CONFIG_SMTP_IPV4 || defined CONFIG_DNS_IPV4
break;
}
#endif
return false;
}
static bool dn_al_eq(struct dn_al_t *a, struct dn_al_t *b)
{
if (a->type != b->type)
return false;
switch (a->type) {
case literal_ip:
if (ip_eq(&a->smtp_ips[0], &b->smtp_ips[0]))
return true;
break;
case domain_name:
if (slice_eq(&a->dn, &b->dn))
return true;
break;
}
return false;
}
static struct dn_al_t *dn_al_locate(struct dn_al_t *dn_al)
{
u64 i;
i = 0;
loop {
struct dn_al_t *cur;
if (i == dn_al_n_v)
return 0;
cur = &dn_al_v[i];
if (dn_al_eq(cur, dn_al))
return cur;
++i;
}
}
static struct dn_al_t *dn_al_insert(struct dn_al_t *dn_al,
struct str_slice_t *local_part)
{
struct dn_al_t *dest;
dest = dn_al_locate(dn_al);
if (dest == 0) { /* not found */
/* partial "copy constructor" for the oo retards */
dest = &dn_al_v[dn_al_n_v];
dest->type = dn_al->type;
dest->local_parts_n = 0;
if (dn_al->type == domain_name) {
memcpy(&dest->dn, &dn_al->dn, sizeof(dest->dn));
dest->smtp_ips_n = 0;
} else if (dn_al->type == literal_ip) {
memcpy(&dest->smtp_ips[0], &dn_al->smtp_ips[0],
sizeof(dest->smtp_ips[0]));
dest->smtp_ips_n = 1;
}
++dn_al_n_v;
}
if (dest->local_parts_n == SMTP_RFC_RCPTS_N_MAX) {
PERR("0:SYNCSM:DOMAIN_NAME/ADDRESS_LITERAL:ERROR:too many recipients for \"%s\"\n", local_part->s);
exit(1);
}
memcpy(&dest->local_parts[dest->local_parts_n], local_part, sizeof(*local_part));
++(dest->local_parts_n);
return dest;
}
static bool to_ipv6(u8 *ipv6, u8 *s, u8 *l)
{
/* we must check for the tag */
if ((l - s + 1) < CSTRLEN("IPv6:"))
return false;
if (memcmp(s,"IPv6:", CSTRLEN("IPv6:")))
return false;
if (!to_ipv6_blk(ipv6, s + CSTRLEN("IPv6:"), l))
return false;
return true;
}
static void rcpt_dn_al_process(struct str_slice_t *local_part,
struct str_slice_t *dn_al_slice)
{
struct dn_al_t rcpt_dn_al;
struct dn_al_t *dn_al;
if (*dn_al_slice->s == '[') { /* it's an address literal */
if (*dn_al_slice->l != ']') {
PERR("0:SYNCSM:RECIPIENT:ERROR:\"%s\" is missing the closing ']' of its address literal\n", local_part->s);
exit(1);
}
if ((dn_al_slice->s + 1) == dn_al_slice->l) {
PERR("0:SYNCSM:RECIPIENT:ERROR:\"%s\" has an empty ss literal\n", local_part->s);
exit(1);
}
#ifdef CONFIG_SMTP_IPV4
if (to_ipv4_blk(&rcpt_dn_al.smtp_ips[0].ipv4_net,
dn_al_slice->s + 1, dn_al_slice->l - 1)) {
PERR("0:SYNCSM:RECIPIENT:\"%s\" has an IPv4 address literal '0x%08x'\n", local_part->s, be32_to_cpu(rcpt_dn_al.smtp_ips[0].ipv4_net));
rcpt_dn_al.smtp_ips[0].type = ipv4;
rcpt_dn_al.smtp_ips_n = 1;
rcpt_dn_al.type = literal_ip;
}
else
#endif
if (to_ipv6(rcpt_dn_al.smtp_ips[0].ipv6_net, dn_al_slice->s + 1,
dn_al_slice->l - 1)) {
PERR("0:SYNCSM:RECIPIENT:\"%s\" has an IPv6 address literal '0x%016lx%016lx'\n", local_part->s, be64_to_cpu(rcpt_dn_al.smtp_ips[0].ipv6_net_h), be64_to_cpu(rcpt_dn_al.smtp_ips[0].ipv6_net_l));
#ifdef CONFIG_SMTP_IPV4
rcpt_dn_al.smtp_ips[0].type = ipv6;
rcpt_dn_al.smtp_ips_n = 1;
rcpt_dn_al.type = literal_ip;
#endif
} else {
#ifdef CONFIG_SMTP_IPV4
PERR("0:SYNCSM:RECIPIENT:\"%s\" has neither an IPv4 nor an IPv6 address literal\n", local_part->s);
#else
PERR("0:SYNCSM:RECIPIENT:\"%s\" has not an IPv6 address literal\n", local_part->s);
#endif
PERR("0:SYNCSM:RECIPIENT:ERROR:\"%s\", unable to interpret its address literal\n", local_part->s);
exit(1);
}
} else { /* it should be a domain name */
rcpt_dn_al.type = domain_name;
memcpy(&rcpt_dn_al.dn, dn_al_slice, sizeof(*dn_al_slice));
rcpt_dn_al.smtp_ips_n = 0;
}
dn_al = dn_al_insert(&rcpt_dn_al, local_part);
PERR("0:SYNCSM:DOMAIN_NAME/ADDRESS_LITERAL:\"%.*s\" has now %d local parts\n", dn_al_slice->l - dn_al_slice->s + 1, dn_al_slice->s, dn_al->local_parts_n);
}
static void rcpt_prepare(u8 *rcpt)
{
struct str_slice_t local_part;
struct str_slice_t dn_al_slice;
PERR("0:SYNCSM:RECIPIENT:preparing sending email to \"%s\"\n", rcpt);
rcpt_slices(rcpt, &local_part, &dn_al_slice);
rcpt_dn_al_process(&local_part, &dn_al_slice);
}
static void rcpts_prepare(void)
{
u8 **rcpt;
rcpt = rcpts;
loop {
if (*rcpt == 0)
break;
rcpt_prepare(*rcpt);
++rcpt;
}
}
#ifdef CONFIG_DISABLE_SMTP_TRANSPARENCY
static void email_read_from_stdin(void)
{
email_sz_v = 0;
loop {
sl r;
r = read(0, email_v + email_sz_v, CONFIG_EMAIL_ADDRESS_SPACE
- email_sz_v);
if (ISERR(r)) {
if ((r == -EAGAIN) || (r == -EINTR))
continue;
PERR("0:SYNCSM:EMAIL:ERROR:%ld:error while reading the email from stdin\n", r);
exit(1);
}
if (r == 0) /* end of input */
break;
email_sz_v += (u64)r;
}
PERR("0:SYNCSM:EMAIL:%lu bytes read from stdin\n", email_sz_v);
if (email_sz_v == 0) {
PERR("0:SYNCSM:EMAIL:no email to send, exiting\n");
exit(0);
}
}
#else /* CONFIG_DISABLE_SMTP_TRANSPARENCY */
/* will return the count of read bytes. 0 means the end of file */
static u64 stdin_read(u8 *buf, u64 at_most_sz)
{
loop {
sl r;
r = read(0, buf, at_most_sz);
if (!ISERR(r) && (r >= 0))
return (u64)r;
if ((r != -EAGAIN) && (r != -EINTR)) {
PERR("0:SYNCSM:EMAIL:ERROR:%ld:error while reading the email from stdin\n", r);
exit(1);
}
/*
* insist on EAGAIN and EINTR, namely untill we get the
* end of file or a real error
*/
}
}
static inline void email_write_byte(u8 c)
{
if (email_sz_v == CONFIG_EMAIL_ADDRESS_SPACE) {
PERR("0:SYNCSM:EMAIL:ERROR:email buffer is full (%lu bytes)\n", CONFIG_EMAIL_ADDRESS_SPACE);
exit(1);
}
email_v[email_sz_v] = c;
++email_sz_v;
}
static inline void email_write_bytes(u8 *s, u64 sz)
{
if ((email_sz_v + sz) > CONFIG_EMAIL_ADDRESS_SPACE) {
PERR("0:SYNCSM:EMAIL:ERROR:email buffer too small (max %lu bytes)\n", CONFIG_EMAIL_ADDRESS_SPACE);
exit(1);
}
memcpy(email_v + email_sz_v, s, sz);
email_sz_v += sz;
}
#define NORMAL 0x01
#define MATCHING_TERMINATOR 0x02
struct match {
u8 *terminator_s;
u8 *terminator_l;
u8 *terminator_expected;
u64 escapes_n;
};
static u8 normal(struct match *match, u8 c)
{
if (c == *(match->terminator_s)) {
match->terminator_expected = match->terminator_s + 1;
return MATCHING_TERMINATOR;
}
email_write_byte(c);
return NORMAL;
}
static void flush_partial_terminator(struct match *match)
{
u8 *partial_match_l;
partial_match_l = match->terminator_expected - 1;
/* write the partially matched sequence */
email_write_bytes(match->terminator_s,
partial_match_l - match->terminator_s + 1);
}
static u8 matching_terminator(struct match *match, u8 c)
{
/* not a terminator sequence of chars */
if (c != *(match->terminator_expected)) {
flush_partial_terminator(match);
/* then rescan in normal state the "faulty" char */
return normal(match, c);
}
/* expected terminator char */
/* last expected terminator char, escaping! */
if (match->terminator_expected == match->terminator_l) {
/* write the escape sequence */
email_write_bytes(SMTP_RFC_TERMINATOR_ESCAPE,
CSTRLEN(SMTP_RFC_TERMINATOR_ESCAPE));
++(match->escapes_n);
return NORMAL;
}
/* not the last expected terminator char, next */
++(match->terminator_expected);
return MATCHING_TERMINATOR;
}
/*
* XXX: read the WHOLE email and escape the smtp terminator sequence
* '<crlf>.<crlf>' to '<crlf>..<crlf>': this is called 'smtp transparency'.
* this is a mini regex automaton.
*/
static void email_read_and_escape_from_stdin(void)
{
static u8 read_buf[CONFIG_BUFSIZ];
u8 *c;
u8 state;
struct match match;
/* init the escaped email buffer */
email_sz_v = 0;
/* terminator matching */
match.terminator_s = SMTP_RFC_TERMINATOR;
match.terminator_l = match.terminator_s + CSTRLEN(SMTP_RFC_TERMINATOR) - 1;
match.escapes_n = 0;
/* we start in NORMAL state */
state = NORMAL;
loop {
u64 read_bytes_n;
u8 *read_bytes_e;
read_bytes_n = stdin_read(read_buf, sizeof(read_buf));
if (read_bytes_n == 0) { /* end of file */
if (state == MATCHING_TERMINATOR)
flush_partial_terminator(&match);
break;
}
read_bytes_e = read_buf + read_bytes_n;
c = read_buf;
loop {
if (c == read_bytes_e)
break;
switch (state) {
case NORMAL:
state = normal(&match, *c);
break;
case MATCHING_TERMINATOR:
state = matching_terminator(&match, *c);
break;
}
++c;
}
}
PERR("0:SYNCSM:EMAIL:%lu bytes from stdin and %u smtp transparency escapes (<CRLF>.<CRLF> to <CRLF>..<CRLF>)\n", email_sz_v, match.escapes_n);
if (email_sz_v == 0) {
PERR("0:SYNCSM:EMAIL:no email to send, exiting\n");
exit(0);
}
}
#undef NORMAL
#undef MATCHING_TERMINATOR
#endif /* CONFIG_DISABLE_SMTP_TRANSPARENCY */
static void stdin_nonblock_status(void)
{
sl r;
ul status_flags;
r = fcntl(0, F_GETFL, 0);
if (ISERR(r)) {
PERR("0:SYNCSM:STDIN:ERROR:%ld:unable to get file descriptor status flags\n", r);
exit(1);
}
status_flags = (ul)r;
status_flags |= O_NONBLOCK;
r = fcntl(0, F_SETFL, status_flags);
if (ISERR(r)) {
PERR("0:SYNCSM:STDIN:ERROR:%ld:unable to set file descriptor status flags\n", r);
exit(1);
}
PERR("1:SYNCSM:STDIN:switched to nonblock-ing operations (status flags are 0x%lx/0%lo)\n", status_flags, status_flags);
}
/* we expect the list of rcpts after '--' */
static void rcpts_locate_from(u8 *abi_stack)
{
u8 **args = (u8**)(abi_stack + sizeof(ul)); /* skip argc */
u8 arg_idx = 1; /* skip program path */
loop {
u8 *arg = args[arg_idx];
if (arg == 0)
break;
/* lookup for the '--' separator */
if (arg[0] == '-' && arg[1] == '-' && arg[2] == 0) {
rcpts = args + arg_idx + 1;
break;
}
++arg_idx;
}
if (rcpts == 0 || rcpts[0] == 0) {
PERR("0:SYNCSM:no recipient, exiting\n");
exit(0);
}
}
static void globals_init(void)
{
sl r;
/* initialize a 0 terminating byte */
syncsm_dprint_buf[CONFIG_BUFSIZ - 1] = 0;
rcpts = 0; /* no recipient */
r = mmap(0, CONFIG_EMAIL_ADDRESS_SPACE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0);
if (ISERR(r)) {
PERR("0:SYNCSM:ERROR:%ld:unable to allocate email address space\n", r);
exit(1);
}
email_v = (u8*)r;
}
/* this symbol is outside this compilation unit */
void ulinux_start(u8 *abi_stack)
{
globals_init();
rcpts_locate_from(abi_stack);
if (rcpts == 0)
exit(0);
stdin_nonblock_status();
#ifndef CONFIG_DISABLE_SMTP_TRANSPARENCY
email_read_and_escape_from_stdin();
#else
email_read_from_stdin();
#endif
rcpts_prepare();
dns_init();
dns_resolver();
smtp_init();
smtp_send();
exit(0);
}
/*----------------------------------------------------------------------------*/
#define CLEANUP
#include "namespace/ulinux.h"
#include "namespace/syncsm.h"
#include "namespace/dns.h"
#include "namespace/smtp.h"
#include "namespace/syncsm.c"
#undef CLEANUP
#endif