mirror of
https://github.com/myronblair/infra
synced 2026-06-30 17:50:10 -05:00
52f6073593
AI context/memory from Claude Code sessions covering all infrastructure: JARVIS, NovaCPX, DO sites, Proxmox, FusionPBX, MediaStack, and project feedback/preferences.
63 lines
3.4 KiB
Markdown
63 lines
3.4 KiB
Markdown
---
|
|
name: feedback-yealink-api
|
|
description: "Yealink T48S web API quirks — RSA/AES login, token-gated writes, correct page/field names for SIP account config"
|
|
metadata:
|
|
node_type: memory
|
|
type: feedback
|
|
originSessionId: 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.
|