/syncsm.c (1169541482e1d1c3e5a2e61c0dcb338d2d3c5689) (13459 bytes) (mode 100644) (type blob)

#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


Mode Type Size Ref File
100644 blob 853 cfee40c9af4999813fcbb3a32e07de953696d003 ABBREVIATIONS
100644 blob 3802 1058e91164c6384b9d271e249ee74a7df9952884 README
100644 blob 967 eb0eb02192cae4ec269d7836bcccf789dd3baa09 RULES_GLOBAL_NAMESPACE
100644 blob 226 47e8cbfba1358960f381e80943afaa64d6083673 RULES_POINTER
100644 blob 141 a7eef9c825adfb6cb392c5f61048ae1a9961144f TODO
100644 blob 186 e08b0072d50fa683daf4b72ffcca9afe28f33928 all.S
100644 blob 907 68a9ab080cac0188391966e29be979548a77b537 all.c
100644 blob 2570 9c5f66db8962e15d99644ac8f42fc207e27d3178 config.default.h
100644 blob 448 b3b72c25ea89e0f17ab77ead13e89b915186ec95 dns.h
040000 tree - 60975c8ad01566be3264e1648a304087dae2e6e4 dns
100755 blob 1246 6ebc4041142bda951a9a02320d45deb7ee43f39b make_gcc_aarch64
100755 blob 1245 303ed53e1ef0a6f89a5d13c2eb21c271e0b6dcd0 make_gcc_x86_64
040000 tree - fcae74b207287320abbbd373133626ae7ee67ed0 namespace
100644 blob 434 6b73d3e06f341d75c9f17c00b0720b85498ef628 perr.h
100644 blob 450 88813b86df39c72cc32605e178edfdd544297e42 smtp.h
040000 tree - eca08c8db66629459c72e1223d89a03895dc8bab smtp
100644 blob 13459 1169541482e1d1c3e5a2e61c0dcb338d2d3c5689 syncsm.c
100644 blob 1948 678eed4da259f00ba708f070aeddb69229322df6 syncsm.h
100644 blob 880 82aad126f033e8ecb5c919dbd27996b094bbfbe8 ulinux.h
040000 tree - 503817b57582aa9b3b6a5482104fe81605a6a035 ulinux
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/sylware/syncsm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/sylware/syncsm

Clone this repository using git:
git clone git://git.rocketgit.com/user/sylware/syncsm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main