1 /* oauth.h -- OAuth 2.0 implementation for XOAUTH2 in SMTP and POP3. 2 * 3 * Google defined XOAUTH2 for SMTP, and that's what we use here. If other 4 * providers implement XOAUTH2 or some similar OAuth-based SMTP authentication 5 * protocol, it should be simple to extend this. 6 * 7 * OAuth https://tools.ietf.org/html/rfc6749 8 * SMTP https://developers.google.com/gmail/xoauth2_protocol 9 * POP3 http://googleappsdeveloper.blogspot.com/2014/10/updates-on-authentication-for-gmail.html 10 * 11 * Presumably [2] should document POP3 and that is an over-sight. As it stands, 12 * that blog post is the closest we have to documentation. 13 * 14 * According to [1] 2.1 Client Types, this is a "native application", a 15 * "public" client. 16 * 17 * To summarize the flow: 18 * 19 * 1. User runs mhlogin which prints a URL the user must visit, and prompts for 20 * a code retrieved from that page. 21 * 22 * 2. User visits this URL in browser, signs in with some Google account, and 23 * copies and pastes the resulting code back to mhlogin. 24 * 25 * 3. mhlogin does HTTP POST to Google to exchange the user-provided code for a 26 * short-lived access token and a long-lived refresh token. 27 * 28 * 4. send uses the access token in SMTP auth if not expired. If it is expired, 29 * it does HTTP POST to Google including the refresh token and gets back a 30 * new access token (and possibly refresh token). If the refresh token has 31 * become invalid (e.g. if the user took some reset action on the Google 32 * account), the user must use mhlogin again, then re-run send. 33 */ 34 35 typedef enum { 36 /* error loading profile */ 37 MH_OAUTH_BAD_PROFILE = OK + 1, 38 39 /* error initializing libcurl */ 40 MH_OAUTH_CURL_INIT, 41 42 /* local error initializing HTTP request */ 43 MH_OAUTH_REQUEST_INIT, 44 45 /* error executing HTTP POST request */ 46 MH_OAUTH_POST, 47 48 /* HTTP response body is too big. */ 49 MH_OAUTH_RESPONSE_TOO_BIG, 50 51 /* Can't process HTTP response body. */ 52 MH_OAUTH_RESPONSE_BAD, 53 54 /* The authorization server rejected the grant (authorization code or 55 * refresh token); possibly the user entered a bad code, or the refresh 56 * token has become invalid, etc. */ 57 MH_OAUTH_BAD_GRANT, 58 59 /* HTTP server indicates something is wrong with our request. */ 60 MH_OAUTH_REQUEST_BAD, 61 62 /* Attempting to refresh an access token without a refresh token. */ 63 MH_OAUTH_NO_REFRESH, 64 65 66 /* requested user not in cred file */ 67 MH_OAUTH_CRED_USER_NOT_FOUND, 68 69 /* error loading serialized credentials */ 70 MH_OAUTH_CRED_FILE 71 } mh_oauth_err_code; 72 73 typedef struct mh_oauth_ctx mh_oauth_ctx; 74 75 typedef struct mh_oauth_cred mh_oauth_cred; 76 77 typedef struct mh_oauth_service_info mh_oauth_service_info; 78 79 struct mh_oauth_service_info { 80 /* Name of service, so we can search static internal services array 81 * and for determining default credential file name. */ 82 char *name; 83 84 /* Human-readable name of the service; in mh_oauth_ctx::svc this is not 85 * another buffer to free, but a pointer to either static SERVICE data 86 * (below) or to the name field. */ 87 char *display_name; 88 89 /* [1] 2.2 Client Identifier, 2.3.1 Client Password */ 90 char *client_id; 91 /* [1] 2.3.1 Client Password */ 92 char *client_secret; 93 /* [1] 3.1 Authorization Endpoint */ 94 char *auth_endpoint; 95 /* [1] 3.1.2 Redirection Endpoint */ 96 char *redirect_uri; 97 /* [1] 3.2 Token Endpoint */ 98 char *token_endpoint; 99 /* [1] 3.3 Access Token Scope */ 100 char *scope; 101 }; 102 103 /* 104 * Do the complete dance for XOAUTH2 as used by POP3 and SMTP. 105 * 106 * Load tokens for svc from disk, refresh if necessary, and return the 107 * client response in client_response and client_response_len. 108 * 109 * If refreshing, writes freshened tokens to disk. 110 * 111 * Exits via adios on any error. 112 * 113 * Always returns OK for now, but in the future could return NOTOK on error. 114 */ 115 116 int 117 mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, 118 size_t *oauth_res_len, FILE *log); 119 120 /* 121 * Allocate and initialize a new OAuth context. 122 * 123 * Caller must call mh_oauth_free(ctx) when finished, even on error. 124 * 125 * svc_name must point to a null-terminated string identifying the service 126 * provider. Support for "gmail" is built-in; anything else must be defined in 127 * the user's profile. The profile can also override "gmail" settings. 128 * 129 * Accesses global m_defs via context_find. 130 * 131 * On error, return FALSE and set an error in ctx; ctx is always allocated. 132 */ 133 boolean 134 mh_oauth_new(mh_oauth_ctx **ctx, const char *svc_name); 135 136 /* 137 * Free all resources associated with ctx. 138 */ 139 void 140 mh_oauth_free(mh_oauth_ctx *ctx); 141 142 /* 143 * Return null-terminated human-readable name of the service, e.g. "Gmail". 144 * 145 * Never returns NULL. 146 */ 147 const char * 148 mh_oauth_svc_display_name(const mh_oauth_ctx *ctx); 149 150 /* 151 * Enable logging for subsequent operations on ctx. 152 * 153 * log must not be closed until after mh_oauth_free. 154 * 155 * For all HTTP requests, the request is logged with each line prefixed with 156 * "< ", and the response with "> ". Other messages are prefixed with "* ". 157 */ 158 void 159 mh_oauth_log_to(FILE *log, mh_oauth_ctx *ctx); 160 161 /* 162 * Return the error code after some function indicated an error. 163 * 164 * Must not be called if an error was not indicated. 165 */ 166 mh_oauth_err_code 167 mh_oauth_get_err_code(const mh_oauth_ctx *ctx); 168 169 /* 170 * Return null-terminated error message after some function indicated an error. 171 * 172 * Never returns NULL, but must not be called if an error was not indicated. 173 */ 174 const char * 175 mh_oauth_get_err_string(mh_oauth_ctx *ctx); 176 177 /* 178 * Return the null-terminated URL the user needs to visit to authorize access. 179 * 180 * URL may be invalidated by subsequent calls to mh_oauth_get_authorize_url, 181 * mh_oauth_authorize, or mh_oauth_refresh. 182 * 183 * On error, return NULL. 184 */ 185 const char * 186 mh_oauth_get_authorize_url(mh_oauth_ctx *ctx); 187 188 /* 189 * Exchange code provided by the user for access (and maybe refresh) token. 190 * 191 * On error, return NULL. 192 */ 193 mh_oauth_cred * 194 mh_oauth_authorize(const char *code, mh_oauth_ctx *ctx); 195 196 /* 197 * Refresh access (and maybe refresh) token if refresh token present. 198 * 199 * On error, return FALSE and leave cred untouched. 200 */ 201 boolean 202 mh_oauth_refresh(mh_oauth_cred *cred); 203 204 /* 205 * Return whether access token is present and not expired at time T. 206 */ 207 boolean 208 mh_oauth_access_token_valid(time_t t, const mh_oauth_cred *cred); 209 210 /* 211 * Free all resources associated with cred. 212 */ 213 void 214 mh_oauth_cred_free(mh_oauth_cred *cred); 215 216 /* 217 * Return the null-terminated file name for storing this service's OAuth tokens. 218 * 219 * Accesses global m_defs via context_find. 220 * 221 * Never returns NULL. 222 */ 223 const char * 224 mh_oauth_cred_fn(const char *svc_name); 225 226 /* 227 * Serialize OAuth tokens to file. 228 * 229 * On error, return FALSE. 230 */ 231 boolean 232 mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user); 233 234 /* 235 * Load OAuth tokens from file. 236 * 237 * Calls m_getfld(), which writes to stderr with advise(). 238 * 239 * On error, return NULL. 240 */ 241 mh_oauth_cred * 242 mh_oauth_cred_load(FILE *fp, mh_oauth_ctx *ctx, const char *user); 243 244 /* 245 * Return null-terminated SASL client response for XOAUTH2 from access token. 246 * 247 * Store the length in res_len. 248 * 249 * Must not be called except after successful mh_oauth_access_token_valid or 250 * mh_oauth_refresh call; i.e. must have a valid access token. 251 */ 252 const char * 253 mh_oauth_sasl_client_response(size_t *res_len, 254 const char *user, const mh_oauth_cred *cred); 255 256 /* 257 * Retrieve the various entries for the OAuth mechanism 258 */ 259 260 boolean 261 mh_oauth_get_service_info(const char *svc_name, mh_oauth_service_info *svcinfo, 262 char *errbuf, size_t errbuflen); 263