Skip to content

Instantly share code, notes, and snippets.

@jay0lee
Last active August 30, 2021 18:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jay0lee/adb39b36fa3c4863140cc84f236d1e45 to your computer and use it in GitHub Desktop.
Save jay0lee/adb39b36fa3c4863140cc84f236d1e45 to your computer and use it in GitHub Desktop.
Shell script that can do OAuth, domain-wide delegation or password based IMAP/POP/SMTP authentication
#!/bin/bash
#
# This script should be run from Linux and uses basic tools like grep, awk, date and openssl.
# You need to populate the client_id and client_secret variables below
# with your own GCP project values. Sample command lines:
#
# Use 3-legged IMAP OAuth (default):
# bash popimap.sh --email myaccount@example.com --protocol imap
#
# Use a service account and domain-wide delegation:
# bash popimap.sh --email myaccount@example.com --service-account-file creds-from-gcp.json
#
# Use POP and username/password auth:
# bash popimap.sh --email myaccount@example.com --password MyP@ssw3rd --protocol pop
#
# Use SMTP (smtp.gmail.com):
# bash popimap.sh --email myaccount@example.com --protocol smtp
#
# Use SMTP relay (smtp-relay.gmail.com)
# bash popimap.sh --email myaccount@example.com --protocol smtp-relay
#
# Show debug output:
# bash popimap.sh --email myaccount@example.com --debug
#
scope="https://mail.google.com/"
# create your own client id/secret
# https://developers.google.com/identity/protocols/OAuth2InstalledApp#creatingcred
client_id='726549120613-5tteml7hdqvk8grb3afra32i3iddjnjb.apps.googleusercontent.com'
client_secret='Yuyb_xMSFo7MoWvNyqb44K4_'
protocol="imap"
password=""
debug=0
safile=""
PARAMS=""
while (( "$#" )); do
case "$1" in
-d|--debug)
debug=1
shift
;;
-e|--email)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
email=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-p|--protocol)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
protocol=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--password)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
password=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-s|--service-account-file)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
safile=$2
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-*|--*=) # unsupported flags
echo "Error: Unsupported flag $1" >&2
exit 1
;;
*) # preserve positional arguments
PARAMS="$PARAMS $1"
shift
;;
esac
done
# set positional arguments in their proper place
eval set -- "$PARAMS"
if [ "$password" == "" ] && [ "$safile" == "" ]; then
# 3-legged user OAuth
# Store our credentials in our home directory with a file called .
my_creds=~/.`basename $0`
if [ -s $my_creds ]; then
# if we already have a token stored, use it
. $my_creds
time_now=`date +%s`
else
# Form the request URL
# https://developers.google.com/identity/protocols/OAuth2InstalledApp#step-2-send-a-request-to-googles-oauth-20-server
auth_url="https://accounts.google.com/o/oauth2/v2/auth?client_id=$client_id&scope=$scope&response_type=code&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
echo "Please go to:"
echo
echo "$auth_url"
echo
echo "after accepting, enter the code you are given:"
read auth_code
# exchange authorization code for access and refresh tokens
# https://developers.google.com/identity/protocols/OAuth2InstalledApp#exchange-authorization-code
auth_result=$(curl -s "https://www.googleapis.com/oauth2/v4/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d code=$auth_code \
-d client_id=$client_id \
-d client_secret=$client_secret \
-d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
-d grant_type=authorization_code)
access_token=$(echo -e "$auth_result" | \
grep -Po '"access_token" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
refresh_token=$(echo -e "$auth_result" | \
grep -Po '"refresh_token" *: *.*?[^\\]",*' | \
awk -F'"' '{ print $4 }')
expires_in=$(echo -e "$auth_result" | \
grep -Po '"expires_in" *: *.*' | \
awk -F' ' '{ print $3 }' | awk -F',' '{ print $1}')
time_now=`date +%s`
expires_at=$((time_now + expires_in - 60))
echo -e "access_token=$access_token\nrefresh_token=$refresh_token\nexpires_at=$expires_at" > $my_creds
fi
# if our access token is expired, use the refresh token to get a new one
# https://developers.google.com/identity/protocols/OAuth2InstalledApp#offline
if [ $time_now -gt $expires_at ]; then
refresh_token=$(jq -r ".refresh_token" "${credentials_file}")
refresh_result=$(curl -s "https://www.googleapis.com/oauth2/v4/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d refresh_token=$refresh_token \
-d client_id=$client_id \
-d client_secret=$client_secret \
-d grant_type=refresh_token)
access_token=$(echo -e "$refresh_result" | \
grep -Po '"access_token" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
expires_in=$(echo -e "$refresh_result" | \
grep -Po '"expires_in" *: *.*' | \
awk -F' ' '{ print $3 }' | awk -F',' '{ print $1 }')
time_now=`date +%s`
expires_at=$(($time_now + $expires_in - 60))
echo -e "access_token=$access_token\nrefresh_token=$refresh_token\nexpires_at=$expires_at" > $my_creds
fi
if [ "$debug" == "1" ]; then
echo "Using user access token of $access_token"
fi
elif [ "$safile" != "" ]; then
# Service account Domain-wide Delegation
if [[ ! -r $safile ]]; then
echo "ERROR: cannot read $safile"
exit 1
fi
private_key=$(cat "$safile" | \
grep -Po -e '-----BEGIN PRIVATE KEY-----.*-----END PRIVATE KEY-----')
kid=$(cat "$safile" | \
grep -Po '"private_key_id" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
iss=$(cat "$safile" | \
grep -Po '"client_email" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
iat=$(date +%s)
exp=$(expr $iat + 3600)
raw_header="{\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"$kid\"}"
if [ "$debug" == "1" ]; then
echo "JWT Header: $raw_header"
fi
header=$(echo -n "${raw_header}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
raw_payload="{\"iat\":\"${iat}\",\"exp\":\"${exp}\",\"iss\":\"${iss}\",\"sub\":\"${email}\",\"aud\":\"https://oauth2.googleapis.com/token\",\"scope\":\"${scope}\"}"
if [ "$debug" == "1" ]; then
echo "JWT Payload: $raw_payload"
fi
payload=$( echo -n "${raw_payload}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
header_payload="${header}.${payload}"
signature=$(openssl dgst -sha256 -sign <(echo -ne "${private_key}") <(echo -n "${header_payload}") | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
jwt="${header_payload}.${signature}"
if [ "$debug" == "1" ]; then
echo "Final JWT: $jwt"
fi
refresh_result=$(curl -s "https://oauth2.googleapis.com/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d assertion="$jwt" \
-d grant_type="urn:ietf:params:oauth:grant-type:jwt-bearer")
access_token=$(echo -e "$refresh_result" | \
grep -Po '"access_token" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
if [ "$debug" == "1" ]; then
echo "Exchanged service account JWT for access token of $access_token"
fi
fi
if [ -n "${access_token}" ]; then
xoauth2="user=$emailauth=Bearer $access_token"
if [ "$debug" == "1" ]; then
echo "Access string is $xoauth2"
echo
fi
fi
b64_xoauth2="$(echo -n $xoauth2 | base64 -w 0)"
if [ "$debug" == "1" ]; then
echo "and in base64 it's $b64_xoauth2"
echo
fi
if [ "$protocol" == "imap" ]; then
if [ "$password" != "" ]; then
declare -a auth_lines=("A01 LOGIN $email $password")
else
declare -a auth_lines=("A01 AUTHENTICATE XOAUTH2 $b64_xoauth2")
fi
command_line="A02 GETQUOTAROOT INBOX"
hostport="imap.gmail.com:993"
elif [ "$protocol" == "pop" ]; then
if [ "$password" != "" ]; then
declare -a auth_lines=("user $email" "pass $password")
else
declare -a auth_lines="AUTH XOAUTH2 $b64_xoauth2"
fi
command_line="STAT"
hostport="pop.gmail.com:995"
elif [ "$protocol" == "smtp" ] || [ "$protocol" == "smtp-relay" ]; then
domain="${email#*@}"
echo "domain is: ${domain}."
if [ "$password" != "" ]; then
b64_email="$(echo -n $email | base64 -w 0)"
b64_pass="$(echo -n $password | base64 -w 0)"
declare -a auth_lines=("EHLO ${domain}" "auth login" "${b64_email}" "${b64_pass}")
else
declare -a auth_lines=("EHLO ${domain}" "AUTH XOAUTH2 $b64_xoauth2")
fi
command_line="mail from:<${email}>"
hostport="${protocol}.gmail.com:465"
else
echo "--protocol should be imap, pop or smtp. Got $protocol"
exit 3
fi
if [ "$debug" == "1" ]; then
echo -e "Auth lines are:\n $auth_lines"
echo
fi
if [ "$debug" == "1" ]; then
openssl_verbosity=""
else
openssl_verbosity="--quiet"
fi
(sleep 1
for auth_line in "${auth_lines[@]}"; do
echo -e ">> ${auth_line}" 1>&2
echo -e "${auth_line}"
sleep 1
done
echo -e ">> ${command_line}" 1>&2
echo -e "$command_line"
sleep 1) | openssl s_client $openssl_verbosity -crlf -connect $hostport
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment