X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/aaf014c77a4fb19bdc33370f5b6af5b8497decf8..2b60a54adb3b0bf5a9b927708085492b816a6015:/sbr/oauth.c diff --git a/sbr/oauth.c b/sbr/oauth.c index f4564d0f..8e3d6b05 100755 --- a/sbr/oauth.c +++ b/sbr/oauth.c @@ -1,4 +1,5 @@ -/* +/* oauth.c -- OAuth 2.0 implementation for XOAUTH2 in SMTP and POP3. + * * This code is Copyright (c) 2014, by the authors of nmh. See the * COPYRIGHT file in the root directory of the nmh distribution for * complete copyright information. @@ -23,16 +24,17 @@ #include #include +#include "lock_file.h" #define JSON_TYPE "application/json" -/* We pretend access tokens expire 30 seconds earlier than they actually do to +/* We pretend access tokens expire 60 seconds earlier than they actually do to * allow for separate processes to use and refresh access tokens. The process * that uses the access token (post) has an error if the token is expired; the * process that refreshes the access token (send) must have already refreshed if * the expiration is close. * - * 30s is arbitrary, and hopefully is enough to allow for clock skew. + * 60s is arbitrary, and hopefully is enough to allow for clock skew. * Currently only Gmail supports XOAUTH2, and seems to always use a token * life-time of 3600s, but that is not guaranteed. It is possible for Gmail to * issue an access token with a life-time so short that even after send @@ -44,7 +46,7 @@ * (not counting header and not null-terminated) */ #define RESPONSE_BODY_MAX 8192 -/* Maxium size for URLs and URI-encoded query strings, null-terminated. +/* Maximum size for URLs and URI-encoded query strings, null-terminated. * * Actual maximum we need is based on the size of tokens (limited by * RESPONSE_BODY_MAX), code user copies from a web page (arbitrarily large), and @@ -138,11 +140,12 @@ mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, FILE *fp; char *client_res; - if (!mh_oauth_new (&ctx, svc)) adios(NULL, mh_oauth_get_err_string(ctx)); + if (!mh_oauth_new (&ctx, svc)) + adios(NULL, "%s", mh_oauth_get_err_string(ctx)); if (log != NULL) mh_oauth_log_to(stderr, ctx); - fn = getcpy(mh_oauth_cred_fn(svc)); + fn = mh_xstrdup(mh_oauth_cred_fn(svc)); fp = lkfopendata(fn, "r+", &failed_to_lock); if (fp == NULL) { if (errno == ENOENT) { @@ -155,7 +158,7 @@ mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, } if ((cred = mh_oauth_cred_load(fp, ctx, user)) == NULL) { - adios(NULL, mh_oauth_get_err_string(ctx)); + adios(NULL, "%s", mh_oauth_get_err_string(ctx)); } if (!mh_oauth_access_token_valid(time(NULL), cred)) { @@ -166,13 +169,13 @@ mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, if (mh_oauth_get_err_code(ctx) == MH_OAUTH_BAD_GRANT) { adios(NULL, "credentials rejected -- run mhlogin -saslmech xoauth2 -authservice %s", svc); } - advise(NULL, "error refreshing OAuth2 token"); - adios(NULL, mh_oauth_get_err_string(ctx)); + inform("error refreshing OAuth2 token"); + adios(NULL, "%s", mh_oauth_get_err_string(ctx)); } fseek(fp, 0, SEEK_SET); if (!mh_oauth_cred_save(fp, cred, user)) { - adios(NULL, mh_oauth_get_err_string(ctx)); + adios(NULL, "%s", mh_oauth_get_err_string(ctx)); } } @@ -182,7 +185,7 @@ mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, free(fn); /* XXX writeBase64raw modifies the source buffer! make a copy */ - client_res = getcpy(mh_oauth_sasl_client_response(oauth_res_len, user, + client_res = mh_xstrdup(mh_oauth_sasl_client_response(oauth_res_len, user, cred)); mh_oauth_cred_free(cred); mh_oauth_free(ctx); @@ -196,7 +199,7 @@ static boolean is_json(const char *content_type) { return content_type != NULL - && strncasecmp(content_type, JSON_TYPE, sizeof JSON_TYPE - 1) == 0; + && strncasecmp(content_type, JSON_TYPE, LEN(JSON_TYPE)) == 0; } static void @@ -239,24 +242,19 @@ set_err_http(mh_oauth_ctx *ctx, const struct curl_ctx *curl_ctx) } static char * -make_user_agent() +make_user_agent(void) { const char *curl = curl_version_info(CURLVERSION_NOW)->version; - char *s = mh_xmalloc(strlen(user_agent) - + 1 - + sizeof "libcurl" - + 1 - + strlen(curl) - + 1); - sprintf(s, "%s libcurl/%s", user_agent, curl); - return s; + return concat(user_agent, " libcurl/", curl, NULL); } boolean mh_oauth_new(mh_oauth_ctx **result, const char *svc_name) { - mh_oauth_ctx *ctx = *result = mh_xmalloc(sizeof *ctx); + mh_oauth_ctx *ctx; + NEW(ctx); + *result = ctx; ctx->curl = NULL; ctx->log = NULL; @@ -328,7 +326,6 @@ mh_oauth_get_err_code(const mh_oauth_ctx *ctx) const char * mh_oauth_get_err_string(mh_oauth_ctx *ctx) { - char *result; const char *base; free(ctx->err_formatted); @@ -372,12 +369,11 @@ mh_oauth_get_err_string(mh_oauth_ctx *ctx) base = "unknown error"; } if (ctx->err_details == NULL) { - return ctx->err_formatted = getcpy(base); + return ctx->err_formatted = mh_xstrdup(base); } - /* length of the two strings plus ": " and '\0' */ - result = mh_xmalloc(strlen(base) + strlen(ctx->err_details) + 3); - sprintf(result, "%s: %s", base, ctx->err_details); - return ctx->err_formatted = result; + + ctx->err_formatted = concat(base, ": ", ctx->err_details, NULL); + return ctx->err_formatted; } const char * @@ -507,7 +503,7 @@ mh_oauth_authorize(const char *code, mh_oauth_ctx *ctx) return NULL; } - result = mh_xmalloc(sizeof *result); + NEW(result); result->ctx = ctx; result->access_token = result->refresh_token = NULL; @@ -620,31 +616,33 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) boolean success = FALSE; char name[NAMESZ], value_buf[BUFSIZ]; int state; - m_getfld_state_t getfld_ctx = 0; + m_getfld_state_t getfld_ctx; - struct user_creds *user_creds = mh_xmalloc(sizeof *user_creds); + struct user_creds *user_creds; + NEW(user_creds); user_creds->alloc = 4; user_creds->len = 0; user_creds->creds = mh_xmalloc(user_creds->alloc * sizeof *user_creds->creds); + getfld_ctx = m_getfld_state_init(fp); for (;;) { int size = sizeof value_buf; - switch (state = m_getfld(&getfld_ctx, name, value_buf, &size, fp)) { + switch (state = m_getfld2(&getfld_ctx, name, value_buf, &size)) { case FLD: case FLDPLUS: { char **save, *expire; time_t *expires_at = NULL; - if (strncmp(name, "access-", 7) == 0) { + if (has_prefix(name, "access-")) { const char *user = name + 7; mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds, user); save = &creds->access_token; - } else if (strncmp(name, "refresh-", 8) == 0) { + } else if (has_prefix(name, "refresh-")) { const char *user = name + 8; mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds, user); save = &creds->refresh_token; - } else if (strncmp(name, "expire-", 7) == 0) { + } else if (has_prefix(name, "expire-")) { const char *user = name + 7; mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds, user); @@ -661,7 +659,7 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) char *tmp = getcpy(value_buf); while (state == FLDPLUS) { size = sizeof value_buf; - state = m_getfld(&getfld_ctx, name, value_buf, &size, fp); + state = m_getfld2(&getfld_ctx, name, value_buf, &size); tmp = add(value_buf, tmp); } *save = trimcpy(tmp); @@ -687,7 +685,7 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) break; default: - /* Not adding details for LENERR/FMTERR because m_getfld already + /* Not adding details for LENERR/FMTERR because m_getfld2 already * wrote advise message to stderr. */ set_err(ctx, MH_OAUTH_CRED_FILE); break; @@ -814,17 +812,13 @@ const char * mh_oauth_sasl_client_response(size_t *res_len, const char *user, const mh_oauth_cred *cred) { - size_t len = sizeof "user=" - 1 - + strlen(user) - + sizeof "\1auth=Bearer " - 1 - + strlen(cred->access_token) - + sizeof "\1\1" - 1; - free(cred->ctx->sasl_client_res); - cred->ctx->sasl_client_res = mh_xmalloc(len + 1); - *res_len = len; - sprintf(cred->ctx->sasl_client_res, "user=%s\1auth=Bearer %s\1\1", - user, cred->access_token); - return cred->ctx->sasl_client_res; + char **p; + + p = &cred->ctx->sasl_client_res; + free(*p); + *p = concat("user=", user, "\1auth=Bearer ", cred->access_token, "\1\1", NULL); + *res_len = strlen(*p); + return *p; } /******************************************************************************* @@ -850,7 +844,10 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...) len = 0; prefix = ""; } else { - len = sprintf(s, "%s", base_url); + len = strlen(base_url); + if (len > size - 1) /* Less one for NUL. */ + return FALSE; + strcpy(s, base_url); prefix = "?"; } @@ -884,7 +881,7 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...) } static int -debug_callback(const CURL *handle, curl_infotype type, const char *data, +debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) { FILE *fp = userptr; @@ -904,7 +901,7 @@ debug_callback(const CURL *handle, curl_infotype type, const char *data, } fwrite(data, 1, size, fp); if (data[size - 1] != '\n') { - fputs("\n", fp); + putc('\n', fp); } fflush(fp); return 0; @@ -956,7 +953,7 @@ post(struct curl_ctx *ctx, const char *url, const char *req_body) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx); - if (strncmp(url, "http://127.0.0.1:", 17) == 0) { + if (has_prefix(url, "http://127.0.0.1:")) { /* Hack: on Cygwin, curl doesn't fail to connect with ECONNREFUSED. Instead, it waits to timeout. So set a really short timeout, but just on localhost (for convenience of the user, and the test