We have been using the stomp client developed by Julian Lyndon-Smith/dot.r for a couple of years. So far, we have been sending unencrypted messages.
We would now like to send messages over TLS. So naturally, I've changed the port from 61613 to 61614 which is the secured one but this give the following error.
None of the widgets used in the WAIT-FOR statement are in a state (such as SENSITIVE) such that the specified event can occur. WAIT-FOR terminated. (4123)
This error occur on line
WAIT-FOR "U2":U OF SocketHandle PAUSE 5.
in method CONNECT()
of class dotr.Stomp.StompConnection
.
In the cloudAMQP server log we have the following log when attempting to connect using port 61614.
2022-03-14 14:07:36.915534+00:00 [noti] <0.23840.19> TLS server: In state hello at tls_record.erl:558 generated SERVER ALERT: Fatal - Unexpected Message
2022-03-14 14:07:36.915534+00:00 [noti] <0.23840.19> - {unsupported_record_type,67}
Here is what the log say when we connect using the unsecured port 61613. (I've hidden IP address by replacing it with x)
2022-03-14 14:08:05.388817+00:00 [info] <0.23882.19> accepting STOMP connection <0.23882.19> (xxx.xx.xxx.xxx:63837 -> xx.xx.xx.x:61613) 2022-03-14 14:08:10.445381+00:00 [info] <0.23882.19> closing STOMP connection <0.23882.19> (xxx.xx.xxx.xxx:63837 -> xx.xx.xx.x:61613)
The server log also contains the following warning that occur at night time.
certificate chain verification is not enabled for this TLS connection. Please see https://rabbitmq.com/ssl.html for more information.
but according to documentation, this warning can be ignored because they don't recommend client certs at the moment.
Using Julian's stomp client or another, can we sent message over TLS? If yes, how?
Sorry if the question is a little broad, but my knowledge on TLS are limited and master google didn't had a solution for this one.
Here is the code of the class StompConnection we are using.
/*
Copyright (c) 2011-2012, Julian Lyndon-Smith ([email protected])
http://www.dotr.com
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction except as noted below, including without limitation
the rights to use,copy, modify, merge, publish, distribute,
and/or sublicense, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The Software and/or source code cannot be copied in whole and
sold without meaningful modification for a profit.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
USING Progress.Lang.*.
USING dotr.Stomp.*.
USING dotr.Stomp.Interface.*.
ROUTINE-LEVEL ON ERROR UNDO, THROW.
CLASS dotr.Stomp.StompConnection FINAL:
DEF PRIVATE VAR StompConfig AS StompConfig NO-UNDO.
DEF PRIVATE VAR ServerResponseFrames AS CHAR INIT "CONNECTED,RECEIPT,ERROR":U NO-UNDO.
DEF PRIVATE VAR DisconnectID AS CHAR NO-UNDO.
DEF PUBLIC PROPERTY UserName AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY Password AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY ConnectionID AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY ClientID AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY StompVersion AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY StompServer AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY ErrorMessage AS CHAR NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY ConnectionEstablished AS LOGI NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY ConnectedBroker AS CHAR NO-UNDO
GET():
RETURN SUBST("&1:&2":U,StompConfig:StompActiveServer,StompConfig:StompActivePort).
END GET. PRIVATE SET.
DEF PUBLIC PROPERTY SocketHandle AS HANDLE NO-UNDO GET. PRIVATE SET.
DEF PUBLIC PROPERTY SocketHandler AS HANDLE NO-UNDO GET. PRIVATE SET.
DEF PUBLIC EVENT NewMessage SIGNATURE VOID (p_Message AS dotr.Stomp.StompMessage).
DEF PUBLIC EVENT NewRawMessage SIGNATURE VOID (p_Message AS LONGCHAR).
DEF PUBLIC EVENT DisconnectedEvent SIGNATURE VOID ().
DEF PUBLIC EVENT ConnectedEvent SIGNATURE VOID ().
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
CONSTRUCTOR StompConnection(p_StompConfig AS dotr.Stomp.StompConfig,
p_ClientID AS CHAR,
p_UserName AS CHAR,
p_Password AS CHAR):
IF p_UserName <> "":U THEN ASSIGN p_StompConfig:login = p_UserName.
IF p_Password <> "":U THEN ASSIGN p_StompConfig:passcode = p_Password.
ASSIGN THIS-OBJECT:StompConfig = p_StompConfig
THIS-OBJECT:UserName = p_StompConfig:login
THIS-OBJECT:Password = p_StompConfig:passcode
THIS-OBJECT:ClientId = (IF p_ClientID = "":U THEN GUID ELSE p_ClientID).
THIS-OBJECT:CONNECT().
END CONSTRUCTOR.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
CONSTRUCTOR StompConnection(p_ClientID AS CHAR,p_UserName AS CHAR,p_Password AS CHAR):
THIS-OBJECT(dotr.Stomp.StompConfig:Default,p_ClientID,p_UserName,p_Password).
END CONSTRUCTOR.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
DESTRUCTOR StompConnection():
THIS-OBJECT:DISCONNECT() NO-ERROR.
IF VALID-HANDLE(SocketHandle) THEN
DO: SocketHandle:DISCONNECT() NO-ERROR.
DELETE OBJECT SocketHandle NO-ERROR.
END.
IF VALID-HANDLE(SocketHandler) THEN
DO: RUN shutdown IN SocketHandler.
DELETE PROCEDURE SocketHandler NO-ERROR.
END.
END DESTRUCTOR.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PUBLIC VOID Disconnect():
DEF VAR ID AS CHAR NO-UNDO.
ConnectionEstablished = FALSE.
ASSIGN id = GUID.
SendFrame(SUBST("DISCONNECT~nreceipt:&1~n~n":U,id)).
ASSIGN DisconnectId = id. /** no more frames to be sent */
IF VALID-HANDLE(SocketHandle) THEN
DO: SocketHandle:DISCONNECT() NO-ERROR.
DELETE OBJECT SocketHandle NO-ERROR.
END.
IF VALID-HANDLE(SocketHandler) THEN
DO: RUN shutdown IN SocketHandler.
DELETE PROCEDURE SocketHandler NO-ERROR.
END.
CATCH e AS Progress.Lang.AppError : /** swallow all errors, as we're disconnecting anyway */
DELETE OBJECT e NO-ERROR.
END CATCH.
END METHOD.
/** Raise the SocketDisconnected event
*/
METHOD PUBLIC VOID DoSocketDisconnected():
ConnectionEstablished = FALSE.
DisconnectedEvent:PUBLISH() NO-ERROR.
END METHOD.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PUBLIC VOID ProcessData(p_Message AS LONGCHAR):
DEF VAR lv_Frame AS CHAR NO-UNDO.
DEF VAR lv_Dest AS CHAR NO-UNDO.
DEF VAR lv_Header AS CHAR NO-UNDO.
DEF VAR lv_Body AS LONGCHAR NO-UNDO.
DEF VAR lv_Index AS INT NO-UNDO.
ASSIGN lv_Frame = ENTRY(1,p_message,"~n":U).
ASSIGN lv_Frame = ENTRY(2,p_message,"~n":U) WHEN lv_Frame = "":U
lv_index = INDEX(p_Message,"~n~n":U)
lv_Body = IF lv_index = 0 THEN "":U ELSE TRIM(SUBSTRING(p_message,lv_index + 1))
lv_Header = TRIM(SUBSTRING(p_message,1,lv_index))
lv_Index = INDEX(lv_Header,"destination:":U).
ASSIGN lv_Dest = ENTRY(2,ENTRY(1,SUBSTRING(lv_Header,lv_index),"~n":U),":":U) WHEN lv_index > 0
lv_Dest = SUBSTR(lv_Dest,INDEX(lv_Dest,"/",2) + 1) WHEN lv_index > 0 .
IF lv_header = "":U AND INDEX(ServerResponseFrames,lv_Frame) > 0 THEN
lv_Header = STRING(TRIM(SUBSTRING(p_message,LENGTH(lv_Frame) + 1))).
CASE lv_frame:
WHEN "CONNECTED" THEN
DO: setConnectionDetails(lv_Header).
ConnectionEstablished = TRUE.
ConnectedEvent:PUBLISH() NO-ERROR.
APPLY "U2":U TO SocketHandle.
END.
WHEN "RECEIPT" THEN
DO: APPLY "U1":U TO SocketHandle.
END.
WHEN "ERROR" THEN
DO: ErrorMessage = lv_Body.
APPLY "U1":U TO SocketHandle.
END.
END CASE.
NewRawMessage:Publish(p_Message) NO-ERROR.
THIS-OBJECT:MessageParsed(lv_Frame,lv_Dest,lv_Header,lv_Body) NO-ERROR.
END METHOD.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PROTECTED VOID MessageParsed(p_Frame AS CHAR,p_Dest AS CHAR,p_Header AS CHAR, p_Body AS LONGCHAR):
NewMessage:Publish(NEW dotr.Stomp.StompMessage(p_frame,p_Dest,p_Header,p_Body)) NO-ERROR.
END METHOD.
/*-----------------------------------------------------------------------------
Purpose: Send message
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PUBLIC VOID SendFrame(INPUT p_Data AS LONGCHAR):
DEF VAR lv_MemPtr AS MEMPTR NO-UNDO.
DEF VAR lv_ToWrite AS INT NO-UNDO.
DEF VAR lv_offset AS INT NO-UNDO.
IF DisconnectId <> "":U THEN RETURN. /** Clients MUST NOT send any more frames after the DISCONNECT frame is sent. */
IF NOT VALID-HANDLE(SocketHandle) OR p_Data = "":U OR p_Data = ? THEN RETURN.
ASSIGN lv_ToWrite = PutStringIntoMemoryPointer(p_Data, lv_MemPtr)
lv_offset = 0.
DO WHILE lv_ToWrite > 0:
SocketHandle:WRITE(lv_MemPtr,lv_offset + 1, lv_ToWrite) NO-ERROR.
IF SocketHandle:BYTES-WRITTEN = 0 THEN
UNDO, THROW NEW AppError("IO Exception":U,0).
ASSIGN lv_offset = lv_offset + SocketHandle:BYTES-WRITTEN
lv_ToWrite = lv_ToWrite - SocketHandle:BYTES-WRITTEN.
END.
FINALLY:
SET-SIZE(lv_MemPtr) = 0.
END FINALLY.
END METHOD.
/*-----------------------------------------------------------------------------
Purpose: Put string into given memory pointer.
Returns: Return the number of bytes in the memory pointer.
Notes: Make sure character are converted from current session encoding to UTF-8 for french characters (accent).
-----------------------------------------------------------------------------*/
METHOD PRIVATE INT PutStringIntoMemoryPointer(INPUT p_Data AS LONGCHAR, INPUT p_MemPtr AS MEMPTR):
DEF VAR lv_DataUTF8 AS LONGCHAR NO-UNDO.
DEF VAR lv_NbBytesToWrite AS INT NO-UNDO.
DEF VAR lv_NbCharToWrite AS INT NO-UNDO.
DEF VAR lv_NbCharWritten AS INT NO-UNDO.
DEF VAR lv_NbBytesWritten AS INT NO-UNDO.
DEF VAR vl_CurrentCharacter AS CHAR NO-UNDO.
FIX-CODEPAGE (lv_DataUTF8) = 'utf-8'.
lv_DataUTF8 = p_Data.
ASSIGN lv_NbBytesToWrite = LENGTH(lv_DataUTF8, "RAW":U) + 1 /* add 1 bytes for 0-terminated string */
lv_NbCharToWrite = LENGTH(lv_DataUTF8, "CHARACTER":U)
lv_NbCharWritten = 0
lv_NbBytesWritten = 0.
SET-SIZE(p_MemPtr) = lv_NbBytesToWrite.
//Line `COPY-LOB FROM p_Data TO p_MemPtr CONVERT TARGET CODEPAGE "UTF-8".` does work.
//However, it is so slow that it cause error(s) when using it.
//Thus, we convert encoding character by character with the following loop instead.
DO lv_NbCharWritten = 1 TO lv_NbCharToWrite:
vl_CurrentCharacter = CHR(ASC(STRING(SUBSTRING(lv_DataUTF8, lv_NbCharWritten, 1, "CHARACTER")), "UTF-8":U, SESSION:CPINTERNAL), "UTF-8":U, "UTF-8":U).
PUT-STRING(p_MemPtr, lv_NbBytesWritten + 1) = vl_CurrentCharacter.
lv_NbBytesWritten = lv_NbBytesWritten + LENGTH(vl_CurrentCharacter, "RAW").
END.
RETURN lv_NbBytesToWrite.
END METHOD.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PUBLIC VOID CONNECT():
IF NOT VALID-HANDLE(SocketHandle) THEN
DO: CREATE SOCKET SocketHandle.
RUN dotr/Stomp/stompSocketProc.p PERSISTENT SET SocketHandler (THIS-OBJECT).
SocketHandle:SET-READ-RESPONSE-PROCEDURE(IF StompConfig:LargeMessageSupport THEN "SocketBigReadHandler":U
ELSE "SocketReadHandler":U,SocketHandler).
END.
SocketHandle:CONNECT(SUBSTITUTE("-H &1 -S &2":U, StompConfig:StompServer, StompConfig:StompPort)) NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES > 0 OR NOT SocketHandle:CONNECTED() THEN
DO:
/* try connecting using the slave connection details, if set */
IF StompConfig:FailoverSupported THEN
DO: SocketHandle:CONNECT(SUBSTITUTE("-H &1 -S &2":U, StompConfig:StompSlaveServer, StompConfig:StompSlavePort)) NO-ERROR.
StompConfig:SetActive(StompConfig:StompSlaveServer,StompConfig:StompSlavePort).
END.
IF ERROR-STATUS:NUM-MESSAGES > 0 OR NOT SocketHandle:CONNECTED() THEN
UNDO, THROW NEW AppError(SUBSTITUTE("Cannot connect to Stomp Server with Server:&1 Port:&2":U,
StompConfig:Stompserver + "/" + StompConfig:StompSlaveServer,
StompConfig:StompPort + "/" + StompConfig:StompSlavePort),0).
END.
ELSE StompConfig:SetActive(StompConfig:Stompserver,StompConfig:StompPort).
SocketHandle:SET-SOCKET-OPTION ("SO-RCVBUF":U,STRING(1048576)).
SocketHandle:SET-SOCKET-OPTION ("TCP-NODELAY":U,"TRUE":U).
SocketHandle:SET-SOCKET-OPTION ("SO-KEEPALIVE":U,"TRUE":U).
SendFrame(SUBSTITUTE("CONNECT~naccept-version:1.0,1.1,2.0~nhost:&3~nclient-id:&4~nlogin:&1~npasscode:&2~n~n":U, THIS-OBJECT:UserName, THIS-OBJECT:Password,StompConfig:StompHost,THIS-OBJECT:ClientID)).
WAIT-FOR "U2":U OF SocketHandle PAUSE 5.
IF ConnectionEstablished = FALSE THEN
UNDO, THROW NEW AppError(SUBSTITUTE("Cannot connect to Stomp Server with Server:&1 Port:&2":U,
StompConfig:Stompserver + "/" + StompConfig:StompSlaveServer,
StompConfig:StompPort + "/" + StompConfig:StompSlavePort),0).
END METHOD.
/*-----------------------------------------------------------------------------
Purpose:
Parameters:
Notes:
-----------------------------------------------------------------------------*/
METHOD PRIVATE VOID setConnectionDetails (p_Header AS CHAR):
DEF VAR i AS INT NO-UNDO.
DEF VAR lv_header AS CHAR NO-UNDO.
DO i = 1 TO NUM-ENTRIES(p_Header,"~n":U):
lv_header = ENTRY(i,p_Header,"~n":U).
CASE ENTRY(1,lv_header,":":U):
WHEN "session":U THEN THIS-OBJECT:ConnectionID = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
WHEN "server":U THEN THIS-OBJECT:stompServer = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
WHEN "version":U THEN THIS-OBJECT:stompVersion = SUBSTRING(lv_header,INDEX(lv_header,":":U) + 1).
END CASE.
END.
END METHOD.
END CLASS.