UNPKG

vodafone-station-cli

Version:

Access your Vodafone Station from the comfort of the command line.

183 lines (182 loc) 7.15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Arris = void 0; exports.normalizeChannelStatus = normalizeChannelStatus; exports.normalizeDocsisStatus = normalizeDocsisStatus; const modem_1 = require("./modem"); const crypto_1 = require("./tools/crypto"); const html_parser_1 = require("./tools/html-parser"); function normalizeChannelStatus(channelStatus) { const frequency = {}; if (channelStatus.ChannelType === 'SC-QAM') { frequency.frequency = channelStatus.Frequency; } if (['OFDM', 'OFDMA'].includes(channelStatus.ChannelType)) { const ofdmaFrequency = String(channelStatus.Frequency).split('~'); frequency.frequencyStart = Number(ofdmaFrequency[0]); frequency.frequencyEnd = Number(ofdmaFrequency[1]); } return { channelId: channelStatus.ChannelID, channelType: channelStatus.ChannelType, lockStatus: channelStatus.LockStatus, modulation: channelStatus.Modulation, powerLevel: Number.parseFloat(channelStatus.PowerLevel.split('/')[0]), snr: Number.parseInt(`${channelStatus.SNRLevel ?? 0}`, 10), ...frequency, }; } function normalizeDocsisStatus(arrisDocsisStatus) { const result = { downstream: [], downstreamOfdm: [], time: arrisDocsisStatus.time, upstream: [], upstreamOfdma: [], }; result.downstream = arrisDocsisStatus.downstream .filter(downstream => downstream.ChannelType === 'SC-QAM') .map(channel => normalizeChannelStatus(channel)); result.downstreamOfdm = arrisDocsisStatus.downstream .filter(downstream => downstream.ChannelType === 'OFDM') .map(channel => normalizeChannelStatus(channel)); result.upstream = arrisDocsisStatus.upstream .filter(upstream => upstream.ChannelType === 'SC-QAM') .map(channel => normalizeChannelStatus(channel)); result.upstreamOfdma = arrisDocsisStatus.upstream .filter(upstream => upstream.ChannelType === 'OFDMA') .map(channel => normalizeChannelStatus(channel)); return result; } class Arris extends modem_1.Modem { modemIp; protocol; logger; csrfNonce = ''; constructor(modemIp, protocol, logger) { super(modemIp, protocol, logger); this.modemIp = modemIp; this.protocol = protocol; this.logger = logger; } async addCredentialToCookie() { const credential = await this.fetchCredential(); this.logger.debug('Credential: ', credential); // set obligatory static cookie this.cookieJar.setCookie(`credential= ${credential}`, this.baseUrl); } async createServerRecord(setPasswordRequest) { try { const { data } = await this.httpClient.post('/php/ajaxSet_Password.php', setPasswordRequest); // TODO handle wrong password case // { p_status: 'Lockout', p_waitTime: 1 } if (data.p_status === 'Lockout') { throw new Error(`Remote user locked out for: ${data.p_waitTime}s`); } return data; } catch (error) { this.logger.error('Could not pass password on remote router.', error); throw error; } } async docsis() { if (!this.csrfNonce) { throw new Error('A valid csrfNonce is required in order to query the modem.'); } try { const { data } = await this.httpClient.get('/php/status_docsis_data.php', { headers: { Connection: 'keep-alive', csrfNonce: this.csrfNonce, Referer: `${this.baseUrl}/?status_docsis&mid=StatusDocsis`, }, }); return normalizeDocsisStatus((0, html_parser_1.extractDocsisStatus)(data)); } catch (error) { this.logger.error('Could not fetch remote docsis status', error); throw error; } } encryptPassword(password, cryptoVars) { const jsData = `{"Password": "${password}", "Nonce": "${cryptoVars.sessionId}"}`; const key = (0, crypto_1.deriveKey)(password, cryptoVars.salt); const authData = 'loginPassword'; const encryptData = (0, crypto_1.encrypt)(key, jsData, cryptoVars.iv, authData); return { AuthData: authData, EncryptData: encryptData, Name: modem_1.Modem.USERNAME, }; } async fetchCredential() { try { const { data } = await this.httpClient.get('/base_95x.js'); return (0, html_parser_1.extractCredentialString)(data); } catch (error) { this.logger.error('Could not fetch credential.', error); throw error; } } async getCurrentCryptoVars() { try { const { data } = await this.httpClient.get('/', { headers: { Accept: 'text/html,application/xhtml+xml,application/xml' }, }); const cryptoVars = (0, html_parser_1.extractCryptoVars)(data); this.logger.debug('Parsed crypto vars: ', cryptoVars); return cryptoVars; } catch (error) { this.logger.error('Could not get the index page from the router', error); throw error; } } async login(password) { const cryptoVars = await this.getCurrentCryptoVars(); const encPw = this.encryptPassword(password, cryptoVars); this.logger.debug('Encrypted password: ', encPw); const serverSetPassword = await this.createServerRecord(encPw); this.logger.debug('ServerSetPassword: ', serverSetPassword); const csrfNonce = this.loginPasswordCheck(serverSetPassword.encryptData, cryptoVars, (0, crypto_1.deriveKey)(password, cryptoVars.salt)); this.logger.debug('Csrf nonce: ', csrfNonce); await this.addCredentialToCookie(); this.csrfNonce = csrfNonce; } loginPasswordCheck(encryptedData, cryptoVars, key) { const csrfNonce = (0, crypto_1.decrypt)(key, encryptedData, cryptoVars.iv, 'nonce'); return csrfNonce; } async logout() { try { this.logger.log('Logging out...'); return this.httpClient.post('/php/logout.php'); } catch (error) { this.logger.error('Could not do a full session logout', error); throw error; } } async restart() { try { const { data } = await this.httpClient.post('php/ajaxSet_status_restart.php', { RestartReset: 'Restart', }, { headers: { Connection: 'keep-alive', csrfNonce: this.csrfNonce, Referer: `${this.baseUrl}/?status_docsis&mid=StatusDocsis`, }, }); this.logger.log('Router is restarting'); return data; } catch (error) { this.logger.error('Could not restart router.', error); throw error; } } } exports.Arris = Arris;