Skip to content

Instantly share code, notes, and snippets.

@ownerer
Last active November 18, 2025 15:10
Show Gist options
  • Select an option

  • Save ownerer/30dc9356e08539caf716018f7e8558db to your computer and use it in GitHub Desktop.

Select an option

Save ownerer/30dc9356e08539caf716018f7e8558db to your computer and use it in GitHub Desktop.
Yeastar TG series - SMS to webhook

After a lot of digging I found out where the SMS messages are stored on the Yeastar TG series. They're in a sqlite database that can be found at /persistent/var/lib/asterisk/db/MyPBX.sqlite, in the smsrecv table.

The SMS API is incredibly poorly documented. Even if you can get it working it still doesn't provide a way to get messages that arrived while there was no active TCP connection. Effectively there is no way to just read the inbox.

The system runs BusyBox, so available packages are very limited and are outdated on top of that. After a lot of trial and error and back and forth with ChatGPT, I managed to create a script that will run on the TG device and do the following at your desired interval:

  • Get all unread messages from the sqlite db
  • Add them in a temporary sqlite db
  • gzip and base64 encode that temporary database
  • Do an HTTP GET request to whatever URL you want with that base64 string as payload
  • If the request was successful, update all records in the main database to "read"

On the other end a webhook can live and then take the payload, decode and unzip the database and just process the records in whatever way is needed. I personally have a n8n flow that does this for example.

The script will always execute the HTTP call, whether there is data or not. It doubles as a sort of heartbeat mechanism to be able to monitor if the script is still running.

Setting up the scripts on the TG device requires the following steps:

  • Get the add.sh, loop.sh and process_texts.sh scripts from this gist
  • Edit process_texts.sh to configure your webhook URL
  • Edit add.sh if you want to change the interval at which the script runs (it's set to 10 seconds by default)
  • Enable FTP in the UI if it isn't yet
  • Upload the loop.sh and process_texts.sh scripts to the /persistent/script folder
  • Upload the add.sh script to the /persistent folder
  • Reboot the device

You can check if the script is running by:

  • Enabling SSH in the UI
  • Connecting to the terminal of the device
  • Running this command: ps aux | grep loop.sh
  • There should be an entry along the lines of xxxx root 0:00 /bin/sh /persistent/script/loop.sh /persistent/script/process_texts.sh 10

Disclaimer: this works on my device. The scripts are provided as-is with no guarantees. Use them at your own risk.

#!/bin/sh
# anything added here gets executed as the final step of the /etc/rc.local file
# before it gets called rc.local will make sure this script is executable, yay!
chmod +x /persistent/script/loop.sh
chmod +x /persistent/script/process_texts.sh
/persistent/script/loop.sh /persistent/script/process_texts.sh 10 &
#!/bin/sh
script_to_run=$1
wait_seconds=$2
if [ -z "$script_to_run" ] || [ -z "$wait_seconds" ]; then
echo "Usage: $0 <script_to_execute> <seconds_to_wait>"
exit 1
fi
while true; do
$script_to_run
sleep "$wait_seconds"
done
#!/bin/sh
HTTP_ENDPOINT="<ADD YOUR WEBHOOK URL HERE>"
# Paths
SQLITE3="sqlite3"
DB_PATH="/persistent/var/lib/asterisk/db/MyPBX.sqlite"
TEMP_DB="/persistent/tmp/temp_sms_db.sqlite"
COMPRESSED_DB="/persistent/tmp/compressed_sms_db.gz"
PROCESSED_IDS="/persistent/tmp/processed_ids.txt"
# Create a new temporary SQLite database and copy relevant data into it
$SQLITE3 "$TEMP_DB" <<EOF
-- Create the table schema
CREATE TABLE smsrecv (
id VARCHAR(64) PRIMARY KEY,
sender TEXT,
smsc TEXT,
portid TEXT,
content TEXT,
recvtime TEXT,
hasread TEXT
);
-- Attach the original database with a different alias
ATTACH DATABASE '$DB_PATH' AS original_db;
-- Insert the rows with hasread = 'No' from the original database into the temp DB
INSERT INTO smsrecv (id, sender, smsc, portid, content, recvtime, hasread)
SELECT id, sender, smsc, portid, content, recvtime, hasread
FROM original_db.smsrecv
WHERE hasread = 'No';
EOF
# Check if there are unread messages in the temporary database
UNREAD_MESSAGES=$($SQLITE3 "$TEMP_DB" "SELECT COUNT(*) FROM smsrecv")
if [ "$UNREAD_MESSAGES" -eq 0 ]; then
echo "No unread messages found. Sending empty payload."
QUERY_STRING="payload="
else
# Get the ids of the rows that were inserted into the temp DB
$SQLITE3 "$TEMP_DB" "SELECT id FROM smsrecv" > "$PROCESSED_IDS"
# Compress the temporary SQLite database
gzip -c "$TEMP_DB" > "$COMPRESSED_DB"
# Base64 encode the compressed database
BASE64_PAYLOAD=$(base64 "$COMPRESSED_DB")
# Remove newlines and spaces from the Base64-encoded string
CLEAN_BASE64_PAYLOAD=$(echo "$BASE64_PAYLOAD" | tr -d '\n' | tr -d ' ')
# URL-encode the Base64-encoded payload to make it safe for HTTP transmission
URL_ENCODED_PAYLOAD=$(echo "$CLEAN_BASE64_PAYLOAD" | sed 's/ /%20/g' | sed 's/+/%2B/g' | sed 's/\//%2F/g' | sed 's/=/%3D/g')
# Construct the GET request query string with the URL-encoded Base64-encoded gzipped data
QUERY_STRING="payload=$URL_ENCODED_PAYLOAD"
fi
# Send the data as a GET request
wget "${HTTP_ENDPOINT}?${QUERY_STRING}" -O /dev/null
# Check if the GET request was successful
if [ $? -eq 0 ]; then
echo "GET request successful."
if [ "$UNREAD_MESSAGES" -gt 0 ]; then
echo "Updating rows..."
# Update only rows that were processed (those in the temporary database)
while read -r ID; do
$SQLITE3 "$DB_PATH" <<EOF
UPDATE smsrecv
SET hasread = 'Yes'
WHERE id = '$ID';
EOF
done < "$PROCESSED_IDS"
echo "Rows updated."
else
echo "No rows to update."
fi
else
echo "GET request failed."
fi
# Clean up temporary files
rm -f "$TEMP_DB" "$COMPRESSED_DB" "$PROCESSED_IDS"
exit 0
@akkharawat1150
Copy link

what is default password for ftp?

@ownerer
Copy link
Author

ownerer commented Mar 4, 2025

FTP credentials are the same as SSH, and those get shown in a browser alert when you enable SSH.

@Xsmael
Copy link

Xsmael commented Aug 15, 2025

Hey i'm Having trouble find the credential, the password i find the browser alert doesn't work, i'm using a Yeastar TG200 . can you please share the credentials you used ? even on SSH it doesn't work for me.
here is the password given in the browser alert : iyeastar but it doesn't work!
image

@Xsmael
Copy link

Xsmael commented Aug 15, 2025

I'm also curious to know, why do you create a temporary DB tat you ten compress, and base64 before sending it, why don't you just send data as JSON directly, you could still convert it into base64) but why send it as a data base ?
I want to modify the script and skip the temporary database, but wanted to know first if there was any particular advantage doing it that way

