Turn off SSL certificate verification in Delphi - indy

I have a Web Service running on Windows Server 2012R2 with sTunnel.
When using Postman I have to turn of SSL verification for it to work or I get a: no connection error.
Some of my client using our Delphi Windows application get the Error HTTP 1.1 500 and the message Reject due to policy restriction.
The following shows in the sTunnel log: SSL routines: ssl3_read_bytes: sslv3 alert certificate unknow
I have the latest open SSl dll in the System32 folder.
I don't know if I can turn something on/off in Delphi or in sTunnel.
Here is the code for sending the SMS and the send the result to my webserver.
procedure SendSMS.Execute;
var
JsonToSend: TStringStream;
url, SMSText, Rtext, AppId, Json: String;
IdHTTP1: TIdHTTP;
IdSSLIOHandlerSocketOpenSSL2: TIdSSLIOHandlerSocketOpenSSL;
jsonRecived: TJSONObject;
begin
AppId := 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx';
mySMSSent := False;
if (Length(DataM1.ComTbl.FieldByName('SMSToken').AsString) > 10) and (Length(SMSMessageText) > 3) then
begin
SMSText := StringReplace(SMSMessageText,#$A,'\n',[rfReplaceAll, rfIgnoreCase]);
SMSText := StringReplace(SMSText,#$D,'',[rfReplaceAll, rfIgnoreCase]);
Try
IdSSLIOHandlerSocketOpenSSL2 := TIdSSLIOHandlerSocketOpenSSL.Create;
IdHTTP1 := TIdHTTP.Create;
IdHTTP1.Request.UserAgent := 'Mozilla/3.0 (compatible; Indy Library)';
IdHTTP1.Request.ContentType := 'application/json';
IdHTTP1.Request.BasicAuthentication := true;
IdHTTP1.Request.Username := SMSPass;
IdHTTP1.Request.Password := SMSToken;
IdSSLIOHandlerSocketOpenSSL2.SSLOptions.Method := sslvTLSv1_2;
IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL2;
IdHTTP1.HandleRedirects := False;
if Length(SMSMedia) > 5 then
Json := '{"from": "+1' + SMSPhone + '","to": "+1' + ToPhone + '","text": "' + SMSText + '","applicationId": "' + AppId + '","media": "' + SMSMedia + '","tag": "' + NameID + '"}'
else
Json := '{"from": "+1' + SMSPhone + '","to": "+1' + ToPhone + '","text": "' + SMSText + '","applicationId": "' + AppId + '","tag": "' + NameID + '"}';
url:='https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?';
JsonToSend := TStringStream.Create(Json);
try
Rtext:=IdHTTP1.Post(url, JsonToSend);
except
on E:Exception do
begin
SMSText := E.Classname + ': ' + E.Message;
mySMSSent := True;
end;
end;
if Pos('owner',Rtext) > 0 then // Send to web service //
begin
jsonRecived := TJSONObject.create(rtext);
if jsonRecived <> nil then
begin
Json := '{"id": "';
Json := Json + jsonRecived.optString('id') + '","from": "+1';
Json := Json + SMSPhone + '","time": "';
Json := Json + jsonRecived.optString('time') + '","direction": "';
Json := Json + jsonRecived.optString('direction') + '","text": "';
Json := Json + SMSText + '","to": "+1' + ToPhone + '"}';
Try
if Assigned(JsonToSend) then
FreeAndNil(JsonToSend);
JsonToSend := TStringStream.Create(Json);
url:='https://mywebservice';
IdHTTP1.Post(url, JsonToSend);
Except
End;
end;
end;
Finally
IdHTTP1.Disconnect;
IdSSLIOHandlerSocketOpenSSL2.Free;
IdHTTP1.Free;
JsonToSend.Free;
End;
end;
end;

Related

Downloading SFTP files through WinSCP on a Windows Service freezes

I'm trying to build a service that downloads some log files using SFTP and imports them to the database.
Because Delphi doesn't come with SFTP components, I have created a BAT file to download the logs using WinSCP
DownloadLogs.bat:
WinSCP.com < DownloadLogs.commands
DownloadLogs.commands:
open sftp://root:password#myserver.com
option confirm off
get -delete /var/lib/3cxpbx/Instance1/Data/Logs/CDRLogs files
exit
This is my service:
procedure TsrvCentralita.ServiceExecute(Sender: TService);
const SecondsBetweenExecutions = 10;
var Counter: integer;
dmLogs: TdmLogs;
begin
Counter := 0;
while not Terminated do begin
Inc(Counter);
if Counter > SecondsBetweenExecutions then begin
Counter := 0;
dmLogs := TdmLogs.Create(Self);
try
if dmLogs.DownloadLogs then dmLogs.ImportLogs;
finally
dmLogs.Free;
end;
end;
Sleep(1000);
ServiceThread.ProcessRequests(False);
end;
end;
And this is how I call the BAT file:
function ExecAppWait(AppName: string; Params: string = ''; Directory: string = ''; Hidden: boolean = False): Boolean;
var ShellExInfo: TShellExecuteInfo;
begin
FillChar(ShellExInfo, SizeOf(ShellExInfo), 0);
with ShellExInfo do begin
cbSize := SizeOf(ShellExInfo);
fMask := see_Mask_NoCloseProcess;
Wnd := Application.Handle;
lpFile := PChar(AppName);
lpDirectory := PChar(Directory);
lpParameters := PChar(Params);
if Hidden then nShow := sw_Hide
else nShow := sw_ShowNormal;
end;
Result := ShellExecuteEx(#ShellExInfo);
if Result then
while WaitForSingleObject(ShellExInfo.HProcess, 100) = WAIT_TIMEOUT do begin
Application.ProcessMessages; // give processor time to other tasks
if Application.Terminated then
Break;
end;
end;
function TdmLogs.DownloadLogs(Hidden: boolean = True): boolean;
var Path: string;
begin
Path := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'SFTP');;
ExecAppWait(TPath.Combine(Path, 'LogsCentralita.bat'), '', Hidden);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
When I debug the DownloadLogs function on my application, it works fine, but when running as a service it freezes. Do you know what is wrong ?, shouldn't I be able to call CMD.exe from a service ?.
Thank you.
update
Following Martin Prikryl's answer I now execute WinSCP this way:
function TdmCentralita.DownloadLogs(SaveOutput: boolean = False): boolean;
var IniFile: TIniFile;
Path, Params, User, Password, Server, Hostkey, RemotePath: string;
begin
IniFile := TIniFile.Create(TPath.ChangeExtension(GetModuleName(HInstance), '.ini'));
Server := IniFile.ReadString('Centralita', 'Servidor', '');
Hostkey := IniFile.ReadString('Centralita', 'Hostkey', '');
User := IniFile.ReadString('Centralita', 'Usuario', 'root');
Password := DecryptStr(IniFile.ReadString('Centralita', 'Password', ''), 223);
RemotePath := IniFile.ReadString('Centralita', 'PathRemoto', '');
IniFile.Free;
while (RightStr(RemotePath, 1) = '\') or (RightStr(RemotePath, 1) = '/') do RemotePath := Copy(RemotePath, 1, Length(RemotePath) - 1);
RemotePath := RemotePath + '/*.log';
Path := TPath.Combine(TPath.GetDirectoryName(GetModuleName(HInstance)), 'SFTP');
if not TDirectory.Exists(TPath.Combine(Path, 'files')) then TDirectory.CreateDirectory(TPath.Combine(Path, 'files'));
Params := '/ini=null /command "open sftp://' + User + ':' + Password + '#' + Server + ' -hostkey=""' + Hostkey + '""" "option confirm off" "get -delete ' + RemotePath + ' files\*" "exit"';
if SaveOutput then Params := Params + ' /log="' + Path + '\Log.txt" /loglevel=0';
ExecAppWait('WinSCP.com', Params, Path, True);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
Your script does not contain SSH host key. And due to the strange way you provide the commands (an input redirection instead of /script or /command switches), WinSCP starts in an interactive mode. So it prompts for hostkey verification, and hangs.
Add -hostkey switch to your open command. See:
Verifying the host key in script
My script works fine when executed manually, but fails or hangs when run by Windows Scheduler, SSIS or other automation service. What am I doing wrong?
And use /script or /command switches to make WinSCP abort on any problem, instead of hanging.
You should also read the batch file output for better error handling in the future.

How to enable Perfect Forward Secrecy In Indy 10?

I'm using OpenSSL 1.0.2o with Indy 10.6.2 in Delphi 2010.
This is what I have done so far:
procedure TServerForm.FormCreate(Sender: TObject);
var
LEcdh: PEC_KEY;
FSslCtx: PSSL_CTX;
SSL: PSSL;
FSSLContext: TIdSSLContext;
begin
//mServer.Active := True;
FSingle:=TCriticalSection.Create;
appdir := ExtractFilePath(ParamStr(0));
IdServerIOHandlerSSLOpenSSL1.SSLOptions.RootCertFile := appdir + 'EccCA.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.KeyFile := appdir + 'EccSite.key';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.CertFile := appdir + 'EccSite.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.DHParamsFile := appdir + 'dhparam.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.Method := sslvTLSv1_2;
IdServerIOHandlerSSLOpenSSL1.SSLOptions.SSLVersions := [sslvTLSv1_2];
IdServerIOHandlerSSLOpenSSL1.SSLOptions.CipherList :=
//'ECDHE-ECDSA-AES128-GCM-SHA256:' +
'ECDHE-RSA-AES128-GCM-SHA256:' +
//'ECDHE-RSA-AES256-GCM-SHA384:' +
//'ECDHE-ECDSA-AES256-GCM-SHA384:' +
//'DHE-RSA-AES128-GCM-SHA256:' +
//'ECDHE-RSA-AES128-SHA256:' +
//'DHE-RSA-AES128-SHA256:' +
//'ECDHE-RSA-AES256-SHA384:' +
//'DHE-RSA-AES256-SHA384:' +
//'ECDHE-RSA-AES256-SHA256:' +
//'DHE-RSA-AES256-SHA256:' +
'HIGH:' +
'!aNULL:' +
'!eNULL:' +
'!EXPORT:' +
'!DES:' +
'!RC4:' +
'!MD5:' +
'!PSK:' +
'!SRP:' +
'!CAMELLIA';
MServer.IndyServer.IOHandler := IdServerIOHandlerSSLOpenSSL1;
mServer.Active := True;
//FSSLContext := TIdSSLContext(IdServerIOHandlerSSLOpenSSL1.SSLContext);
end;
This does not work.
Does anyone have good suggestions?
First off, make sure that you update your version of Indy to the latest SVN snapshot. After the previous discussion I had with Roberto Frances on the Embarcadero forums, I added SSL_CTRL_SET_ECDH_AUTO and SSL_CTX_set_ecdh_auto() to Indy's IdSSLOpenSSLHeaders unit.
So, the only piece missing from the code in that other discussion is the definition of TMyIdSSLContext, which I assume is simply this:
type
TMyIdSSLContext = class(TIdSSLContext)
end;
Since the TIdSSLContext.fContext member is declared as protected, the unit that declares TMyIdSSLContext gains access to TIdSSLContext's protected members. Thus, your code can then look like this:
type
TMyIdSSLContext = class(TIdSSLContext)
end;
procedure TServerForm.FormCreate(Sender: TObject);
var
FSSLContext: TMyIdSSLContext;
begin
FSingle := TCriticalSection.Create;
appdir := ExtractFilePath(ParamStr(0));
IdServerIOHandlerSSLOpenSSL1.SSLOptions.RootCertFile := appdir + 'EccCA.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.KeyFile := appdir + 'EccSite.key';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.CertFile := appdir + 'EccSite.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.DHParamsFile := appdir + 'dhparam.pem';
IdServerIOHandlerSSLOpenSSL1.SSLOptions.Method := sslvTLSv1_2;
IdServerIOHandlerSSLOpenSSL1.SSLOptions.SSLVersions := [sslvTLSv1_2];
IdServerIOHandlerSSLOpenSSL1.SSLOptions.CipherList :=
//'ECDHE-ECDSA-AES128-GCM-SHA256:' +
'ECDHE-RSA-AES128-GCM-SHA256:' +
//'ECDHE-RSA-AES256-GCM-SHA384:' +
//'ECDHE-ECDSA-AES256-GCM-SHA384:' +
//'DHE-RSA-AES128-GCM-SHA256:' +
//'ECDHE-RSA-AES128-SHA256:' +
//'DHE-RSA-AES128-SHA256:' +
//'ECDHE-RSA-AES256-SHA384:' +
//'DHE-RSA-AES256-SHA384:' +
//'ECDHE-RSA-AES256-SHA256:' +
//'DHE-RSA-AES256-SHA256:' +
'HIGH:' +
'!aNULL:' +
'!eNULL:' +
'!EXPORT:' +
'!DES:' +
'!RC4:' +
'!MD5:' +
'!PSK:' +
'!SRP:' +
'!CAMELLIA';
MServer.IndyServer.IOHandler := IdServerIOHandlerSSLOpenSSL1;
mServer.Active := True;
FSSLContext := TMyIdSSLContext(IdServerIOHandlerSSLOpenSSL1.SSLContext);
SSL_CTX_set_ecdh_auto(FSSLContext.fContext, 1);
end;

How to prevent TRichMemo from resetting text attributes when you add new text

I have a TRichMemo object which I create and populate with text at runtime.
I have a timer that triggers a function each 10 seconds. The function looks something like this:
procedure TServerSideForm.NewLineTimerTimer(Sender: TObject);
var
timeForward: TDateTime;
timerText: wideString;
startRange, endRange: longInt;
begin
timeForward := Time;
timeForward := IncSecond(timeForward, ServerSideForm.NewLineTimer.Interval div 1000);
//...
timerText := TimeToStr(Time) + ' - ' + TimeToStr(timeForward);
startRange := Length(WindowMemo.Text);
WindowMemo.Text := WindowMemo.Text + sLineBreak + sLineBreak + timerText + sLineBreak + sLineBreak;
endRange := Length(WindowMemo.Text) - 1;
WindowMemo.SetRangeColor(startRange, endRange, clGreen);
//...
end;
Everything works perfectly, text in the desired range becomes green.
But as soon as I add some new text to my TRichMemo, everything resets back to black text.
Why is this happening? Is there a way to prevent this reset from happening?
P.S Same situation happens, when I use the SetRangeParams function.
Use Append method instead of accessing a type String value Text as it keeps only the literals not the format.
Change
WindowMemo.Text := WindowMemo.Text + sLineBreak + sLineBreak + timerText + sLineBreak + sLineBreak;
with
WindowMemo.Append(sLineBreak + sLineBreak + timerText + sLineBreak + sLineBreak);
METHOD 2
Should you decide to add text withot line breaks you can replace the mentioned line with
uses RichMemoUtils;
...
InsertColorStyledText(WindowMemo,timerText,Random($FFFFFF),[],Length(WindowMemo.Text) -1);

How to control/remove borders of embedded chm help file in delphi windows/vcl application?

I've got a Delphi Windows/VCL (XE7) program that embeds CHM help pages in various panels of the program. This largely works fine but the panels always shows an ugly recessed border (looks very windows 95). Here is a screenshot:
Does anyone know how to display the help files with no border? Below is the code I use at the moment. Thanks for any help!
Procedure DoShowEmbeddedHelp(TheWinName: string; ThePanel: TPanel;
var HelpWinHandle: integer; HelpTopic: string; var LastTopic: string;
ByContext: boolean; ContextData: integer; var LastContext: integer);
var
wintypedef: THHWinType;
hf, fn: string;
begin
hf := Gl.ProgramPath + 'leap.chm';
if not FileExists(hf) then
MessageDlg('Help file not found: ' + hf, mtError, [mbOK], 0)
else if ((not ByContext) and (HelpTopic <> LastTopic)) or
(ByContext and (ContextData <> LastContext)) then
begin
if not ByContext then
begin
LastTopic := HelpTopic;
LastContext := 0;
end
else
begin
LastContext := ContextData;
LastTopic := '';
end;
fn := hf + '>' + TheWinName;
FillChar(wintypedef, sizeof(wintypedef), 0);
with wintypedef do
begin
cbStruct := sizeof(wintypedef);
fUniCodeStrings := false;
pszType := PAnsiChar(TheWinName);
fsValidMembers :=
HHWIN_PARAM_PROPERTIES or
HHWIN_PARAM_STYLES or
HHWIN_PARAM_EXSTYLES or
HHWIN_PARAM_RECT or
HHWIN_PARAM_NAV_WIDTH or
HHWIN_PARAM_SHOWSTATE or
HHWIN_PARAM_TB_FLAGS or
HHWIN_PARAM_EXPANSION;
fsWinProperties :=
HHWIN_PROP_NOTITLEBAR or
HHWIN_PROP_NO_TOOLBAR or HHWIN_PROP_NODEF_STYLES or
HHWIN_PROP_NODEF_EXSTYLES or
HHWIN_PROP_TRI_PANE;
wintypedef.pszCaption := '';
wintypedef.dwStyles := WS_VISIBLE or WS_CHILDWINDOW;
wintypedef.dwExStyles := WS_EX_LEFT;
wintypedef.rcWindowPos := Rect(0, 0, ThePanel.ClientWidth, ThePanel.ClientHeight);
wintypedef.nShowState := SW_SHOW;
wintypedef.fsToolBarFlags := HHWIN_BUTTON_PRINT or HHWIN_BUTTON_BACK;
fNotExpanded := true;
end;
if integer(HtmlHelp(0, nil, HH_SET_WIN_TYPE, DWORD(#wintypedef))) < 0 then
ShowMessage('Help failed on topic: ' + HelpTopic)
else if ByContext then
HelpWinHandle := HtmlHelp(ThePanel.Handle, PChar(fn), HH_HELP_CONTEXT, ContextData)
else
HelpWinHandle := HtmlHelp(ThePanel.Handle, PChar(fn), HH_DISPLAY_TOPIC, DWORD(PChar('Expressions\' + HelpTopic + '.htm')));
end;
end;

Issue setting caller ID in a TSP

I have developed a TSP to talk to a CTI server. In the most part it works, but when setting the caller/called ID parties, in
function TSPI_lineGetCallInfo(
hdCall : HDRVCALL;
lpCallInfo : LPLINECALLINFO
) : LONG;
I am finding the offsets are all corrects but the size fields are NOT. At the end of the function I output (to debugger) the size and offsets of each field and they are what I expect them to be. But when I inspect the values using a TAPI program the sizes are different, (but the offsets are EXACTLY the same as per the debug statements) in fact the size field 5 regardless of what is actually there, whereas the debug statements at the end of the code below shows the correct values...
Any help greatly appreciated.
lpCallInfo^.dwCallerIDOffset := 0;
lpCallInfo^.dwCallerIDSize := 0;
lpCallInfo^.dwCalledIDOffset := 0;
lpCallInfo^.dwCalledIDSize := 0;
lpCallInfo^.dwConnectedIDOffset := 0;
lpCallInfo^.dwConnectedIDSize := 0;
extnid := thiscall.CallItem.ExtnId;
phoneno := thiscall.CallItem.DialNum;
extnid_size := (Length(extnid) + 1) * sizeof(WCHAR);
phoneno_size := (Length(phoneno) + 1) * sizeof(WCHAR);
extnidw := StringToWideStringEx(extnid, CP_ACP);
phonenow := StringToWideStringEx(phoneno, CP_ACP);
if lpCallInfo^.dwOrigin = LINECALLORIGIN_INTERNAL then
begin
{me}
lpCallInfo^.dwCallerIDOffset := sizeof(TLINECALLINFO);
lpCallInfo^.dwCallerIDSize := extnid_size;
Move(ExtnIdw[1], ptr^, extnid_size * 2);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwCallerIDSize);
{other party}
if phoneno <> '' then
begin
lpCallInfo^.dwCalledIDOffset :=
sizeof(TLINECALLINFO) + lpCallInfo^.dwCallerIDSize;
lpCallInfo^.dwCalledIDSize := phoneno_size;
Move(phonenow[1], ptr^, phoneno_size * 2);
end;
end
else
begin
if thiscall.CallItem.CallType = 1 then
begin {incoming call}
{agent is the called party}
lpCallInfo^.dwCalledIDOffset := sizeof(TLINECALLINFO);
lpCallInfo^.dwCalledIDSize := extnid_size;
Move(ExtnIdw[1], ptr^, extnid_size);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwCalledIDSize);
{other party is the caller}
if phoneno <> '' then
begin
lpCallInfo^.dwCallerIDOffset :=
sizeof(TLINECALLINFO) + lpCallInfo^.dwCalledIDSize;
lpCallInfo^.dwCallerIDSize := phoneno_size;
Move(phonenow[1], ptr^, phoneno_size);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwCallerIDSize);
end;
end
else
begin
{agnet is the caller}
lpCallInfo^.dwCallerIDOffset := sizeof(TLINECALLINFO);
lpCallInfo^.dwCallerIDSize := extnid_size;
Move(ExtnIdw[1], ptr^, extnid_size);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwCallerIDSize);
{dialed number is the called party}
if phoneno <> '' then
begin
lpCallInfo^.dwCalledIDOffset :=
sizeof(TLINECALLINFO) + lpCallInfo^.dwCallerIDSize;
lpCallInfo^.dwCalledIDSize := phoneno_size;
Move(phonenow[1], ptr^, phoneno_size);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwCalledIDSize);
end;
end;
if (thiscall.CallItem.CallState = cs_Connected) and
(phoneno <> '') then
begin
lpCallInfo^.dwConnectedIDOffset := sizeof(TLINECALLINFO) +
lpCallInfo^.dwCallerIDSize + lpCallInfo^.dwCalledIDSize;
lpCallInfo^.dwConnectedIDSize := phoneno_size;
Move(phonenow[1], ptr^, phoneno_size);
ptr := Pointer(integer(ptr) + lpCallInfo^.dwConnectedIDSize);
end;
end;
end;
DEBUG('TSPI_lineGetCallInfo::dwCallerIDOffset=' + intToStr(lpCallInfo^.dwCallerIDOffset));
DEBUG('TSPI_lineGetCallInfo::dwCallerIDSize=' + intToStr(lpCallInfo^.dwCallerIDSize));
DEBUG('TSPI_lineGetCallInfo::dwCalledIDOffset=' + intToStr(lpCallInfo^.dwCalledIDOffset));
DEBUG('TSPI_lineGetCallInfo::dwCalledIDSize=' + intToStr(lpCallInfo^.dwCalledIDSize));
DEBUG('TSPI_lineGetCallInfo::dwConnectedIDOffset=' + intToStr(lpCallInfo^.dwConnectedIDOffset));
DEBUG('TSPI_lineGetCallInfo::dwConnectedIDSize=' + intToStr(lpCallInfo^.dwConnectedIDSize));
These are strange results. Your code seems to check out. It may be a longshot but the result could be caused by too few memory reserved for the lpCallInfo structure. What tapi program do you use? Most programs just reserve a large surplus beforehand. However, another commonly used approach is to 'ask' the TSP the exact amount needed by first calling TSPI_lineGetCallInfo and then reserving the exact amount after you set the dwNeededSize and returning LINEERR_STRUCTURETOOSMALL. You don't seem to check the dwTotalSize or set the dwNeededSize and dwUsedSize fields (which is dangerous).
Please look at the : LINEERR constants
and let me know if it solves the issue. If it doesn't, I would be curious to see the structure log from the Tapi Browser, but let's hope it works. Good luck!

Resources