Files
infra/ai-memory/feedback_yealink_api.md
myron 52f6073593 Add Claude Code AI memory files
AI context/memory from Claude Code sessions covering all
infrastructure: JARVIS, NovaCPX, DO sites, Proxmox, FusionPBX,
MediaStack, and project feedback/preferences.
2026-06-26 03:06:26 +00:00

3.4 KiB

name, description, metadata
name description metadata
feedback-yealink-api Yealink T48S web API quirks — RSA/AES login, token-gated writes, correct page/field names for SIP account config
node_type type originSessionId
memory feedback 002fe81e-7e03-414d-b842-1f94f1390a22

Yealink T48S web API (firmware 66.86.0.15) — complete working flow:

Login (PKCS1v15 + AES-CBC hybrid)

  1. GET /servlet?m=mod_listener&p=login&q=loginForm — fetch RSA public key (g_rsa_n, g_rsa_e) and initial JSESSIONID cookie
  2. Generate random 16-byte AES key + IV; encrypt plaintext {rand};{JSESSIONID};{password} with AES-128-CBC zero-padding (NOT PKCS7), base64 result → pwd
  3. RSA-encrypt AES key hex string and IV hex string separately with PKCS1v15 → rsakey, rsaiv
    • Critical: encrypt the ASCII hex string (e.g. "a1b2c3...") not raw bytes — Yealink's pkcs1pad2 uses charCodeAt per character
    • Critical: AES key/IV hex must be lowercase
  4. POST username=admin&pwd=<b64>&rsakey=<hex>&rsaiv=<hex> to /servlet?m=mod_listener&p=login&q=login with JSESSIONID cookie
  5. Returns {"authstatus":"done"} on success; cookie jar updates with new JSESSIONID

Lockout: 3 failed attempts → ~10 min lockout (polling the login page also resets the timer — stop ALL requests to the phone during lockout)

SIP Account Config (account-register page)

  • Page: account-register (NOT account-basic — that page only has anonymous-call advanced fields)
  • Load: GET /servlet?m=mod_data&p=account-register&q=load
  • Write: POST /servlet?m=mod_data&p=account-register&q=write&token=<g_strToken>
    • Token is required — without it returns 403; with it returns 200 + empty _RES_INFO_ div (that empty response IS success)
    • Token comes from g_strToken variable in the loaded page HTML

Correct field names for SIP account 1:

Field Value
var_accountID 0 (0-indexed)
AccountEnable 1
AccountLabel display label
AccountDisplayName caller ID name
AccountRegisterName SIP auth username (e.g. 1000)
AccountUserName SIP username (e.g. 1000)
server1 SIP server IP (e.g. 134.209.72.226)
port1 SIP port (e.g. 5080)
Transport1 0 = UDP
Expires1 registration expiry seconds
AccountPassword AES-encrypted password (same AES key/IV as login)

Password encryption for writes: Same AES-CBC approach as login — encrypt plaintext password bytes with zero-padding, base64 result → AccountPassword. Send same rsakey + rsaiv alongside.

Autoprovision Trigger

GET /servlet?m=mod_data&p=settings-autop&q=autopnow&token=<g_strToken> → returns {"ret":"ok","data":"3"} on success

Reboot

POST /servlet?m=mod_data&p=settings-upgrade&q=write&type=reboot

SIP Registration Status

Load page contains JS: ccStatus = g_json.ParseJSON(...) with JSON like {"Account1":"1000@134.209.72.226:2"} — status codes: 0=disabled, 1=registered, 2=registering, 3=failed

Why: All this was reverse-engineered from Yealink's commonjs.js (pkcs1pad2 function) across multiple sessions after many failed approaches (textbook RSA, wrong plaintext format, wrong field names, missing token). How to apply: Use this as the reference any time we script Yealink T48S configuration via its web API. Scripts are saved at /tmp/yfix_server.py and /tmp/ydiag_write.py (on PVE1) as working examples.