@@ -56,6 +56,7 @@ static THREAD_HANDLE s_qr_thread = NULL;
5656static uint8_t * s_wx_cacert = NULL ;
5757static size_t s_wx_cacert_len = 0 ;
5858static bool s_wx_tls_no_verify = false;
59+ static char s_wx_cert_host [128 ] = {0 };
5960
6061static bool s_session_paused = false;
6162static uint32_t s_session_pause_start = 0 ;
@@ -68,7 +69,7 @@ static uint32_t s_fail_delay_ms = IM_WX_FAIL_BASE_MS;
6869
6970static void weixin_poll_task (void * arg );
7071static 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 */
104110static 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