[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