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