]> diplodocus.org Git - nmh/blobdiff - sbr/oauth.c
new.c: Order two return statements to match comment.
[nmh] / sbr / oauth.c
old mode 100644 (file)
new mode 100755 (executable)
index 85fe714..aac6b43
@@ -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.
  * 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.
 
 #include <h/oauth.h>
 #include <h/utils.h>
 
 #include <h/oauth.h>
 #include <h/utils.h>
+#include "lock_file.h"
 
 #define JSON_TYPE "application/json"
 
 
 #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.
  *
  * 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
  * 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
 
  * (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
  *
  * 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
@@ -108,7 +110,7 @@ struct curl_ctx {
 
     /* Whether the response was too big; if so, the rest of the output fields
      * are undefined. */
 
     /* 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;
 
     /* HTTP response code */
     long res_code;
@@ -123,9 +125,9 @@ struct curl_ctx {
     char res_body[RESPONSE_BODY_MAX];
 };
 
     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,
 
 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;
 
     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);
 
 
     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) {
     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) {
     }
 
     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)) {
     }
 
     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);
             }
             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)) {
         }
 
         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 */
     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);
                                                       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;
 }
 
     return OK;
 }
 
-static boolean
+static bool
 is_json(const char *content_type)
 {
     return content_type != NULL
 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
 }
 
 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,
     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;
         && error != NULL) {
         if (strcmp(error, "invalid_grant") == 0) {
             code = MH_OAUTH_BAD_GRANT;
@@ -239,24 +242,19 @@ set_err_http(mh_oauth_ctx *ctx, const struct curl_ctx *curl_ctx)
 }
 
 static char *
 }
 
 static char *
-make_user_agent()
+make_user_agent(void)
 {
     const char *curl = curl_version_info(CURLVERSION_NOW)->version;
 {
     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
+bool
 mh_oauth_new(mh_oauth_ctx **result, const char *svc_name)
 {
 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;
     ctx->curl = NULL;
 
     ctx->log = NULL;
@@ -265,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);
     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);
     }
 
     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);
 
     }
     curl_easy_setopt(ctx->curl, CURLOPT_ERRORBUFFER, ctx->err_buf);
 
@@ -280,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);
     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
 }
 
 void
@@ -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)
 {
 const char *
 mh_oauth_get_err_string(mh_oauth_ctx *ctx)
 {
-    char *result;
     const char *base;
 
     free(ctx->err_formatted);
     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) {
         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 *
 }
 
 const char *
@@ -390,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,
                         "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;
 }
 
         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)
 {
 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)) {
     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;
     }
 
     access_token = expires_in = refresh_token = NULL;
@@ -414,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,
                           "access_token", &access_token,
                           "expires_in", &expires_in,
                           "refresh_token", &refresh_token,
-                          (void *)NULL)) {
+                          NULL)) {
         goto out;
     }
 
         goto out;
     }
 
@@ -425,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;
 
     free(cred->access_token);
     cred->access_token = access_token;
@@ -460,7 +456,7 @@ cred_from_response(mh_oauth_cred *cred, const char *content_type,
     return result;
 }
 
     return result;
 }
 
-static boolean
+static bool
 do_access_request(mh_oauth_cred *cred, const char *req_body)
 {
     mh_oauth_ctx *ctx = cred->ctx;
 do_access_request(mh_oauth_cred *cred, const char *req_body)
 {
     mh_oauth_ctx *ctx = cred->ctx;
@@ -474,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);
         }
         } 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);
     }
 
     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);
     }
 
     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 *
 }
 
 mh_oauth_cred *
@@ -502,12 +498,12 @@ 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,
                         "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;
     }
 
         set_err(ctx, MH_OAUTH_REQUEST_INIT);
         return NULL;
     }
 
-    result = mh_xmalloc(sizeof *result);
+    NEW(result);
     result->ctx = ctx;
     result->access_token = result->refresh_token = NULL;
 
     result->ctx = ctx;
     result->access_token = result->refresh_token = NULL;
 
@@ -519,15 +515,15 @@ mh_oauth_authorize(const char *code, mh_oauth_ctx *ctx)
     return result;
 }
 
     return result;
 }
 
-boolean
+bool
 mh_oauth_refresh(mh_oauth_cred *cred)
 {
 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);
     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,
     }
 
     if (!make_query_url(ctx->buf, sizeof ctx->buf, ctx->curl, NULL,
@@ -535,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,
                         "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);
         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");
     }
 
     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;
 }
 
     }
 
     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;
 mh_oauth_access_token_valid(time_t t, const mh_oauth_cred *cred)
 {
     return cred->access_token != NULL && t + EXPIRY_FUDGE < cred->expires_at;
@@ -614,37 +610,39 @@ free_user_creds(struct user_creds *user_creds)
     free(user_creds);
 }
 
     free(user_creds);
 }
 
-static boolean
+static bool
 load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx)
 {
 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;
     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);
 
     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;
     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;
         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;
                 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;
                 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);
                 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;
                 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);
                     tmp = add(value_buf, tmp);
                 }
                 *save = trimcpy(tmp);
@@ -683,11 +681,11 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx)
 
         case BODY:
         case FILEEOF:
 
         case BODY:
         case FILEEOF:
