diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7305366 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +on: + release: + types: [published] +permissions: + contents: read + packages: write +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/github-script@v4 + with: + script: core.setOutput('version', context.ref.replace(/refs\/tags\/v?/, '')) + id: version + - uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - uses: docker/build-push-action@v2 + with: + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }} + ghcr.io/${{ github.repository }}:latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ff62f8d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +on: pull_request +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: docker/build-push-action@v2 + id: image + - run: >- + docker run ${{ steps.image.outputs.digest }} + --message="$MESSAGE" + --recipient=10123456789 + --sender=19876543210 + --date=1970-12-31T23:59:59 + --reference=255 + --status=INBOUND_NOT_READ | + diff - <(printf "$RESULT") + env: + MESSAGE: | + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. + RESULT: | + 0307910121436587F9400B919178563412F0000007211332959500A0050003FF0201986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D1FA7DD6750999DA6B314F33219447E83CAE9FABCFD2683E8E536FC2D07A5DDE334394DAEBBE9A03A1DC40E8BDFF232A84C0791DFECB7BC0C6A87CFEE3028CC4EC7EB6197A24A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E7 + 0307910121436587F9400B919178563412F000000721133295950056050003FF020240EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F8AD6087C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE5200 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e98aae4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:2.7-alpine +WORKDIR /opt/montlhery +COPY requirements.txt . +COPY montlhery.py montlhery +RUN pip install --requirement requirements.txt +ENTRYPOINT ["python", "montlhery"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..00301a3 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Montlhéry + +Tool to generate concatenated SIM card SMS records in the EFSMS format. Named after the telegraph of Montlhéry from [Le Comte de Monte-Cristo](https://fr.wikisource.org/wiki/Le_Comte_de_Monte-Cristo/Chapitre_61). + +## Example + +Message `HELLO` with `INBOUND_NOT_READ` status, sent from `+1 987 654 3210` to `+1 012 345 6789` with reference `255` and date/time 1970-12-31 23:59:59. + +```console +$ montlhery \ +> --message=HELLO \ +> --recipient=10123456789 \ +> --sender=19876543210 \ +> --date=1970-12-31T23:59:59 \ +> --reference=255 \ +> --status=INBOUND_NOT_READ +0307910121436587F9400B919178563412F00000072113329595000C050003FF0101904526F309 +``` + +## Installation + +This project only supports Python 2 [(obsolete)](https://www.python.org/doc/sunset-python-2) because of the [`smspdu`](https://pypi.org/project/smspdu) package. Use the provided container images to avoid [XKCD 1987](https://xkcd.com/1987). + +```console +$ docker run --rm ghcr.io/0x2b3bfa0/montlhery --help +``` + +## References + +* [3GPP TS 31.102 V17.4.0 (2021-12) 4.2.25](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1803) +* [3GPP TS 24.011 V17.0.0 (2020-12) 8.2.5.2](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1017) +* [3GPP TS 23.040 V17.1.0 (2021-06) 9.2.2.1](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=747) +* [3GPP TS 23.040 V17.1.0 (2021-06) 9.2.3.24.1](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=747) +* [UNDERSTANDING SMS: Practitioner's Basics](https://mobileforensics.files.wordpress.com/2007/06/understanding_sms.pdf) diff --git a/montlhery.py b/montlhery.py new file mode 100644 index 0000000..fb0bca0 --- /dev/null +++ b/montlhery.py @@ -0,0 +1,47 @@ +import time +import click +from smspdu.pdu import SMS_GENERIC, SMS_DELIVER + + +STATUSES = { + "INBOUND_READ": 0b001, + "INBOUND_NOT_READ": 0b011, + "OUTBOUND_SENT": 0b101, + "OUTBOUND_NOT_SENT": 0b111, +} + + +@click.command() +@click.option('--message', type=str, required=True) +@click.option('--recipient', type=str, required=True) +@click.option('--sender', type=str, required=True) +@click.option('--date', type=click.DateTime(), default=None) +@click.option('--reference', type=int, default=0) +@click.option('--limit', type=int, default=153) +@click.option('--status', type=click.Choice(STATUSES.keys())) +def montlhery(sender, recipient, message, date, reference, limit, status): + """ + Create SMS records in the EF.SMS SIM card format. + """ + length, type, data = SMS_GENERIC.determineAddress(recipient) + recipient = (chr(length - 4) + chr(type) + data).encode("hex") + + chunks = range(0, len(message), limit) + for chunk, offset in enumerate(chunks, start=1): + header = (0, (reference, len(chunks), chunk)) + data = message[offset:offset+limit] + + deliver = SMS_DELIVER.create( + sender=sender, + recipient=None, + user_data=data, + datestamp=time.mktime(date.timetuple()), + user_data_headers=[header] + ).toPDU() + + print("%02X" % STATUSES[status] + recipient.upper() + deliver) + + +if __name__ == '__main__': + montlhery() + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..691ce9b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +click==7.1.2 +smspdu==1.0