Skip to content

Commit b793020

Browse files
committed
Enhance Weixin bot functionality by adding TLS certificate management for dynamic hosts, implementing URL encoding for query parameters, and refining message authorization logic. Remove deprecated Python prototype for Weixin bot. Update configuration to disable Weixin support on ESP32-S3.
1 parent 9ee0646 commit b793020

5 files changed

Lines changed: 88 additions & 520 deletions

File tree

IM/channels/weixin_bot.c

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static THREAD_HANDLE s_qr_thread = NULL;
5656
static uint8_t *s_wx_cacert = NULL;
5757
static size_t s_wx_cacert_len = 0;
5858
static bool s_wx_tls_no_verify = false;
59+
static char s_wx_cert_host[128] = {0};
5960

6061
static bool s_session_paused = false;
6162
static uint32_t s_session_pause_start = 0;
@@ -68,7 +69,7 @@ static uint32_t s_fail_delay_ms = IM_WX_FAIL_BASE_MS;
6869

6970
static void weixin_poll_task(void *arg);
7071
static void weixin_qr_login_task(void *arg);
71-
72+
static const char *effective_host(void);
7273
/* ---------------------------------------------------------------------------
7374
* Utility helpers
7475
* --------------------------------------------------------------------------- */
@@ -86,7 +87,7 @@ static void weixin_make_uin(char *out, size_t out_size)
8687
size_t dec_len;
8788
size_t b64_len = 0;
8889

89-
snprintf(dec, sizeof(dec), "%u", val);
90+
snprintf(dec, sizeof(dec), "%lu", val);
9091
dec_len = strlen(dec);
9192
mbedtls_base64_encode((uint8_t *)out, out_size, &b64_len,
9293
(const uint8_t *)dec, dec_len);
@@ -98,31 +99,95 @@ static void weixin_make_uin(char *out, size_t out_size)
9899
}
99100

