@henningw commented on this pull request.

Thanks again. Besides the conflict in the PR (probably because of the merging 
of the auth_web3 module), I have added a bunch of review comments. Mostly 
related to error logging. error handling, name space topics and some 
recommendations about core function usage.

> +#include <stddef.h>
+
+// Configuration constants
+#define MAX_ENS_LENGTH 256
+#define MAX_ADDRESS_LENGTH 43  // 0x + 40 hex chars + null terminator
+#define MAX_SIGNATURE_LENGTH 132  // 0x + 130 hex chars + null terminator
+#define MAX_UUID_LENGTH 64
+#define MAX_RESPONSE_SIZE 4096
+
+// Return codes
+#define ARNACON_SUCCESS 200
+#define ARNACON_FAILURE 404
+#define ARNACON_ERROR -1
+
+// Structure for HTTP response data
+struct ResponseData {

This (commonly named) structures should be prefixed by the module name, see 
also below.

> + * @param ens ENS name
+ * @param registry_address ENS Registry contract address
+ * @param rpc_url RPC endpoint URL
+ * @param debug Enable debug logging
+ * @return 1 if user exists, 0 if not, -1 on error
+ */
+int arnacon_core_user_exists(const char *ens, const char *registry_address,
+                            const char *rpc_url, int debug);
+
+/**
+ * Compute ENS namehash for a given domain name
+ * @param name The ENS domain name
+ * @param hash_hex Output buffer for the hex-encoded hash (65 bytes minimum)
+ * @return 0 on success, -1 on error
+ */
+int compute_namehash(const char *name, char *hash_hex);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + * @param name The ENS domain name
+ * @param hash_hex Output buffer for the hex-encoded hash (65 bytes minimum)
+ * @return 0 on success, -1 on error
+ */
+int compute_namehash(const char *name, char *hash_hex);
+
+/**
+ * Make a blockchain RPC call to get ENS owner with dynamic Name Wrapper 
detection
+ * @param ens_name The ENS domain name
+ * @param owner_address Output buffer for the owner address (43 bytes minimum)
+ * @param registry_address ENS Registry contract address
+ * @param rpc_url RPC endpoint URL
+ * @param debug Enable debug logging
+ * @return 0 on success, -1 on error
+ */
+int get_ens_owner(const char *ens_name, char *owner_address, 

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + * @param owner_address Output buffer for the owner address (43 bytes 
> minimum)
+ * @param registry_address ENS Registry contract address
+ * @param rpc_url RPC endpoint URL
+ * @param debug Enable debug logging
+ * @return 0 on success, -1 on error
+ */
+int get_ens_owner(const char *ens_name, char *owner_address, 
+                 const char *registry_address, const char *rpc_url, int debug);
+
+/**
+ * Parse X-Data parameter to extract UUID and timestamp
+ * @param x_data The X-Data string
+ * @param parsed_data Output structure with parsed data
+ * @return 0 on success, -1 on error
+ */
+int parse_x_data(const char *x_data, struct XData *parsed_data);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> +
+/**
+ * Parse X-Data parameter to extract UUID and timestamp
+ * @param x_data The X-Data string
+ * @param parsed_data Output structure with parsed data
+ * @return 0 on success, -1 on error
+ */
+int parse_x_data(const char *x_data, struct XData *parsed_data);
+
+/**
+ * Validate timestamp against current time
+ * @param timestamp The timestamp to validate
+ * @param max_age_seconds Maximum age in seconds
+ * @return 0 if valid, -1 if expired
+ */
+int validate_timestamp(uint64_t timestamp, int max_age_seconds);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + * Validate timestamp against current time
+ * @param timestamp The timestamp to validate
+ * @param max_age_seconds Maximum age in seconds
+ * @return 0 if valid, -1 if expired
+ */
+int validate_timestamp(uint64_t timestamp, int max_age_seconds);
+
+/**
+ * Recover Ethereum address from ECDSA signature
+ * @param message The signed message
+ * @param signature The signature (hex string starting with 0x)
+ * @param recovered_address Output buffer for recovered address (43 bytes 
minimum)
+ * @param debug Enable debug logging
+ * @return 0 on success, -1 on error
+ */
+int recover_ethereum_address(const char *message, const char *signature, 

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + * @param signature The signature (hex string starting with 0x)
+ * @param recovered_address Output buffer for recovered address (43 bytes 
minimum)
+ * @param debug Enable debug logging
+ * @return 0 on success, -1 on error
+ */
+int recover_ethereum_address(const char *message, const char *signature, 
+                            char *recovered_address, int debug);
+
+/**
+ * Convert hex string to bytes
+ * @param hex_str Input hex string
+ * @param bytes Output byte array
+ * @param max_bytes Maximum number of bytes to convert
+ * @return Number of bytes converted, -1 on error
+ */
+int hex_to_bytes(const char *hex_str, unsigned char *bytes, size_t max_bytes);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> +/**
+ * Convert hex string to bytes
+ * @param hex_str Input hex string
+ * @param bytes Output byte array
+ * @param max_bytes Maximum number of bytes to convert
+ * @return Number of bytes converted, -1 on error
+ */
+int hex_to_bytes(const char *hex_str, unsigned char *bytes, size_t max_bytes);
+
+/**
+ * Convert bytes to hex string
+ * @param bytes Input byte array
+ * @param len Length of byte array
+ * @param hex_str Output hex string buffer
+ */
+void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> +
+/**
+ * Convert bytes to hex string
+ * @param bytes Input byte array
+ * @param len Length of byte array
+ * @param hex_str Output hex string buffer
+ */
+void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str);
+
+/**
+ * Normalize Ethereum address (convert to lowercase and ensure 0x prefix)
+ * @param address Input address
+ * @param normalized Output normalized address (43 bytes minimum)
+ * @return 0 on success, -1 on error
+ */
+int normalize_ethereum_address(const char *address, char *normalized);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + * Normalize Ethereum address (convert to lowercase and ensure 0x prefix)
+ * @param address Input address
+ * @param normalized Output normalized address (43 bytes minimum)
+ * @return 0 on success, -1 on error
+ */
+int normalize_ethereum_address(const char *address, char *normalized);
+
+/**
+ * cURL callback function for collecting response data
+ * @param contents Response data
+ * @param size Size of each element
+ * @param nmemb Number of elements
+ * @param userp User data (ResponseData structure)
+ * @return Number of bytes processed
+ */
+size_t response_write_callback(void *contents, size_t size, size_t nmemb, 
struct ResponseData *response);

Does this needs to be exported in the header file, or could it be made static 
in the c implementation?

> + */
+void arnacon_core_cleanup(void) {
+    curl_global_cleanup();
+    LM_INFO("Core authentication system cleaned up");
+}
+
+/**
+ * cURL callback function for collecting response data
+ */
+size_t response_write_callback(void *contents, size_t size, size_t nmemb, 
struct ResponseData *response) {
+    size_t realsize;
+    char *ptr;
+    
+    realsize = size * nmemb;
+    
+    if (response->memory == NULL) {

This is using another logic as the auth_web3 module. I think this logic is 
probably better, just noticed it. The auth_web3 module is also not logging any 
errors here.

> +        }
+        
+        bytes[i] = (unsigned char)val;
+    }
+    
+    return (int)byte_len;
+}
+
+/**
+ * Convert bytes to hex string
+ */
+void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str) {
+    size_t i;
+    
+    for (i = 0; i < len; i++) {
+        sprintf(hex_str + 2 * i, "%02x", bytes[i]);

Error checks?

> +        bytes[i] = (unsigned char)val;
+    }
+    
+    return (int)byte_len;
+}
+
+/**
+ * Convert bytes to hex string
+ */
+void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex_str) {
+    size_t i;
+    
+    for (i = 0; i < len; i++) {
+        sprintf(hex_str + 2 * i, "%02x", bytes[i]);
+    }
+    hex_str[2 * len] = '\0';

Are we sure that the hex_str is actually that large, as there are no input 
checks?

> +        LM_DBG("Checking if contract is NameWrapper: %s", contract_address);
+    }
+    
+    curl_easy_setopt(curl, CURLOPT_URL, rpc_url);
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_write_callback);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
+    
+    headers = curl_slist_append(NULL, "Content-Type: application/json");
+    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+    
+    res = curl_easy_perform(curl);
+    
+    if (res != CURLE_OK) {
+        if (debug) {

Wondering if we should log an error in any case, maybe without additional 
information? Otherwise the user will get no feedback that the curl request 
failed?

> +    
+    headers = curl_slist_append(NULL, "Content-Type: application/json");
+    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+    
+    res = curl_easy_perform(curl);
+    
+    if (res != CURLE_OK) {
+        if (debug) {
+            LM_DBG("curl_easy_perform() failed: %s (contract may not have 
name() function)", curl_easy_strerror(res));
+        }
+        goto cleanup;
+    }
+    
+    if (!response.memory) {
+        if (debug) {
+            LM_DBG("No response from blockchain");

Similar as above, maybe we should return an error? In the 
make_blockchain_call(..) we are having this logic, maybe its not important for 
this function? This is present in other checks below as well.

> +static int try_recover_with_recovery_id(secp256k1_context *ctx, const 
> unsigned char *sig_bytes, 
+                                       const unsigned char *message_hash, int 
recovery_id, 
+                                       char *recovered_address, int debug) {
+    secp256k1_ecdsa_recoverable_signature recoverable_sig;
+    secp256k1_pubkey pubkey;
+    unsigned char pubkey_bytes[65];
+    size_t pubkey_len;
+    SHA3_CTX addr_ctx;
+    unsigned char addr_hash[32];
+    int ret;
+    
+    (void)debug; /* Unused parameter */
+    
+    /* Create recoverable signature */
+    if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, 
&recoverable_sig, sig_bytes, recovery_id)) {
+        return -1;

Error logging? Also see below

> +    
+    (void)debug; /* Unused parameter */
+    
+    /* Create recoverable signature */
+    if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, 
&recoverable_sig, sig_bytes, recovery_id)) {
+        return -1;
+    }
+    
+    /* Recover public key */
+    if (!secp256k1_ecdsa_recover(ctx, &pubkey, &recoverable_sig, 
message_hash)) {
+        return -1;
+    }
+    
+    /* Serialize public key (uncompressed format) */
+    pubkey_len = sizeof(pubkey_bytes);
+    secp256k1_ec_pubkey_serialize(ctx, pubkey_bytes, &pubkey_len, &pubkey, 
SECP256K1_EC_UNCOMPRESSED);

Error checking?

> +    
+    LM_ERR("Failed to recover address with any recovery ID");
+    secp256k1_context_destroy(ctx);
+    return -1;
+}
+
+/**
+ * Check if a string is a valid Ethereum address (0x + 40 hex chars)
+ */
+static int is_ethereum_address(const char *str) {
+    int i;
+    
+    if (!str) return 0;
+    
+    /* Check for 0x prefix */
+    if (strncmp(str, "0x", 2) != 0 && strncmp(str, "0X", 2) != 0) {

Error logging? Also see below

> +    
+    /* Step 6: Compare addresses */
+    if (debug) {
+        LM_DBG("=== Address Comparison ===");
+        LM_DBG("ENS owner address:   %s", normalized_ens_address);
+        LM_DBG("Recovered address:   %s", normalized_recovered_address);
+    }
+    
+    if (strcmp(normalized_ens_address, normalized_recovered_address) == 0) {
+        if (debug) {
+            LM_DBG("Authentication SUCCESS: Addresses match!");
+        }
+        return ARNACON_SUCCESS;
+    } else {
+        if (debug) {
+            LM_DBG("Authentication FAILED: Addresses do not match");

Error logging?

> +}
+
+/**
+ * Module destroy function
+ */
+static void destroy(void)
+{
+    LM_INFO("Shutting down");
+    arnacon_core_cleanup();
+}
+
+/**
+ * Extract string parameter from Kamailio function call
+ * Handles both literal strings and pseudo-variables
+ */
+static int get_str_param(struct sip_msg *msg, char *param, str *result)

Why is this necessary? If I am not mistaken, there are functions to just print 
a string from the cfg, regardless if its a literal string or contains PVs.

> +    }
+}
+
+/**
+ * Extract X-Data and X-Sign headers from SIP message
+ */
+static int extract_auth_headers(struct sip_msg *msg, str *x_data, str *x_sign)
+{
+    struct hdr_field *hdr;
+    
+    /* Look for X-Data and X-Sign headers */
+    for (hdr = msg->headers; hdr; hdr = hdr->next) {
+        if (hdr->type == HDR_OTHER_T) {
+            if (hdr->name.len == 6 && strncasecmp(hdr->name.s, "X-Data", 6) == 
0) {
+                *x_data = hdr->body;
+                /* Trim whitespace */

Consider using trim_len from core/ut.h, its used from other modules in similar 
situations

> +    for (hdr = msg->headers; hdr; hdr = hdr->next) {
+        if (hdr->type == HDR_OTHER_T) {
+            if (hdr->name.len == 6 && strncasecmp(hdr->name.s, "X-Data", 6) == 
0) {
+                *x_data = hdr->body;
+                /* Trim whitespace */
+                while (x_data->len > 0 && (x_data->s[0] == ' ' || x_data->s[0] 
== '\t')) {
+                    x_data->s++;
+                    x_data->len--;
+                }
+                while (x_data->len > 0 && (x_data->s[x_data->len-1] == ' ' || 
x_data->s[x_data->len-1] == '\t' || x_data->s[x_data->len-1] == '\r' || 
x_data->s[x_data->len-1] == '\n')) {
+                    x_data->len--;
+                }
+            }
+            else if (hdr->name.len == 6 && strncasecmp(hdr->name.s, "X-Sign", 
6) == 0) {
+                *x_sign = hdr->body;
+                /* Trim whitespace */

Consider using trim_len from core/ut.h - its used from other modules in similar 
situations

-- 
Reply to this email directly or view it on GitHub:
https://github.com/kamailio/kamailio/pull/4470#pullrequestreview-3572646188
You are receiving this because you are subscribed to this thread.

Message ID: <kamailio/kamailio/pull/4470/review/[email protected]>
_______________________________________________
Kamailio - Development Mailing List -- [email protected]
To unsubscribe send an email to [email protected]
Important: keep the mailing list in the recipients, do not reply only to the 
sender!

Reply via email to