diff --git a/README.md b/README.md index ab77e83..27f2697 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,31 @@ Optionally you may want to include folderfilter = lambda folder: not folder.startswith('Calendar') and not folder.startswith('Contacts') ``` to filter out folders containing non-mail items. + +# Sending Mail + +Sending mail with a program like msmtp using SMTP requires an access token. The +access token has a short life and has to be refreshed periodically using the +refresh token. + +`refresh_token.py` takes the refresh token stored in the file named in +config.RefreshTokenFileName and uses the MSAL library to request a new access +token. The new access token comes with a new refresh token and this is stored +in config.RefreshTokenFileName as well. + +Optionally, `refresh_token.py` also prints the access token, so it can easily +be used in password scripts that work with your sendmail program. For example, +the sendmail configuration in msmtprc would read: + +``` +account myaccount +host smtp.office365.com +port 587 +auth xoauth2 +tls on +tls_starttls on +from +user +passwordeval "cd /usr/local/src/M365-IMAP/; python3 refresh_token.py" +``` + diff --git a/config.py b/config.py index 361f160..c28e8d5 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,5 @@ -ClientId = "" -ClientSecret = "" -Scopes = ['https://outlook.office365.com/IMAP.AccessAsUser.All'] -OutputFileName = "refresh_token" \ No newline at end of file +ClientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584" +ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82" +Scopes = ['https://outlook.office.com/IMAP.AccessAsUser.All','https://outlook.office.com/SMTP.Send'] +RefreshTokenFileName = "imap_smtp_refresh_token" +AccessTokenFileName = "imap_smtp_access_token" diff --git a/get_token.py b/get_token.py index 51fca8f..38e7136 100644 --- a/get_token.py +++ b/get_token.py @@ -29,6 +29,10 @@ if 'error' in token: print(token) sys.exit("Failed to get access token") -print(f'Access token acquired, writing to file {config.OutputFileName}') -with open(config.OutputFileName, 'w') as f: - f.write(cache.find('RefreshToken')[0]['secret']) +with open(config.RefreshTokenFileName, 'w') as f: + print(f'Refresh token acquired, writing to file {config.RefreshTokenFileName}') + f.write(token['refresh_token']) + +with open(config.AccessTokenFileName, 'w') as f: + print(f'Access token acquired, writing to file {config.AccessTokenFileName}') + f.write(token['access_token']) diff --git a/refresh_token.py b/refresh_token.py new file mode 100644 index 0000000..cd2577a --- /dev/null +++ b/refresh_token.py @@ -0,0 +1,29 @@ +from msal import ConfidentialClientApplication, SerializableTokenCache +import config +import sys + +redirect_uri = "http://localhost" +print_access_token = True + +# We use the cache to extract the refresh token +cache = SerializableTokenCache() +app = ConfidentialClientApplication(config.ClientId, client_credential=config.ClientSecret, token_cache=cache) + + +old_refresh_token = open(config.RefreshTokenFileName,'r').read() + +token = app.acquire_token_by_refresh_token(old_refresh_token,config.Scopes) + +if 'error' in token: + print(token) + sys.exit("Failed to get access token") + +# you're supposed to save the old refresh token each time +with open(config.RefreshTokenFileName, 'w') as f: + #f.write(cache.find('RefreshToken')[0]['secret']) + f.write(token['refresh_token']) + +with open(config.AccessTokenFileName, 'w') as f: + f.write(token['access_token']) + if print_access_token: + print(token['access_token'])