diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 654bc0c..3f0cc90 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 # Runs a set of commands using the runners shell - name: Run shellcheck diff --git a/Dockerfile b/Dockerfile index a729999..7994575 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,5 @@ FROM alpine:3.15.4 RUN apk --no-cache add curl jq w3m xclip util-linux -RUN curl -L "https://raw.githubusercontent.com/sdushantha/tmpmail/master/tmpmail" > tmpmail && chmod +x tmpmail +COPY tmpmail /bin/tmpmail -RUN mv tmpmail /bin/ diff --git a/README.md b/README.md index 2252b6a..ed8a488 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,12 @@
A temporary email right from your terminal written in POSIX sh
`tmpmail` is a command line utility written in POSIX `sh` that allows you to create a temporary email address
-and receive emails to the temporary email address. It uses 1secmail's [API](https://www.1secmail.com/api/)
-to receive emails.
+and receive emails to the temporary email address.
+
+> **This is a maintained fork of [sdushantha/tmpmail](https://github.com/sdushantha/tmpmail).**
+> Upstream used 1secmail's API, which has shut down (every request now returns `403 Forbidden`), so the
+> original script no longer works. This fork talks to [Guerrilla Mail](https://www.guerrillamail.com/)
+> by default, with [mail.tm](https://mail.tm/) as an alternative (`--provider mailtm`).
By default `w3m` is used to render the HTML emails on the terminal.
But if you prefer another text based web browser or would rather view the email in a GUI web browser such as Firefox, simply
@@ -19,37 +23,22 @@ use the `--browser` argument followed by the command needed to launch the web br
- `w3m`
- `curl`
- [`jq`](https://github.com/stedolan/jq)
-- `xclip`
+- `xclip` (optional, only for `--copy`)
## Installation
### Install locally
```bash
# Download the tmpmail file and make it executable
-$ curl -L "https://raw.githubusercontent.com/sdushantha/tmpmail/master/tmpmail" > tmpmail && chmod +x tmpmail
+$ curl -L "https://raw.githubusercontent.com/3-mmc/tmpmail/master/tmpmail" > tmpmail && chmod +x tmpmail
# Then move it somewhere in your $PATH. Here is an example:
$ mv tmpmail ~/bin/
```
-### AUR
-`tmpmail` is available on the [AUR](https://aur.archlinux.org/packages/tmpmail-git/), which is currently being maintained by [Benjamin Bädorf](https://github.com/b12f)
-
-```bash
-$ yay -S tmpmail-git
-```
-
-### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)
-`tmpmail` is available on the [pacstall-programs repository](https://github.com/pacstall/pacstall-programs/blob/master/packages/tmpmail-bin/tmpmail-bin.pacscript), which is being currently being maintained by [wizard-28](https://github.com/wizard-28)
-
-```
-$ pacstall -I tmpmail-bin
-```
-
-### Nixpkgs
-`tmpmail` is also available in the [nix package collection (only unstable currently)](https://search.nixos.org/packages?channel=unstable&show=tmpmail&from=0&size=50&sort=relevance&query=tmpmail), which is maintained by [legendofmiracles](https://github.com/legendofmiracles)
-
-Either add it to your system packages, install it with nix-env or try it out in a ephemeral nix-shell `nix-shell -p tmpmail`
+### Packages
+The AUR (`tmpmail-git`), Pacstall (`tmpmail-bin`) and Nixpkgs (`tmpmail`) packages build the **upstream** script,
+which still points at the dead 1secmail API. Until they switch to this fork, install it locally as shown above.
### Docker
@@ -67,12 +56,12 @@ $ docker run -it mail;
$ tmpmail --help
tmpmail
tmpmail -h | --version
-tmpmail -g [ADDRESS]
+tmpmail [-p PROVIDER] -g [ADDRESS | USERNAME]
tmpmail [-t | -b BROWSER] -r | ID
When called with no option and no argument, tmpmail lists the messages in
-the inbox and their numeric IDs. When called with one argument, tmpmail
-shows the email message with specified ID.
+the inbox and their numbers. When called with one argument, tmpmail
+shows the email message with that number (1 = newest).
-b, --browser BROWSER
Specify BROWSER that is used to render the HTML of
@@ -84,11 +73,15 @@ shows the email message with specified ID.
Copy the email address to your clipboard
-d, --domains
Show list of available domains
--g, --generate [ADDRESS]
- Generate a new email address, either the specified ADDRESS, or
- randomly create one
+-g, --generate [ADDRESS | USERNAME]
+ Generate a new email address, either the specified ADDRESS, a
+ USERNAME on a random domain, or a completely random one
-h, --help
Show help
+-p, --provider PROVIDER
+ Service used for new addresses: guerrilla (default) or mailtm.
+ Can also be set with TMPMAIL_PROVIDER. An existing address keeps
+ the provider it was made with.
-r, --recent
View the most recent email message
-t, --text
@@ -98,30 +91,52 @@ shows the email message with specified ID.
Show version
```
+### Providers
+| | Guerrilla Mail (`guerrilla`, default) | mail.tm (`mailtm`) |
+| --- | --- | --- |
+| Account | none, an address is just a username | created for every address |
+| Domains | 11, all delivering to the same inbox | usually one |
+| Mail kept | about an hour | until the account is deleted |
+| Privacy | anyone who guesses the username can read the inbox | password protected |
+| Limits | | account creation is rate limited |
+
+The address, its provider and (for mail.tm) its password are stored in `/tmp/tmpmail`, so they are gone after a reboot.
+
### Examples
Create random email
```console
$ tmpmail --generate
-xoithrjagpx@1secmail.net
+an8xsqzo1ct@guerrillamailblock.com
```
Create custom email
```console
-$ tmpmail --generate mycustomemail@1secmail.com
-mycustomemail@1secmail.com
+$ tmpmail --generate mycustomemail@sharklasers.com
+mycustomemail@sharklasers.com
+```
+
+Pick only the username, on a random domain
+```console
+$ tmpmail --generate mycustomname
+mycustomname@guerrillamail.net
+```
+
+Use mail.tm instead
+```console
+$ tmpmail --provider mailtm --generate
```
View the inbox
```console
$ tmpmail
-[ Inbox for wdebivbyjor@1secmail.com ]
+[ Inbox for mycustomname@guerrillamail.net ]
-83414443 username@example.com Test Email
+1 no-reply@guerrillamail.com Welcome to Guerrilla Mail
```
View the email
```console
-$ tmpmail 83414443
+$ tmpmail 1
```
View the most recent email
@@ -131,17 +146,18 @@ $ tmpmail -r
View emails as pure text
```console
-$ tmpmail -t 83414443
-To: wdebivbyjor@1secmail.com
-From: username@example.com
-Subject: Test Email
-
-Hello World
+$ tmpmail -t 1
+To: mycustomname@guerrillamail.net
+From: no-reply@guerrillamail.com
+Subject: Welcome to Guerrilla Mail
-[Attachments]
-https://is.gd/aBCdEf [apple.jpg]
-https://is.gd/AbCDeF [ball.jpg]
+Dear Random User,
+...
```
+Attachments are downloaded to `/tmp/tmpmail/attachments/` and linked from the email.
+
## Credits
This script is heavily inspired by Mitch Weaver's [`1secmail`](https://github.com/mitchweaver/bin/blob/master/OLD/1secmail) script
+
+The original `tmpmail` is by [Siddharth Dushantha](https://github.com/sdushantha).
diff --git a/tmpmail b/tmpmail
index b5739d1..321b0d5 100755
--- a/tmpmail
+++ b/tmpmail
@@ -1,55 +1,46 @@
#!/usr/bin/env sh
#
# by Siddharth Dushantha 2020
+# fork: https://github.com/3-mmc/tmpmail
+#
+# The 1secmail API this was built on shut down (every request returns 403),
+# so tmpmail now speaks to one of these providers:
+#
+# guerrilla Guerrilla Mail (default): no account, many domains, mails are
+# deleted after an hour
+# mailtm mail.tm: real account behind each address, account creation
+# is rate limited
+#
+# Message IDs are short inbox numbers (1 = newest).
#
# Dependencies: jq, curl, w3m
#
-version=1.2.3
+version=1.3.0
-# By default 'tmpmail' uses 'w3m' as it's web browser to render
-# the HTML of the email
browser="w3m"
-
-# The default command that will be used to copy the email address to
-# the user's clipboard when running 'tmpmail --copy'
copy_to_clipboard_cmd="xclip -selection c"
-
-# If the value is set to 'true' tmpmail will convert the HTML email
-# to raw text and send that to stdout
raw_text=false
+provider=${TMPMAIL_PROVIDER:-guerrilla}
-# Everything related to 'tmpmail' will be stored in /tmp/tmpmail
-# so that the old emails and email addresses get cleared after
-# restarting the computer
+# Kept under /tmp on purpose: the address and its mails vanish on reboot
tmpmail_dir="/tmp/tmpmail"
-
-# tmpmail_email_address is where we store the temporary email address
-# that gets generated. This prevents the user from providing
-# the email address everytime they run tmpmail
tmpmail_email_address="$tmpmail_dir/email_address"
-
-# tmpmail.html is where the email gets stored.
-# Even though the file ends with a .html extension, the raw text version of
-# the email will also be stored in this file so that w3m and other browsers
-# are able to open this file
+tmpmail_provider="$tmpmail_dir/provider"
+tmpmail_password="$tmpmail_dir/password"
tmpmail_html_email="$tmpmail_dir/tmpmail.html"
-
-# Default 1secmail API URL
-tmpmail_api_url="https://www.1secmail.com/api/v1/"
+tmpmail_attachments="$tmpmail_dir/attachments"
usage() {
- # Using 'cat << EOF' we can easily output a multiline text. This is much
- # better than using 'echo' for each line or using '\n' to create a new line.
cat <_domains one domain per line +#
_create ADDRESS register ADDRESS (user@domain) and print the +# address the service actually assigned +#
_login prepare $auth_header / session for the calls below +#
_list JSON array of {id, from, subject}, newest first +#
_read ID JSON {from, subject, html, text, +# attachments: [{url, filename}]} +# --------------------------------------------------------------------------- + +guerrilla_api="https://api.guerrillamail.com/ajax.php" + +guerrilla_domains() { + # Every Guerrilla Mail domain delivers into the same inbox for a username + printf "%s\n" guerrillamail.com guerrillamail.net guerrillamail.org \ + guerrillamail.biz guerrillamail.de guerrillamail.info \ + guerrillamailblock.com sharklasers.com grr.la pokemail.net spam4.me +} + +guerrilla_call() { + http GET "$guerrilla_api?$1${sid:+&sid_token=$sid}" +} + +guerrilla_create() { + sid="" + data=$(guerrilla_call "f=get_email_address") || exit 1 + sid=$(printf %s "$data" | jq -r .sid_token) + data=$(guerrilla_call "f=set_email_user&email_user=${1%@*}") || exit 1 + # The service may alter the username (it lowercases and strips symbols) + printf "%s@%s\n" "$(printf %s "$data" | jq -r '.email_addr | split("@")[0]')" "${1#*@}" +} + +guerrilla_login() { + # Sessions expire after 18 idle minutes; the inbox belongs to the + # username, so a fresh session that claims it again sees the same mail. + # Not via $(...), so $sid survives for the calls that follow. + sid="" + data=$(guerrilla_call "f=get_email_address") || exit 1 + sid=$(printf %s "$data" | jq -r .sid_token) + guerrilla_call "f=set_email_user&email_user=${email_address%@*}" >/dev/null || exit 1 +} + +guerrilla_list() { + data=$(guerrilla_call "f=get_email_list&offset=0") || exit 1 + printf %s "$data" | jq -c '[.list[] | {id: .mail_id, from: .mail_from, + subject: (.mail_subject | gsub("?39;"; "'"'"'") | gsub("""; "\"") | gsub("<"; "<") | gsub(">"; ">") | gsub("&"; "&"))}] + | sort_by(.id | tonumber) | reverse' +} + +guerrilla_read() { + data=$(guerrilla_call "f=fetch_email&email_id=$1") || exit 1 + printf %s "$data" | jq -c --arg sid "$sid" --arg id "$1" '{ + from: .mail_from, subject: .mail_subject, html: .mail_body, text: "", + attachments: [(.att_info // [])[] | + {filename: (.f // "attachment"), + url: "https://www.guerrillamail.com/inbox?get_att&email_id=\($id)&part_id=\(.p)&sid_token=\($sid)"}]}' +} - # Number of available domains - data_length=$(printf %s "$data" | jq length) +mailtm_api="https://api.mail.tm" + +# mail.tm answers a bare array for application/json lists; also accept a +# hydra collection in case that ever changes +mailtm_members='def members: if type == "array" then . else ."hydra:member" end;' + +mailtm_domains() { + data=$(http GET "$mailtm_api/domains") || exit 1 + printf %s "$data" | jq -r "$mailtm_members"' members[] | select(.isActive) | .domain' +} + +mailtm_create() { + password=$(random_string 'A-Za-z0-9' 24) + http POST "$mailtm_api/accounts" "{\"address\":\"$1\",\"password\":\"$password\"}" >/dev/null || exit 1 + (umask 077 && printf %s "$password" >"$tmpmail_password") + printf "%s\n" "$1" +} + +mailtm_login() { + [ -s "$tmpmail_password" ] || die "Password for $email_address is missing, run 'tmpmail -g'" + data=$(http POST "$mailtm_api/token" "{\"address\":\"$email_address\",\"password\":\"$(cat "$tmpmail_password")\"}") || exit 1 + auth_header="Authorization: Bearer $(printf %s "$data" | jq -r .token)" +} + +mailtm_list() { + data=$(http GET "$mailtm_api/messages") || exit 1 + printf %s "$data" | jq -c "$mailtm_members"' [members[] | {id, from: .from.address, subject}]' +} + +mailtm_read() { + data=$(http GET "$mailtm_api/messages/$1") || exit 1 + printf %s "$data" | jq -c --arg api "$mailtm_api" '{ + from: (.from | if .name != "" then "\(.name) <\(.address)>" else .address end), + subject, html: ((.html // []) | join("")), text: (.text // ""), + attachments: [(.attachments // [])[] | {filename, url: "\($api)\(.downloadUrl)"}]}' +} - # If the length of the data we got is 0, that means the email address - # has not received any emails yet. - [ "$data_length" -eq 0 ] && echo "1secmail API error for getting domains list" && exit +# --------------------------------------------------------------------------- - # Getting rid of quotes, braces and replace comma with space - printf "%s" "$data" | tr -d "[|]|\"" | tr "," " " +check_provider() { + case "$provider" in + guerrilla | mailtm) ;; + *) die "Unknown provider '$provider'. Use guerrilla or mailtm." ;; + esac } show_list_of_domains() { - # Convert the list of domains which are in a singal line, into multiple lines - # with a dash in the beginning of each domain for a clean output - domains=$(printf "%s" "$(get_list_of_domains)" | tr " " "\n" | sed "s/^/- /g") - printf "List of available domains: \n%s\n" "$domains" + domains=$("${provider}_domains") || exit 1 + printf "List of available %s domains: \n%s\n" "$provider" "$(printf %s "$domains" | sed "s/^/- /")" } generate_email_address() { - # There are 2 ways which this function is called in this script. - # [1] The user wants to generate a new email and runs 'tmpmail --generate' - # [2] The user runs 'tmpmail' to check the inbox , but /tmp/tmpmail/email_address - # is empty or nonexistant. Therefore a new email gets automatically - # generated before showing the inbox. But of course the inbox will - # be empty as the newly generated email address has not been - # sent any emails. - # - # When the function 'generate_email_address()' is called with the arguement - # 'true', it means that the function was called because the user - # ran 'tmpmail --generate'. - # - # We need this variable so we can know whether or not we need to show the user - # what the email was. <-- More about this can be found further down in this function. externally=${1:-false} - - # This variable lets generate_email_address know if the user has provided a custom - # email address which they want to use. custom is set to false if $2 has no value. custom=${2:-false} - # Generate a random email address. - # This function is called whenever the user wants to generate a new email - # address by running 'tmpmail --generate' or when the user runs 'tmpmail' - # but /tmp/tmpmail/email_address is empty or nonexistent. - # - # We create a random username by taking the first 10 lines from /dev/random - # and delete all the characters which are *not* lower case letters from A to Z. - # So charcters such as dashes, periods, underscore, and numbers are all deleted, - # giving us a text which only contains lower case letters form A to Z. We then take - # the first 10 characters, which will be the username of the email address - username=$(head /dev/urandom | LC_ALL=C tr -dc "[:alnum:]" | cut -c1-11 | tr "[:upper:]" "[:lower:]") - - # Generate a regex for valif email adress by fetching the list of supported domains - valid_email_address_regex=$(printf "[a-z0-9]+@%s" "$(get_list_of_domains | tr ' ' '|')") - username_black_list_regex="(abuse|webmaster|contact|postmaster|hostmaster|admin)" - username_black_list="- abuse\n- webmaster\n- contact\n- postmaster\n- hostmaster\n- admin" - - # Randomly pick one of the domains mentioned above. - domain=$(printf "%b" "$(get_list_of_domains)" | tr " " "\n" | randomize | tail -1) - - email_address="$username@$domain" - - # If the user provided a custom email address then use that email address + domains=$("${provider}_domains") || exit 1 + [ -z "$domains" ] && die "$provider has no domains available right now" + if [ "$custom" != false ]; then - email_address=$custom - - # Check if the user is using username in the email address which appears - # in the black list. - if printf %b "$email_address" | grep -Eq "$username_black_list_regex"; then - die "For security reasons, that username cannot be used. Here are the blacklisted usernames:\n$username_black_list" - fi - - # Do a regex check to see if the email address provided by the user is a - # valid email address - if ! printf %b "$email_address" | grep -Eq "$valid_email_address_regex"; then - die "Provided email is invalid. Must match $valid_email_address_regex" - fi + email_address=$(printf %s "$custom" | tr "[:upper:]" "[:lower:]") + # A bare username gets a random domain + case "$email_address" in + *@*) ;; + *) email_address="$email_address@$(printf %s "$domains" | randomize | tail -1)" ;; + esac + valid_email_address_regex="^[a-z0-9._-]+@($(printf %s "$domains" | sed 's/\./\\./g' | tr '\n' '|'))$" + printf %s "$email_address" | grep -Eq "$valid_email_address_regex" || + die "Provided email is invalid. The domain must be one of:\n$(printf %s "$domains" | sed 's/^/- /')" + else + domain=$(printf %s "$domains" | randomize | tail -1) + email_address="$(random_string 'a-z0-9' 11)@$domain" fi - # Save the generated email address to the $tmpmail_email_address file - # so that it can be whenever 'tmpmail' is run + email_address=$("${provider}_create" "$email_address") || exit 1 + printf %s "$email_address" >"$tmpmail_email_address" + printf %s "$provider" >"$tmpmail_provider" - # If this function was called because the user wanted to generate a new - # email address, show them the email address - [ "$externally" = true ] && cat "$tmpmail_email_address" && printf "\n" + if [ "$externally" = true ]; then + printf "%s\n" "$email_address" + fi } -get_email_address() { - # This function is only called once and that is when this script - # get executed. The output of this function gets stored in $email_address - # - # If the file that contains the email address is empty, - # that means we do not have an email address, so generate one. - [ ! -s "$tmpmail_email_address" ] && generate_email_address +load_email_address() { + # Addresses from before the provider switch have no provider file and + # belong to the dead 1secmail or an older mail.tm port, so start over + if [ ! -s "$tmpmail_email_address" ] || [ ! -s "$tmpmail_provider" ]; then + (generate_email_address) || exit 1 + fi + email_address=$(head -n 1 "$tmpmail_email_address") + provider=$(cat "$tmpmail_provider") + check_provider +} - # Output the email address by getting the first line of $tmpmail_email - head -n 1 "$tmpmail_email_address" +fetch_messages() { + messages=$("${provider}_list") || exit 1 } list_emails() { - # List all the received emails in a nicely formatted order - # - # Fetch the email data using 1secmail's API - data=$(curl -sL "$tmpmail_api_url?action=getMessages&login=$username&domain=$domain") - - # Using 'jq' we get the length of the JSON data. From this we can determine whether or not - # the email address has gotten any emails - data_length=$(printf %s "$data" | jq length) - - # We are showing what email address is currently being used - # in case the user has forgotten what the email address was. + fetch_messages printf "[ Inbox for %s ]\n\n" "$email_address" - # If the length of the data we got is 0, that means the email address - # has not received any emails yet. - [ "$data_length" -eq 0 ] && echo "No new mail" && exit - - # This is where we store all of our emails, which is then - # displayed using 'column' - inbox="" - - # Go through each mail that has been received - index=1 - while [ $index -le "${data_length}" ]; do - # Since arrays in JSON data start at 0, we must subtract - # the value of $index by 1 so that we dont miss one of the - # emails in the array - mail_data=$(printf %s "$data" | jq -r ".[$index-1]") - id=$(printf %s "$mail_data" | jq -r ".id") - from=$(printf %s "$mail_data" | jq -r ".from") - subject=$(printf %s "$mail_data" | jq -r ".subject") - - # The '||' are used as a divideder for 'column'. 'column' will use this divider as - # a point of reference to create the division. By default 'column' uses a blank space - # but that would not work in our case as the email subject could have multiple white spaces - # and 'column' would split the words that are seperated by white space, in different columns. - inbox="$inbox$id ||$from ||$subject\n" - index=$((index + 1)) - done - - # Show the emails cleanly - printf "%b" "$inbox" | column -t -s "||" -} + [ "$(printf %s "$messages" | jq length)" -eq 0 ] && echo "No new mail" && exit -randomize() { - # We could use 'shuf' and 'sort -R' but they are not a part of POSIX - awk 'BEGIN {srand();} {print rand(), $0}' | \ - sort -n -k1 | cut -d' ' -f2 + printf %s "$messages" | + jq -r 'to_entries[] | "\(.key + 1) ||\(.value.from) ||\(.value.subject)"' | + column -t -s "||" } view_email() { - # View an email by providing it's ID - # - # The first argument provided to this function will be the ID of the email - # that has been received - email_id="$1" - data=$(curl -sL "$tmpmail_api_url?action=readMessage&login=$username&domain=$domain&id=$email_id") - - # After the data is retrieved using the API, we have to check if we got any emails. - # Luckily 1secmail's API is not complicated and returns 'Message not found' as plain text - # if our email address as not received any emails. - # If we received the error message from the API just quit because there is nothing to do - [ "$data" = "Message not found" ] && die "Message not found" - - # We pass the $data to 'jq' which extracts the values - from=$(printf %s "$data" | jq -r ".from") - subject=$(printf %s "$data" | jq -r ".subject") - html_body=$(printf %s "$data" | jq -r ".htmlBody") - attachments=$(printf %s "$data" | jq -r ".attachments | length") - - # If you get an email that is in pure text, the .htmlBody field will be empty and - # we will need to get the content from .textBody instead - [ -z "$html_body" ] && html_body="
$(printf %s "$data" | jq -r ".textBody")" - - # Create the HTML with all the information that is relevant and then - # assigning that HTML to the variable html_mail. This is the best method - # to create a multiline variable + fetch_messages + email_id=$(printf %s "$messages" | jq -r --argjson n "$1" '.[$n - 1].id // empty') + [ -z "$email_id" ] && die "Message not found" + + data=$("${provider}_read" "$email_id") || exit 1 + + from=$(printf %s "$data" | jq -r .from | html_escape) + # Guerrilla Mail already sends the subject HTML-escaped + subject=$(printf %s "$data" | jq -r .subject) + [ "$provider" = mailtm ] && subject=$(printf %s "$subject" | html_escape) + html_body=$(printf %s "$data" | jq -r .html) + [ -z "$html_body" ] && html_body="
$(printf %s "$data" | jq -r .text | html_escape)" + html_mail=$(cat <