mirror of
https://github.com/ElvishArtisan/rivendell.git
synced 2025-04-07 01:13:50 +02:00
* Added a '--body-file' switch to the 'sendmail_test' test harness. Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
297 lines
7.0 KiB
C++
297 lines
7.0 KiB
C++
// rdsendmail.cpp
|
|
//
|
|
// Send an e-mail message using sendmail(1)
|
|
//
|
|
// (C) Copyright 2020 Fred Gleason <fredg@paravelsystems.com>
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2 as
|
|
// published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public
|
|
// License along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
//
|
|
|
|
#include <QObject>
|
|
|
|
#include "rdsendmail.h"
|
|
#include "rduser.h"
|
|
|
|
#include <QProcess>
|
|
|
|
bool __RDSendMail_IsAscii(const QString &str)
|
|
{
|
|
for(int i=0;i<str.length();i++) {
|
|
QChar ch=str.at(i);
|
|
if((ch.cell()>127)||(ch.row()>0)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
QByteArray __RDSendMail_EncodeBody(QString *charset,QString *encoding,
|
|
const QString &str)
|
|
{
|
|
QByteArray raw;
|
|
int index=0;
|
|
QByteArray ret;
|
|
|
|
if(__RDSendMail_IsAscii(str)) {
|
|
//
|
|
// All 7 Bit Characters
|
|
//
|
|
*charset="";
|
|
*encoding="";
|
|
|
|
//
|
|
// Ensure no naked CR or LF characters (RFC5322 Section 2.3)
|
|
//
|
|
ret=str.toAscii();
|
|
index=0;
|
|
while((index=ret.indexOf("/r",index))>=0) {
|
|
if(ret.mid(index+1,1)!="/n") {
|
|
ret.insert(index+1,"/n");
|
|
index++;
|
|
}
|
|
}
|
|
index=0;
|
|
while((index=ret.indexOf("\n",index))>=0) {
|
|
if((index==0)||(ret.mid(index-1,1)!="\r")) {
|
|
ret.insert(index,"\r");
|
|
index+=2;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// 8+ Bit Characters
|
|
//
|
|
*charset=";charset=utf8";
|
|
*encoding="Content-Transfer-Encoding: base64\r\n";
|
|
raw=str.toUtf8();
|
|
for(int i=0;i<raw.length();i+=48) {
|
|
ret+=raw.mid(i,48).toBase64()+"\r\n";
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
QByteArray __RDSendMail_EncodeHeader(const QString &str)
|
|
{
|
|
if(__RDSendMail_IsAscii(str)) {
|
|
return str.toAscii();
|
|
}
|
|
return QByteArray("=?utf-8?B?")+str.toUtf8().toBase64()+"?=";
|
|
}
|
|
|
|
|
|
QByteArray __RDSendMail_EncodeAddress(const QString &str,bool *ok)
|
|
{
|
|
//
|
|
// See RFC5322 Section 3.4 for these formats
|
|
//
|
|
int start=0;
|
|
int end=0;
|
|
QString addr;
|
|
QString name;
|
|
|
|
addr=str;
|
|
if(str.contains("<")&&str.contains(">")) {
|
|
start=str.indexOf("<");
|
|
end=str.indexOf(">");
|
|
if(start<end) {
|
|
addr=str.mid(start+1,end-start-1).trimmed();
|
|
name=str.left(start).trimmed();
|
|
}
|
|
}
|
|
if(str.contains("(")&&str.contains(")")) {
|
|
start=str.indexOf("(");
|
|
end=str.indexOf(")");
|
|
if(start<end) {
|
|
name=str.mid(start+1,end-start-1).trimmed();
|
|
addr=str;
|
|
addr=addr.remove("("+name+")").trimmed();
|
|
}
|
|
}
|
|
if(!RDUser::emailIsValid(addr)) {
|
|
*ok=false;
|
|
return QByteArray();
|
|
}
|
|
*ok=true;
|
|
|
|
//
|
|
// Output in "display-name <local@domain>" format
|
|
//
|
|
// FIXME: Add support for IDNA (see RFC5891)
|
|
//
|
|
if(name.isEmpty()) {
|
|
return addr.toAscii();
|
|
}
|
|
return __RDSendMail_EncodeHeader(name)+" <"+addr.toAscii()+">";
|
|
}
|
|
|
|
|
|
//
|
|
// This implements a basic email sending capability using the system's
|
|
// sendmail(1) interface.
|
|
//
|
|
bool RDSendMail(QString *err_msg,const QString &subject,const QString &body,
|
|
const QString &from_addr,const QStringList &to_addrs,
|
|
const QStringList &cc_addrs,const QStringList &bcc_addrs,
|
|
bool dry_run)
|
|
{
|
|
QStringList args;
|
|
QProcess *proc=NULL;
|
|
QString msg="";
|
|
QByteArray from_addr_enc;
|
|
QList<QByteArray> to_addrs_enc;
|
|
QList<QByteArray> cc_addrs_enc;
|
|
QList<QByteArray> bcc_addrs_enc;
|
|
bool ok=false;
|
|
|
|
*err_msg="";
|
|
|
|
//
|
|
// Validate Addresses
|
|
//
|
|
if(from_addr.isEmpty()) {
|
|
*err_msg+=QObject::tr("You must supply a \"from\" address")+"\n";
|
|
}
|
|
else {
|
|
from_addr_enc=__RDSendMail_EncodeAddress(from_addr,&ok);
|
|
if(!ok) {
|
|
*err_msg+=QObject::tr("address")+" \""+from_addr+"\" "+
|
|
QObject::tr("is invalid")+"\n";
|
|
}
|
|
}
|
|
for(int i=0;i<to_addrs.size();i++) {
|
|
to_addrs_enc.push_back(__RDSendMail_EncodeAddress(to_addrs.at(i),&ok));
|
|
if(!ok) {
|
|
*err_msg+=QObject::tr("address")+" \""+to_addrs.at(i)+"\" "+
|
|
QObject::tr("is invalid")+"\n";
|
|
}
|
|
}
|
|
for(int i=0;i<cc_addrs.size();i++) {
|
|
cc_addrs_enc.push_back(__RDSendMail_EncodeAddress(cc_addrs.at(i),&ok));
|
|
if(!ok) {
|
|
*err_msg+=QObject::tr("address")+" \""+cc_addrs.at(i)+"\" "+
|
|
QObject::tr("is invalid")+"\n";
|
|
}
|
|
}
|
|
for(int i=0;i<bcc_addrs.size();i++) {
|
|
bcc_addrs_enc.push_back(__RDSendMail_EncodeAddress(bcc_addrs.at(i),&ok));
|
|
if(!ok) {
|
|
*err_msg+=QObject::tr("address")+" \""+bcc_addrs.at(i)+"\" "+
|
|
QObject::tr("is invalid")+"\n";
|
|
}
|
|
}
|
|
|
|
if(!err_msg->isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Compose Message
|
|
//
|
|
QString charset;
|
|
QString encoding;
|
|
QByteArray raw=__RDSendMail_EncodeBody(&charset,&encoding,body);
|
|
|
|
msg+="From: "+from_addr_enc+"\r\n";
|
|
|
|
msg+="Content-Type: text/plain"+charset+"\r\n";
|
|
msg+=encoding;
|
|
|
|
if(to_addrs_enc.size()>0) {
|
|
msg+="To: ";
|
|
for(int i=0;i<to_addrs_enc.size();i++) {
|
|
msg+=to_addrs_enc.at(i)+", ";
|
|
}
|
|
msg=msg.left(msg.length()-2);
|
|
msg+="\r\n";
|
|
}
|
|
if(cc_addrs_enc.size()>0) {
|
|
msg+="Cc: ";
|
|
for(int i=0;i<cc_addrs_enc.size();i++) {
|
|
msg+=cc_addrs_enc.at(i)+", ";
|
|
}
|
|
msg=msg.left(msg.length()-2);
|
|
msg+="\r\n";
|
|
}
|
|
if(bcc_addrs_enc.size()>0) {
|
|
msg+="Bcc: ";
|
|
for(int i=0;i<bcc_addrs_enc.size();i++) {
|
|
msg+=bcc_addrs_enc.at(i)+", ";
|
|
}
|
|
msg=msg.left(msg.length()-2);
|
|
msg+="\r\n";
|
|
}
|
|
msg+="Subject: "+__RDSendMail_EncodeHeader(subject)+"\r\n";
|
|
|
|
msg+="\r\n";
|
|
msg+=raw;
|
|
|
|
if(dry_run) {
|
|
printf("*** MESSAGE STARTS ***\n");
|
|
printf("%s",msg.toAscii().constData());
|
|
printf("*** MESSAGE ENDS ***\n");
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Send message
|
|
//
|
|
args.clear();
|
|
args.push_back("-bm");
|
|
args.push_back("-t");
|
|
proc=new QProcess();
|
|
proc->start("sendmail",args);
|
|
if(!proc->waitForStarted()) {
|
|
*err_msg=QObject::tr("unable to start sendmail")+"\n";
|
|
delete proc;
|
|
return false;
|
|
}
|
|
proc->write(msg.toUtf8());
|
|
proc->closeWriteChannel();
|
|
proc->waitForFinished();
|
|
if(proc->exitStatus()!=QProcess::NormalExit) {
|
|
*err_msg=QObject::tr("sendmail crashed")+"\r\n";
|
|
delete proc;
|
|
return false;
|
|
}
|
|
if(proc->exitCode()!=0) {
|
|
*err_msg=QObject::tr("sendmail returned non-zero exit code")+
|
|
QString().sprintf(": %d [",proc->exitCode())+
|
|
QString::fromUtf8(proc->readAllStandardError())+"]\n";
|
|
delete proc;
|
|
return false;
|
|
}
|
|
delete proc;
|
|
|
|
*err_msg=QObject::tr("ok");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool RDSendMail(QString *err_msg,const QString &subject,const QString &body,
|
|
const QString &from_addr,const QString &to_addrs,
|
|
const QString &cc_addrs,const QString &bcc_addrs,bool dry_run)
|
|
{
|
|
return RDSendMail(err_msg,subject,body,from_addr,
|
|
to_addrs.split(",",QString::SkipEmptyParts),
|
|
cc_addrs.split(",",QString::SkipEmptyParts),
|
|
bcc_addrs.split(",",QString::SkipEmptyParts),
|
|
dry_run);
|
|
}
|