@ownerer
Copy link
Author

ownerer commented Aug 19, 2025

@Xsmael

The password for me was ys123456 (TG100).

Regarding the base64 encoded temp db file: tbh I don't even remember exactly.
I'm 99% sure I experimented with getting JSON to work, because of course that's the most logical way to go.
But I simply did not succeed in building a working setup with the very limited and outdated tools available on the device.
The SQLite version on my device does not support outputting results as JSON, jq package is not available etc.
Pretty sure there was always some sort of issue with getting the format right, escaping strings from SMS contents, the list goes on.
And if I remember correctly I couldn't even get POST requests to work with the wget version that's on the device.

So yeah, I eventually ended up with this setup, which works, and I'm not touching it again πŸ˜‚

You mention you have a TG200, so who knows, maybe the tools available to you are better.
No way for me to tell, as I only have the one TG100.

@Xsmael
Copy link

Xsmael commented Aug 27, 2025

@ownerer

Thank you for your reply!
I tried that password also, but didn't work neither did anything i could find on the internet, I even seduced and deceived ChatGPT to any possible extent; went down some rabbit holes, but couldn't possibly find the password. I then came to the conclusion That Yeastar must have done this intentionally. Because i had and older TG200 (with older firmware) where it worked just perfect! but on the newer one it didn't

the password they gave (in the screenshot i shared earlier) was for a user named support( and they don't even tell you that either! damn!). and that user account is basically useless everything is read-only and you barely have access to any important directory. so i understood.

The best i could get:
After much digging and going from rabbit holes to the next one, i found a CVE affecting my firmware where you could manage to get the /etc/passwd file in which there are hashes including the one for root password. and chatGPT suprisingly helped me find ways to decrypt it using some hackying tools with dictionnaries ( i might have gone hard on the deception here πŸ˜† ) but unfortenately i couldn't decrupt it with any of the tools. so i was just left with the hash. Bruteforcing this will take so much time i didn't even try.

Oh yeah the GPT also suggested me to dismantle the device and connect to the UART port, that he was going to show me the pins connections and everthing and i would boot into a certain mode where i'll do some stuff.... and i just say no right away! πŸ˜…

That's so sad. maybe in the future when i feel confident enough i'll try that.

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment