mirror of
https://github.com/srlabs/blue-merle.git
synced 2025-04-05 22:05:40 -04:00
Compare commits
117 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d3aa7ae988 | ||
![]() |
5e0f652e40 | ||
![]() |
39cda00dbf | ||
![]() |
822bca6f4f | ||
![]() |
f3ef189b26 | ||
![]() |
770072b531 | ||
![]() |
037ef9f306 | ||
![]() |
6d61722bf2 | ||
![]() |
bc67b8107c | ||
![]() |
53d8f3afe7 | ||
![]() |
c31b0b1779 | ||
![]() |
8a20a370c8 | ||
![]() |
76c0fe482b | ||
![]() |
1aa741ffd8 | ||
![]() |
8f33aeac76 | ||
![]() |
68e15a863c | ||
![]() |
a2770e81c3 | ||
![]() |
b18e73fe9e | ||
![]() |
d5721f3279 | ||
![]() |
feddf2ec55 | ||
![]() |
bdc8837660 | ||
![]() |
948db4b6fa | ||
![]() |
9a3f072c23 | ||
![]() |
4b427bbcfc | ||
![]() |
a3fff24042 | ||
![]() |
1cd1125cfe | ||
![]() |
dfbcbe7506 | ||
![]() |
1a052c57e2 | ||
![]() |
ea05c4441b | ||
![]() |
488fdaa18f | ||
![]() |
b97c0f06eb | ||
![]() |
f8b8fa4805 | ||
![]() |
5d8efbb4e3 | ||
![]() |
5c6976aa11 | ||
![]() |
c68661e1a5 | ||
![]() |
9c08efc19a | ||
![]() |
d4886a54a9 | ||
![]() |
d47916552d | ||
![]() |
03c262a8c3 | ||
![]() |
fe4021feb9 | ||
![]() |
f8d35b15d5 | ||
![]() |
7b64f3aa9b | ||
![]() |
c8634591b2 | ||
![]() |
512da23200 | ||
![]() |
a0fad3a833 | ||
![]() |
9466707119 | ||
![]() |
87c420e2de | ||
![]() |
db8de09293 | ||
![]() |
8b4d371c9f | ||
![]() |
1c3c5f79e1 | ||
![]() |
0fbb00612c | ||
![]() |
c88d04003b | ||
![]() |
f7a9494a0c | ||
![]() |
958a0ecc99 | ||
![]() |
580d456ed1 | ||
![]() |
fa8e630714 | ||
![]() |
242235757a | ||
![]() |
0fa4d89602 | ||
![]() |
8053d44ce7 | ||
![]() |
0b4b04c48b | ||
![]() |
ed6856c490 | ||
![]() |
eab0633ad9 | ||
![]() |
db1c0c4c69 | ||
![]() |
ae40dcec1f | ||
![]() |
396ff7ea0e | ||
![]() |
2cf6495aad | ||
![]() |
6b7e11137b | ||
![]() |
881e9792c7 | ||
![]() |
29db0fab27 | ||
![]() |
df8578e402 | ||
![]() |
11a8afd0a9 | ||
![]() |
53bf156fd0 | ||
![]() |
4a16277ac2 | ||
![]() |
6ac26e1a33 | ||
![]() |
955d0f993e | ||
![]() |
3b87b1b8f4 | ||
![]() |
b02faa7adb | ||
![]() |
fa11fc45ca | ||
![]() |
b15a9390ec | ||
![]() |
259bffc515 | ||
![]() |
119a287304 | ||
![]() |
4636d77222 | ||
![]() |
ce77151081 | ||
![]() |
55a6f8d10f | ||
![]() |
9bd78b4a1d | ||
![]() |
4a4b5a4e0b | ||
![]() |
4f584b4b6c | ||
![]() |
4beae781a2 | ||
![]() |
6137fc0ea7 | ||
![]() |
8327c706d6 | ||
![]() |
e02de82dba | ||
![]() |
0e6af2eb77 | ||
![]() |
7531d2b691 | ||
![]() |
e867547c4f | ||
![]() |
04b1e6dfcb | ||
![]() |
6e6a7fdca6 | ||
![]() |
226687590e | ||
![]() |
5163b47811 | ||
![]() |
9894f936e0 | ||
![]() |
ea66e67d07 | ||
![]() |
3e686c79d3 | ||
![]() |
4c29fe2732 | ||
![]() |
843ddf9e7d | ||
![]() |
1a17e06a12 | ||
![]() |
9c9ab738ac | ||
![]() |
61a7466117 | ||
![]() |
406360fad2 | ||
![]() |
862ad1f7e3 | ||
![]() |
ef35ca9899 | ||
![]() |
da1a2f071c | ||
![]() |
187378a1d6 | ||
![]() |
75b1291e00 | ||
![]() |
09d2f8c087 | ||
![]() |
928d4360ca | ||
![]() |
d934a6c028 | ||
![]() |
7aaf65e8bd | ||
![]() |
0f2b358b31 |
63
.github/workflows/ci.yml
vendored
Normal file
63
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: CI
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-packages:
|
||||
name: Build Packages
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
env:
|
||||
owrt-package: blue-merle
|
||||
SDK_URL: "https://downloads.openwrt.org/releases/23.05.0/targets/ath79/nand/openwrt-sdk-23.05.0-ath79-nand_gcc-12.3.0_musl.Linux-x86_64.tar.xz"
|
||||
SDK_FILENAME: "openwrt-sdk-23.05.0-ath79-nand_gcc-12.3.0_musl.Linux-x86_64.tar.xz"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || 'main' }}
|
||||
- name: Get the SDK
|
||||
run: |
|
||||
mkdir sdk
|
||||
cd sdk
|
||||
wget "${SDK_URL}"
|
||||
tar xf "${SDK_FILENAME}"
|
||||
- name: Set up package dir
|
||||
run: |
|
||||
cd "sdk/${SDK_FILENAME%.tar.xz}"
|
||||
mkdir package/${{ env.owrt-package }}
|
||||
ln -s "${GITHUB_WORKSPACE}"/Makefile package/${{ env.owrt-package }}/
|
||||
ln -s "${GITHUB_WORKSPACE}"/files package/${{ env.owrt-package }}/
|
||||
- name: Update packages feed
|
||||
run: |
|
||||
cd "sdk/${SDK_FILENAME%.tar.xz}"
|
||||
scripts/feeds update packages >/dev/null
|
||||
- name: Set up OpenWrt config
|
||||
run: |
|
||||
cd "sdk/${SDK_FILENAME%.tar.xz}"
|
||||
echo "CONFIG_SIGNED_PACKAGES=n" > .config
|
||||
make defconfig
|
||||
- name: Build package
|
||||
run: |
|
||||
cd "sdk/${SDK_FILENAME%.tar.xz}"
|
||||
make -j$(nproc) V=s package/${{ env.owrt-package }}/compile
|
||||
make -j1 V=s package/index
|
||||
- name: Generate artifact name
|
||||
id: generate-name
|
||||
run: echo "artifact-name=${{ github.event.repository.name }}-${{ github.ref_name }}-${{ github.sha }}" >> $GITHUB_OUTPUT
|
||||
- name: Prepare artifact
|
||||
run: |
|
||||
mkdir package-output
|
||||
rsync -av sdk/"${SDK_FILENAME%.tar.xz}"/bin/packages/mips_24kc/base/"${{ env.owrt-package }}"*.ipk package-output/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.generate-name.outputs.artifact-name }}
|
||||
path: package-output/
|
Binary file not shown.
100
Makefile
100
Makefile
@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=blue-merle
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_VERSION:=2.0.4
|
||||
PKG_RELEASE:=$(AUTORELEASE)
|
||||
|
||||
PKG_MAINTAINER:=Matthias <matthias@srlabs.de>
|
||||
@ -12,7 +12,7 @@ include $(INCLUDE_DIR)/package.mk
|
||||
define Package/blue-merle
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
DEPENDS:=+bash +coreutils-shred +python3 +python3-pyserial +patch
|
||||
EXTRA_DEPENDS:=luci-base, gl-sdk4-mcu, coreutils-shred, python3-pyserial
|
||||
TITLE:=Anonymity Enhancements for GL-E750 Mudi
|
||||
endef
|
||||
|
||||
@ -29,8 +29,10 @@ endef
|
||||
define Package/blue-merle/install
|
||||
$(CP) ./files/* $(1)/
|
||||
$(INSTALL_BIN) ./files/etc/init.d/* $(1)/etc/init.d/
|
||||
$(INSTALL_BIN) ./files/lib/blue-merle/mac-wipe.sh $(1)/lib/blue-merle/mac-wipe.sh
|
||||
$(INSTALL_BIN) ./files/usr/bin/blue-merle $(1)/usr/bin/blue-merle
|
||||
$(INSTALL_BIN) ./files/etc/gl-switch.d/* $(1)/etc/gl-switch.d/
|
||||
$(INSTALL_BIN) ./files/usr/bin/* $(1)/usr/bin/
|
||||
$(INSTALL_BIN) ./files/usr/libexec/blue-merle $(1)/usr/libexec/blue-merle
|
||||
$(INSTALL_BIN) ./files/lib/blue-merle/imei_generate.py $(1)/lib/blue-merle/imei_generate.py
|
||||
endef
|
||||
|
||||
define Package/blue-merle/preinst
|
||||
@ -42,7 +44,7 @@ define Package/blue-merle/preinst
|
||||
if [ -f "/tmp/sysinfo/model" ] && [ -f "/etc/glversion" ]; then
|
||||
echo "You have a `cat /tmp/sysinfo/model`, running firmware version `cat /etc/glversion`."
|
||||
fi
|
||||
echo "blue-merle has only been tested with GL-E750 Mudi Version 3.215."
|
||||
echo "blue-merle has only been tested with GL-E750 Mudi Versions up to 4.3.19"
|
||||
echo "The device or firmware version you are using have not been verified to work with blue-merle."
|
||||
echo -n "Would you like to continue on your own risk? (y/N): "
|
||||
read answer
|
||||
@ -58,80 +60,46 @@ define Package/blue-merle/preinst
|
||||
fi
|
||||
}
|
||||
|
||||
UPDATE_MCU() {
|
||||
echo "6e6b86e3ad7fec0d5e426eb9a41c51c6f0d6b68a4d341ec553edeeade3e4b470 /tmp/e750-mcu-V1.0.7.bin" > /tmp/e750-mcu.bin.sha256
|
||||
wget -O /tmp/e750-mcu-V1.0.7.bin https://github.com/gl-inet/GL-E750-MCU-instruction/blob/master/e750-mcu-V1.0.7-56a1cad7f0eb8318ebe3c3c46a4cf3ff.bin?raw=true
|
||||
if sha256sum -cs /tmp/e750-mcu.bin.sha256; then
|
||||
ubus call service delete '{"name":"e750_mcu"}'
|
||||
mcu_update /tmp/e750-mcu-V1.0.7.bin
|
||||
else
|
||||
echo "Failed to update MCU, verification of the binary failed."
|
||||
echo "Your device needs to be connected to the Internet in order to download the MCU binary."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
CHECK_MCUVERSION() {
|
||||
function version { echo "$$@" | cut -d' ' -f2 | awk -F. '{ printf("%d%03d%03d%03d\n", $$1,$$2,$$3,$$4); }'; }
|
||||
mcu_version=`echo \{\"version\": \"1\"} > /dev/ttyS0; sleep 0.1; cat /dev/ttyS0|tr -d '\n'`
|
||||
if [ $$(version "$$mcu_version") -ge $$(version "V 1.0.7") ]; then
|
||||
return 0
|
||||
else
|
||||
echo
|
||||
echo "Your MCU version has not been verified to work with blue-merle."
|
||||
echo "Automatic shutdown may not work."
|
||||
echo "The install script can initiate an update of the MCU."
|
||||
echo "The device will reboot and, after reboot, you need to run opkg install blue-merle again."
|
||||
echo -n "Would you like to update your MCU? (y/N): "
|
||||
read answer
|
||||
case $$answer in
|
||||
Y*) answer=0;;
|
||||
y*) answer=0;;
|
||||
*) answer=1;;
|
||||
esac
|
||||
if [[ "$$answer" -eq 0 ]]; then
|
||||
UPDATE_MCU
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if grep -q "GL.iNet GL-E750" /proc/cpuinfo; then
|
||||
if grep -q -w "3.215" /etc/glversion; then
|
||||
CHECK_MCUVERSION
|
||||
echo "Device is supported, installing blue-merle."
|
||||
exit 0
|
||||
else
|
||||
ABORT_GLVERSION
|
||||
fi
|
||||
GL_VERSION=$$(cat /etc/glversion)
|
||||
case $$GL_VERSION in
|
||||
4.3.19)
|
||||
echo Version $$GL_VERSION is supported
|
||||
exit 0
|
||||
;;
|
||||
4.*)
|
||||
echo Version $$GL_VERSION is *probably* supported
|
||||
ABORT_GLVERSION
|
||||
;;
|
||||
*)
|
||||
echo Unknown version $$GL_VERSION
|
||||
ABORT_GLVERSION
|
||||
;;
|
||||
esac
|
||||
CHECK_MCUVERSION
|
||||
else
|
||||
ABORT_GLVERSION
|
||||
fi
|
||||
|
||||
# Our volatile-mac service gets started during the installation
|
||||
# but it modifies the client database held by the gl_clients process.
|
||||
# So we stop that process now, have the database put onto volatile storage
|
||||
# and start the service after installation
|
||||
/etc/init.d/gl_clients stop
|
||||
endef
|
||||
|
||||
define Package/blue-merle/postinst
|
||||
#!/bin/sh
|
||||
uci set switch-button.@main[0].func='sim'
|
||||
uci commit switch-button
|
||||
|
||||
patch -b /www/src/temple/settings/index.js /lib/blue-merle/patches/index.js.patch
|
||||
patch -b /www/src/temple/settings/index.html /lib/blue-merle/patches/index.html.patch
|
||||
patch -b /usr/bin/switchaction /lib/blue-merle/patches/switchaction.patch
|
||||
patch -b /usr/bin/switch_queue /lib/blue-merle/patches/switch_queue.patch
|
||||
/etc/init.d/gl_clients start
|
||||
|
||||
uci set glconfig.switch_button='service'
|
||||
uci set glconfig.switch_button.enable='1'
|
||||
uci set glconfig.switch_button.function='sim'
|
||||
uci commit glconfig
|
||||
echo {\"msg\": \"Successfully installed Blue Merle\"} > /dev/ttyS0
|
||||
endef
|
||||
|
||||
define Package/blue-merle/postrm
|
||||
#!/bin/sh
|
||||
|
||||
mv /www/src/temple/settings/index.js.orig /www/src/temple/settings/index.js
|
||||
mv /www/src/temple/settings/index.html.orig /www/src/temple/settings/index.html
|
||||
mv /usr/bin/switchaction.orig /usr/bin/switchaction
|
||||
mv /usr/bin/switch_queue.orig /usr/bin/switch_queue
|
||||
|
||||
rm /tmp/sim_change_start
|
||||
rm /tmp/sim_change_switch
|
||||
uci set switch-button.@main[0].func='tor'
|
||||
endef
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
||||
|
116
README.md
116
README.md
@ -5,26 +5,97 @@ The *blue-merle* software package enhances anonymity and reduces forensic tracea
|
||||
*blue-merle* addresses the traceability drawbacks of the Mudi router by adding the following features to the Mudi router:
|
||||
|
||||
1. Mobile Equipment Identity (IMEI) changer
|
||||
|
||||
2. Media Access Control (MAC) address log wiper
|
||||
|
||||
3. Basic Service Set Identifier (BSSID) randomization
|
||||
4. MAC Address randomization
|
||||
|
||||
## Installing prebuild package
|
||||
## Compatibility
|
||||
|
||||
Download the [prebuild package](https://github.com/srlabs/blue-merle/releases) and copy it onto your Mudi, preferably into the /tmp folder. Then install the package file:
|
||||
**This README covers the v2.0 release**, which has been verified to work with GL-E750 Mudi version 4.3.8 - 4.3.12 .
|
||||
Refer back to the [v1.0 README file](https://github.com/srlabs/blue-merle/tree/cb4d73731fe432e0f101284307101c250ca4b845) for information about the first release, which works on older firmware releases.
|
||||
|
||||
```
|
||||
A MCU version >= 1.0.7 is required. The MCU may be updated through the *blue-merle* package installer or [manually](https://github.com/gl-inet/GL-E750-MCU-instruction). SRLabs cannot guarantee that the project assets within this Git repository will be compatible with future firmware updates.
|
||||
|
||||
## Installation
|
||||
|
||||
### Online install & upgrade
|
||||
|
||||
The online install method requires an **active Internet connection** on your Mudi device to **download up-to-date dependencies**.
|
||||
|
||||
Download the [prebuilt v2.0 release package](https://github.com/srlabs/blue-merle/releases/download/v2.0/blue-merle_2.0.0-0_mips_24kc.ipk) and copy it onto your Mudi (e.g. via `scp`), preferably into the `/tmp` folder. Then install the package file:
|
||||
|
||||
```sh
|
||||
opkg update
|
||||
opkg install blue-merle*.ipk
|
||||
```
|
||||
|
||||
Now you may initiate an IMEI update on the command line by running `blue-merle` or by using Mudi's toggle button. Both the command line and hardware button version of *blue-merle* will guide you through the IMEI update process in order to minimize the risk of IMEI leaks.
|
||||
|
||||
The *blue-merle* package has been verified to work with GL-E750 Mudi version 3.215. A MCU version >= 1.0.7 is required. The MCU may be updated through the *blue-merle* package installer or [manually](https://github.com/gl-inet/GL-E750-MCU-instruction). SRLabs cannot guarantee that the project assets within this Git repository will be compatible with future firmware updates.
|
||||
|
||||
## Build package
|
||||
To uprade blue-merle, download the newest blue-merle*.ipk, copy it to your Mudi and reinstall with:
|
||||
```sh
|
||||
opkg install --force-reinstall blue-merle*.ipk
|
||||
```
|
||||
|
||||
### Offline install
|
||||
|
||||
The offline install method does **not need an active Internet connection** on your Mudi device.
|
||||
|
||||
Download the [prebuilt v2.0 offline release package](https://github.com/srlabs/blue-merle/releases/download/v2.0/blue-merle_2.0.0-0_offline_install.zip), then execute the following commands:
|
||||
|
||||
```sh
|
||||
## Execute the following commands on the computer connected to the Mudi via WiFi / LAN
|
||||
|
||||
unzip /path/to/downloaded.zip
|
||||
|
||||
# Copy the offline release package to your Mudi
|
||||
# -O might be needed due to SSH daemon used by Mudi
|
||||
scp -O -r blue_merle_install root@192.168.8.1:/tmp
|
||||
|
||||
# Connect to Mudi via SSH
|
||||
ssh root@192.168.8.1
|
||||
|
||||
## Execute the following commands inside the SSH tunnel
|
||||
# Install dependencies and blue-merle
|
||||
cd /tmp/blue_merle_install
|
||||
./install.sh
|
||||
```
|
||||
|
||||
**Note**: The offline install package bundles dependencies collected in October 2023. These dependencies could be outdated at the time of installation and might not be compatible with future Mudi firmware versions.
|
||||
|
||||
## Usage
|
||||
|
||||
You may initiate an IMEI update in three different ways:
|
||||
|
||||
1. **CLI**: via SSH on the command line,
|
||||
2. **Toggle**: using the Mudi's physical toggle switch, or
|
||||
3. **Web**: via the LuCI web interface.
|
||||
|
||||
You can set a deterministic or randomized IMEI on the command line. *Blue-merle*'s web and toggle interfaces always set a randomized IMEI.
|
||||
|
||||
### CLI
|
||||
|
||||
Connect to the device via SSH, then execute the `blue-merle` command. The command guides you through the process of **changing your SIM card**. We advise you to **reboot the device** after changing the IMEI.
|
||||
|
||||
### Toggle
|
||||
|
||||
This is a two-stage process.
|
||||
|
||||
Flip the Mudi's hardware switch to initiate the first stage of changing your device's IMEI. Follow the instructions on the display, which will ask you to **replace the SIM card** at the end.
|
||||
|
||||
After replacing the SIM card, flip the switch again. The second stage **changes the IMEI** and then **powers off** the device. You should **change location** before booting again.
|
||||
|
||||
**Note**: Occasionally, commands may take longer than expected to execute on the device. This can result in the display switching off (standby) for a few seconds before displaying the expected final message (e.g. instructions to replace the SIM card). Wait for the final message to appear before pulling the switch again. If no message is displayed after a minute, the script might have exited or you might have missed the message. In this case, pull the switch to continue / restart the process.
|
||||
|
||||
### Web
|
||||
|
||||
Open LuCI from `System` > `Advanced Settings` in Mudi's web interface. Find the `Blue Merle` settings under the `Network` tab. The web interface displays the current IMEI and IMSI and provides a button (`"SIM Swap..."`) to set a new randomized IMEI.
|
||||
|
||||
**Shutdown the device** once the process is complete. Then **swap your SIM card** and **change location** before booting again.
|
||||
|
||||
## Building
|
||||
|
||||
This repository contains a CI script to auto-build the project using GitHub actions. Simply fork the repository or replicate the workflow on your local machine to build packages.
|
||||
|
||||
You can also setup a full OpenWRT development environment and build the *blue-merle* package using:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/openwrt/openwrt
|
||||
cd openwrt
|
||||
git clone https://github.com/srlabs/blue-merle package/blue-merle
|
||||
@ -39,31 +110,30 @@ make menuconfig
|
||||
make
|
||||
make package/blue-merle/compile
|
||||
```
|
||||
|
||||
You will find the package in `./bin/packages/mips_24kc/base/`
|
||||
|
||||
## Implementation details
|
||||
|
||||
### IMEI randomization
|
||||
|
||||
The Mudi router's baseband unit is a Quectel EP06-E/A Series LTE Cat 6 Mini PCIe [module](https://www.quectel.com/wp-content/uploads/pdfupload/Quectel_EP06_Series_LTE-A_Specification_V1.7.pdf>).
|
||||
The Mudi router's baseband unit is a Quectel EP06-E/A Series LTE Cat 6 Mini PCIe [module](https://www.quectel.com/wp-content/uploads/pdfupload/Quectel_EP06_Series_LTE-A_Specification_V1.7.pdf).
|
||||
|
||||
The Mudi router's IMEI can be changed by issuing Quectel LTE series-standard AT commands. The AT command to write a new IMEI to a Quectel EP06-E/A-based device is `AT+EGMR`.
|
||||
|
||||
Our IMEI randomization functionality is built around this command and implements two approaches to IMEI generation. The first deterministic method seeds the new value with the user's ISMI, while the second generates a random IMEI.
|
||||
|
||||
To change the IMEI on the command-line, run `blue-merle` and follow the instructions. Alternatively, you can use the hardware switch button to set a random IMEI.
|
||||
Our IMEI randomization functionality is built around this command and implements two approaches to IMEI generation. The deterministic IMEI generation method generates a pseudo-random IMEI based on the inserted SIM's IMSI. This method will generate the same IMEI for the same IMSI, regardless of which particular *blue-merle*-enabled Mudi device is used. The second approach generates a random IMEI.
|
||||
|
||||
SRLabs researchers verified that the Mudi router's IMEI can be changed persistently by connecting the device to a custom telco base station set-up. The changed IMEI is recorded within the new base station database entry, confirming that the IMEI change is observed both on the device- and ISP-level.
|
||||
|
||||
Furthermore, to ensure that there is no leakage of the old IMEI after changing the SIM card and setting a new IMEI, the Mudi router's radio is turned off in advance. Both the command-line and hardware switch version of *blue-merle* will guide you through the IMEI update process in order to minimize the risk of IMEI leaks.
|
||||
Furthermore, to ensure that there is no leakage of the old IMEI after changing the SIM card and setting a new IMEI, the Mudi router's radio is turned off in advance and an interim randomized IMEI is set. Both the command-line and hardware switch version of *blue-merle* will guide you through the IMEI update process in order to minimize the risk of IMEI leaks.
|
||||
|
||||
Running *blue-merle* will disrupt the device's connection with the ISP during the time the IMEI is changed, and by default the connection is only reestablished once the device is rebooted.
|
||||
Running *blue-merle* will disrupt the device's connection with the ISP during the time the IMEI is changed, and by default the connection is only reestablished once the device is rebooted.
|
||||
|
||||
This process can be observed in Figure 1, where there is a large break in connectivity between entries 70 and 80. This break is the result of turning the radio off.
|
||||
|
||||

|
||||

|
||||
|
||||
[Figure 1](https://github.com/srlabs/blue-merle/blob/main/IMEI%20randomization.png) The router's radio is turned off and the IMEI is randomized between entries 70 and 80. The ISP cannot connect to it.
|
||||
[Figure 1](./IMEI%20randomization.png) The router's radio is turned off and the IMEI is randomized between entries 70 and 80. The ISP cannot connect to it.
|
||||
|
||||
### Basic Service Set Identifier (BSSID) randomization
|
||||
|
||||
@ -75,10 +145,12 @@ The BSSID randomization feature is run on boot, ensuring that a new BSSID is gen
|
||||
|
||||
### MAC address log wiping
|
||||
|
||||
Connecting devices' MAC addresses are stored within the Mudi router at `/tmp/tertf(_bak)` and `/etc/tertf(_bak)`. The MAC address log wiper first symbolically links the gl_tertf file responsible for the gltertf process, which reads and writes MAC addresses to the above-mentioned directories. It then kills the gltertf process if active, checks if either directory contains any data, and uses shred to delete any data if found.
|
||||
Connecting devices' MAC addresses are stored persistently within the Mudi router at `/etc/oui-tertf`. On boot, *blue-merle* deletes (using `shred`) the client database, then mounts a `tmpfs` filesystem at this location and restarts the services that manage the client database. This ensures the client database is only retained in RAM and not on disk while retaining the web UI functionality.
|
||||
|
||||
### MAC Address Randomization
|
||||
|
||||
*Blue-merle* sets a randomized MAC address for the WAN interface. If you use the device in repeater mode to connect to another WiFI AP, the Mudi's MAC address will change after every boot. This might interfere with MAC filtering if enabled on the upstream WiFi AP.
|
||||
|
||||
The MAC address log wiper is run on boot, ensuring that the Mudi device's initial MAC read/write functionality is disrupted each time the device is started.
|
||||
|
||||
## Acknowledgement: blue merle
|
||||
|
||||
The Mudi device shares a name with a Hungarian dog breed typically used to guard and herd flocks of livestock. Mudi dogs are agile, fast-learners and extremely friendly.
|
||||
|
30
files/etc/gl-switch.d/sim.sh
Normal file
30
files/etc/gl-switch.d/sim.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
action=$1
|
||||
logger -p notice -t blue-merle-toggle "Called... ${action}"
|
||||
|
||||
|
||||
. /lib/functions/gl_util.sh
|
||||
|
||||
|
||||
|
||||
if [ "$action" = "on" ];then
|
||||
mcu_send_message "Blue Merle ${action}"
|
||||
echo "on" > /tmp/sim_change_switch
|
||||
flock -n /tmp/blue-merle-switch.lock logger -p notice -t blue-merle-toggle "Running Stage 1" || logger -p notice -t blue-merle-toggle "Lockfile busy"
|
||||
flock -n /tmp/blue-merle-switch.lock timeout 90 /usr/bin/blue-merle-switch-stage1
|
||||
|
||||
elif [ "$action" = "off" ];then
|
||||
# We check for any previous run and eventually execute the second stage. We could check for the age of this marker and only activate the second stage is the marker is young enough.
|
||||
if [ -f /tmp/blue-merle-stage1 ]; then
|
||||
flock -n /tmp/blue-merle-switch.lock || logger -p notice -t blue-merle-toggle "Lockfile busy" &
|
||||
flock -n /tmp/blue-merle-switch.lock timeout 90 /usr/bin/blue-merle-switch-stage2
|
||||
else
|
||||
logger -p notice -t blue-merle-toggle "No Stage 1; Toggling Off"
|
||||
fi
|
||||
echo "off" > /tmp/sim_change_switch
|
||||
|
||||
else
|
||||
echo "off" > /tmp/sim_change_switch
|
||||
fi
|
||||
logger -p notice -t blue-merle-toggle "Finished Switch $action"
|
||||
sleep 1
|
@ -2,16 +2,15 @@
|
||||
|
||||
. /lib/blue-merle/functions.sh
|
||||
|
||||
START=81
|
||||
# We intend to be started before the first network-related service is started.
|
||||
# According to https://openwrt.org/docs/techref/initscripts, /etc/rc.d/ determines
|
||||
# the order of the services to be started (or stopped). The lower the number,
|
||||
# the earlier the service is started.
|
||||
# We observe "repeater" having the value 15. "network" 20. We certainly want to ahead of those.
|
||||
START=10
|
||||
STOP=99
|
||||
|
||||
start() {
|
||||
/lib/blue-merle/mac-wipe.sh
|
||||
CHECKMACSYMLINK
|
||||
RESET_BSSIDS
|
||||
}
|
||||
|
||||
stop() {
|
||||
/lib/blue-merle/mac-wipe.sh
|
||||
}
|
||||
|
||||
start() {
|
||||
RESET_BSSIDS
|
||||
RANDOMIZE_MACADDR
|
||||
}
|
||||
|
30
files/etc/init.d/volatile-client-macs
Normal file
30
files/etc/init.d/volatile-client-macs
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
# MAC addresses of connected clients are stored in a sqlite database.
|
||||
# Having the database seems to be necessary for the device to be working properly.
|
||||
# We intent to have the device store the database in RAM rather than on flash.
|
||||
# We replace the directory with a memory-backed tmpfs which is as volatile as we can make it.
|
||||
|
||||
# We want to run ahead of "gl-tertf" which, currently, has a prioprity of 60.
|
||||
# We also want to run ahead of "gl_clients" which has 99.
|
||||
START=9
|
||||
STOP=99
|
||||
|
||||
start() {
|
||||
tmpdir="$(mktemp -d)"
|
||||
# We mount a tmpfs so that the client database will be stored in memory only
|
||||
mount -t tmpfs / "$tmpdir"
|
||||
cp -a /etc/oui-tertf/client.db "$tmpdir"
|
||||
shred --force --remove /etc/oui-tertf/client.db || rm -f /etc/oui-tertf/client.db
|
||||
# If this script runs multiple times, we accumulate mounts; we try to avoid having mounts over mounts, so we unmount any existing tmpfs
|
||||
umount -t tmpfs -l /etc/oui-tertf
|
||||
|
||||
mount -t tmpfs / /etc/oui-tertf
|
||||
cp -a "$tmpdir/client.db" /etc/oui-tertf/client.db
|
||||
umount -t tmpfs -l "$tmpdir"
|
||||
}
|
||||
|
||||
stop() {
|
||||
shred /etc/oui-tertf/client.db || rm -f /etc/oui-tertf/client.db
|
||||
}
|
||||
|
@ -2,27 +2,6 @@
|
||||
|
||||
# This script provides helper functions for blue-merle
|
||||
|
||||
# check that MAC wiping/linking to dev/null is still in place
|
||||
CHECKMACSYMLINK () {
|
||||
local loc_file="/etc/init.d/gl_tertf"
|
||||
if [ $(readlink -f "$loc_file") == "/dev/null" ]
|
||||
then
|
||||
echo "TEST: EXISTS"
|
||||
else
|
||||
echo "TEST: DOES NOT EXIST"
|
||||
cp "$loc_file" "$loc_file.bak" # todo: consider if we need to move this backup elsewhere?
|
||||
ln -sf /dev/null "$loc_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Restore gl_tertf from back-up
|
||||
RESTORE_GL_TERTF () {
|
||||
local loc_file="/etc/init.d/gl_tertf"
|
||||
local loc_backup="/etc/init.d/gl_tertf.bak"
|
||||
#local loc_location="/etc/init.d"
|
||||
rm "$loc_file"
|
||||
mv "$loc_backup" "$loc_file"
|
||||
}
|
||||
|
||||
UNICAST_MAC_GEN () {
|
||||
loc_mac_numgen=`python3 -c "import random; print(f'{random.randint(0,2**48) & 0b111111101111111111111111111111111111111111111111:0x}'.zfill(12))"`
|
||||
@ -35,9 +14,25 @@ RESET_BSSIDS () {
|
||||
uci set wireless.@wifi-iface[1].macaddr=`UNICAST_MAC_GEN`
|
||||
uci set wireless.@wifi-iface[0].macaddr=`UNICAST_MAC_GEN`
|
||||
uci commit wireless
|
||||
wifi # need to reset wifi for changes to apply
|
||||
# you need to reset wifi for changes to apply, i.e. executing "wifi"
|
||||
}
|
||||
|
||||
|
||||
RANDOMIZE_MACADDR () {
|
||||
# This changes the MAC address clients see when connecting to the WiFi spawned by the device.
|
||||
# You can check with "arp -a" that your endpoint, e.g. your laptop, sees a different MAC after a reboot of the Mudi.
|
||||
uci set network.@device[1].macaddr=`UNICAST_MAC_GEN`
|
||||
# Here we change the MAC address the upstream wifi sees
|
||||
uci set glconfig.general.macclone_addr=`UNICAST_MAC_GEN`
|
||||
uci commit network
|
||||
# You need to restart the network, i.e. /etc/init.d/network restart
|
||||
}
|
||||
|
||||
READ_ICCID() {
|
||||
gl_modem AT AT+CCID
|
||||
}
|
||||
|
||||
|
||||
READ_IMEI () {
|
||||
local answer=1
|
||||
while [[ "$answer" -eq 1 ]]; do
|
||||
@ -82,11 +77,28 @@ READ_IMSI () {
|
||||
echo $imsi
|
||||
}
|
||||
|
||||
|
||||
GENERATE_IMEI() {
|
||||
local seed=$(head -100 /dev/urandom | tr -dc "0123456789" | head -c10)
|
||||
local imei=$(lua /lib/blue-merle/luhn.lua $seed)
|
||||
echo -n $imei
|
||||
}
|
||||
|
||||
SET_IMEI() {
|
||||
local imei="$1"
|
||||
|
||||
if [[ ${#imei} -eq 14 ]]; then
|
||||
gl_modem AT AT+EGMR=1,7,${imei}
|
||||
else
|
||||
echo "IMEI is ${#imei} not 14 characters long"
|
||||
fi
|
||||
}
|
||||
|
||||
CHECK_ABORT () {
|
||||
sim_change_switch=`cat /tmp/sim_change_switch`
|
||||
if [[ "$sim_change_switch" = "off" ]]; then
|
||||
e750-mcu "SIM change aborted."
|
||||
sleep 1
|
||||
exit 1
|
||||
if [[ "$sim_change_switch" = "off" ]]; then
|
||||
echo '{ "msg": "SIM change aborted." }' > /dev/ttyS0
|
||||
sleep 1
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import serial
|
||||
import re
|
||||
from functools import reduce
|
||||
from enum import Enum
|
||||
from tacs import tacs
|
||||
|
||||
|
||||
class Modes(Enum):
|
||||
@ -17,6 +18,8 @@ class Modes(Enum):
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("-v", "--verbose", help="Enables verbose output",
|
||||
action="store_true")
|
||||
ap.add_argument("-g", "--generate-only", help="Only generates an IMEI rather than setting it",
|
||||
action="store_true")
|
||||
modes = ap.add_mutually_exclusive_group()
|
||||
modes.add_argument("-d", "--deterministic", help="Switches IMEI generation to deterministic mode", action="store_true")
|
||||
modes.add_argument("-s", "--static", help="Sets user-defined IMEI",
|
||||
@ -27,11 +30,11 @@ modes.add_argument("-r", "--random", help="Sets random IMEI",
|
||||
# Example IMEI: 490154203237518
|
||||
imei_length = 14 # without validation digit
|
||||
# IDEA: make prefix configurable via CLI option
|
||||
imei_prefix = ["35674108", "35290611", "35397710", "35323210", "35384110",
|
||||
"35982748", "35672011", "35759049", "35266891", "35407115",
|
||||
"35538025", "35480910", "35324590", "35901183", "35139729",
|
||||
"35479164"]
|
||||
|
||||
# imei_prefix = ["35674108", "35290611", "35397710", "35323210", "35384110",
|
||||
# "35982748", "35672011", "35759049", "35266891", "35407115",
|
||||
# "35538025", "35480910", "35324590", "35901183", "35139729",
|
||||
# "35479164"]
|
||||
imei_prefix = tacs
|
||||
verbose = False
|
||||
mode = None
|
||||
|
||||
@ -42,7 +45,11 @@ TIMEOUT = 3
|
||||
|
||||
|
||||
def get_imsi():
|
||||
if (verbose):
|
||||
print(f'Obtaining Serial {TTY} with timeout {TIMEOUT}...')
|
||||
with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser:
|
||||
if (verbose):
|
||||
print('Getting IMSI')
|
||||
ser.write(b'AT+CIMI\r')
|
||||
# TODO: read loop until we have 'enough' of what to expect
|
||||
output = ser.read(64)
|
||||
@ -141,6 +148,10 @@ def generate_imei(imei_prefix, imsi_d):
|
||||
|
||||
|
||||
def validate_imei(imei):
|
||||
# before anything check if length is 14 characters
|
||||
if len(imei) != 14:
|
||||
print(f"NOT A VALID IMEI: {imei} - IMEI must be 14 characters in length")
|
||||
return False
|
||||
# cut off last digit
|
||||
validation_digit = int(imei[-1])
|
||||
imei_verify = imei[0:14]
|
||||
@ -172,10 +183,12 @@ def validate_imei(imei):
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = ap.parse_args()
|
||||
imsi_d = None
|
||||
if args.verbose:
|
||||
verbose = args.verbose
|
||||
if args.deterministic:
|
||||
mode = Modes.DETERMINISTIC
|
||||
imsi_d = get_imsi()
|
||||
if args.random:
|
||||
mode = Modes.RANDOM
|
||||
if args.static is not None:
|
||||
@ -188,11 +201,11 @@ if __name__ == '__main__':
|
||||
else:
|
||||
exit(-1)
|
||||
else:
|
||||
imsi_d = get_imsi()
|
||||
imei = generate_imei(imei_prefix, imsi_d)
|
||||
if (verbose):
|
||||
print(f"Generated new IMEI: {imei}")
|
||||
if not set_imei(imei):
|
||||
exit(-1)
|
||||
if not args.generate_only:
|
||||
if not set_imei(imei):
|
||||
exit(-1)
|
||||
|
||||
exit(0)
|
||||
|
61
files/lib/blue-merle/luhn.lua
Normal file
61
files/lib/blue-merle/luhn.lua
Normal file
@ -0,0 +1,61 @@
|
||||
---- Adapted from https://cybersecurity.att.com/blogs/labs-research/luhn-checksum-algorithm-lua-implementation
|
||||
|
||||
local bit = require("bit")
|
||||
|
||||
local band, bor, bxor = bit.band, bit.bor, bit.bxor
|
||||
|
||||
function luhn_checksum(card)
|
||||
local num = 0
|
||||
local nDigits = card:len()
|
||||
odd = band(nDigits, 1)
|
||||
|
||||
for count = 0,nDigits-1 do
|
||||
|
||||
digit = tonumber(string.sub(card, count+1,count+1))
|
||||
|
||||
if (bxor(band(count, 1),odd)) == 0
|
||||
then
|
||||
digit = digit * 2
|
||||
end
|
||||
|
||||
if digit > 9 then
|
||||
digit = digit - 9
|
||||
end
|
||||
|
||||
num = num + digit
|
||||
end
|
||||
|
||||
return num
|
||||
end
|
||||
|
||||
function luhn_digit (s)
|
||||
local num = luhn_checksum (s)
|
||||
return (10 - (num % 10))
|
||||
end
|
||||
|
||||
function is_valid_luhn (s)
|
||||
local num = luhn_checksum (s)
|
||||
return ((num % 10) == 0)
|
||||
end
|
||||
|
||||
|
||||
function make_imei (premei)
|
||||
local imei = premei .. tostring(luhn_digit(premei .. "0"))
|
||||
return imei
|
||||
end
|
||||
|
||||
function make_random_imei ()
|
||||
local nDigits = 14
|
||||
local premei = ""
|
||||
for count = 0, nDigits-1 do
|
||||
premei = premei .. tostring (math.random (0, 9))
|
||||
end
|
||||
|
||||
local imei = make_imei (tostring (premei))
|
||||
|
||||
return imei
|
||||
end
|
||||
|
||||
math.randomseed(tonumber(arg[1]))
|
||||
|
||||
print (make_random_imei ())
|
@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env ash
|
||||
|
||||
# This script wipes all MAC address data from the device and is called upon boot
|
||||
|
||||
tmp_dir="/tmp/tertf"
|
||||
tmp_file="/tmp/tertf/tertfinfo_bak"
|
||||
|
||||
etc_dir="/etc/tertf"
|
||||
etc_file="/etc/tertf/tertfinfo_bak"
|
||||
|
||||
# Check for directories
|
||||
CHECKDIR_TMP () {
|
||||
if [ -d "$tmp_dir" ]; then
|
||||
echo "The /tmp/ directory exists."
|
||||
else
|
||||
echo "The /tmp/ directory does not exist. This should be fine..."
|
||||
fi
|
||||
}
|
||||
|
||||
CHECKDIR_ETC () {
|
||||
if [ -d "$etc_dir" ]; then
|
||||
echo "The /etc/ directory exists."
|
||||
else
|
||||
echo "The /etc/ directory does not exist. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# trick the gl_tertf file into moving stuff to the void
|
||||
GASLIGHT () { # good job lil dude you're doing so well
|
||||
local file="/etc/init.d/gl_tertf"
|
||||
ln -sf /dev/null "$file"
|
||||
}
|
||||
|
||||
CHECKDIR_TMP
|
||||
CHECKDIR_ETC
|
||||
GASLIGHT
|
||||
|
||||
# Kills process responsible for manipulating (and protecting) the /tmp/ file instance
|
||||
killall -9 gltertf
|
||||
|
||||
# shredding /tmp/tertf
|
||||
if [ -f "$tmp_file" ];then
|
||||
echo "Files found within /tmp/. Let's get to it."
|
||||
shred -v -u "$tmp_file"
|
||||
else
|
||||
echo "No file found within /tmp/tertf. No shredding to be done there."
|
||||
fi
|
||||
|
||||
# shredding /etc/tertf
|
||||
if [ -f "$etc_file" ]; then
|
||||
echo "Files found in /etc/. Let's get to it."
|
||||
shred -v -u "$etc_file" #-v provides verbose output to ease my anxious mind and -u deletes files after they are overwritten
|
||||
else
|
||||
echo "No file found within /etc/tertf. No shredding to be done there."
|
||||
fi
|
||||
|
||||
# check if the files have been removed
|
||||
if [ ! -f "$tmp_file" ]; then
|
||||
echo "Looks like /tmp/ is clean!"
|
||||
else
|
||||
echo "Something went wrong in /tmp/."
|
||||
fi
|
||||
|
||||
if [ ! -f "$etc_file" ]; then
|
||||
echo "Looks like /etc/ is clean!"
|
||||
else
|
||||
echo "Something went wrong in /etc/."
|
||||
fi
|
||||
|
||||
exit 0
|
@ -1,39 +0,0 @@
|
||||
--- orig/index.html
|
||||
+++ patch/index.html
|
||||
@@ -6,7 +6,7 @@
|
||||
<span class="circle active"></span>
|
||||
<span>{{t($lang.setting.btnSetting)}}</span>
|
||||
</h4>
|
||||
- <gl-btn type="purple" class="pull-right moblieBtnLate" @click="checkType" :disabled="btnStatus">{{t($lang.button.apply)}}</gl-btn>
|
||||
+ <gl-btn type="purple" class="pull-right moblieBtnLate" @click="checkType" :disabled="true">{{t($lang.button.apply)}}</gl-btn>
|
||||
</div>
|
||||
<div class="panel-body panel-status">
|
||||
<span class="setting-box clearfix list-group">
|
||||
@@ -64,6 +64,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+
|
||||
+ <div v-show="parstatus=='sim'">
|
||||
+ <div style="width:47%; display:inline-block;vertical-align:top;">
|
||||
+ <div id="title_left" style=" font-weight:bold">SIM Change Start</div>
|
||||
+ <div id="content_left" class="content_switch">{{'SIM change will be initiated'}}.
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ <div style="width:47%; display:inline-block;vertical-align:top">
|
||||
+ <div id="title_right" style="font-weight:bold">SIM Change Stop</div>
|
||||
+ <div id="content_right" class="content_switch">
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+
|
||||
</div>
|
||||
<div style="display:none">
|
||||
<label class="div-help-title">{{t($lang.setting.diy)}}</label>
|
||||
@@ -84,4 +98,4 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
-</div>
|
||||
\ No newline at end of file
|
||||
+</div>
|
@ -1,41 +0,0 @@
|
||||
--- orig/index.js
|
||||
+++ patch/index.js
|
||||
@@ -6,7 +6,7 @@
|
||||
data: function data() {
|
||||
return {
|
||||
isShow: false,
|
||||
- chooseState: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)"],
|
||||
+ chooseState: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)", "SIM Change (Start/Stop)"],
|
||||
// chooseState4G: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)", ],
|
||||
param: "",
|
||||
btnStatus: true,
|
||||
@@ -79,6 +79,9 @@
|
||||
case "Tor Toggle (On/Off)":
|
||||
status = "tor";
|
||||
break;
|
||||
+ case "SIM Change (Start/Stop)":
|
||||
+ status="sim";
|
||||
+ break;
|
||||
}
|
||||
return status;
|
||||
},
|
||||
@@ -108,6 +111,9 @@
|
||||
case "tor":
|
||||
_this.param = "Tor Toggle (On/Off)";
|
||||
break;
|
||||
+ case "sim":
|
||||
+ _this.param = "SIM Change (Start/Stop)";
|
||||
+ break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -170,6 +176,9 @@
|
||||
case 'tor':
|
||||
_this.openSetting();
|
||||
break;
|
||||
+ case 'sim':
|
||||
+ _this.openSetting();
|
||||
+ break;
|
||||
}
|
||||
},
|
||||
openSetting: function openSetting() {
|
@ -1,23 +0,0 @@
|
||||
--- orig/switch_queue
|
||||
+++ patch/switch_queue
|
||||
@@ -81,6 +81,10 @@
|
||||
"tor")
|
||||
e750-mcu " Turning TOR ON"
|
||||
;;
|
||||
+ "sim")
|
||||
+# e750-mcu " Starting SIM swap"
|
||||
+ blue-merle-switch&
|
||||
+ ;;
|
||||
"*")
|
||||
;;
|
||||
esac
|
||||
@@ -98,6 +102,9 @@
|
||||
check_other_vpn tor
|
||||
e750-mcu " Turning TOR OFF"
|
||||
;;
|
||||
+ "sim")
|
||||
+# e750-mcu " Stopping SIM swap"
|
||||
+ ;;
|
||||
"*")
|
||||
;;
|
||||
esac
|
@ -1,38 +0,0 @@
|
||||
--- orig/switchaction
|
||||
+++ patch/switchaction
|
||||
@@ -10,6 +10,15 @@
|
||||
fi
|
||||
}
|
||||
|
||||
+toggle_sim(){
|
||||
+ local action=$1
|
||||
+ if [ "$action" = "OFF" ];then
|
||||
+ sim_switch off
|
||||
+ else
|
||||
+ sim_switch on
|
||||
+ fi
|
||||
+}
|
||||
+
|
||||
check_other_vpn(){
|
||||
wg_server=$(uci get wireguard_server.@servers[0].enable)
|
||||
ov_server=$(uci get vpn_service.global.enable)
|
||||
@@ -136,6 +145,9 @@
|
||||
check_other_vpn tor
|
||||
toggle_tor ON
|
||||
;;
|
||||
+ "sim")
|
||||
+ toggle_sim ON
|
||||
+ ;;
|
||||
"*")
|
||||
;;
|
||||
esac
|
||||
@@ -157,6 +169,9 @@
|
||||
"tor")
|
||||
toggle_tor OFF
|
||||
;;
|
||||
+ "sim")
|
||||
+ toggle_sim OFF
|
||||
+ ;;
|
||||
"*")
|
||||
;;
|
||||
esac
|
3
files/lib/blue-merle/tacs.py
Normal file
3
files/lib/blue-merle/tacs.py
Normal file
File diff suppressed because one or more lines are too long
@ -44,6 +44,14 @@ while [[ "$answer" -eq 1 ]]; do
|
||||
fi
|
||||
done
|
||||
|
||||
## We have just disabled the modem so it should not log on to any network.
|
||||
## We set a random IMEI now only to have it overwritten very soon after
|
||||
## the SIM card has been replaced. We intend to prevent an accidential
|
||||
## leak of the new SIM's IMSI with the old IMEI just in case the modem
|
||||
## accidentally tried to log in to the network.
|
||||
python3 /lib/blue-merle/imei_generate.py -r
|
||||
|
||||
|
||||
echo -n "Please now replace the SIM card and press any key to continue. "
|
||||
read answer
|
||||
|
||||
@ -112,7 +120,8 @@ case $answer in
|
||||
*) answer=1;;
|
||||
esac
|
||||
if [[ "$answer" -eq 1 ]]; then
|
||||
echo {\"poweroff\": \"1\"} >/tmp/mcu_message && sleep 0.5 && killall -17 e750-mcu
|
||||
echo '{ "msg": "Shutting down..." }' > /dev/ttyS0
|
||||
echo '{ "poweroff": "1" }' > /dev/ttyS0
|
||||
else
|
||||
echo "Resetting modem..."
|
||||
until gl_modem AT AT+QPOWD | grep -q OK
|
||||
|
@ -1,138 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/blue-merle/functions.sh
|
||||
|
||||
if [ ! -c "/dev/ttyUSB3" ]; then
|
||||
e750-mcu "Error: /dev/ttyUSB3 does not exist."
|
||||
sleep 3
|
||||
e750-mcu "Please reboot & contact maintainer if problem persists."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if [ ! -f "/tmp/sim_change_start" ]; then
|
||||
echo 0 > /tmp/sim_change_start
|
||||
fi
|
||||
|
||||
if [ ! -f "/tmp/sim_change_switch" ]; then
|
||||
sim_switch off
|
||||
fi
|
||||
|
||||
now=$(date +%s)
|
||||
sim_change_last=`cat /tmp/sim_change_start`
|
||||
sim_change_diff=$((now-sim_change_last))
|
||||
|
||||
if [[ "$sim_change_diff" -lt 60 ]]; then
|
||||
e750-mcu "Please wait >1min between two SIM swaps. ($sim_change_diff s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$now" > /tmp/sim_change_start
|
||||
|
||||
e750-mcu "Starting SIM swap."
|
||||
sleep 3
|
||||
|
||||
i=5
|
||||
until [[ $i -lt 0 ]]
|
||||
do
|
||||
e750-mcu "Pull switch to abort ($i). "
|
||||
i=$((i-1))
|
||||
sleep 1
|
||||
|
||||
CHECK_ABORT
|
||||
done
|
||||
|
||||
e750-mcu "Continuing ..."
|
||||
sleep 1
|
||||
|
||||
e750-mcu "Disabling the MEfrom transmit- ting and recei- ving RF signals."
|
||||
sleep 3
|
||||
|
||||
old_imei=$(READ_IMEI)
|
||||
old_imsi=$(READ_IMSI)
|
||||
|
||||
CHECK_ABORT
|
||||
|
||||
answer=1
|
||||
while [[ "$answer" -eq 1 ]]; do
|
||||
gl_modem AT AT+CFUN=4 | grep -q OK
|
||||
if [[ $? -eq 1 ]]; then
|
||||
e750-mcu "Disabling failed. Trying again."
|
||||
CHECK_ABORT
|
||||
else
|
||||
answer=0
|
||||
e750-mcu "Disabled."
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
e750-mcu "Replace the SIM card. Then pull the switch."
|
||||
|
||||
while [[ `cat /tmp/sim_change_switch` = "on" ]]; do
|
||||
e750-mcu "Replace the SIM card. Then pull the switch."
|
||||
sleep 3
|
||||
done
|
||||
|
||||
e750-mcu "Switch pulled. Continuing..."
|
||||
sleep 1
|
||||
sim_switch on
|
||||
|
||||
leak=0
|
||||
|
||||
until gl_modem AT AT+CFUN=0 | grep -q OK
|
||||
do
|
||||
e750-mcu "CFUN=0 failed. Trying again."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
until gl_modem AT AT+CFUN=4 | grep -q OK
|
||||
do
|
||||
leak=1
|
||||
e750-mcu "CFUN=4 failed. Trying again."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ $leak -eq 1 ]]; then
|
||||
e750-mcu "WARNING: Reset took longer than expected."
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
new_imsi=$(READ_IMSI)
|
||||
|
||||
if [[ "$old_imsi" == "$new_imsi" ]]; then
|
||||
e750-mcu "WARNING: Old IMSI equals new IMSI."
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
e750-mcu "Setting random IMEI"
|
||||
python3 /lib/blue-merle/imei_generate.py -r
|
||||
|
||||
new_imei=$(READ_IMEI)
|
||||
|
||||
if [[ "$old_imei" == "$new_imei" ]]; then
|
||||
e750-mcu "WARNING: Old IMEI equals new IMEI."
|
||||
sleep 3
|
||||
else
|
||||
mkdir -p /tmp/modem.1-1.2
|
||||
echo "$new_imei" > /tmp/modem.1-1.2/modem-imei
|
||||
fi
|
||||
|
||||
|
||||
e750-mcu "The device will shutdown now."
|
||||
sleep 3
|
||||
e750-mcu "You should change your location before booting again."
|
||||
sleep 5
|
||||
|
||||
|
||||
i=5
|
||||
until [[ $i -eq 0 ]]
|
||||
do
|
||||
i=$((i-1))
|
||||
e750-mcu "Shutting down... ($i)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo {\"poweroff\": \"1\"} >/tmp/mcu_message && sleep 0.5 && killall -17 e750-mcu
|
||||
|
||||
exit 0
|
73
files/usr/bin/blue-merle-switch-stage1
Normal file
73
files/usr/bin/blue-merle-switch-stage1
Normal file
@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/blue-merle/functions.sh
|
||||
. /lib/functions/gl_util.sh
|
||||
|
||||
if [ ! -f "/tmp/sim_change_start" ]; then
|
||||
echo 0 > /tmp/sim_change_start
|
||||
fi
|
||||
|
||||
if [ ! -f "/tmp/sim_change_switch" ]; then
|
||||
sim_switch off
|
||||
fi
|
||||
|
||||
now=$(date +%s)
|
||||
sim_change_last=`cat /tmp/sim_change_start`
|
||||
sim_change_diff=$((now-sim_change_last))
|
||||
|
||||
if [[ "$sim_change_diff" -lt 60 ]]; then
|
||||
mcu_send_message "Please wait >1min between two SIM swaps. ($sim_change_diff s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$now" > /tmp/sim_change_start
|
||||
|
||||
mcu_send_message "Starting SIM swap."
|
||||
sleep 3
|
||||
|
||||
|
||||
## We're disabling this abort functionality for the moment because the switch keeps being blocked and we cannot notice the pulled switch. We could abort by default and require another toggle to continue.
|
||||
#i=5
|
||||
#until [[ $i -lt 0 ]]
|
||||
#do
|
||||
# mcu_send_message "Pull switch to abort ($i). "
|
||||
# i=$((i-1))
|
||||
# sleep 1
|
||||
#
|
||||
# CHECK_ABORT
|
||||
#done
|
||||
#
|
||||
#mcu_send_message "Continuing ..."
|
||||
#sleep 1
|
||||
|
||||
mcu_send_message "Disabling the ME from transmitting and receiving RF signals."
|
||||
sleep 3
|
||||
|
||||
old_imei=$(READ_IMEI)
|
||||
old_imsi=$(READ_IMSI)
|
||||
|
||||
#CHECK_ABORT
|
||||
|
||||
answer=1
|
||||
while [[ "$answer" -eq 1 ]]; do
|
||||
gl_modem AT AT+CFUN=4 | grep -q OK
|
||||
if [[ $? -eq 1 ]]; then
|
||||
mcu_send_message "Disabling failed. Trying again."
|
||||
CHECK_ABORT
|
||||
else
|
||||
answer=0
|
||||
mcu_send_message "Disabled."
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
## We generate a random IMEI to prevent a leak of the
|
||||
## new SIM's IMSI under the old IMEI in case the modem
|
||||
## still talks to the network
|
||||
timeout 15 python3 /lib/blue-merle/imei_generate.py -r
|
||||
|
||||
|
||||
mcu_send_message "Replace the SIM card. Then pull the switch."
|
||||
|
||||
echo done > /tmp/blue-merle-stage1
|
||||
logger -p notice -t blue-merle-toggle "Finished with Stage 1"
|
79
files/usr/bin/blue-merle-switch-stage2
Normal file
79
files/usr/bin/blue-merle-switch-stage2
Normal file
@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/blue-merle/functions.sh
|
||||
. /lib/functions/gl_util.sh
|
||||
|
||||
rm -f /tmp/blue-merle-stage1
|
||||
|
||||
mcu_send_message "Switch pulled. Continuing..."
|
||||
sleep 1
|
||||
sim_switch on
|
||||
|
||||
leak=0
|
||||
|
||||
until gl_modem AT AT+CFUN=0 | grep -q OK
|
||||
do
|
||||
mcu_send_message "CFUN=0 failed. Trying again."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
until gl_modem AT AT+CFUN=4 | grep -q OK
|
||||
do
|
||||
leak=1
|
||||
mcu_send_message "CFUN=4 failed. Trying again."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ $leak -eq 1 ]]; then
|
||||
mcu_send_message "WARNING: Reset took longer than expected."
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
new_imsi=$(READ_IMSI)
|
||||
|
||||
if [[ "$old_imsi" == "$new_imsi" ]]; then
|
||||
mcu_send_message "WARNING: Old IMSI equals new IMSI. Did you swap the SIM?"
|
||||
sleep 3
|
||||
fi
|
||||
old_imei=$(READ_IMEI)
|
||||
mcu_send_message "Setting random IMEI"
|
||||
timeout 15 python3 /lib/blue-merle/imei_generate.py -r
|
||||
|
||||
new_imei=$(READ_IMEI)
|
||||
|
||||
|
||||
if [[ "$old_imei" == "$new_imei" ]]; then
|
||||
mcu_send_message "WARNING: Old IMEI equals new IMEI."
|
||||
sleep 3
|
||||
else
|
||||
#FIXME old_imei is not the real old imei because in stage1 its already generated a new one
|
||||
# so this is a bit missleading but for the prupose of visualy see the change it works
|
||||
mcu_send_message "IMEI Old:...... ${old_imei}IMEI New:...... ${new_imei}"
|
||||
sleep 5
|
||||
mkdir -p /tmp/modem.1-1.2
|
||||
echo "$new_imei" > /tmp/modem.1-1.2/modem-imei
|
||||
fi
|
||||
|
||||
logger -p notice -t blue-merle-toggle "Changed IMEI from ${old_imei} to ${new_imei}"
|
||||
|
||||
|
||||
mcu_send_message "The device will shutdown now."
|
||||
sleep 3
|
||||
mcu_send_message "You should change your location before booting again."
|
||||
sleep 5
|
||||
|
||||
|
||||
i=5
|
||||
until [[ $i -eq 0 ]]
|
||||
do
|
||||
i=$((i-1))
|
||||
mcu_send_message "Shutting down... ($i)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
logger -p notice -t blue-merle-toggle "Finished with Stage 2"
|
||||
|
||||
# Note: calling /sbin/poweroff directly results in the device rebooting into a broken state
|
||||
echo '{ "poweroff": "1" }' >/dev/ttyS0
|
49
files/usr/libexec/blue-merle
Executable file
49
files/usr/libexec/blue-merle
Executable file
@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/blue-merle/functions.sh
|
||||
|
||||
|
||||
show_message() {
|
||||
# There is mcu_send_message() in /lib/functions/gl_util.sh but we don't want to load the file, thinking that it will take too long
|
||||
echo {\"msg\": \"$1\"} > /dev/ttyS0
|
||||
}
|
||||
|
||||
|
||||
logger -p notice -t blue-merle-libexec "Libexec $1"
|
||||
|
||||
if [ "$1" == "read-imei" ]; then
|
||||
imei="$(READ_IMEI)"
|
||||
echo -n $imei
|
||||
show_message "My IMEI: $imei"
|
||||
|
||||
elif [ "$1" == "read-imsi" ]; then
|
||||
imsi="$(READ_IMSI)"
|
||||
if [ "x$imsi" == "x" ]; then
|
||||
echo "No IMSI found $imsi" >&2
|
||||
exit 1
|
||||
else
|
||||
echo -n $imsi
|
||||
show_message "My IMSI: $imsi"
|
||||
fi
|
||||
|
||||
elif [ "$1" == "random-imei" ]; then
|
||||
flock -n /tmp/blue-merle-imei-generate.lock timeout 15 /lib/blue-merle/imei_generate.py --random
|
||||
READ_IMEI
|
||||
|
||||
elif [ "$1" == "shutdown-modem" ]; then
|
||||
exec gl_modem AT AT+CFUN=4
|
||||
|
||||
elif [ "$1" == "shutdown" ]; then
|
||||
show_message "Shutting down..."
|
||||
echo -n "Shutting down"
|
||||
logger -p notice -t blue-merle-libexec "Shutting down"
|
||||
echo '{ "poweroff": "1" }' > /dev/ttyS0
|
||||
|
||||
elif [ "$1" == "write-imei" ]; then
|
||||
new_imei=$2
|
||||
echo -n { "action": "write" }
|
||||
else
|
||||
echo -n '{"msg":"Hello, World!"}'
|
||||
#echo 'foo'>&2
|
||||
echo 0
|
||||
fi
|
13
files/usr/share/luci/menu.d/luci-app-blue-merle.json
Normal file
13
files/usr/share/luci/menu.d/luci-app-blue-merle.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"admin/network/blue-merle": {
|
||||
"title": "Blue Merle",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "blue-merle"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-blue-merle" ]
|
||||
}
|
||||
}
|
||||
}
|
26
files/usr/share/rpcd/acl.d/luci-app-blue-merle.json
Normal file
26
files/usr/share/rpcd/acl.d/luci-app-blue-merle.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"luci-app-blue-merle": {
|
||||
"description": "Grant access to opkg management",
|
||||
"read": {
|
||||
"cgi-io": [ "exec" ],
|
||||
"file": {
|
||||
"/usr/libexec/blue-merle": [ "exec" ],
|
||||
"/usr/libexec/blue-merle shred": [ "exec" ],
|
||||
"/usr/libexec/blue-merle *": [ "exec" ],
|
||||
"/etc/opkg.conf": [ "read" ],
|
||||
"/etc/opkg/*.conf": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci": [ "getMountPoints" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/usr/libexec/blue-merle": [ "exec" ],
|
||||
"/usr/libexec/blue-merle shred": [ "exec" ],
|
||||
"/usr/libexec/blue-merle *": [ "exec" ],
|
||||
"/tmp/upload.ipk": [ "write" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
420
files/www/luci-static/resources/view/blue-merle.js
Normal file
420
files/www/luci-static/resources/view/blue-merle.js
Normal file
@ -0,0 +1,420 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
|
||||
var css = ' \
|
||||
.controls { \
|
||||
display: flex; \
|
||||
margin: .5em 0 1em 0; \
|
||||
flex-wrap: wrap; \
|
||||
justify-content: space-around; \
|
||||
} \
|
||||
\
|
||||
.controls > * { \
|
||||
padding: .25em; \
|
||||
white-space: nowrap; \
|
||||
flex: 1 1 33%; \
|
||||
box-sizing: border-box; \
|
||||
display: flex; \
|
||||
flex-wrap: wrap; \
|
||||
} \
|
||||
\
|
||||
.controls > *:first-child, \
|
||||
.controls > * > label { \
|
||||
flex-basis: 100%; \
|
||||
min-width: 250px; \
|
||||
} \
|
||||
\
|
||||
.controls > *:nth-child(2), \
|
||||
.controls > *:nth-child(3) { \
|
||||
flex-basis: 20%; \
|
||||
} \
|
||||
\
|
||||
.controls > * > .btn { \
|
||||
flex-basis: 20px; \
|
||||
text-align: center; \
|
||||
} \
|
||||
\
|
||||
.controls > * > * { \
|
||||
flex-grow: 1; \
|
||||
align-self: center; \
|
||||
} \
|
||||
\
|
||||
.controls > div > input { \
|
||||
width: auto; \
|
||||
} \
|
||||
\
|
||||
.td.version, \
|
||||
.td.size { \
|
||||
white-space: nowrap; \
|
||||
} \
|
||||
\
|
||||
ul.deps, ul.deps ul, ul.errors { \
|
||||
margin-left: 1em; \
|
||||
} \
|
||||
\
|
||||
ul.deps li, ul.errors li { \
|
||||
list-style: none; \
|
||||
} \
|
||||
\
|
||||
ul.deps li:before { \
|
||||
content: "↳"; \
|
||||
display: inline-block; \
|
||||
width: 1em; \
|
||||
margin-left: -1em; \
|
||||
} \
|
||||
\
|
||||
ul.deps li > span { \
|
||||
white-space: nowrap; \
|
||||
} \
|
||||
\
|
||||
ul.errors li { \
|
||||
color: #c44; \
|
||||
font-size: 90%; \
|
||||
font-weight: bold; \
|
||||
padding-left: 1.5em; \
|
||||
} \
|
||||
\
|
||||
ul.errors li:before { \
|
||||
content: "⚠"; \
|
||||
display: inline-block; \
|
||||
width: 1.5em; \
|
||||
margin-left: -1.5em; \
|
||||
} \
|
||||
';
|
||||
|
||||
var isReadonlyView = !L.hasViewPermission() || null;
|
||||
|
||||
var callMountPoints = rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getMountPoints',
|
||||
expect: { result: [] }
|
||||
});
|
||||
|
||||
var packages = {
|
||||
available: { providers: {}, pkgs: {} },
|
||||
installed: { providers: {}, pkgs: {} }
|
||||
};
|
||||
|
||||
var languages = ['en'];
|
||||
|
||||
var currentDisplayMode = 'available', currentDisplayRows = [];
|
||||
|
||||
|
||||
|
||||
function handleReset(ev)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
function callBlueMerle(arg) {
|
||||
const cmd = "/usr/libexec/blue-merle";
|
||||
var prom = fs.exec(cmd, [arg]);
|
||||
return prom.then(
|
||||
function(res) {
|
||||
console.log("Blue Merle arg", arg, "res", res);
|
||||
if (res.code != 0) {
|
||||
throw new Error("Return code " + res.code);
|
||||
} else {
|
||||
return res.stdout;
|
||||
}
|
||||
}
|
||||
).catch(
|
||||
function(err) {
|
||||
console.log("Error calling Blue Merle", arg, err);
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function readIMEI() {
|
||||
return callBlueMerle("read-imei");
|
||||
}
|
||||
|
||||
function randomIMEI() {
|
||||
callBlueMerle("random-imei").then(
|
||||
function(res){
|
||||
readIMEI().then(
|
||||
console.log("new IMEI", imei)
|
||||
);
|
||||
}
|
||||
).catch(
|
||||
function(err){
|
||||
console.log("Error", err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function readIMSI() {
|
||||
return callBlueMerle("read-imsi");
|
||||
}
|
||||
|
||||
function handleConfig(ev)
|
||||
{
|
||||
var conf = {};
|
||||
|
||||
const cmd = "/usr/libexec/blue-merle";
|
||||
var dlg = ui.showModal(_('Executing blue merle'), [
|
||||
E('p', { 'class': 'spinning' },
|
||||
_('Waiting for the <em>%h</em> command to complete…').format(cmd))
|
||||
]);
|
||||
|
||||
var argv = ["random-imei"];
|
||||
console.log("Calling ", cmd, argv);
|
||||
// FIXME: Investigate whether we should be using fs.exec()
|
||||
fs.exec_direct(cmd, argv, 'text').then(function(res) {
|
||||
console.log("Res:", res, "stdout", res.stdout, "stderr", res.stderr, "code", res.code);
|
||||
|
||||
if (res.stdout)
|
||||
dlg.appendChild(E('pre', [ res.stdout ]));
|
||||
|
||||
if (res.stderr) {
|
||||
dlg.appendChild(E('h5', _('Errors')));
|
||||
dlg.appendChild(E('pre', { 'class': 'errors' }, [ res.stderr ]));
|
||||
}
|
||||
|
||||
console.log("Res.code: ", res.code);
|
||||
if (res.code !== 0)
|
||||
dlg.appendChild(E('p', _('The <em>%h %h</em> command failed with code <code>%d</code>.').format(cmd, argv, (res.code & 0xff) || -1)));
|
||||
|
||||
dlg.appendChild(E('div', { 'class': 'right' },
|
||||
E('div', {
|
||||
'class': 'btn',
|
||||
'click': L.bind(function(res) {
|
||||
if (ui.menu && ui.menu.flushCache)
|
||||
ui.menu.flushCache();
|
||||
|
||||
ui.hideModal();
|
||||
|
||||
if (res.code !== 0)
|
||||
rejectFn(new Error(res.stderr || 'opkg error %d'.format(res.code)));
|
||||
else
|
||||
resolveFn(res);
|
||||
}, this, res)
|
||||
}, _('Dismiss'))));
|
||||
}).catch(function(err) {
|
||||
ui.addNotification(null, E('p', _('Unable to execute <em>opkg %s</em> command: %s').format(cmd, err)));
|
||||
ui.hideModal();
|
||||
});
|
||||
|
||||
|
||||
|
||||
fs.list('/etc/opkg').then(function(partials) {
|
||||
var files = [ '/etc/opkg.conf' ];
|
||||
|
||||
for (var i = 0; i < partials.length; i++)
|
||||
if (partials[i].type == 'file' && partials[i].name.match(/\.conf$/))
|
||||
files.push('/etc/opkg/' + partials[i].name);
|
||||
|
||||
return Promise.all(files.map(function(file) {
|
||||
return fs.read(file)
|
||||
.then(L.bind(function(conf, file, res) { conf[file] = res }, this, conf, file))
|
||||
.catch(function(err) {
|
||||
});
|
||||
}));
|
||||
}).then(function() {
|
||||
var body = [
|
||||
E('p', {}, _('Below is a listing of the various configuration files used by <em>opkg</em>. Use <em>opkg.conf</em> for global settings and <em>customfeeds.conf</em> for custom repository entries. The configuration in the other files may be changed but is usually not preserved by <em>sysupgrade</em>.'))
|
||||
];
|
||||
|
||||
Object.keys(conf).sort().forEach(function(file) {
|
||||
body.push(E('h5', {}, '%h'.format(file)));
|
||||
body.push(E('textarea', {
|
||||
'name': file,
|
||||
'rows': Math.max(Math.min(L.toArray(conf[file].match(/\n/g)).length, 10), 3)
|
||||
}, '%h'.format(conf[file])));
|
||||
});
|
||||
|
||||
body.push(E('div', { 'class': 'right' }, [
|
||||
E('div', {
|
||||
'class': 'btn cbi-button-neutral',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('div', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': function(ev) {
|
||||
var data = {};
|
||||
findParent(ev.target, '.modal').querySelectorAll('textarea[name]')
|
||||
.forEach(function(textarea) {
|
||||
data[textarea.getAttribute('name')] = textarea.value
|
||||
});
|
||||
|
||||
ui.showModal(_('OPKG Configuration'), [
|
||||
E('p', { 'class': 'spinning' }, _('Saving configuration data…'))
|
||||
]);
|
||||
|
||||
Promise.all(Object.keys(data).map(function(file) {
|
||||
return fs.write(file, data[file]).catch(function(err) {
|
||||
ui.addNotification(null, E('p', {}, [ _('Unable to save %s: %s').format(file, err) ]));
|
||||
});
|
||||
})).then(ui.hideModal);
|
||||
},
|
||||
'disabled': isReadonlyView
|
||||
}, _('Save')),
|
||||
]));
|
||||
|
||||
//ui.showModal(_('OPKG Configuration'), body);
|
||||
});
|
||||
}
|
||||
|
||||
function handleShutdown(ev)
|
||||
{
|
||||
return callBlueMerle("shutdown")
|
||||
}
|
||||
|
||||
function handleRemove(ev)
|
||||
{
|
||||
}
|
||||
|
||||
function handleSimSwap(ev) {
|
||||
const spinnerID = 'swap-spinner-id';
|
||||
var dlg = ui.showModal(_('Starting SIM swap...'),
|
||||
[
|
||||
E('p', { 'class': 'spinning', 'id': spinnerID },
|
||||
_('Shutting down modem…')
|
||||
)
|
||||
]
|
||||
);
|
||||
callBlueMerle("shutdown-modem").then(
|
||||
function(res) {
|
||||
dlg.appendChild(
|
||||
E('pre', { 'class': 'result'},
|
||||
res
|
||||
)
|
||||
);
|
||||
dlg.appendChild(
|
||||
E('p', { 'class': 'text'},
|
||||
_("Generating Random IMEI")
|
||||
)
|
||||
);
|
||||
callBlueMerle("random-imei").then(
|
||||
function(res) {
|
||||
document.getElementById(spinnerID).style = "display:none";
|
||||
dlg.appendChild(
|
||||
E('div', { 'class': 'text'},
|
||||
[
|
||||
E('p', { 'class': 'text'},
|
||||
_("IMEI set:") + " " + res
|
||||
),
|
||||
E('p', { 'class': 'text'},
|
||||
_("Please shutdown the device, swap the SIM, then go to another place before booting")
|
||||
),
|
||||
E('button', { 'class': 'btn cbi-button-positive', 'click': handleShutdown, 'disabled': isReadonlyView },
|
||||
[ _('Shutdown…') ]
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
).catch(
|
||||
function(err) {
|
||||
dlg.appendChild(
|
||||
E('p',{'class': 'error'},
|
||||
_('Error setting IMEI! ') + err
|
||||
)
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
).catch(
|
||||
function(err) {
|
||||
dlg.appendChild(
|
||||
E('p',{'class': 'error'},
|
||||
_('Error! ') + err
|
||||
)
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleOpkg(ev)
|
||||
{
|
||||
}
|
||||
|
||||
function handleUpload(ev)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
function handleInput(ev) {
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
},
|
||||
|
||||
render: function(listData) {
|
||||
var query = decodeURIComponent(L.toArray(location.search.match(/\bquery=([^=]+)\b/))[1] || '');
|
||||
|
||||
const imeiInputID = 'imei-input';
|
||||
const imsiInputID = 'imsi-input';
|
||||
|
||||
var view = E([], [
|
||||
E('style', { 'type': 'text/css' }, [ css ]),
|
||||
|
||||
E('h2', {}, _('Blue Merle')),
|
||||
|
||||
E('div', { 'class': 'controls' }, [
|
||||
E('div', {}, [
|
||||
E('label', {}, _('IMEI') + ':'),
|
||||
E('span', { 'class': 'control-group' }, [
|
||||
E('input', { 'id':imeiInputID, 'type': 'text', 'name': 'filter', 'placeholder': _('e.g. 31428392718429'), 'minlength':14, 'maxlenght':14, 'required':true, 'value': query, 'input': handleInput, 'disabled': true })
|
||||
//, E('button', { 'class': 'btn cbi-button', 'click': handleReset }, [ _('Clear') ])
|
||||
//, E('button', { 'class': 'btn cbi-button', 'click': randomIMEI }, [ _('Set Random') ])
|
||||
])
|
||||
]),
|
||||
|
||||
E('div', {}, [
|
||||
E('label', {}, _('IMSI') + ':'),
|
||||
E('span', { 'class': 'control-group' }, [
|
||||
E('input', { 'id':imsiInputID, 'type': 'text', 'name': 'filter', 'placeholder': _('e.g. 31428392718429'), 'minlength':14, 'maxlenght':14, 'required':true, 'value': query, 'input': handleInput, 'disabled': true })
|
||||
//, E('button', { 'class': 'btn cbi-button', 'click': handleReset }, [ _('Clear') ])
|
||||
])
|
||||
]),
|
||||
]),
|
||||
|
||||
E('div', {}, [
|
||||
E('label', {}, _('Actions') + ':'), ' ',
|
||||
E('span', { 'class': 'control-group' }, [
|
||||
E('button', { 'class': 'btn cbi-button-positive', 'data-command': 'update', 'click': handleSimSwap, 'disabled': isReadonlyView }, [ _('SIM swap…') ]), ' '
|
||||
//, E('button', { 'class': 'btn cbi-button-action', 'click': handleUpload, 'disabled': isReadonlyView }, [ _('IMEI change…') ]), ' '
|
||||
//, E('button', { 'class': 'btn cbi-button-neutral', 'click': handleConfig }, [ _('Shred config…') ])
|
||||
])
|
||||
])
|
||||
|
||||
]);
|
||||
|
||||
readIMEI().then(
|
||||
function(imei) {
|
||||
const e = document.getElementById(imeiInputID);
|
||||
console.log("Input: ", e, e.placeholder, e.value);
|
||||
e.value = imei;
|
||||
}
|
||||
).catch(
|
||||
function(err){
|
||||
console.log("Error: ", err)
|
||||
}
|
||||
)
|
||||
|
||||
readIMSI().then(
|
||||
function(imsi) {
|
||||
const e = document.getElementById(imsiInputID);
|
||||
e.value = imsi;
|
||||
}
|
||||
).catch(
|
||||
function(err){
|
||||
const e = document.getElementById(imsiInputID);
|
||||
e.value = "No IMSI found";
|
||||
}
|
||||
)
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSave: null,
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user