From 39a8672735a2f757028e911451feee5c9701b23a Mon Sep 17 00:00:00 2001 From: Diego Dassie Date: Mon, 24 Apr 2023 09:48:05 +0200 Subject: [PATCH] Implemented enhanced authentication for MQTTAsync. Signed-off-by: Diego Dassie --- src/Clients.h | 1 + src/MQTTAsync.c | 75 ++++++++++++++++++++++- src/MQTTAsync.h | 71 +++++++++++++++++++--- src/MQTTAsyncUtils.c | 127 +++++++++++++++++++++++++++++++++++++++ src/MQTTAsyncUtils.h | 3 + src/MQTTClient.c | 74 ++++++++++++++++++++--- src/MQTTClient.h | 81 +++++++++++++++++++++++-- src/MQTTPacket.c | 64 +++++++++++++++++++- src/MQTTPacket.h | 13 ++++ src/MQTTPacketOut.c | 45 ++++++++++++++ src/MQTTPacketOut.h | 2 + src/MQTTProtocolClient.c | 60 ++++++++++++++++++ src/MQTTProtocolOut.h | 1 + 13 files changed, 591 insertions(+), 26 deletions(-) diff --git a/src/Clients.h b/src/Clients.h index 130a9622b..7b9bf49aa 100644 --- a/src/Clients.h +++ b/src/Clients.h @@ -153,6 +153,7 @@ typedef struct int sessionExpiry; /**< MQTT 5 session expiry */ char* httpProxy; /**< HTTP proxy */ char* httpsProxy; /**< HTTPS proxy */ + char* authMethod; /**< MQTT 5 enhanced authentication method */ #if defined(OPENSSL) MQTTClient_SSLOptions *sslopts; /**< the SSL/TLS connect options */ SSL_SESSION* session; /**< SSL session pointer for fast handhake */ diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index 9866a3d3b..5ff29d02c 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -562,7 +562,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) goto exit; } - if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 8) + if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 9) { rc = MQTTASYNC_BAD_STRUCTURE; goto exit; @@ -693,6 +693,11 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) if (options->httpsProxy) m->c->httpsProxy = MQTTStrdup(options->httpsProxy); } + if (options->MQTTVersion >= MQTTVERSION_5 && options->struct_version >= 9) + { + if (options->authMethod) + m->c->authMethod = MQTTStrdup(options->authMethod); + } if (m->c->will) { @@ -893,7 +898,6 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) if (MQTTProperties_hasProperty(options->connectProperties, MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL)) m->c->sessionExpiry = MQTTProperties_getNumericValue(options->connectProperties, MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL); - } if (options->willProperties) { @@ -909,6 +913,50 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) } m->c->cleanstart = options->cleanstart; } + if (options->struct_version >= 9) + { + if (m->c->authMethod) + { + MQTTAsync_authHandleData authData = MQTTAsync_authHandleData_initializer; + MQTTProperty property; + int authrc = 0; + + if (!m->connectProps) + { + MQTTProperties initialized = MQTTProperties_initializer; + + if ((m->connectProps = malloc(sizeof(MQTTProperties))) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + *m->connectProps = initialized; + } + + property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_METHOD; + property.value.data.data = m->c->authMethod; + property.value.data.len = (int)strlen(m->c->authMethod); + rc = MQTTProperties_add(m->connectProps, &property); + if (rc) + goto exit; + + if (m->auth_handle) + { + authrc = (*(m->auth_handle))(m->auth_handle_context, &authData); + if (authrc < 0) + goto exit; + } + + property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_DATA; + property.value.data.data = authData.authDataOut.data; + property.value.data.len = authData.authDataOut.len; + rc = MQTTProperties_add(m->connectProps, &property); + free(authData.authDataOut.data); + if (rc) + goto exit; + } + } /* Add connect request to operation queue */ if ((conn = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) @@ -1711,6 +1759,29 @@ int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersi } +int MQTTAsync_setHandleAuth(MQTTAsync handle, void *context, + MQTTAsync_authHandle *authenticate) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs *m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS) + rc = MQTTASYNC_FAILURE; + else + { + m->auth_handle_context = context; + m->auth_handle = authenticate; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level) { Log_setTraceLevel((enum LOG_LEVELS)level); diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index 04be815a3..9cd4efb21 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -530,6 +530,56 @@ LIBMQTT_API int MQTTAsync_setBeforePersistenceWrite(MQTTAsync handle, void* cont */ LIBMQTT_API int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersistence_afterRead* co); +/** The authentication data that is populated when MQTTv5 enhanced authentication + * is requested by an operation. */ +typedef struct +{ + /** The eyecatcher for this structure. Will be MQAD. */ + char struct_id[4]; + /** The version number of this structure. Will be 0 */ + int struct_version; + /** The MQTT reason code received from the AUTH packet. */ + enum MQTTReasonCodes reasonCode; + /** + * The data received from the MQTTv5 AUTH packet AUTHENTICATION_DATA property. + * Data is NULL if no AUTHENTICATION_DATA was received. + */ + struct { + int len; /**< binary input AUTHENTICATION_DATA length */ + const void* data; /**< binary input AUTHENTICATION_DATA data */ + } authDataIn; + /** + * The data to populate the MQTTv5 AUTH packet AUTHENTICATION_DATA property. + * Set data to NULL to remove. To change, allocate new + * storage with ::MQTTAsync_malloc - this will then be freed later by the library. + */ + struct { + int len; /**< binary output AUTHENTICATION_DATA length */ + void* data; /**< binary output AUTHENTICATION_DATA data */ + } authDataOut; +} MQTTAsync_authHandleData; + +#define MQTTAsync_authHandleData_initializer {{'M', 'Q', 'A', 'D'}, 0, 0, {0, NULL}, {0, NULL}} + +/** + * This is a callback function which will allow the client application to update the + * connection data. + * @param data The connection data which can be modified by the application. + * @return Return a zero or positive value to indicate sucess, a negative value on failure. + */ +typedef int MQTTAsync_authHandle(void* context, MQTTAsync_authHandleData* data); + +/** + * Sets the MQTTAsync_authenticate() callback function for a client. + * @param handle A valid client handle from a successful call to MQTTAsync_create(). + * @param context A pointer to any application-specific context. The + * the context pointer is passed to the callback function to + * provide access to the context information in the callback. + * @param co A pointer to an MQTTAsync_authHandle() callback + * function. NULL removes the callback setting. + */ +LIBMQTT_API int MQTTAsync_setHandleAuth(MQTTAsync handle, void* context, MQTTAsync_authHandle* authenticate); + /** The data returned on completion of an unsuccessful API call in the response callback onFailure. */ typedef struct @@ -1208,6 +1258,7 @@ typedef struct * 5 signifies no MQTTV5 properties * 6 signifies no HTTP headers option * 7 signifies no HTTP proxy and HTTPS proxy options + * 8 signifies no MQTTV5 enhanced authentication option */ int struct_version; /** The "keep alive" interval, measured in seconds, defines the maximum time @@ -1378,27 +1429,31 @@ typedef struct * HTTPS proxy */ const char* httpsProxy; + /** + * MQTTv5 authentication method + */ + const char* authMethod; } MQTTAsync_connectOptions; /** Initializer for connect options for MQTT 3.1.1 non-WebSocket connections */ -#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 65535, NULL, NULL, NULL, 30, 0,\ -NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 9, 60, 1, 65535, NULL, NULL, NULL, 30, 0,\ +NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 5.0 non-WebSocket connections */ -#define MQTTAsync_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 65535, NULL, NULL, NULL, 30, 0,\ -NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define MQTTAsync_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 9, 60, 0, 65535, NULL, NULL, NULL, 30, 0,\ +NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 3.1.1 WebSockets connections. * The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts. */ -#define MQTTAsync_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 65535, NULL, NULL, NULL, 30, 0,\ -NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define MQTTAsync_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 9, 45, 1, 65535, NULL, NULL, NULL, 30, 0,\ +NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 5.0 WebSockets connections. * The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts. */ -#define MQTTAsync_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 65535, NULL, NULL, NULL, 30, 0,\ -NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +#define MQTTAsync_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 9, 45, 0, 65535, NULL, NULL, NULL, 30, 0,\ +NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} /** diff --git a/src/MQTTAsyncUtils.c b/src/MQTTAsyncUtils.c index c9e68d857..326a93d45 100644 --- a/src/MQTTAsyncUtils.c +++ b/src/MQTTAsyncUtils.c @@ -62,6 +62,10 @@ static int cmdMessageIDCompare(void* a, void* b); static void MQTTAsync_retry(void); static MQTTPacket* MQTTAsync_cycle(SOCKET* sock, unsigned long timeout, int* rc); static int MQTTAsync_connecting(MQTTAsyncs* m); +static enum MQTTReasonCodes MQTTAsync_processAuth(MQTTAsync_authHandle *func, void *context, + MQTTAsync_authHandleData *data); +static int MQTTAsync_verifyAuthMethod(const char* authMethod, + const char* data, int dataLen); extern MQTTProtocol state; /* defined in MQTTAsync.c */ extern ClientStates* bstate; /* defined in MQTTAsync.c */ @@ -2133,6 +2137,44 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n) int sessionPresent = connack->flags.bits.sessionPresent; rc = MQTTAsync_completeConnection(m, connack); + if (rc == MQTTASYNC_SUCCESS && m->c->authMethod) + { + MQTTAsync_authHandleData authHandleData = MQTTAsync_authHandleData_initializer; + MQTTProperty *authMethodProp = NULL; + char *authMethod = NULL; + int authMethodLen = 0; + MQTTProperty *authData = NULL; + + authMethodProp = MQTTProperties_getProperty(&connack->properties, + MQTTPROPERTY_CODE_AUTHENTICATION_METHOD); + if (authMethodProp) + { + authMethod = authMethodProp->value.data.data; + authMethodLen = authMethodProp->value.data.len; + } + + if ((connack->rc == 0 && authMethodProp == NULL) || + MQTTAsync_verifyAuthMethod(m->c->authMethod, authMethod, + authMethodLen) == 0) + { + authData = MQTTProperties_getProperty(&connack->properties, + MQTTPROPERTY_CODE_AUTHENTICATION_DATA); + + authHandleData.reasonCode = connack->rc; + if (authData && authMethod) + { + authHandleData.authDataIn.data = authData->value.data.data; + authHandleData.authDataIn.len = authData->value.data.len; + } + + rc = MQTTAsync_processAuth(m->auth_handle, + m->auth_handle_context, + &authHandleData); + } + else + rc = MQTTREASONCODE_BAD_AUTHENTICATION_METHOD; + } + if (rc == MQTTASYNC_SUCCESS) { int onSuccess = 0; @@ -2366,6 +2408,58 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n) m->c->connected = 0; /* don't send disconnect packet back */ nextOrClose(m, discrc, "Received disconnect"); } + else if (pack->header.bits.type == AUTH) + { + Auth *auth = (Auth *)pack; + enum MQTTReasonCodes authrc = MQTTREASONCODE_SUCCESS; + MQTTProperty *authMethodProp = NULL; + MQTTProperty *authData = NULL; + char* authMethod = NULL; + int authMethodLen = 0; + MQTTAsync_authHandleData authHandleData = MQTTAsync_authHandleData_initializer; + + if (m->c->authMethod) + { + authMethodProp = MQTTProperties_getProperty(&auth->properties, + MQTTPROPERTY_CODE_AUTHENTICATION_METHOD); + if (authMethodProp) + { + authMethod = authMethodProp->value.data.data; + authMethodLen = authMethodProp->value.data.len; + } + + if ((auth->rc == 0 && authMethodProp == NULL) || + MQTTAsync_verifyAuthMethod(m->c->authMethod, authMethod, + authMethodLen) == 0) + { + authData = MQTTProperties_getProperty(&auth->properties, + MQTTPROPERTY_CODE_AUTHENTICATION_DATA); + + authHandleData.reasonCode = auth->rc; + if (authData && authMethod) + { + authHandleData.authDataIn.data = authData->value.data.data; + authHandleData.authDataIn.len = authData->value.data.len; + } + + authrc = MQTTAsync_processAuth(m->auth_handle, + m->auth_handle_context, + &authHandleData); + } + else + authrc = MQTTREASONCODE_BAD_AUTHENTICATION_METHOD; + } + else + authrc = MQTTREASONCODE_PROTOCOL_ERROR; + + rc = MQTTProtocol_handleAuth(pack, m->c->net.socket, authrc, + authHandleData.authDataOut.data, + authHandleData.authDataOut.len); + free(authHandleData.authDataOut.data); + if (authrc != MQTTREASONCODE_SUCCESS && + authrc != MQTTREASONCODE_CONTINUE_AUTHENTICATION) + nextOrClose(m, authrc, "Authentication failed"); + } } } } @@ -3224,3 +3318,36 @@ int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m) MQTTAsync_unlock_mutex(mqttcommand_mutex); return count; } + + +enum MQTTReasonCodes MQTTAsync_processAuth(MQTTAsync_authHandle *func, void *context, + MQTTAsync_authHandleData *data) +{ + int rc; + + if (func == NULL) + return MQTTREASONCODE_NOT_AUTHORIZED; + + rc = (*(func))(context, data); + if (rc < 0) + return MQTTREASONCODE_NOT_AUTHORIZED; + + if (rc > 0) + return MQTTREASONCODE_CONTINUE_AUTHENTICATION; + + return MQTTREASONCODE_SUCCESS; +} + + +int MQTTAsync_verifyAuthMethod(const char *authMethod, const char *data, int dataLen) +{ + if (authMethod && data && dataLen > 0) + { + if (strlen(authMethod) == dataLen && memcmp(authMethod, data, dataLen) == 0) + { + return 0; + } + } + + return -1; +} diff --git a/src/MQTTAsyncUtils.h b/src/MQTTAsyncUtils.h index 0f6e43437..930e2c644 100644 --- a/src/MQTTAsyncUtils.h +++ b/src/MQTTAsyncUtils.h @@ -113,6 +113,9 @@ typedef struct MQTTAsync_struct MQTTAsync_selectInterface* selectInterface; void* selectInterface_context; + MQTTAsync_authHandle* auth_handle; + void* auth_handle_context; + /* Each time connect is called, we store the options that were used. These are reused in any call to reconnect, or an automatic reconnect attempt */ MQTTAsync_command connect; /* Connect operation properties */ diff --git a/src/MQTTClient.c b/src/MQTTClient.c index 7ac9a8682..4781816eb 100644 --- a/src/MQTTClient.c +++ b/src/MQTTClient.c @@ -313,10 +313,8 @@ typedef struct MQTTClient_selectInterface* selectInterface; void* selectInterface_context; -#if 0 - MQTTClient_authHandle* auth_handle; + MQTTClient_handleAuth* auth_handle; void* auth_handle_context; /* the context to be associated with the authHandle callback*/ -#endif sem_type connect_sem; int rc; /* getsockopt return code in connect */ @@ -781,7 +779,6 @@ int MQTTClient_setPublished(MQTTClient handle, void* context, MQTTClient_publish } -#if 0 int MQTTClient_setHandleAuth(MQTTClient handle, void* context, MQTTClient_handleAuth* auth_handle) { int rc = MQTTCLIENT_SUCCESS; @@ -814,13 +811,13 @@ static thread_return_type WINAPI call_auth_handle(void* context) { struct props_rc_parms* pr = (struct props_rc_parms*)context; - (*(pr->m->auth_handle))(pr->m->auth_handle_context, pr->properties, pr->reasonCode); + //(*(pr->m->auth_handle))(pr->m->auth_handle_context, pr->properties, pr->reasonCode); + abort(); //TODO: Implement for MQTTClient MQTTProperties_free(pr->properties); free(pr->properties); free(pr); return 0; } -#endif /* This is the thread function that handles the calling of callback functions if set */ @@ -956,8 +953,7 @@ static thread_return_type WINAPI MQTTClient_run(void* n) } free(disc); } -#if 0 - if (pack->header.bits.type == AUTH && m->auth_handle) + else if (pack->header.bits.type == AUTH && m->auth_handle) { struct props_rc_parms dp; Ack* disc = (Ack*)pack; @@ -969,7 +965,6 @@ static thread_return_type WINAPI MQTTClient_run(void* n) Log(TRACE_MIN, -1, "Calling auth_handle for client %s", m->c->clientID); Thread_start(call_auth_handle, &dp); } -#endif } } else if (m->c->connect_state == TCP_IN_PROGRESS) @@ -1503,6 +1498,7 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO ELAPSED_TIME_TYPE millisecsTimeout = 30000L; MQTTResponse rc = MQTTResponse_initializer; int MQTTVersion = 0; + int freeConnectProperties = 0; FUNC_ENTRY; rc.reasonCode = SOCKET_ERROR; @@ -1537,6 +1533,11 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO if (options->httpsProxy) m->c->httpsProxy = MQTTStrdup(options->httpsProxy); } + if (options->MQTTVersion >= MQTTVERSION_5 && options->struct_version >= 9) + { + if (options->authMethod) + m->c->authMethod = MQTTStrdup(options->authMethod); + } if (m->c->will) { @@ -1681,6 +1682,53 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO } memcpy((void*)m->c->password, options->binarypwd.data, m->c->passwordlen); } + if (options->struct_version >= 9) + { + if (m->c->authMethod) + { + MQTTClient_handleAuthData authData = MQTTClient_handleAuthData_initializer; + MQTTProperty property; + int authrc = 0; + + if (!connectProperties) + { + /* free connectProperties if we allocated it */ + freeConnectProperties = 1; + + MQTTProperties initialized = MQTTProperties_initializer; + + if ((connectProperties = malloc(sizeof(MQTTProperties))) == NULL) + { + rc.reasonCode = PAHO_MEMORY_ERROR; + goto exit; + } + + *connectProperties = initialized; + } + + property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_METHOD; + property.value.data.data = m->c->authMethod; + property.value.data.len = (int)strlen(m->c->authMethod); + rc.reasonCode = MQTTProperties_add(connectProperties, &property); + if (rc.reasonCode) + goto exit; + + if (m->auth_handle) + { + authrc = (*(m->auth_handle))(m->auth_handle_context, &authData); + if (authrc < 0) + goto exit; + } + + property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_DATA; + property.value.data.data = authData.authDataOut.data; + property.value.data.len = authData.authDataOut.len; + rc.reasonCode = MQTTProperties_add(connectProperties, &property); + free(authData.authDataOut.data); + if (rc.reasonCode) + goto exit; + } + } if (options->struct_version >= 3) MQTTVersion = options->MQTTVersion; @@ -1702,6 +1750,12 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO connectProperties, willProperties); exit: + if (freeConnectProperties) + { + MQTTProperties_free(connectProperties); + free(connectProperties); + } + FUNC_EXIT_RC(rc.reasonCode); return rc; } @@ -1762,7 +1816,7 @@ MQTTResponse MQTTClient_connectAll(MQTTClient handle, MQTTClient_connectOptions* goto exit; } - if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 8) + if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 9) { rc.reasonCode = MQTTCLIENT_BAD_STRUCTURE; goto exit; diff --git a/src/MQTTClient.h b/src/MQTTClient.h index ac8eee92a..2e3c2b8fb 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -489,6 +489,63 @@ typedef void MQTTClient_published(void* context, int dt, int packet_type, MQTTPr LIBMQTT_API int MQTTClient_setPublished(MQTTClient handle, void* context, MQTTClient_published* co); +/** The authentication data that is populated when MQTTv5 enhanced authentication + * is requested by an operation. */ +typedef struct +{ + /** The eyecatcher for this structure. Will be MQAD. */ + char struct_id[4]; + /** The version number of this structure. Will be 0 */ + int struct_version; + /** The MQTT reason code received from the AUTH packet. */ + enum MQTTReasonCodes reasonCode; + /** + * The data received from the MQTTv5 AUTH packet AUTHENTICATION_DATA property. + * Data is NULL if no AUTHENTICATION_DATA was received. + */ + struct { + int len; /**< binary input AUTHENTICATION_DATA length */ + const void* data; /**< binary input AUTHENTICATION_DATA data */ + } authDataIn; + /** + * The data to populate the MQTTv5 AUTH packet AUTHENTICATION_DATA property. + * Set data to NULL to remove. To change, allocate new + * storage with ::MQTTClient_malloc - this will then be freed later by the library. + */ + struct { + int len; /**< binary output AUTHENTICATION_DATA length */ + void* data; /**< binary output AUTHENTICATION_DATA data */ + } authDataOut; +} MQTTClient_handleAuthData; + +#define MQTTClient_handleAuthData_initializer {{'M', 'Q', 'A', 'D'}, 0, 0, {0, NULL}, {0, NULL}} + +/** + * This is a callback function, which will be called when a MQTTv5 enhanced + * authentication packet is either received from the server or needs to be + * populated by the client. This applies to MQTT V5 and above only. + * @param context A pointer to the context value originally passed to + * ::MQTTClient_setHandleAuth(), which contains any application-specific context. + * @param data The MQTTClient_handleAuthData. + */ +typedef int MQTTClient_handleAuth(void* context, MQTTClient_handleAuthData* data); + +/** + * Sets the MQTTClient_setHandleAuth() callback function for a client. This will be called + * if an authentication packet is either received from the server or needs to be + * populated by the client. Only valid for MQTT V5 and above. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param context A pointer to any application-specific context. The + * the context pointer is passed to each of the callback functions to + * provide access to the context information in the callback. + * @param auth_handle A pointer to an MQTTClient_handleAuth() callback + * function. NULL removes the callback setting. + * @return ::MQTTCLIENT_SUCCESS if the callbacks were correctly set, + * ::MQTTCLIENT_FAILURE if an error occurred. + */ +LIBMQTT_API int MQTTClient_setHandleAuth(MQTTClient handle, void* context, MQTTClient_handleAuth* auth_handle); + /** * This function creates an MQTT client ready for connection to the * specified server and using the specified persistent storage (see @@ -834,6 +891,7 @@ typedef struct * 5 signifies no maxInflightMessages and cleanstart * 6 signifies no HTTP headers option * 7 signifies no HTTP proxy and HTTPS proxy options + * 8 signifies no MQTTv5 enhanced authentication method option */ int struct_version; /** The "keep alive" interval, measured in seconds, defines the maximum time @@ -976,27 +1034,31 @@ typedef struct * HTTPS proxy */ const char* httpsProxy; + /** + * MQTTv5 enhanced authentication method + */ + const char* authMethod; } MQTTClient_connectOptions; /** Initializer for connect options for MQTT 3.1.1 non-WebSocket connections */ -#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 1, NULL, NULL, NULL, 30, 0, NULL,\ -0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL} +#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 9, 60, 1, 1, NULL, NULL, NULL, 30, 0, NULL,\ +0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 5.0 non-WebSocket connections */ #define MQTTClient_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 1, NULL, NULL, NULL, 30, 0, NULL,\ -0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL} +0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 3.1.1 WebSockets connections. * The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts. */ #define MQTTClient_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 1, NULL, NULL, NULL, 30, 0, NULL,\ -0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL} +0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL, NULL} /** Initializer for connect options for MQTT 5.0 WebSockets connections. * The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts. */ #define MQTTClient_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 1, NULL, NULL, NULL, 30, 0, NULL,\ -0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL} +0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL, NULL} /** * This function attempts to connect a previously-created client (see @@ -1391,6 +1453,15 @@ LIBMQTT_API int MQTTClient_receive(MQTTClient handle, char** topicName, int* top */ LIBMQTT_API void MQTTClient_freeMessage(MQTTClient_message** msg); +/** + * This function is used to allocate memory to be used or freed by the MQTT C client library, + * especially the data in the ::MQTTPersistence_afterRead and ::MQTTPersistence_beforeWrite + * callbacks. This is needed on Windows when the client library and application + * program have been compiled with different versions of the C compiler. + * @param size The size of the memory to be allocated. + */ +LIBMQTT_API void* MQTTClient_malloc(size_t size); + /** * This function frees memory allocated by the MQTT C client library, especially the * topic name. This is needed on Windows when the client libary and application diff --git a/src/MQTTPacket.c b/src/MQTTPacket.c index d01110fc3..a632a4b67 100644 --- a/src/MQTTPacket.c +++ b/src/MQTTPacket.c @@ -87,7 +87,7 @@ pf new_packets[] = MQTTPacket_header_only, /**< PINGREQ */ MQTTPacket_header_only, /**< PINGRESP */ MQTTPacket_ack, /**< DISCONNECT */ - MQTTPacket_ack /**< AUTH */ + MQTTPacket_auth /**< AUTH */ }; @@ -635,6 +635,20 @@ void MQTTPacket_freeAck(Ack* pack) } +/** + * Free allocated storage for an auth packet. + * @param pack pointer to the publish packet structure + */ +void MQTTPacket_freeAuth(Auth* pack) +{ + FUNC_ENTRY; + if (pack->MQTTVersion >= MQTTVERSION_5) + MQTTProperties_free(&pack->properties); + free(pack); + FUNC_EXIT; +} + + /** * Send an MQTT acknowledgement packet down a socket. * @param MQTTVersion the version of MQTT being used @@ -841,6 +855,54 @@ void* MQTTPacket_ack(int MQTTVersion, unsigned char aHeader, char* data, size_t return pack; } +/** + * Function used in the new packets table to create authentication packets. + * @param MQTTVersion the version of MQTT being used + * @param aHeader the MQTT header byte + * @param data the rest of the packet + * @param datalen the length of the rest of the packet + * @return pointer to the packet structure + */ +void* MQTTPacket_auth(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen) +{ + Auth* pack = NULL; + char* curdata = data; + char* enddata = &data[datalen]; + + FUNC_ENTRY; + if ((pack = malloc(sizeof(Auth))) == NULL) + goto exit; + pack->MQTTVersion = MQTTVersion; + pack->header.byte = aHeader; + if (MQTTVersion < MQTTVERSION_5) + goto exit; + + MQTTProperties props = MQTTProperties_initializer; + + pack->rc = MQTTREASONCODE_SUCCESS; + pack->properties = props; + + /* AUTH has no msgid, if datalen is 0 then reason code is 0, if not we read it */ + if (datalen > 0) + pack->rc = readChar(&curdata); /* reason code */ + + if (datalen > 1) + { + if (MQTTProperties_read(&pack->properties, &curdata, enddata) != 1) + { + if (pack->properties.array) + free(pack->properties.array); + free(pack); + pack = NULL; /* signal protocol error */ + goto exit; + } + } + +exit: + FUNC_EXIT; + return pack; +} + /** * Send an MQTT PUBLISH packet down a socket. diff --git a/src/MQTTPacket.h b/src/MQTTPacket.h index 1fc8e06e4..93f866041 100644 --- a/src/MQTTPacket.h +++ b/src/MQTTPacket.h @@ -225,6 +225,17 @@ typedef Ack Pubrec; typedef Ack Pubrel; typedef Ack Pubcomp; +/** + * Data for the authentication packet. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ + unsigned char rc; /**< MQTT 5 reason code */ + int MQTTVersion; /**< the version of MQTT */ + MQTTProperties properties; /**< MQTT 5.0 properties */ +} Auth; + int MQTTPacket_encode(char* buf, size_t length); int MQTTPacket_decode(networkHandles* net, size_t* value); int readInt(char** pptr); @@ -249,10 +260,12 @@ void MQTTPacket_freePublish(Publish* pack); int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, networkHandles* net, const char* clientID); int MQTTPacket_send_puback(int MQTTVersion, int msgid, networkHandles* net, const char* clientID); void* MQTTPacket_ack(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen); +void* MQTTPacket_auth(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen); void MQTTPacket_freeAck(Ack* pack); void MQTTPacket_freeSuback(Suback* pack); void MQTTPacket_freeUnsuback(Unsuback* pack); +void MQTTPacket_freeAuth(Auth* pack); int MQTTPacket_send_pubrec(int MQTTVersion, int msgid, networkHandles* net, const char* clientID); int MQTTPacket_send_pubrel(int MQTTVersion, int msgid, int dup, networkHandles* net, const char* clientID); int MQTTPacket_send_pubcomp(int MQTTVersion, int msgid, networkHandles* net, const char* clientID); diff --git a/src/MQTTPacketOut.c b/src/MQTTPacketOut.c index fdff8cf06..0bdcf5467 100644 --- a/src/MQTTPacketOut.c +++ b/src/MQTTPacketOut.c @@ -131,6 +131,51 @@ int MQTTPacket_send_connect(Clients* client, int MQTTVersion, } +/** + * Send an MQTT AUTH packet down a socket for V5 or later + * @param client a structure from which to get all the required values + * @param MQTTVersion the MQTT version to connect with + * @param properties MQTT V5 properties for the authentication packet + * @return the completion code (e.g. TCPSOCKET_COMPLETE) + */ +int MQTTPacket_send_auth(Clients *client, enum MQTTReasonCodes reason, + MQTTProperties *props) +{ + Header header; + int rc = 0; + + FUNC_ENTRY; + header.byte = 0; + header.bits.type = AUTH; + + if (client->MQTTVersion >= 5 && (props || reason != MQTTREASONCODE_SUCCESS)) + { + size_t buflen = 1 + ((props == NULL) ? 0 : MQTTProperties_len(props)); + char *buf = NULL; + char *ptr = NULL; + + if ((buf = malloc(buflen)) == NULL) + { + rc = SOCKET_ERROR; + goto exit; + } + ptr = buf; + writeChar(&ptr, reason); + if (props) + MQTTProperties_write(&ptr, props); + if ((rc = MQTTPacket_send(&client->net, header, buf, buflen, 1, + client->MQTTVersion)) != TCPSOCKET_INTERRUPTED) + free(buf); + } + else + rc = MQTTPacket_send(&client->net, header, NULL, 0, 0, client->MQTTVersion); + exit: + Log(LOG_PROTOCOL, 28, NULL, client->net.socket, client->clientID, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + /** * Function used in the new packets table to create connack packets. * @param MQTTVersion MQTT 5 or less? diff --git a/src/MQTTPacketOut.h b/src/MQTTPacketOut.h index d9625544b..d0b28ecf1 100644 --- a/src/MQTTPacketOut.h +++ b/src/MQTTPacketOut.h @@ -36,4 +36,6 @@ void* MQTTPacket_suback(int MQTTVersion, unsigned char aHeader, char* data, size int MQTTPacket_send_unsubscribe(List* topics, MQTTProperties* props, int msgid, int dup, Clients* client); void* MQTTPacket_unsuback(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen); +int MQTTPacket_send_auth(Clients* client, enum MQTTReasonCodes reason, MQTTProperties* props); + #endif diff --git a/src/MQTTProtocolClient.c b/src/MQTTProtocolClient.c index e4b8f16c7..e898eff3d 100644 --- a/src/MQTTProtocolClient.c +++ b/src/MQTTProtocolClient.c @@ -693,6 +693,64 @@ int MQTTProtocol_handlePubcomps(void* pack, SOCKET sock, Publications** pubToRem } +/** + * Process an incoming authentication packet for a socket + * @param pack pointer to the auth packet + * @param sock the socket on which the packet was received + * @return completion code + */ +int MQTTProtocol_handleAuth(void *pack, SOCKET sock, enum MQTTReasonCodes reason, + void *data, int dataLen) +{ + Auth *auth = (Auth *)pack; + Clients *client = NULL; + int rc = TCPSOCKET_COMPLETE; + MQTTProperties props = MQTTProperties_initializer; + MQTTProperty prop; + + FUNC_ENTRY; + client = (Clients *)(ListFindItem(bstate->clients, &sock, + clientSocketCompare)->content); + Log(LOG_PROTOCOL, 30, NULL, sock, client->clientID, auth->rc); + + switch(reason) + { + case MQTTREASONCODE_SUCCESS: + rc = MQTTPacket_send_auth(client, reason, NULL); + break; + case MQTTREASONCODE_CONTINUE_AUTHENTICATION: + prop.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_METHOD; + prop.value.data.data = client->authMethod; + prop.value.data.len = (int)strlen(client->authMethod); + if (MQTTProperties_add(&props, &prop)) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + prop.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_DATA; + prop.value.data.data = data; + prop.value.data.len = dataLen; + if (MQTTProperties_add(&props, &prop)) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + rc = MQTTPacket_send_auth(client, reason, &props); + break; + default: + break; + } + + exit: + MQTTProperties_free(&props); + MQTTPacket_freeAuth(auth); + FUNC_EXIT_RC(rc); + return rc; +} + + /** * MQTT protocol keepAlive processing. Sends PINGREQ packets as required. * @param now current time @@ -956,6 +1014,8 @@ void MQTTProtocol_freeClient(Clients* client) free(client->httpsProxy); if (client->net.http_proxy_auth) free(client->net.http_proxy_auth); + if (client->authMethod) + free(client->authMethod); #if defined(OPENSSL) if (client->net.https_proxy_auth) free(client->net.https_proxy_auth); diff --git a/src/MQTTProtocolOut.h b/src/MQTTProtocolOut.h index adc95abfd..396b353b5 100644 --- a/src/MQTTProtocolOut.h +++ b/src/MQTTProtocolOut.h @@ -62,5 +62,6 @@ int MQTTProtocol_handleSubacks(void* pack, SOCKET sock); int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID, MQTTProperties* props); int MQTTProtocol_handleUnsubacks(void* pack, SOCKET sock); int MQTTProtocol_handleDisconnects(void* pack, SOCKET sock); +int MQTTProtocol_handleAuth(void* pack, SOCKET sock, enum MQTTReasonCodes reason, void *data, int dataLen); #endif