]> diplodocus.org Git - nmh/blobdiff - sbr/oauth.c
mhbuildsbr.c: Flip logic, moving goto to then-block; no need for else.
[nmh] / sbr / oauth.c
index f4564d0f0d982b7ee1769be3be22d736b09fb935..8e3d6b05c103f2c74fb5ce3816629b62989564a9 100755 (executable)
@@ -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.
 
 #include <h/oauth.h>
 #include <h/utils.h>
+#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