Переслать электронную почту, но изменить адрес FROM

У меня есть постфиксный сервер, работающий на экземпляре EC2. Я хочу переслать всю электронную почту через SES в свой личный почтовый ящик.

Проблема: AWS разрешает только адрес FROM, который проверяется в консоли AWS, а адрес FROM в этом случае может быть любым, например: twitter.com. Я не могу выполнить white-list IP-адрес моего сервера и сказать: «Принять все электронные письма из этого места независимо от отправителя» (было бы плохой идеей)

  • Может ли пользователь linux изменить свой пароль, не зная текущего пароля?
  • Время выполнения вычисления pi в сценариях оболочки
  • Включите параллельный порт и используйте его для последовательной связи (RS-232)
  • Сканер отпечатков пальцев на Asus Zenbook
  • Можно ли ограничить количество копировальных аппаратов?
  • Расшифровать второй зашифрованный LVM во время загрузки безглавых серверов?
  • Итак, мне нужно выяснить способ перенаправления моего письма с подтвержденным адресом, но я не хочу потерять адрес исходного отправителя.

    Есть ли способ сделать это?

  • Как установить последнюю версию Node js для элементарной ОС (ubuntu)
  • Как выполнить команду при входе в систему для пользователя системы без домашней папки и личного файла .bashrc?
  • Получите обновляющий список запущенных определенных процессов
  • Недопустимые подписи при запуске обновления apt-get
  • Отключение блокировки затвора с помощью setxkbmap делает ключ Shift в Emacs
  • Существуют ли программы Red Hat, но не Ubuntu?
  • 2 Solutions collect form web for “Переслать электронную почту, но изменить адрес FROM”

    Основываясь на нашем обсуждении в чате, я собираюсь предоставить вам свое хакерское, индивидуальное решение, которое изменит адрес «FROM», как мы ожидаем, и затем доставим в исходную точку назначения, но добавим «Reply-To», заголовок.

    Это очень хакерский подход, но должен манипулировать сообщениями, как вы ожидаете, прежде чем отправлять их через PostFix туда, где им нужно идти.

    Во-первых, порты PostFix необходимо изменить . Нам нужно изменить SMTP-порт Postfix на что-то иное, чем 25 чтобы наш обработчик SMTP-питонов, который мы собираемся настроить, будет работать на этом порту.

    Измените /etc/postfix/master.cf . Вы будете искать такую ​​строку:

     smtp inet n - y - - smtpd 

    Прокомментируйте эту строку, и под этой строкой используйте вместо этого:

     10025 inet n - y - - smtpd 

    Это говорит Postfix, что мы не хотим, чтобы он прослушивал стандартный SMTP-порт. Перезагрузите службу postfix, когда закончите с этим шагом.


    Далее, обработчик SMTP Python, о котором я говорил выше. Это будет обрабатывать входящие сообщения, манипулировать ими и отправлять их в PostFix в вашей системе. Предполагая, конечно, что вся почта отправляется на порт 25, даже локально.

    Этот код существует в GITHub GIST и основан на типичном примере кода сервера SMTP-сервера Python, который я где-то получил (но не помню, откуда это было жалко!), И с тех пор манипулировал.

    Код также здесь, он находится на Python 3, если вам интересно, и написано с Python 3 в качестве целевой версии Python:

     #!/usr/bin/env python3 # Libraries import smtplib import smtpd import asyncore import email import sys from datetime import datetime print('Starting custom mail handling server...') # We need to know where the SMTP server is heh. SMTP_OUTBOUND = 'localhost' # We also need to know what we want the "FROM" address to be FROM_ADDR = "foo@bar.baz" DESTINATION_ADDRESS = "foo@bar.baz" ############# ############# # SMTP SERVER ############# ############# # noinspection PyMissingTypeHints,PyBroadException class AutoForwardHandlerSMTP(smtpd.SMTPServer): def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): print('MESSAGE RECEIVED - [%s]' % datetime.now().strftime('%Y-%m-%d %H:%M:%S')) print('Receiving message from:', peer) print('Message addressed from:', mailfrom) print('Message addressed to :', rcpttos) print('Message length :', len(data)) print(data) # Flush the output buffered (handy with the nohup launch) sys.stdout.flush() # Analyze and extract data headers msg = email.message_from_string(data) orig_from = '' try: orig_from = msg['From'] msg['Reply-To'] = orig_from # We have to use 'replace header' methods to overwrite existing headers. msg.replace_header("From", FROM_ADDR) except: print("Error manipulating headers:", sys.exc_info()[0]) conn = smtplib.SMTP(SMTP_OUTBOUND, 10025) conn.sendmail(FROM_ADDR, msg["To"], msg.as_string()) # Flush the output buffered (handy with the nohup launch) print("\n\n") sys.stdout.flush() return # Listen to port 25 ( 0.0.0.0 can be replaced by the ip of your server but that will work with 0.0.0.0 ) server = AutoForwardHandlerSMTP(('0.0.0.0', 25), None) # Wait for incoming emails asyncore.loop() 

    Сохраните это как /opt/PythonAutoForwarderSMTP.py или что бы вы ни назвали. Запустите его с правами root (либо с помощью sudo либо в root строке root ), во-первых, чтобы убедиться, что он работает так, как мы ожидаем:

     python3 /opt/PythonAutoForwarderSMTP.py 

    Как только он будет подтвержден, отправьте сообщение по электронной почте через сервер. Его нужно подобрать и предоставить вам данные журнала из этого сценария, чтобы сообщение было получено и обработано. Затем вы также должны увидеть подключение к журналам Postfix, и это будет доставлено куда-нибудь после Postfix. Если все это выглядит нормально, и вы правильно обрабатываете сообщение и видите его с другим адресом «От», где, наконец, заканчивается сообщение электронной почты, мы можем работать, чтобы запустить его сейчас! (Вы можете просто нажать Ctrl + C, чтобы закрыть процесс python, прежде чем продолжить).

    Предполагая, что мы хотим, чтобы он начинался при загрузке, мы должны настроить его для этого.

    Как root , запустите crontab -e и добавьте следующее в root crontab:

     @reboot /usr/bin/python3 /opt/PythonAutoForwarderSMTP.py 2>&1 >> /var/log/PythonSMTP.log & 

    Сохраните файл crontab. Если вы не хотите перезагружать свой сервер, выполните только что добавленную командную строку, минус часть @reboot , чтобы запустить обработчик SMTP Python.

    Независимо от того, запускается ли cron или нет, процесс, который загружает Python, в конечном итоге будет раздвоен в фоновом режиме, а также выведет все данные (ошибка или иначе в консоли Python) в файл журнала в /var/log/PythonSMTP.log в Добавить режим. Таким образом, вы всегда можете получать журналы, как вам нужно.

    Если все работает так, как ожидалось, это правильно добавит заголовок Reply-To, а также отрегулирует заголовок «From» в сообщении, чтобы быть тем, кем мы его ожидаем. Я не могу гарантировать, что это будет нормально работать для проверки SPF и DKIM, если сообщения подписаны, но я могу сказать, что это будет правильно «препроцессить» сообщения, прежде чем использовать Postfix для их передачи в другом месте.


    ОБЯЗАТЕЛЬНЫЕ ОШИБКИ БЕЗОПАСНОСТИ и уведомления о функциональных изменениях:

    • Проверка отправителя DKIM может завершиться неудачей. Проверка подписи DKIM не срабатывает, когда обрабатываются сообщения, которые подписаны, что означает, что вы могли нарушить подписи DKIM от отправителей. Это означает, что все может быть вызвано спамом из-за неудачной проверки подписи. Возможно, этот сценарий настроен правильно на работу, но я не писал это для проверки DKIM / SPF.
    • Мы должны запустить этот SMTP-сервер Python с root . Это необходимо, потому что в Linux по умолчанию мы не можем привязываться к портам ниже 1024, если мы не являемся суперпользователем; поэтому Postfix имеет главный «root» процесс и выполняет подпроцессы, которые не выполняются в качестве корневого пользователя очень долго, только для привязки к порту.
    • Вся почта на порт 25 будет проходить через этот SMTP-сервер Python . Если Postfix также обрабатывает почту извне-> в, то SMTP-сервер Python займет свое место. Это может привести к некоторым золям, но в конечном итоге делает то, о чем вы просите.
    • Это хрупкое решение. Хотя это не так хрупко, как некоторые другие решения, если процесс Python умирает, он не возвращается автоматически, поэтому вам приходится обрабатывать ошибки в каждом конкретном случае и иногда оживлять процесс Python, если он полностью исчезает.

    Как всегда, вы не должны запускать ничего как root, если не знаете, что делаете. В этом случае я предоставляю код для этого в простом виде, чтобы вы могли различить для себя, что делает этот скрипт, и хотите ли вы запустить его как root или нет, если вы ориентированы на безопасность и параноидальные, как я (я профессионал ИТ-безопасности, а также системный администратор, поэтому прощайте эти обязательные уведомления)

    Как и отличный ответ от @Thomas Ward AWS имеет «предпочтительный» способ, который очень похож, единственное отличие состоит в том, что он использует внутренние инструменты AWS для выполнения задачи вместо внешнего скрипта python.

    Существует одно ключевое различие между этим подходом и другим, этот подход будет осуществлять проверку вирусов и вредоносных программ, а также проверки DKIM и SPF, которые вы можете проверить и посмотреть, будут ли они PASS .

    Итак, я следил за этим README GitHub здесь: https://github.com/arithmetric/aws-lambda-ses-forwarder

    Все из-за этого скрипта. Вы размещаете его на AWS Lambda, и он будет обрабатывать электронные письма для правил SES.

    Вот копия части установки README :

    Примечание : измените такие вещи, как S3-BUCKET-NAME .

    1. Измените значения в объекте config в верхней части index.js чтобы указать ведро S3 и префикс объекта для поиска электронных писем, хранящихся в SES. Также укажите сопоставление пересылки электронной почты из исходных пунктов назначения в новое место назначения.

    2. В AWS Lambda добавьте новую функцию и пропустите выбор чертежа.

      • Назовите функцию «SesForwarder» и опционально дайте ей описание. Убедитесь, что для Runtime установлено значение Node.js 4.3 или 6.10.

      • Для кода функции Lambda скопируйте и вставьте содержимое index.js в редактор встроенного кода или index.js содержимое репозитория и загрузите его напрямую или через S3.

      • Убедитесь, что обработчик установлен в index.handler .

      • Для роли выберите «Основная роль выполнения» в разделе «Создать новую роль». Во всплывающем окне укажите роль имени (например, LambdaSesForwarder). Настройте политику ролей следующим образом: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": "ses:SendRawEmail", "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*" } ] }

      • Память может быть оставлена ​​на уровне 128 МБ, но установите время ожидания на 10 секунд, чтобы быть в безопасности. Задача обычно занимает около 30 МБ и несколько секунд. После тестирования задачи вы можете уменьшить ограничение времени ожидания.

    3. В AWS SES проверьте домены, для которых вы хотите получать и пересылать электронную почту. Также настройте запись DNS MX для этих доменов, чтобы указать на получающую (или входящую) конечную точку SES. См. Документацию по SES для получающих электронную почту конечных точек в каждом регионе.

    4. Если у вас есть уровень доступа к SES для песочницы, проверьте также все адреса электронной почты, на которые вы хотите перенаправить электронную почту, которые не находятся на проверенных доменах.

    5. Если вы не настроили обработку входящей электронной почты, создайте новый набор правил. В противном случае вы можете использовать существующий.

    6. Создайте правило для обработки функций пересылки электронной почты.

      • На странице «Конфигурация получателей» добавьте все адреса электронной почты, с которых вы хотите переслать электронную почту.

      • На странице конфигурации Действия сначала добавьте действие S3, а затем действие Lambda.

      • Для действия S3: Создайте или выберите существующий ведро S3. При необходимости добавьте префикс ключа объекта. Отключить сообщение «Шифровать сообщение», а тема «SNS» установлена ​​на [none].

      • Для действия Lambda: выберите функцию SesForwarder Lambda. Оставьте тип Invocation для Event и SNS Topic установлен на [none].

      • Закончите, назвав правило, гарантируя его включение и проверку спама и вирусов.

      • Если вы получите сообщение об ошибке «Не удалось записать в ведро», выполните шаг 7, прежде чем завершать этот

      • Если вас попросят SES попытаться добавить разрешения для доступа к лямбда: InvokeFunction, соглашайтесь с ним.

    7. Политику байтов S3 необходимо настроить таким образом, чтобы ваш пользователь IAM имел доступ на чтение и запись в ведро S3. Когда вы настраиваете действие S3 в SES, оно может добавить оператор политики bucket, который запрещает всем пользователям, кроме корневого доступа, получать объекты. Это вызывает проблемы доступа из сценария Lambda, поэтому вам, скорее всего, придется скорректировать оператор политики bucket одним из следующих { "Version": "2012-10-17", "Statement": [ { "Sid": "GiveSESPermissionToWriteEmail", "Effect": "Allow", "Principal": { "Service": "ses.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*", "Condition": { "StringEquals": { "aws:Referer": "AWS-ACCOUNT-ID" } } } ] } : { "Version": "2012-10-17", "Statement": [ { "Sid": "GiveSESPermissionToWriteEmail", "Effect": "Allow", "Principal": { "Service": "ses.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*", "Condition": { "StringEquals": { "aws:Referer": "AWS-ACCOUNT-ID" } } } ] }

    8. Необязательно установите жизненный цикл S3 для этого ведра для удаления / истечения срока действия объектов через несколько дней для очистки сохраненных писем.

    Я отправляю версию сценария со времени создания этого ответа с одним или двумя изменениями.

    Я заметил, что эти письма, которые были перенаправлены дважды через проверенные домены, были изменены этим сценарием, поэтому для удивительной перспективы я исправил, что

     "use strict"; var AWS = require('aws-sdk'); console.log("AWS Lambda SES Forwarder // @arithmetric // Version 4.2.0"); // Configure the S3 bucket and key prefix for stored raw emails, and the // mapping of email addresses to forward from and to. // // Expected keys/values: // // - fromEmail: Forwarded emails will come from this verified address // // - subjectPrefix: Forwarded emails subject will contain this prefix // // - emailBucket: S3 bucket name where SES stores emails. // // - emailKeyPrefix: S3 key name prefix where SES stores email. Include the // trailing slash. // // - forwardMapping: Object where the key is the lowercase email address from // which to forward and the value is an array of email addresses to which to // send the message. // // To match all email addresses on a domain, use a key without the name part // of an email address before the "at" symbol (ie `@example.com`). // // To match a mailbox name on all domains, use a key without the "at" symbol // and domain part of an email address (ie `info`). var defaultConfig = { fromEmail: "", subjectPrefix: "", emailBucket: "ses-sammaye", emailKeyPrefix: "email/", forwardMapping: { "@vvv.com": [ "sammaye@xxx.com" ], "@fff.com": [ "sammaye@xxx.com" ], "@ggg.com": [ "sammaye@xxx.com" ], }, verifiedDomains: [ 'vvv.com', 'fff.com', 'ggg.com' ] }; /** * Parses the SES event record provided for the `mail` and `receipients` data. * * @param {object} data - Data bundle with context, email, etc. * * @return {object} - Promise resolved with data. */ exports.parseEvent = function(data) { // Validate characteristics of a SES event record. if (!data.event || !data.event.hasOwnProperty('Records') || data.event.Records.length !== 1 || !data.event.Records[0].hasOwnProperty('eventSource') || data.event.Records[0].eventSource !== 'aws:ses' || data.event.Records[0].eventVersion !== '1.0') { data.log({message: "parseEvent() received invalid SES message:", level: "error", event: JSON.stringify(data.event)}); return Promise.reject(new Error('Error: Received invalid SES message.')); } data.email = data.event.Records[0].ses.mail; data.recipients = data.event.Records[0].ses.receipt.recipients; return Promise.resolve(data); }; /** * Transforms the original recipients to the desired forwarded destinations. * * @param {object} data - Data bundle with context, email, etc. * * @return {object} - Promise resolved with data. */ exports.transformRecipients = function(data) { var newRecipients = []; data.originalRecipients = data.recipients; data.recipients.forEach(function(origEmail) { var origEmailKey = origEmail.toLowerCase(); if (data.config.forwardMapping.hasOwnProperty(origEmailKey)) { newRecipients = newRecipients.concat( data.config.forwardMapping[origEmailKey]); data.originalRecipient = origEmail; } else { var origEmailDomain; var origEmailUser; var pos = origEmailKey.lastIndexOf("@"); if (pos === -1) { origEmailUser = origEmailKey; } else { origEmailDomain = origEmailKey.slice(pos); origEmailUser = origEmailKey.slice(0, pos); } if (origEmailDomain && data.config.forwardMapping.hasOwnProperty(origEmailDomain)) { newRecipients = newRecipients.concat( data.config.forwardMapping[origEmailDomain]); data.originalRecipient = origEmail; } else if (origEmailUser && data.config.forwardMapping.hasOwnProperty(origEmailUser)) { newRecipients = newRecipients.concat( data.config.forwardMapping[origEmailUser]); data.originalRecipient = origEmail; } } }); if (!newRecipients.length) { data.log({message: "Finishing process. No new recipients found for " + "original destinations: " + data.originalRecipients.join(", "), level: "info"}); return data.callback(); } data.recipients = newRecipients; return Promise.resolve(data); }; /** * Fetches the message data from S3. * * @param {object} data - Data bundle with context, email, etc. * * @return {object} - Promise resolved with data. */ exports.fetchMessage = function(data) { // Copying email object to ensure read permission data.log({level: "info", message: "Fetching email at s3://" + data.config.emailBucket + '/' + data.config.emailKeyPrefix + data.email.messageId}); return new Promise(function(resolve, reject) { data.s3.copyObject({ Bucket: data.config.emailBucket, CopySource: data.config.emailBucket + '/' + data.config.emailKeyPrefix + data.email.messageId, Key: data.config.emailKeyPrefix + data.email.messageId, ACL: 'private', ContentType: 'text/plain', StorageClass: 'STANDARD' }, function(err) { if (err) { data.log({level: "error", message: "copyObject() returned error:", error: err, stack: err.stack}); return reject( new Error("Error: Could not make readable copy of email.")); } // Load the raw email from S3 data.s3.getObject({ Bucket: data.config.emailBucket, Key: data.config.emailKeyPrefix + data.email.messageId }, function(err, result) { if (err) { data.log({level: "error", message: "getObject() returned error:", error: err, stack: err.stack}); return reject( new Error("Error: Failed to load message body from S3.")); } data.emailData = result.Body.toString(); return resolve(data); }); }); }); }; /** * Processes the message data, making updates to recipients and other headers * before forwarding message. * * @param {object} data - Data bundle with context, email, etc. * * @return {object} - Promise resolved with data. */ exports.processMessage = function(data) { var match = data.emailData.match(/^((?:.+\r?\n)*)(\r?\n(?:.*\s+)*)/m); var header = match && match[1] ? match[1] : data.emailData; var body = match && match[2] ? match[2] : ''; // Add "Reply-To:" with the "From" address if it doesn't already exists if (!/^Reply-To: /mi.test(header)) { match = header.match(/^From: (.*(?:\r?\n\s+.*)*\r?\n)/m); var from = match && match[1] ? match[1] : ''; if (from) { header = header + 'Reply-To: ' + from; data.log({level: "info", message: "Added Reply-To address of: " + from}); } else { data.log({level: "info", message: "Reply-To address not added because " + "From address was not properly extracted."}); } } // SES does not allow sending messages from an unverified address, // so replace the message's "From:" header with the original // recipient (which is a verified domain) header = header.replace( /^From: (.*(?:\r?\n\s+.*)*)/mg, function(match, from) { var fromText; var fromEmailDomain = from.replace(/(.*)</, '').replace(/.*@/, "").replace('>', '').trim(); if (data.config.verifiedDomains.indexOf(fromEmailDomain) === -1) { if (data.config.fromEmail) { fromText = 'From: ' + from.replace(/<(.*)>/, '').trim() + ' <' + data.config.fromEmail + '>'; } else { fromText = 'From: ' + from.replace('<', 'at ').replace('>', '') + ' <' + data.originalRecipient + '>'; } } else { fromText = 'From: ' + from; } return fromText; }); // Add a prefix to the Subject if (data.config.subjectPrefix) { header = header.replace( /^Subject: (.*)/mg, function(match, subject) { return 'Subject: ' + data.config.subjectPrefix + subject; }); } // Replace original 'To' header with a manually defined one if (data.config.toEmail) { header = header.replace(/^To: (.*)/mg, () => 'To: ' + data.config.toEmail); } // Remove the Return-Path header. header = header.replace(/^Return-Path: (.*)\r?\n/mg, ''); // Remove Sender header. header = header.replace(/^Sender: (.*)\r?\n/mg, ''); // Remove Message-ID header. header = header.replace(/^Message-ID: (.*)\r?\n/mig, ''); // Remove all DKIM-Signature headers to prevent triggering an // "InvalidParameterValue: Duplicate header 'DKIM-Signature'" error. // These signatures will likely be invalid anyways, since the From // header was modified. header = header.replace(/^DKIM-Signature: .*\r?\n(\s+.*\r?\n)*/mg, ''); data.emailData = header + body; return Promise.resolve(data); }; /** * Send email using the SES sendRawEmail command. * * @param {object} data - Data bundle with context, email, etc. * * @return {object} - Promise resolved with data. */ exports.sendMessage = function(data) { var params = { Destinations: data.recipients, Source: data.originalRecipient, RawMessage: { Data: data.emailData } }; data.log({level: "info", message: "sendMessage: Sending email via SES. " + "Original recipients: " + data.originalRecipients.join(", ") + ". Transformed recipients: " + data.recipients.join(", ") + "."}); return new Promise(function(resolve, reject) { data.ses.sendRawEmail(params, function(err, result) { if (err) { data.log({level: "error", message: "sendRawEmail() returned error.", error: err, stack: err.stack}); return reject(new Error('Error: Email sending failed.')); } data.log({level: "info", message: "sendRawEmail() successful.", result: result}); resolve(data); }); }); }; /** * Handler function to be invoked by AWS Lambda with an inbound SES email as * the event. * * @param {object} event - Lambda event from inbound email received by AWS SES. * @param {object} context - Lambda context object. * @param {object} callback - Lambda callback object. * @param {object} overrides - Overrides for the default data, including the * configuration, SES object, and S3 object. */ exports.handler = function(event, context, callback, overrides) { var steps = overrides && overrides.steps ? overrides.steps : [ exports.parseEvent, exports.transformRecipients, exports.fetchMessage, exports.processMessage, exports.sendMessage ]; var data = { event: event, callback: callback, context: context, config: overrides && overrides.config ? overrides.config : defaultConfig, log: overrides && overrides.log ? overrides.log : console.log, ses: overrides && overrides.ses ? overrides.ses : new AWS.SES(), s3: overrides && overrides.s3 ? overrides.s3 : new AWS.S3({signatureVersion: 'v4'}) }; Promise.series(steps, data) .then(function(data) { data.log({level: "info", message: "Process finished successfully."}); return data.callback(); }) .catch(function(err) { data.log({level: "error", message: "Step returned error: " + err.message, error: err, stack: err.stack}); return data.callback(new Error("Error: Step returned error.")); }); }; Promise.series = function(promises, initValue) { return promises.reduce(function(chain, promise) { if (typeof promise !== 'function') { return Promise.reject(new Error("Error: Invalid promise item: " + promise)); } return chain.then(promise); }, Promise.resolve(initValue)); }; 
    Linux и Unix - лучшая ОС в мире.