Server : Apache/2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.4.6 System : Windows NT USER-PC 6.1 build 7601 (Windows 7 Professional Edition Service Pack 1) AMD64 User : User ( 0) PHP Version : 7.4.6 Disable Function : NONE Directory : C:/xampp/sendmail/source/ |
program sendmail; { <license.txt> fake sendmail for windows Copyright (c) 2004-2011, Byron Jones, sendmail@glob.com.au All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the glob nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. </license.txt> <ChangeLog.txt> version 32 (18 june 2011) - fix handling of invalid recipients version 31 (15 sep, 2010) - fix encoding of 8-bit data version 30 (30 aug, 2010) - update to latest indy version (fixes many issues) - add about/version version 29 (sep 8, 2009) - fix for another indy 10 "range check error" (when using ssl) version 28 (aug 12, 2009) - set ERRORLEVEL to -1 to assist php version 27 (aug 3, 2009) - don't treat log write errors as fatal version 26 (apr 1, 2009) - no longer require -t parameter - skip first line if it starts with "from " (mail spool delimiting line) version 25 (mar 29, 2009) - added force_recipient version 24 (dec 2, 2008) - fixes for ssl version 23 (apr 24, 2008) - fix timezone in date header version 22 (jan 14, 2008) - fixes to error handling version 21 (jan 2, 2008) - added TLS support version 20 (apr 3, 2007) - fixed race condition in IIS's pickup delivery version 19 (jul 24, 2006) - added support for delivery via IIS's pickup directory - optionally reads settings from the registry (in absense of the ini file) version 18 (may 1, 2006) - fix for indy 10 "range check error" version 17 (nov 2, 2005) - only process message header - optionally use madexcept for detailed crash dumps version 16 (sep 12, 2005) - send hostname and domain with HELO/EHLO - configurable HELO/EHLO hostname - upgraded to indy 10 version 15 (aug 23, 2005) - fixes error messages when debug_logfile is not specified version 14 (jun 28, 2005) - errors output to STDERR - fixes for delphi 7 compilation - added 'connecting to..' debug logging - reworked error and debug log format version 13 (jun 8, 2005) - added fix to work around invalid multiple header instances version 12 (apr 30, 2005) - added cc and bcc support version 11 (feb 17, 2005) - added pop3 support (for pop before smtp authentication) version 10 (feb 11, 2005) - added support for specifying a different smtp port version 9 (sep 22, 2004) - added force_sender version 8 (sep 22, 2004) - *really* fixes broken smtp auth version 7 (sep 22, 2004) - fixes broken smtp auth version 6 (sep 22, 2004) - correctly quotes MAIL FROM and RCPT TO addresses in <> version 5 (sep 16, 2004) - now sends the message unchanged (rather than getting indy to regenerate it) version 4 (aug 17, 2004) - added debug_logfile parameter - improved error messages version 3 (jul 15, 2004) - smtp authentication support - clearer error message when missing from or to address - optional error logging - adds date: if missing version 2 (jul 6, 2004) - reads default domain from registry (.ini setting overrides) version 1 (jul 1, 2004) - initial release </ChangeLog.txt> requires indy 10.2 or higher i use a Tiburon branch svn pull https://svn.atozed.com:444/svn/Indy10/branches/Tiburon http://www.indyproject.org/Sockets/Docs/Indy10Installation.EN.aspx } {$APPTYPE CONSOLE} {$I IdCompilerDefines.inc} {$IFNDEF INDY100}indy version 10 is required; built against 10_5_6{$ENDIF} {$DEFINE USE_MADEXCEPT} uses Windows, Classes, SysUtils, Registry, IniFiles, IdGlobal, IdResourceStringsCore, IdGlobalProtocols, IdResourceStrings, IdExplicitTLSClientServerBase, IDSmtp, IDPOP3, IdMessage, IdEmailAddress, IdLogFile, IdWinSock2, IdIOHandler, IdSSLOpenSSL, IdException {$IFDEF USE_MADEXCEPT} , madExcept, madLinkDisAsm, madListHardware, madListProcesses, madListModules {$ENDIF} ; // --------------------------------------------------------------------------- const VERSION = '32'; // --------------------------------------------------------------------------- function buildLogLine(data, prefix: string) : string; // ensure the output of error and debug logs are in the same format, regardless of source begin data := StringReplace(data, EOL, RSLogEOL, [rfReplaceAll]); data := StringReplace(data, CR, RSLogCR, [rfReplaceAll]); data := StringReplace(data, LF, RSLogLF, [rfReplaceAll]); result := FormatDateTime('yy/mm/dd hh:nn:ss', now) + ' '; if (prefix <> '') then result := result + prefix + ' '; result := result + data + EOL; end; // --------------------------------------------------------------------------- type // TidLogFile using buildLogLine function TlogFile = class(TidLogFile) protected procedure LogReceivedData(const AText, AData: string); override; procedure LogSentData(const AText, AData: string); override; procedure LogStatus(const AText: string); override; public procedure LogWriteString(const AText: string); override; end; // --------------------------------------------------------------------------- procedure TlogFile.LogReceivedData(const AText, AData: string); begin // ignore AText as it contains the date/time LogWriteString(buildLogLine(Adata, '<<')); end; // --------------------------------------------------------------------------- procedure TlogFile.LogSentData(const AText, AData: string); begin // ignore AText as it contains the date/time LogWriteString(buildLogLine(Adata, '>>')); end; // --------------------------------------------------------------------------- procedure TlogFile.LogStatus(const AText: string); begin LogWriteString(buildLogLine(AText, '**')); end; // --------------------------------------------------------------------------- procedure TlogFile.LogWriteString(const AText: string); begin // protected --> public inherited; end; // --------------------------------------------------------------------------- var errorLogFile: string; debugLogFile: string; debug : TlogFile; // --------------------------------------------------------------------------- procedure writeToLog(const logFilename, logMessage: string; const prefix: string = ''); var f: TextFile; begin AssignFile(f, logFilename); try if (not FileExists(logFilename)) then begin ForceDirectories(ExtractFilePath(logFilename)); Rewrite(f); end else Append(f); write(f, buildLogLine(logMessage, prefix)); closeFile(f); except on e:Exception do writeln(ErrOutput, 'sendmail: Error writing to ' + logFilename + ': ' + logMessage); end; end; // --------------------------------------------------------------------------- procedure debugLog(const logMessage: string); begin if (debug <> nil) and (debug.Active) then debug.LogWriteString(buildLogLine(logMessage, '**')) else if (debugLogFile <> '') then writeToLog(debugLogFile, logMessage, '**'); end; // --------------------------------------------------------------------------- procedure errorLog(const logMessage: string); begin if (errorLogFile <> '') then writeToLog(errorLogFile, logMessage, ':'); debugLog(logMessage); end; // --------------------------------------------------------------------------- function appendDomain(const address, domain: string): string; begin Result := address; if (Pos('@', address) <> 0) then Exit; Result := Result + '@' + domain; end; // --------------------------------------------------------------------------- function joinMultiple(const messageContent: string; fieldName: string): string; // the rfc says that some fields are only allowed once in a message header // for example, to, from, subject // this function joins multiple instances of the specified field into a single comma seperated line var sl : TstringList; i : integer; s : string; n : integer; count : integer; values: TstringList; begin fieldName := LowerCase(fieldName); sl := TStringList.Create; values := TStringList.Create; try sl.text := messageContent; result := ''; // only modify the header if there's more than one instance of the field count := 0; for i := 0 to sl.count - 1 do begin s := sl[i]; if (s = '') then break; n := pos(':', s); if (n = 0) then break; if (lowerCase(copy(s, 1, n - 1)) = fieldName) then inc(count); end; if (count <= 1) then begin result := messageContent; exit; end; // more than on instance of the field, combine into single entry, ignore fields with empty values while (sl.count > 0) do begin s := sl[0]; if (s = '') then break; n := pos(':', s); if (n = 0) then break; if (lowerCase(copy(s, 1, n - 1)) = fieldName) then begin s := trim(copy(s, n + 1, length(s))); if (s <> '') then values.Add(s); end else result := result + s + #13#10; sl.Delete(0); end; if (values.count <> 0) then begin s := UpCaseFirst(fieldName) + ': '; for i := 0 to values.count - 1 do s := s + values[i] + ', '; setLength(s, length(s) - 2); result := result + s + #13#10; end; result := result + sl.Text; finally values.Free; sl.free; end; end; // --------------------------------------------------------------------------- function DateTimeToInternetStr(const Value: TDateTime): string; var day : word; month: word; year : word; begin DecodeDate(Value, year, month, day); Result := Format( '%s, %d %s %d %s %s', [ wdays[DayOfWeek(Value)], day, monthnames[month], year, FormatDateTime('HH":"mm":"ss', Value), UTCOffsetToStr(OffsetFromUTC, false) ] ); end; // --------------------------------------------------------------------------- {$IFDEF USE_MADEXCEPT} procedure madExceptionHandler(const exceptIntf: IMEException; var handled: boolean); var path: string; i : integer; fs : TFileStream; s : string; begin handled := true; path := extractFilePath(debugLogFile); deleteFile(path + 'crash-5.txt'); for i := 4 downto 1 do if (fileExists(path + 'crash-' + intToStr(i) + '.txt')) then RenameFile(path + 'crash-'+ intToStr(i) + '.txt', path + 'crash-' + intToStr(i + 1) + '.txt'); if (fileExists(path + 'crash.txt')) then RenameFile(path + 'crash.txt', path + 'crash-1.txt'); fs := TFileStream.Create(path + 'crash.txt', fmCreate); try s := exceptIntf.GetBugReport; fs.Write(s[1], length(s)); finally fs.free; end; ExitProcess(DWORD(-1)); end; {$ENDIF} // --------------------------------------------------------------------------- var smtpServer : string; smtpPort : string; smtpSSL : (ssAuto, ssSSL, ssTLS, ssNone); defaultDomain : string; messageContent: string; authUsername : string; authPassword : string; forceSender : string; forceRcpt : string; pop3server : string; pop3username : string; pop3password : string; hostname : string; isPickup : boolean; reg : TRegistry; ini : TCustomIniFile; pop3: TIdPop3; smtp: TIdSmtp; i : integer; s : string; ss : TStringStream; msg : TIdMessage; sl : TStringList; header: boolean; fs : TFileStream; validRecipientCount: integer; begin // command line help if (ParamStr(1) = '-h') then begin writeln('fake sendmail version ' + VERSION); writeln('http://glob.com.au/sendmail'); halt(1); end; // read default domain from registry reg := TRegistry.Create; try reg.RootKey := HKEY_LOCAL_MACHINE; if (reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters')) then defaultDomain := reg.ReadString('Domain'); finally reg.Free; end; // read ini s := ChangeFileExt(ParamStr(0), '.ini'); if (FileExists(s)) then ini := TIniFile.Create(s) else begin ini := TRegistryIniFile.Create('\software'); TRegistryIniFile(ini).RegIniFile.RootKey := HKEY_LOCAL_MACHINE; TRegistryIniFile(ini).RegIniFile.OpenKey(TRegistryIniFile(ini).FileName, true); end; try smtpServer := ini.ReadString('sendmail', 'smtp_server', 'mail.mydomain.com'); smtpPort := ini.ReadString('sendmail', 'smtp_port', '25'); defaultDomain := ini.ReadString('sendmail', 'default_domain', defaultDomain); hostname := ini.ReadString('sendmail', 'hostname', ''); errorLogFile := ini.ReadString('sendmail', 'error_logfile', ''); debugLogFile := ini.ReadString('sendmail', 'debug_logfile', ''); authUsername := ini.ReadString('sendmail', 'auth_username', ''); authPassword := ini.ReadString('sendmail', 'auth_password', ''); forceSender := ini.ReadString('sendmail', 'force_sender', ''); forceRcpt := ini.ReadString('sendmail', 'force_recipient', ''); pop3server := ini.ReadString('sendmail', 'pop3_server', ''); pop3username := ini.ReadString('sendmail', 'pop3_username', ''); pop3password := ini.ReadString('sendmail', 'pop3_password', ''); s := LowerCase(ini.ReadString('sendmail', 'smtp_ssl', 'auto')); if (s = 'ssl') then smtpSSL := ssSSL else if (s = 'tls') then smtpSSL := ssTLS else if (s = 'none') then smtpSSL := ssNone else smtpSSL := ssAuto; if (smtpServer = 'mail.mydomain.com') or (defaultDomain = 'mydomain.com') then begin writeln(ErrOutput, 'You must configure the smtp_server and default_domain in:'); writeln(ErrOutput, ' ' + ini.fileName); writeln(ErrOutput, ' or'); writeln(ErrOutput, ' HKLM\Software\Sendmail'); ExitProcess(DWORD(-1)); end; finally ini.Free; end; if (errorLogFile <> '') and (ExtractFilePath(errorLogFile) = '') then errorLogFile := ExtractFilePath(ParamStr(0)) + errorLogFile; if (debugLogFile <> '') and (ExtractFilePath(debugLogFile) = '') then debugLogFile := ExtractFilePath(ParamStr(0)) + debugLogFile; isPickup := DirectoryExists(smtpServer); if (isPickup) then smtpServer := IncludeTrailingPathDelimiter(smtpServer); s := ParamStr(1); if (s <> '') and (s[1] <> '-') and (FileExists(s)) then begin // read email from file fs := TFileStream.Create(ParamStr(1), fmOpenRead + fmShareDenyWrite); try setLength(messageContent, fs.Size); fs.Read(messageContent[1], length(messageContent)); finally fs.Free; end; end else begin // read email from stdin messageContent := ''; while (not eof(Input)) do begin readln(Input, s); if (messageContent = '') and (copy(s, 1, 5) = 'From ') then continue; messageContent := messageContent + s + #13#10; end; end; // make sure message is CRLF delimited if (pos(#10, messageContent) = 0) then messageContent := stringReplace(messageContent, #13, #13#10, [rfReplaceAll]); if (debugLogFile <> '') then begin debugLog('--- MESSAGE BEGIN ---'); sl := TStringList.Create; try sl.Text := messageContent; for i := 0 to sl.Count - 1 do debugLog(sl[i]); finally sl.Free; end; debugLog('--- MESSAGE END ---'); end; // fix multiple to, cc, bcc and subject fields messageContent := joinMultiple(messageContent, 'to'); messageContent := joinMultiple(messageContent, 'cc'); messageContent := joinMultiple(messageContent, 'bcc'); messageContent := joinMultiple(messageContent, 'subject'); // deliver message {$IFDEF USE_MADEXCEPT} RegisterExceptionHandler(madExceptionHandler, stTrySyncCallAlways); {$ENDIF} try if (isPickup) then begin // drop to IIS's pickup directory ForceDirectories(smtpServer + 'Temp'); // generate filename (in the temp directory) setLength(s, MAX_PATH); if (GetTempFileName(pChar(smtpServer + 'Temp'), 'sm', 0, @s[1]) = 0) then RaiseLastOSError; s := strPas(pChar(s)); // write fs := TFileStream.Create(s, fmCreate); try fs.Write(messageContent[1], length(messageContent)); finally fs.free; end; // move into the real pickup directory if (not RenameFile(s, smtpServer + ChangeFileExt(ExtractFileName(s), '.eml'))) then RaiseLastOSError; RemoveDir(smtpServer + 'Temp'); end else begin // deliver via smtp // load message into stream ss := TStringStream.Create(messageContent); msg := nil; try // load message msg := TIdMessage.Create(nil); try msg.LoadFromStream(ss, true); except on e:exception do raise exception.create('Failed to read email message: ' + e.message); end; // check for from and to if (forceSender = '') and (Msg.From.Address = '') then raise Exception.Create('Message is missing sender''s address'); if (forceRcpt = '') and (Msg.Recipients.Count = 0) and (Msg.CCList.Count = 0) and (Msg.BccList.Count = 0) then raise Exception.Create('Message is missing recipient''s address'); if (debugLogFile <> '') then begin try debug := TlogFile.Create(nil); debug.FileName := debugLogFile; debug.Active := True; except // silently ignore debug := nil; end; end else debug := nil; if ((pop3server <> '') and (pop3username <> '')) then begin // pop3 before smtp auth debugLog('Authenticating with POP3 server'); pop3 := TIdPOP3.Create(nil); try if (debug <> nil) then begin pop3.IOHandler := TIdIOHandler.MakeDefaultIOHandler(pop3); pop3.IOHandler.Intercept := debug; pop3.IOHandler.OnStatus := pop3.OnStatus; pop3.ManagedIOHandler := True; end; pop3.Host := pop3server; pop3.Username := pop3username; pop3.Password := pop3password; pop3.ConnectTimeout := 10 * 1000; pop3.Connect; pop3.Disconnect; finally pop3.free; end; end; smtp := TIdSMTP.Create(nil); try // if openSSL libraries are available, use SSL for TLS support smtp.IOHandler := nil; smtp.ManagedIOHandler := True; if (smtpSSL <> ssNone) then begin try TIdSSLContext.Create.Free; smtp.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(smtp); if (smtpSSL = ssAuto) then if (strToIntDef(smtpPort, 25) = 465) then smtpSSL := ssSSL else smtpSSL := ssTLS; if (smtpSSL = ssSSL) then smtp.UseTLS := utUseImplicitTLS else smtp.UseTLS := utUseExplicitTLS; except on e:exception do begin debugLog('Failed to load SSL libraries: ' + e.message); smtp.IOHandler := nil; end; end; end; if (smtp.IOHandler = nil) then begin smtp.IOHandler := TIdIOHandler.MakeDefaultIOHandler(smtp); smtp.UseTLS := utNoTLSSupport; end; if (debug <> nil) then begin smtp.IOHandler.Intercept := debug; smtp.IOHandler.OnStatus := smtp.OnStatus; end; // set host, port i := pos(':', smtpServer); if (i = 0) then begin smtp.host := smtpServer; smtp.port := strToIntDef(smtpPort, 25); end else begin smtp.host := copy(smtpServer, 1, i - 1); smtp.port := strToIntDef(copy(smtpServer, i + 1, length(smtpServer)), 25); end; // set hostname (for helo/ehlo) if (hostname = '') then begin setLength(hostname, 255); GetHostName(pChar(hostname), length(hostname)); hostname := string(pChar(hostname)); if (pos('.', hostname) = 0) and (defaultDomain <> '') then hostname := hostname + '.' + defaultDomain; end; smtp.HeloName := hostname; // connect to server debugLog('Connecting to ' + smtp.Host + ':' + intToStr(smtp.Port)); smtp.ConnectTimeout := 10 * 1000; smtp.Connect; // set up authentication if (authUsername <> '') then begin debugLog('Authenticating as ' + authUsername); smtp.AuthType := satDefault; smtp.Username := authUsername; smtp.Password := authPassword; end; // authenticate and start tls smtp.Authenticate; // sender and recipients validRecipientCount := 0; if (forceSender = '') then smtp.SendCmd('MAIL FROM: <' + appendDomain(Msg.From.Address, defaultDomain) + '>', [250]) else smtp.SendCmd('MAIL FROM: <' + appendDomain(forceSender, defaultDomain) + '>', [250]); if (forceRcpt = '') then begin for i := 0 to msg.Recipients.Count - 1 do if (smtp.SendCmd('RCPT TO: <' + appendDomain(Msg.Recipients[i].Address, defaultDomain) + '>', [250, 550]) = 250) then inc(validRecipientCount) else errorLog('Invalid recipient <' + appendDomain(Msg.Recipients[i].Address, defaultDomain) + '>'); for i := 0 to msg.ccList.Count - 1 do if (smtp.SendCmd('RCPT TO: <' + appendDomain(Msg.ccList[i].Address, defaultDomain) + '>', [250, 550]) = 250) then inc(validRecipientCount) else errorLog('Invalid recipient <' + appendDomain(Msg.ccList[i].Address, defaultDomain) + '>'); for i := 0 to msg.BccList.Count - 1 do if (smtp.SendCmd('RCPT TO: <' + appendDomain(Msg.BccList[i].Address, defaultDomain) + '>', [250, 550]) = 250) then inc(validRecipientCount) else errorLog('Invalid recipient <' + appendDomain(Msg.BccList[i].Address, defaultDomain) + '>'); end else if (smtp.SendCmd('RCPT TO: <' + appendDomain(forceRcpt, defaultDomain) + '>', [250, 550]) = 250) then inc(validRecipientCount) else errorLog('Invalid recipient <' + appendDomain(forceRcpt, defaultDomain) + '>'); if (validRecipientCount = 0) then raise Exception.Create('No valid recipients were found'); // start message content smtp.SendCmd('DATA', [354]); // add date header if missing if (Msg.Headers.Values['date'] = '') then smtp.IOHandler.WriteLn('Date: ' + DateTimeToInternetStr(Now)); // send message line by line sl := TStringList.Create; try sl.Text := messageContent; header := true; for i := 0 to sl.Count - 1 do begin if (i = 0) and (sl[i] = '') then continue; if (sl[i] = '') then header := false; if (header) and (LowerCase(copy(sl[i], 1, 5)) = 'bcc: ') then continue; smtp.IOHandler.WriteLn(sl[i], TIdTextEncoding.Default); end; finally sl.Free; end; // done smtp.SendCmd('.', [250]); try smtp.SendCmd('QUIT'); except on e:EIdConnClosedGracefully do ;// ignore on e:Exception do raise; end; finally if (smtp.Connected) then debugLog('Disconnecting from ' + smtp.Host + ':' + intToStr(smtp.Port)); smtp.Free; end; finally msg.Free; ss.Free; end; end; except on e:Exception do begin writeln(ErrOutput, 'sendmail: Error during delivery: ' + e.message); errorLog(e.Message); {$IFDEF USE_MADEXCEPT} raise; {$ELSE} ExitProcess(DWORD(-1)); {$ENDIF} end; end; end.