-
-
Save HoLengZai/9c97b5906a54fcc795ed5339e1576b52 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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_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