-            success = TRUE;
+            success = true;
             break;
 
         default:
             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;
              * wrote advise message to stderr. */
             set_err(ctx, MH_OAUTH_CRED_FILE);
             break;
@@ -705,25 +703,25 @@ load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx)
     return success;
 }
 
     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) {
 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 (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) {
     }
     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;
 mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user)
 {
     struct user_creds *user_creds;
@@ -732,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)) {
 
     /* 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;
     }
 
     if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) goto err;
@@ -757,12 +755,12 @@ mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user)
 
     free_user_creds(user_creds);
 
 
     free_user_creds(user_creds);
 
-    return TRUE;
+    return true;
 
   err:
     free_user_creds(user_creds);
     set_err(cred->ctx, MH_OAUTH_CRED_FILE);
 
   err:
     free_user_creds(user_creds);
     set_err(cred->ctx, MH_OAUTH_CRED_FILE);
-    return FALSE;
+    return false;
 }
 
 mh_oauth_cred *
 }
 
 mh_oauth_cred *
@@ -814,17 +812,13 @@ const char *
 mh_oauth_sasl_client_response(size_t *res_len,
                               const char *user, const mh_oauth_cred *cred)
 {
 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;
 }
 
 /*******************************************************************************
 }
 
 /*******************************************************************************
@@ -833,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
 
 /*
  * 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.
  */
  * 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, ...)
 {
 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;
     size_t len;
     char *prefix;
     va_list ap;
@@ -850,7 +844,10 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...)
         len = 0;
         prefix = "";
     } else {
         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 = "?";
     }
 
         prefix = "?";
     }
 
@@ -876,7 +873,7 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...)
         prefix = "&";
     }
 
         prefix = "&";
     }
 
-    result = TRUE;
+    result = true;
 
   out:
     va_end(ap);
 
   out:
     va_end(ap);
@@ -884,7 +881,7 @@ make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...)
 }
 
 static int
 }
 
 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;
                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') {
     }
     fwrite(data, 1, size, fp);
     if (data[size - 1] != '\n') {
-        fputs("\n", fp);
+        putc('\n', fp);
     }
     fflush(fp);
     return 0;
     }
     fflush(fp);
     return 0;
@@ -923,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) {
     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;
     }
 
       return 0;
     }
 
@@ -933,13 +930,13 @@ write_callback(const char *ptr, size_t size, size_t nmemb, void *userdata)
     return size;
 }
 
     return size;
 }
 
-static boolean
+static bool
 post(struct curl_ctx *ctx, const char *url, const char *req_body)
 {
     CURL *curl = ctx->curl;
     CURLcode status;
 
 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) {
     ctx->res_len = 0;
 
     if (ctx->log != NULL) {
@@ -948,32 +945,36 @@ post(struct curl_ctx *ctx, const char *url, const char *req_body)
         curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx->log);
     }
 
         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);
 
     }
 
     curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_body);
     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
     curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
 
+    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
+           suite). */
+        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L);
+    }
+
     status = curl_easy_perform(curl);
     /* first check for error from callback */
     if (ctx->too_big) {
     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) {
     }
     /* 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;
 }
 
 /*******************************************************************************
 }
 
 /*******************************************************************************
@@ -992,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.
  */
  *
  * 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)
 {
 parse_json(jsmntok_t **tokens, size_t *tokens_len,
            const char *input, size_t input_len, FILE *log)
 {
@@ -1014,11 +1015,8 @@ parse_json(jsmntok_t **tokens, size_t *tokens_len,
            of the response body. */
         *tokens = mh_xrealloc(*tokens, *tokens_len * sizeof **tokens);
     }
            of the response body. */
         *tokens = mh_xrealloc(*tokens, *tokens_len * sizeof **tokens);
     }
-    if (r <= 0) {
-        return FALSE;
-    }
 
 
-    return TRUE;
+    return r > 0;
 }
 
 /*
 }
 
 /*
@@ -1038,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. */
     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++) {
 
     int i;
     for (i = 1; i <= token_count; i++) {
@@ -1064,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. */
         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) {
             continue;
         }
         if (is_key) {
-            is_key = FALSE;
+            is_key = false;
             continue;
         }
         key = input + tokens[i - 1].start;
             continue;
         }
         key = input + tokens[i - 1].start;
@@ -1080,7 +1078,7 @@ get_json_string(char **val, const char *input, const jsmntok_t *tokens,
             (*val)[val_len] = '\0';
             return;
         }
             (*val)[val_len] = '\0';
             return;
         }
-        is_key = TRUE;
+        is_key = true;
     }
 }
 
     }
 }
 
@@ -1101,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.
  */
  *
  * 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, ...)
 {
 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;
     jsmntok_t *tokens;
     size_t tokens_len;
     va_list ap;
@@ -1118,7 +1116,7 @@ get_json_strings(const char *input, size_t input_len, FILE *log, ...)
         goto out;
     }
 
         goto out;
     }
 
-    result = TRUE;
+    result = true;
 
     va_start(ap, log);
     for (name = va_arg(ap, char *); name != NULL; name = va_arg(ap, char *)) {
 
     va_start(ap, log);
     for (name = va_arg(ap, char *); name != NULL; name = va_arg(ap, char *)) {