X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/a872db591b217f09a78b7027eb246ce0eb20ff4c..94187a80bd60baab4b9c4b949ad820d730578123:/sbr/oauth.c diff --git a/sbr/oauth.c b/sbr/oauth.c index 7977586c..aac6b43a 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,6 +24,7 @@ #include #include +#include "lock_file.h" #define JSON_TYPE "application/json" @@ -108,7 +110,7 @@ struct curl_ctx { /* Whether the response was too big; if so, the rest of the output fields * are undefined. */ - boolean too_big; + bool too_big; /* HTTP response code */ long res_code; @@ -123,9 +125,9 @@ struct curl_ctx { char res_body[RESPONSE_BODY_MAX]; }; -static boolean get_json_strings(const char *, size_t, FILE *, ...); -static boolean make_query_url(char *, size_t, CURL *, const char *, ...); -static boolean post(struct curl_ctx *, const char *, const char *); +static bool get_json_strings(const char *, size_t, FILE *, ...) ENDNULL; +static bool make_query_url(char *, size_t, CURL *, const char *, ...) ENDNULL; +static bool post(struct curl_ctx *, const char *, const char *); int mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, @@ -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_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); @@ -192,11 +195,11 @@ mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res, return OK; } -static boolean +static bool 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 @@ -223,7 +226,7 @@ set_err_http(mh_oauth_ctx *ctx, const struct curl_ctx *curl_ctx) if (curl_ctx->res_len > 0 && is_json(curl_ctx->content_type) && get_json_strings(curl_ctx->res_body, curl_ctx->res_len, ctx->log, - "error", &error, (void *)NULL) + "error", &error, NULL) && error != NULL) { if (strcmp(error, "invalid_grant") == 0) { code = MH_OAUTH_BAD_GRANT; @@ -239,13 +242,13 @@ 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; return concat(user_agent, " libcurl/", curl, NULL); } -boolean +bool mh_oauth_new(mh_oauth_ctx **result, const char *svc_name) { mh_oauth_ctx *ctx; @@ -260,13 +263,13 @@ mh_oauth_new(mh_oauth_ctx **result, const char *svc_name) if (!mh_oauth_get_service_info(svc_name, &ctx->svc, ctx->err_buf, sizeof(ctx->err_buf))) { set_err_details(ctx, MH_OAUTH_BAD_PROFILE, ctx->err_buf); - return FALSE; + return false; } ctx->curl = curl_easy_init(); if (ctx->curl == NULL) { set_err(ctx, MH_OAUTH_CURL_INIT); - return FALSE; + return false; } curl_easy_setopt(ctx->curl, CURLOPT_ERRORBUFFER, ctx->err_buf); @@ -275,10 +278,10 @@ mh_oauth_new(mh_oauth_ctx **result, const char *svc_name) if (curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, ctx->user_agent) != CURLE_OK) { set_err_details(ctx, MH_OAUTH_CURL_INIT, ctx->err_buf); - return FALSE; + return false; } - return TRUE; + return true; } void @@ -366,7 +369,7 @@ 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); } ctx->err_formatted = concat(base, ": ", ctx->err_details, NULL); @@ -383,23 +386,23 @@ mh_oauth_get_authorize_url(mh_oauth_ctx *ctx) "client_id", ctx->svc.client_id, "redirect_uri", ctx->svc.redirect_uri, "scope", ctx->svc.scope, - (void *)NULL)) { + NULL)) { set_err(ctx, MH_OAUTH_REQUEST_INIT); return NULL; } return ctx->buf; } -static boolean +static bool cred_from_response(mh_oauth_cred *cred, const char *content_type, const char *input, size_t input_len) { - boolean result = FALSE; + bool result = false; char *access_token, *expires_in, *refresh_token; const mh_oauth_ctx *ctx = cred->ctx; if (!is_json(content_type)) { - return FALSE; + return false; } access_token = expires_in = refresh_token = NULL; @@ -407,7 +410,7 @@ cred_from_response(mh_oauth_cred *cred, const char *content_type, "access_token", &access_token, "expires_in", &expires_in, "refresh_token", &refresh_token, - (void *)NULL)) { + NULL)) { goto out; } @@ -418,7 +421,7 @@ cred_from_response(mh_oauth_cred *cred, const char *content_type, } } - result = TRUE; + result = true; free(cred->access_token); cred->access_token = access_token; @@ -453,7 +456,7 @@ cred_from_response(mh_oauth_cred *cred, const char *content_type, return result; } -static boolean +static bool do_access_request(mh_oauth_cred *cred, const char *req_body) { mh_oauth_ctx *ctx = cred->ctx; @@ -467,21 +470,21 @@ do_access_request(mh_oauth_cred *cred, const char *req_body) } else { set_err_details(ctx, MH_OAUTH_POST, ctx->err_buf); } - return FALSE; + return false; } if (curl_ctx.res_code != 200) { set_err_http(ctx, &curl_ctx); - return FALSE; + return false; } if (!cred_from_response(cred, curl_ctx.content_type, curl_ctx.res_body, curl_ctx.res_len)) { set_err(ctx, MH_OAUTH_RESPONSE_BAD); - return FALSE; + return false; } - return TRUE; + return true; } mh_oauth_cred * @@ -495,7 +498,7 @@ mh_oauth_authorize(const char *code, mh_oauth_ctx *ctx) "redirect_uri", ctx->svc.redirect_uri, "client_id", ctx->svc.client_id, "client_secret", ctx->svc.client_secret, - (void *)NULL)) { + NULL)) { set_err(ctx, MH_OAUTH_REQUEST_INIT); return NULL; } @@ -512,15 +515,15 @@ mh_oauth_authorize(const char *code, mh_oauth_ctx *ctx) return result; } -boolean +bool mh_oauth_refresh(mh_oauth_cred *cred) { - boolean result; + bool result; mh_oauth_ctx *ctx = cred->ctx; if (cred->refresh_token == NULL) { set_err(ctx, MH_OAUTH_NO_REFRESH); - return FALSE; + return false; } if (!make_query_url(ctx->buf, sizeof ctx->buf, ctx->curl, NULL, @@ -528,22 +531,22 @@ mh_oauth_refresh(mh_oauth_cred *cred) "refresh_token", cred->refresh_token, "client_id", ctx->svc.client_id, "client_secret", ctx->svc.client_secret, - (void *)NULL)) { + NULL)) { set_err(ctx, MH_OAUTH_REQUEST_INIT); - return FALSE; + return false; } result = do_access_request(cred, ctx->buf); if (result && cred->access_token == NULL) { set_err_details(ctx, MH_OAUTH_RESPONSE_BAD, "no access token"); - return FALSE; + return false; } return result; } -boolean +bool mh_oauth_access_token_valid(time_t t, const mh_oauth_cred *cred) { return cred->access_token != NULL && t + EXPIRY_FUDGE < cred->expires_at; @@ -607,13 +610,13 @@ free_user_creds(struct user_creds *user_creds) free(user_creds); } -static boolean +static bool load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) { - boolean success = FALSE; + bool 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; NEW(user_creds); @@ -621,24 +624,25 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) 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); @@ -655,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); @@ -677,11 +681,11 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) case BODY: case FILEEOF: - success = TRUE; + success = true; 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; @@ -699,25 +703,25 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx) return success; } -static boolean +static bool save_user(FILE *fp, const char *user, const char *access, const char *refresh, long expires_at) { if (access != NULL) { - if (fprintf(fp, "access-%s: %s\n", user, access) < 0) return FALSE; + if (fprintf(fp, "access-%s: %s\n", user, access) < 0) return false; } if (refresh != NULL) { - if (fprintf(fp, "refresh-%s: %s\n", user, refresh) < 0) return FALSE; + if (fprintf(fp, "refresh-%s: %s\n", user, refresh) < 0) return false; } if (expires_at > 0) { if (fprintf(fp, "expire-%s: %ld\n", user, (long)expires_at) < 0) { - return FALSE; + return false; } } - return TRUE; + return true; } -boolean +bool mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user) { struct user_creds *user_creds; @@ -726,7 +730,7 @@ mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user) /* Load existing creds if any. */ if (!load_creds(&user_creds, fp, cred->ctx)) { - return FALSE; + return false; } if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) goto err; @@ -751,12 +755,12 @@ mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user) free_user_creds(user_creds); - return TRUE; + return true; err: free_user_creds(user_creds); set_err(cred->ctx, MH_OAUTH_CRED_FILE); - return FALSE; + return false; } mh_oauth_cred * @@ -823,14 +827,14 @@ mh_oauth_sasl_client_response(size_t *res_len, /* * Build null-terminated URL in the array pointed to by s. If the URL doesn't - * fit within size (including the terminating null byte), return FALSE without * + * fit within size (including the terminating null byte), return false without * * building the entire URL. Some of URL may already have been written into the * result array in that case. */ -static boolean +static bool make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...) { - boolean result = FALSE; + bool result = false; size_t len; char *prefix; va_list ap; @@ -842,7 +846,7 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...) } else { len = strlen(base_url); if (len > size - 1) /* Less one for NUL. */ - return FALSE; + return false; strcpy(s, base_url); prefix = "?"; } @@ -869,7 +873,7 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...) prefix = "&"; } - result = TRUE; + result = true; out: va_end(ap); @@ -877,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; @@ -897,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; @@ -916,7 +920,7 @@ write_callback(const char *ptr, size_t size, size_t nmemb, void *userdata) size *= nmemb; new_len = ctx->res_len + size; if (new_len > sizeof ctx->res_body) { - ctx->too_big = TRUE; + ctx->too_big = true; return 0; } @@ -926,13 +930,13 @@ write_callback(const char *ptr, size_t size, size_t nmemb, void *userdata) return size; } -static boolean +static bool post(struct curl_ctx *ctx, const char *url, const char *req_body) { CURL *curl = ctx->curl; CURLcode status; - ctx->too_big = FALSE; + ctx->too_big = false; ctx->res_len = 0; if (ctx->log != NULL) { @@ -941,15 +945,15 @@ post(struct curl_ctx *ctx, const char *url, const char *req_body) curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx->log); } - if ((status = curl_easy_setopt(curl, CURLOPT_URL, url)) != CURLE_OK) { - return FALSE; + if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK) { + return false; } curl_easy_setopt(curl, CURLOPT_POSTFIELDS, 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 @@ -960,21 +964,17 @@ post(struct curl_ctx *ctx, const char *url, const char *req_body) status = curl_easy_perform(curl); /* first check for error from callback */ if (ctx->too_big) { - return FALSE; + return false; } /* now from curl */ if (status != CURLE_OK) { - return FALSE; + return false; } - if ((status = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, - &ctx->res_code)) != CURLE_OK - || (status = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, - &ctx->content_type)) != CURLE_OK) { - return FALSE; - } - - return TRUE; + return curl_easy_getinfo(curl, + CURLINFO_RESPONSE_CODE, &ctx->res_code) == CURLE_OK && + curl_easy_getinfo(curl, + CURLINFO_CONTENT_TYPE, &ctx->content_type) == CURLE_OK; } /******************************************************************************* @@ -993,7 +993,7 @@ post(struct curl_ctx *ctx, const char *url, const char *req_body) * * Even in that case, tokens has been allocated and must be freed. */ -static boolean +static bool parse_json(jsmntok_t **tokens, size_t *tokens_len, const char *input, size_t input_len, FILE *log) { @@ -1015,11 +1015,8 @@ parse_json(jsmntok_t **tokens, size_t *tokens_len, of the response body. */ *tokens = mh_xrealloc(*tokens, *tokens_len * sizeof **tokens); } - if (r <= 0) { - return FALSE; - } - return TRUE; + return r > 0; } /* @@ -1039,7 +1036,7 @@ get_json_string(char **val, const char *input, const jsmntok_t *tokens, int skip_tokens = 0; /* whether the current token represents a field name */ /* The next token will be the value. */ - boolean is_key = TRUE; + bool is_key = true; int i; for (i = 1; i <= token_count; i++) { @@ -1065,11 +1062,11 @@ get_json_string(char **val, const char *input, const jsmntok_t *tokens, if (skip_tokens > 0) { skip_tokens--; /* When we finish with the object or list, we'll have a key. */ - is_key = TRUE; + is_key = true; continue; } if (is_key) { - is_key = FALSE; + is_key = false; continue; } key = input + tokens[i - 1].start; @@ -1081,7 +1078,7 @@ get_json_string(char **val, const char *input, const jsmntok_t *tokens, (*val)[val_len] = '\0'; return; } - is_key = TRUE; + is_key = true; } } @@ -1102,10 +1099,10 @@ get_json_string(char **val, const char *input, const jsmntok_t *tokens, * * log may be used for debug-logging if not NULL. */ -static boolean +static bool get_json_strings(const char *input, size_t input_len, FILE *log, ...) { - boolean result = FALSE; + bool result = false; jsmntok_t *tokens; size_t tokens_len; va_list ap; @@ -1119,7 +1116,7 @@ get_json_strings(const char *input, size_t input_len, FILE *log, ...) goto out; } - result = TRUE; + result = true; va_start(ap, log); for (name = va_arg(ap, char *); name != NULL; name = va_arg(ap, char *)) {