[Asterisk-code-review] app_mail: New SendMail application (asterisk[master])

N A asteriskteam at digium.com
Tue Jun 22 13:42:42 CDT 2021


N A has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/16076 )


Change subject: app_mail: New SendMail application
......................................................................

app_mail: New SendMail application

Adds a mail application to Asterisk which invokes
the system mailer with configurable addressing info
and support for multiple attachments.

ASTERISK-29489

Change-Id: I82a7af95364382ac50edb4dd4660873e5f89d3dc
---
A apps/app_mail.c
A doc/CHANGES-staging/app_mail.txt
2 files changed, 478 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/76/16076/1

diff --git a/apps/app_mail.c b/apps/app_mail.c
new file mode 100644
index 0000000..5f96fe7
--- /dev/null
+++ b/apps/app_mail.c
@@ -0,0 +1,471 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert
+ *
+ * Naveen Albert <asterisk at phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Send email application
+ *
+ * \author Naveen Albert <asterisk at phreaknet.org>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <sys/stat.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+	<application name="SendMail" language="en_US">
+		<synopsis>
+			Sends an email using the system mailer. The recipient and sender info
+			can be customized per invocation, and multiple attachments can be sent.
+		</synopsis>
+		<syntax>
+			<parameter name="recipient" required="true">
+				<para>Pipe-separated list of email addresses.</para>
+			</parameter>
+			<parameter name="subject" required="true" />
+			<parameter name="body" required="true" />
+			<parameter name="options" required="true">
+				<optionlist>
+				<option name="A">
+					<para>Pipe-separated list of attachments, using the full path. The
+					file name will be used as the name of the attachment.</para>
+				</option>
+				<option name="d">
+					<para>Delete successfully attached files once the email is sent.</para>
+				</option>
+				<option name="f">
+					<para>Email address from which the email should be sent.</para>
+				</option>
+				<option name="F">
+					<para>Name from which the email should be sent.</para>
+				</option>
+				<option name="R">
+					<para>Name of the recipient.</para>
+				</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Sends an email using the system mailer.</para>
+			<para>Sets <variable>MAILSTATUS</variable> to one of the following values:</para>
+			<variablelist>
+				<variable name="MAILSTATUS">
+					<value name="SUCCESS">
+						Email was successfully sent. This does not necessarily mean delivery
+						will be successful or that the email will not bounce.
+					</value>
+					<value name="FAILURE">
+						Email was not successfully sent.
+					</value>
+				</variable>
+			</variablelist>
+		</description>
+	</application>
+ ***/
+
+static char *app = "SendMail";
+
+#define	VOICEMAIL_FILE_MODE	0666
+#define BASELINELEN 72
+#define BASEMAXINLINE 256
+#define SENDMAIL "/usr/sbin/sendmail -t"
+#define ENDL "\n"
+
+static int my_umask;
+
+struct baseio {
+	int iocp;
+	int iolen;
+	int linelength;
+	int ateof;
+	unsigned char iobuf[BASEMAXINLINE];
+};
+
+/*!
+ * \brief utility used by inchar(), for base_encode()
+ */
+static int inbuf(struct baseio *bio, FILE *fi)
+{
+	int l;
+
+	if (bio->ateof)
+		return 0;
+
+	if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) != BASEMAXINLINE) {
+		bio->ateof = 1;
+		if (l == 0) {
+			/* Assume EOF */
+			return 0;
+		}
+	}
+
+	bio->iolen = l;
+	bio->iocp = 0;
+
+	return 1;
+}
+
+/*!
+ * \brief utility used by base_encode()
+ */
+static int inchar(struct baseio *bio, FILE *fi)
+{
+	if (bio->iocp>=bio->iolen) {
+		if (!inbuf(bio, fi))
+			return EOF;
+	}
+
+	return bio->iobuf[bio->iocp++];
+}
+
+/*!
+ * \brief utility used by base_encode()
+ */
+static int ochar(struct baseio *bio, int c, FILE *so)
+{
+	if (bio->linelength >= BASELINELEN) {
+		if (fputs(ENDL, so) == EOF) {
+			return -1;
+		}
+
+		bio->linelength = 0;
+	}
+
+	if (putc(((unsigned char) c), so) == EOF) {
+		return -1;
+	}
+
+	bio->linelength++;
+
+	return 1;
+}
+
+/*!
+ * \brief Performs a base 64 encode algorithm on the contents of a File
+ * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
+ * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
+ *
+ * TODO: shouldn't this (and the above 3 support functions) be put into some kind of external utility location, such as funcs/func_base64.c ?
+ *
+ * \return zero on success, -1 on error.
+ */
+static int base_encode(char *filename, FILE *so)
+{
+	static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
+		'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+		'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
+		'1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+	int i, hiteof = 0;
+	FILE *fi;
+	struct baseio bio;
+
+	memset(&bio, 0, sizeof(bio));
+	bio.iocp = BASEMAXINLINE;
+
+	if (!(fi = fopen(filename, "rb"))) {
+		ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
+		return -1;
+	}
+
+	while (!hiteof){
+		unsigned char igroup[3], ogroup[4];
+		int c, n;
+
+		memset(igroup, 0, sizeof(igroup));
+
+		for (n = 0; n < 3; n++) {
+			if ((c = inchar(&bio, fi)) == EOF) {
+				hiteof = 1;
+				break;
+			}
+
+			igroup[n] = (unsigned char) c;
+		}
+
+		if (n > 0) {
+			ogroup[0]= dtable[igroup[0] >> 2];
+			ogroup[1]= dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
+			ogroup[2]= dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
+			ogroup[3]= dtable[igroup[2] & 0x3F];
+
+			if (n < 3) {
+				ogroup[3] = '=';
+
+				if (n < 2)
+					ogroup[2] = '=';
+			}
+
+			for (i = 0; i < 4; i++)
+				ochar(&bio, ogroup[i], so);
+		}
+	}
+
+	fclose(fi);
+
+	if (fputs(ENDL, so) == EOF) {
+		return 0;
+	}
+
+	return 1;
+}
+
+/* same as mkstemp, but return a FILE * */
+static FILE *vm_mkftemp(char *template)
+{
+	FILE *p = NULL;
+	int pfd = mkstemp(template);
+	chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
+	if (pfd > -1) {
+		p = fdopen(pfd, "w+");
+		if (!p) {
+			close(pfd);
+			pfd = -1;
+		}
+	}
+	return p;
+}
+
+static void make_email_file(FILE *p, char *subject, char *body, char *toaddress, char *toname, char *fromaddress, char *fromname, char *attachments, int delete)
+{
+	struct ast_tm tm;
+	char date[256];
+	char host[MAXHOSTNAMELEN] = "";
+	char who[256];
+	char bound[256];
+	struct ast_str *str2 = ast_str_create(16);
+	char filename[256];
+	char *emailsbuf, *email, *attachmentsbuf, *attachment;
+	struct timeval when = ast_tvnow();
+
+	if (!str2) {
+		ast_free(str2);
+		return;
+	}
+	gethostname(host, sizeof(host) - 1);
+
+	if (strchr(toaddress, '@')) {
+		ast_copy_string(who, toaddress, sizeof(who));
+	} else {
+		snprintf(who, sizeof(who), "%s@%s", toaddress, host);
+	}
+
+	ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", ast_localtime(&when, &tm, NULL));
+	fprintf(p, "Date: %s" ENDL, date);
+	fprintf(p, "From: %s <%s>" ENDL, fromname, fromaddress);
+
+	emailsbuf = ast_strdupa(toaddress);
+	fprintf(p, "To:");
+	while ((email = strsep(&emailsbuf, "|"))) {
+		char *next = emailsbuf;
+		fprintf(p, " \"%s\" <%s>%s" ENDL, toname, email, next ? "," : "");
+	}
+	ast_free(str2);
+
+	fprintf(p, "Subject: %s" ENDL, subject);
+	fprintf(p, "Message-ID: <Asterisk-%u-%d@%s>" ENDL, (unsigned int) ast_random(), (int) getpid(), host);
+	fprintf(p, "MIME-Version: 1.0" ENDL);
+	if (!ast_strlen_zero(attachments)) {
+		/* Something unique. */
+		snprintf(bound, sizeof(bound), "----attachment_%d%u", (int) getpid(), (unsigned int) ast_random());
+		fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
+		fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
+		fprintf(p, "--%s" ENDL, bound);
+	}
+	fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, "ISO-8859-1");
+	fprintf(p, "%s", body);
+	if (ast_strlen_zero(attachments)) {
+		return;
+	}
+	attachmentsbuf = ast_strdupa(attachments);
+	while ((attachment = strsep(&attachmentsbuf, "|"))) {
+		snprintf(filename, sizeof(filename), "%s", basename(attachment));
+		if (!ast_file_is_readable(attachment)) {
+			ast_log(AST_LOG_WARNING, "Failed to open file: %s\n", attachment);
+			continue;
+		}
+		ast_debug(5, "Creating attachment: %s\n", filename);
+		fprintf(p, ENDL "--%s" ENDL, bound);
+
+		/* Eww. We want formats to tell us their own MIME type */
+		//char *mime_type = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
+		
+		//fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, mime_type, format, filename);
+		fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
+		fprintf(p, "Content-Description: File attachment." ENDL);
+		fprintf(p, "Content-Disposition: attachment; filename=\"%s\"" ENDL ENDL, filename);
+		base_encode(attachment, p);
+		if (delete) {
+			unlink(attachment);
+		}
+	}
+	fprintf(p, ENDL ENDL "--%s--" ENDL "." ENDL, bound); /* After the last attachment */
+}
+
+enum {
+	OPT_ATTACHMENTS =       (1 << 0),
+	OPT_FROM_ADDRESS =      (1 << 1),
+	OPT_FROM_NAME =         (1 << 2),
+	OPT_TO_NAME =           (1 << 3),
+	OPT_DELETE_ATTACHMENTS = (1 << 4),
+};
+
+enum {
+	OPT_ARG_ATTACHMENTS,
+	OPT_ARG_FROM_ADDRESS,
+	OPT_ARG_FROM_NAME,
+	OPT_ARG_TO_NAME,
+	/* note: this entry _MUST_ be the last one in the enum */
+	OPT_ARG_ARRAY_SIZE,
+};
+
+AST_APP_OPTIONS(mail_exec_options, BEGIN_OPTIONS
+	AST_APP_OPTION_ARG('A', OPT_ATTACHMENTS, OPT_ARG_ATTACHMENTS),
+	AST_APP_OPTION('d', OPT_DELETE_ATTACHMENTS),
+	AST_APP_OPTION_ARG('f', OPT_FROM_ADDRESS, OPT_ARG_FROM_ADDRESS),
+	AST_APP_OPTION_ARG('F', OPT_FROM_NAME, OPT_ARG_FROM_NAME),
+	AST_APP_OPTION_ARG('R', OPT_TO_NAME, OPT_ARG_TO_NAME),
+END_OPTIONS);
+
+static int mail_exec(struct ast_channel *chan, const char *data)
+{
+	char *parse;
+	char *toaddress, *subject, *body;
+	FILE *p = NULL;
+	char tmp[80] = "/tmp/astmail-XXXXXX";
+	char tmp2[256];
+	char *mailcmd = SENDMAIL; /* Mail command */
+	char *fromaddress = "", *toname = "";
+	char *fromname = "Asterisk PBX";
+	char *attachments = "";
+	int delete = 0;
+	int res;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(recipient);
+		AST_APP_ARG(subject);
+		AST_APP_ARG(body);
+		AST_APP_ARG(options);
+	);
+	struct ast_flags64 opts = { 0, };
+	char *opt_args[OPT_ARG_ARRAY_SIZE];
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+	if (args.argc < 3) {
+		ast_log(LOG_ERROR, "Incorrect number of arguments\n");
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	if (ast_strlen_zero(args.recipient)) {
+		ast_log(LOG_NOTICE, "Mail requires a recipient\n");
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	if (ast_strlen_zero(args.subject)) {
+		ast_log(LOG_NOTICE, "Mail requires a subject\n");
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	if (!ast_strlen_zero(args.options) &&
+		ast_app_parse_options64(mail_exec_options, &opts, opt_args, args.options)) {
+		ast_log(LOG_ERROR, "Invalid options: '%s'\n", args.options);
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	toaddress = args.recipient;
+	subject = args.subject;
+	body = args.body;
+	if (ast_test_flag64(&opts, OPT_ATTACHMENTS)
+		&& !ast_strlen_zero(opt_args[OPT_ARG_ATTACHMENTS])) {
+		attachments = opt_args[OPT_ARG_ATTACHMENTS];
+	}
+	if (ast_test_flag64(&opts, OPT_FROM_ADDRESS)
+		&& !ast_strlen_zero(opt_args[OPT_ARG_FROM_ADDRESS])) {
+		fromaddress = opt_args[OPT_ARG_FROM_ADDRESS];
+	} else {
+		ast_log(LOG_ERROR, "Must specify from address\n");
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	if (ast_test_flag64(&opts, OPT_FROM_NAME)
+		&& !ast_strlen_zero(opt_args[OPT_ARG_FROM_NAME])) {
+		fromname = opt_args[OPT_ARG_FROM_NAME];
+	}
+	if (ast_test_flag64(&opts, OPT_TO_NAME)
+		&& !ast_strlen_zero(opt_args[OPT_ARG_TO_NAME])) {
+		toname = opt_args[OPT_ARG_TO_NAME];
+	}
+	if (ast_test_flag64(&opts, OPT_DELETE_ATTACHMENTS)) {
+		delete = 1;
+	}
+
+	/* Make a temporary file instead of piping directly to sendmail, in case the mail
+	   command hangs */
+	if ((p = vm_mkftemp(tmp)) == NULL) {
+		ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		return 0;
+	}
+	make_email_file(p, subject, body, toaddress, toname, fromaddress, fromname, attachments, delete);
+	fclose(p);
+	//snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
+	snprintf(tmp2, sizeof(tmp2), "( %s < %s ; cp %s /tmp/test.txt ) &", mailcmd, tmp, tmp);
+	res = ast_safe_system(tmp2);
+	if ((res < 0) && (errno != ECHILD)) {
+		ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+	} else if (res == 127) {
+		ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
+		pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+	} else {
+		if (res < 0)
+			res = 0;
+		if (res != 0)
+			pbx_builtin_setvar_helper(chan, "MAILSTATUS", "FAILURE");
+		else {
+			ast_debug(1, "Sent mail to %s with command '%s'\n", toaddress, mailcmd);
+			pbx_builtin_setvar_helper(chan, "MAILSTATUS", "SUCCESS");
+		}
+	}
+	ast_autoservice_stop(chan);
+	return 0;
+}
+
+static int unload_module(void)
+{
+	return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+	my_umask = umask(0);
+	umask(my_umask);
+	return ast_register_application_xml(app, mail_exec);
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Sends an email");
diff --git a/doc/CHANGES-staging/app_mail.txt b/doc/CHANGES-staging/app_mail.txt
new file mode 100644
index 0000000..155a674
--- /dev/null
+++ b/doc/CHANGES-staging/app_mail.txt
@@ -0,0 +1,7 @@
+Subject: app_mail
+
+Adds a mail application to Asterisk which
+invokes the system mailer with configurable
+addressing info and support for multiple
+attachments.
+

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/16076
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: I82a7af95364382ac50edb4dd4660873e5f89d3dc
Gerrit-Change-Number: 16076
Gerrit-PatchSet: 1
Gerrit-Owner: N A <mail at interlinked.x10host.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210622/7b3998f6/attachment-0001.html>


More information about the asterisk-code-review mailing list