100101
/**
101-
* @brief Ensure TLS certificate for the Weixin API host is loaded.
102+
* @brief Ensure TLS certificate for the current effective API host is loaded.
103+
*
104+
* Invalidates the cached certificate when effective_host() differs from the
105+
* host the certificate was originally loaded for, so that a server-provided
106+
* custom host always gets its own certificate lookup.
107+
*
102108
* @return OPRT_OK always (falls back to no-verify on failure)
103109
*/
104110
static OPERATE_RET ensure_wx_cert(void)
105111
{
106-
if (s_wx_cacert && s_wx_cacert_len > 0) {
112+
const char *host = effective_host();
113+
114+
/* Cache hit: cert already loaded for the current host */
115+
if (s_wx_cacert && s_wx_cacert_len > 0 && strcmp(s_wx_cert_host, host) == 0) {
107116
return OPRT_OK;
108117
}
109118

110-
OPERATE_RET rt = im_tls_query_domain_certs(WX_API_HOST, &s_wx_cacert, &s_wx_cacert_len);
119+
/* Release stale cert when the host has changed */
120+
if (s_wx_cacert) {
121+
im_free(s_wx_cacert);
122+
s_wx_cacert = NULL;
123+
s_wx_cacert_len = 0;
124+
}
125+
s_wx_cert_host[0] = '\0';
126+
127+
OPERATE_RET rt = im_tls_query_domain_certs(host, &s_wx_cacert, &s_wx_cacert_len);
111128
if (rt != OPRT_OK || !s_wx_cacert || s_wx_cacert_len == 0) {
112129
if (s_wx_cacert) {
113130
im_free(s_wx_cacert);
114131
}
115132
s_wx_cacert = NULL;
116133
s_wx_cacert_len = 0;
117134
s_wx_tls_no_verify = true;
118-
IM_LOGD(TAG, "cert unavailable for %s, TLS no-verify mode", WX_API_HOST);
135+
IM_LOGD(TAG, "cert unavailable for %s, TLS no-verify mode", host);
119136
return OPRT_OK;
120137
}
121138

139+
im_safe_copy(s_wx_cert_host, sizeof(s_wx_cert_host), host);
122140
s_wx_tls_no_verify = false;
123141
return OPRT_OK;
124142
}
125143

144+
/**
145+
* @brief Percent-encode a string for safe inclusion in a URL query parameter.
146+
*
147+
* Only unreserved characters (RFC 3986) are left as-is; all others are
148+
* encoded as %XX. The output is always null-terminated.
149+
*
150+
* @param[in] src source string to encode
151+
* @param[out] dst output buffer
152+
* @param[in] dst_size size of dst (including space for '\0')
153+
* @return number of bytes written (excluding null terminator)
154+
*/
155+
static size_t url_encode(const char *src, char *dst, size_t dst_size)
156+
{
157+
static const char hex[] = "0123456789ABCDEF";
158+
size_t written = 0;
159+
160+
if (!src || !dst || dst_size == 0) {
161+
if (dst && dst_size > 0) {
162+
dst[0] = '\0';
163+
}
164+
return 0;
165+
}
166+
167+
for (const char *p = src; *p != '\0'; p++) {
168+
unsigned char c = (unsigned char)*p;
169+
bool unreserved = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
170+
(c >= '0' && c <= '9') ||
171+
c == '-' || c == '_' || c == '.' || c == '~';
172+
173+
if (unreserved) {
174+
if (written + 2 > dst_size) {
175+
break;
176+
}
177+
dst[written++] = (char)c;
178+
} else {
179+
if (written + 4 > dst_size) {
180+
break;
181+
}
182+
dst[written++] = '%';
183+
dst[written++] = hex[(c >> 4) & 0x0F];
184+
dst[written++] = hex[c & 0x0F];
185+
}
186+
}
187+
dst[written] = '\0';
188+
return written;
189+
}
190+
126191
/**
127192
* @brief Return the effective API hostname (stored or default).
128193
* @return const char* to hostname (no scheme, no port)
@@ -426,9 +491,13 @@ static void process_updates(const char *resp_str)
426491

427492
const char *from_user = from_node->valuestring;
428493

429-
/* allowFrom authorization: drop messages from unknown users */
430-
if (s_allow_from[0] != '\0' && strcmp(s_allow_from, from_user) != 0) {
431-
IM_LOGD(TAG, "drop msg from unauthorised user=%s", from_user);
494+
/* allowFrom authorization: fail-closed — reject all messages when no
495+
* allow_from is configured, and reject messages from non-matching users
496+
* when one is configured. This prevents a token-only provisioned device
497+
* from accepting commands from arbitrary senders. */
498+
if (s_allow_from[0] == '\0' || strcmp(s_allow_from, from_user) != 0) {
499+
IM_LOGD(TAG, "drop msg from unauthorised user=%s (allow_from=%s)",
500+
from_user, s_allow_from[0] ? s_allow_from : "<unset>");
432501
continue;
433502
}
434503

@@ -639,8 +708,10 @@ static void weixin_qr_login_task(void *arg)
639708
while ((uint32_t)(tal_system_get_millisecond() - login_deadline + IM_WX_LOGIN_TIMEOUT_MS)
640709
< IM_WX_LOGIN_TIMEOUT_MS) {
641710

711+
char qrcode_enc[768] = {0};
712+
url_encode(qrcode, qrcode_enc, sizeof(qrcode_enc));
642713
char path[320] = {0};
643-
snprintf(path, sizeof(path), "/ilink/bot/get_qrcode_status?qrcode=%s", qrcode);
714+
snprintf(path, sizeof(path), "/ilink/bot/get_qrcode_status?qrcode=%s", qrcode_enc);
644715

645716
uint16_t status = 0;
646717
OPERATE_RET rt = weixin_api_get(path,
@@ -905,9 +976,9 @@ OPERATE_RET weixin_send_message(const char *user_id, const char *text)
905976

906977
/* Generate client_id */
907978
char client_id[64] = {0};
908-
snprintf(client_id, sizeof(client_id), "wx-%08" PRIx32 "-%04x",
909-
tal_system_get_millisecond(),
910-
(unsigned)(tal_system_get_millisecond() & 0xFFFF));
979+
snprintf(client_id, sizeof(client_id), "wx-%016llx-%04x",
980+
tal_system_get_millisecond(),
981+
(unsigned int)(rand() % 0xFFFF));
911982

912983
/* Build item_list */
913984
cJSON *root = cJSON_CreateObject();

0 commit comments

Comments
 (0)