]> diplodocus.org Git - nmh/commitdiff
support multiple oauth users; mhlogin now requires -user
authorEric Gillespie <epg@pretzelnet.org>
Sun, 10 Apr 2016 06:09:12 +0000 (23:09 -0700)
committerEric Gillespie <epg@pretzelnet.org>
Sun, 10 Apr 2016 06:09:12 +0000 (23:09 -0700)
h/oauth.h
sbr/oauth.c
test/oauth/common.sh
test/oauth/test-inc
test/oauth/test-mhlogin
test/oauth/test-send
test/oauth/test-share
uip/mhlogin.c

index a49cb4aced55cc56513dcca73f343a0b446cf467..0abaaa34907846f1f7dd8cb559cf2104780a4ea0 100644 (file)
--- a/h/oauth.h
+++ b/h/oauth.h
@@ -63,6 +63,10 @@ typedef enum {
     /* Attempting to refresh an access token without a refresh token. */
     MH_OAUTH_NO_REFRESH,
 
     /* Attempting to refresh an access token without a refresh token. */
     MH_OAUTH_NO_REFRESH,
 
+
+    /* requested user not in cred file */
+    MH_OAUTH_CRED_USER_NOT_FOUND,
+
     /* error loading serialized credentials */
     MH_OAUTH_CRED_FILE
 } mh_oauth_err_code;
     /* error loading serialized credentials */
     MH_OAUTH_CRED_FILE
 } mh_oauth_err_code;
@@ -196,7 +200,7 @@ mh_oauth_cred_fn(mh_oauth_ctx *ctx);
  * On error, return FALSE.
  */
 boolean
  * On error, return FALSE.
  */
 boolean
-mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred);
+mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user);
 
 /*
  * Load OAuth tokens from file.
 
 /*
  * Load OAuth tokens from file.
@@ -206,7 +210,7 @@ mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred);
  * On error, return NULL.
  */
 mh_oauth_cred *
  * On error, return NULL.
  */
 mh_oauth_cred *
-mh_oauth_cred_load(FILE *fp, mh_oauth_ctx *ctx);
+mh_oauth_cred_load(FILE *fp, mh_oauth_ctx *ctx, const char *user);
 
 /*
  * Return null-terminated SASL client response for XOAUTH2 from access token.
 
 /*
  * Return null-terminated SASL client response for XOAUTH2 from access token.
index b795eb815e7df675ef71a7e9f2801c1e69a21bbe..70de7dbe6dbf3cc1ba520c545f7eddb71f187e3d 100644 (file)
@@ -108,6 +108,9 @@ struct mh_oauth_cred {
     /* Ignoring token_type ([1] 7.1) because
      * https://developers.google.com/accounts/docs/OAuth2InstalledApp says
      * "Currently, this field always has the value Bearer". */
     /* Ignoring token_type ([1] 7.1) because
      * https://developers.google.com/accounts/docs/OAuth2InstalledApp says
      * "Currently, this field always has the value Bearer". */
+
+    /* only filled while loading cred files, otherwise NULL */
+    char *user;
 };
 
 struct mh_oauth_ctx {
 };
 
 struct mh_oauth_ctx {
@@ -192,7 +195,7 @@ mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log)
         adios(fn, "failed to lock");
     }
 
         adios(fn, "failed to lock");
     }
 
-    if ((cred = mh_oauth_cred_load(fp, ctx)) == NULL) {
+    if ((cred = mh_oauth_cred_load(fp, ctx, user)) == NULL) {
         adios(NULL, mh_oauth_get_err_string(ctx));
     }
 
         adios(NULL, mh_oauth_get_err_string(ctx));
     }
 
@@ -209,7 +212,7 @@ mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log)
         }
 
         fseek(fp, 0, SEEK_SET);
         }
 
         fseek(fp, 0, SEEK_SET);
-        if (!mh_oauth_cred_save(fp, cred)) {
+        if (!mh_oauth_cred_save(fp, cred, user)) {
             adios(NULL, mh_oauth_get_err_string(ctx));
         }
     }
             adios(NULL, mh_oauth_get_err_string(ctx));
         }
     }
@@ -487,6 +490,9 @@ mh_oauth_get_err_string(mh_oauth_ctx *ctx)
     case MH_OAUTH_NO_REFRESH:
         base = "no refresh token";
         break;
     case MH_OAUTH_NO_REFRESH:
         base = "no refresh token";
         break;
+    case MH_OAUTH_CRED_USER_NOT_FOUND:
+        base = "user not found in cred file";
+        break;
     case MH_OAUTH_CRED_FILE:
         base = "error loading cred file";
         break;
     case MH_OAUTH_CRED_FILE:
         base = "error loading cred file";
         break;
@@ -716,49 +722,92 @@ mh_oauth_cred_fn(mh_oauth_ctx *ctx)
     return ctx->cred_fn = result;
 }
 
     return ctx->cred_fn = result;
 }
 
-boolean
-mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred)
+/* for loading multi-user cred files */
+struct user_creds {
+    mh_oauth_cred *creds;
+
+    /* number of allocated mh_oauth_cred structs above points to */
+    size_t alloc;
+
+    /* number that are actually filled in and used */
+    size_t len;
+};
+
+/* If user has an entry in user_creds, return pointer to it.  Else allocate a
+ * new struct in user_creds and return pointer to that. */
+static mh_oauth_cred *
+find_or_alloc_user_creds(struct user_creds user_creds[], const char *user)
 {
 {
-    int fd = fileno(fp);
-    if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) goto err;
-    if (ftruncate(fd, 0) < 0) goto err;
-    if (cred->access_token != NULL) {
-        if (fprintf(fp, "access: %s\n", cred->access_token) < 0) goto err;
-    }
-    if (cred->refresh_token != NULL) {
-        if (fprintf(fp, "refresh: %s\n", cred->refresh_token) < 0) goto err;
+    mh_oauth_cred *creds = user_creds->creds;
+    size_t i;
+    for (i = 0; i < user_creds->len; i++) {
+        if (strcmp(creds[i].user, user) == 0) {
+            return &creds[i];
+        }
     }
     }
-    if (cred->expires_at > 0) {
-        if (fprintf(fp, "expire: %ld\n", (long)cred->expires_at) < 0) goto err;
+    if (user_creds->alloc == user_creds->len) {
+        user_creds->alloc *= 2;
+        user_creds->creds = mh_xrealloc(user_creds->creds, user_creds->alloc);
     }
     }
-    return TRUE;
+    creds = user_creds->creds+user_creds->len;
+    user_creds->len++;
+    creds->user = getcpy(user);
+    creds->access_token = creds->refresh_token = NULL;
+    creds->expires_at = 0;
+    return creds;
+}
 
 
-  err:
-    set_err(cred->ctx, MH_OAUTH_CRED_FILE);
-    return FALSE;
+static void
+free_user_creds(struct user_creds *user_creds)
+{
+    mh_oauth_cred *cred;
+    size_t i;
+    cred = user_creds->creds;
+    for (i = 0; i < user_creds->len; i++) {
+        free(cred[i].user);
+        free(cred[i].access_token);
+        free(cred[i].refresh_token);
+    }
+    free(user_creds->creds);
+    free(user_creds);
 }
 
 static boolean
 }
 
 static boolean
-parse_cred(char **access, char **refresh, char **expire, FILE *fp,
-           mh_oauth_ctx *ctx)
+load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx)
 {
 {
-    boolean result = FALSE;
+    boolean success = FALSE;
     char name[NAMESZ], value_buf[BUFSIZ];
     int state;
     m_getfld_state_t getfld_ctx = 0;
 
     char name[NAMESZ], value_buf[BUFSIZ];
     int state;
     m_getfld_state_t getfld_ctx = 0;
 
+    struct user_creds *user_creds = mh_xmalloc(sizeof *user_creds);
+    user_creds->alloc = 4;
+    user_creds->len = 0;
+    user_creds->creds = mh_xmalloc(user_creds->alloc * sizeof *user_creds->creds);
+
     for (;;) {
     for (;;) {
-       int size = sizeof value_buf;
+       size_t size = sizeof value_buf;
        switch (state = m_getfld(&getfld_ctx, name, value_buf, &size, fp)) {
         case FLD:
         case FLDPLUS: {
        switch (state = m_getfld(&getfld_ctx, name, value_buf, &size, fp)) {
         case FLD:
         case FLDPLUS: {
-            char **save;
-            if (strcmp(name, "access") == 0) {
-                save = access;
-            } else if (strcmp(name, "refresh") == 0) {
-                save = refresh;
-            } else if (strcmp(name, "expire") == 0) {
-                save = expire;
+            char **save, *expire;
+            time_t *expires_at = NULL;
+            if (strncmp(name, "access-", 7) == 0) {
+                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) {
+                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) {
+                const char *user = name + 7;
+                mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds,
+                                                                user);
+                expires_at = &creds->expires_at;
+                save = &expire;
             } else {
                 set_err_details(ctx, MH_OAUTH_CRED_FILE, "unexpected field");
                 break;
             } else {
                 set_err_details(ctx, MH_OAUTH_CRED_FILE, "unexpected field");
                 break;
@@ -776,12 +825,23 @@ parse_cred(char **access, char **refresh, char **expire, FILE *fp,
                 *save = trimcpy(tmp);
                 free(tmp);
             }
                 *save = trimcpy(tmp);
                 free(tmp);
             }
+            if (expires_at != NULL) {
+                errno = 0;
+                *expires_at = strtol(expire, NULL, 10);
+                free(expire);
+                if (errno != 0) {
+                    set_err_details(ctx, MH_OAUTH_CRED_FILE,
+                                    "invalid expiration time");
+                    break;
+                }
+                expires_at = NULL;
+            }
             continue;
         }
 
         case BODY:
         case FILEEOF:
             continue;
         }
 
         case BODY:
         case FILEEOF:
-            result = TRUE;
+            success = TRUE;
             break;
 
         default:
             break;
 
         default:
@@ -793,41 +853,114 @@ parse_cred(char **access, char **refresh, char **expire, FILE *fp,
        break;
     }
     m_getfld_state_destroy(&getfld_ctx);
        break;
     }
     m_getfld_state_destroy(&getfld_ctx);
-    return result;
+
+    if (success) {
+        *result = user_creds;
+    } else {
+        free_user_creds(user_creds);
+    }
+
+    return success;
+}
+
+static boolean
+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 (refresh != NULL) {
+        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 TRUE;
+}
+
+boolean
+mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user)
+{
+    struct user_creds *user_creds;
+    int fd = fileno(fp);
+    size_t i;
+
+    /* Load existing creds if any. */
+    if (!load_creds(&user_creds, fp, cred->ctx)) {
+        return FALSE;
+    }
+
+    if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) goto err;
+    if (ftruncate(fd, 0) < 0) goto err;
+    if (fseek(fp, 0, SEEK_SET) < 0) goto err;
+
+    /* Write all creds except for this user. */
+    for (i = 0; i < user_creds->len; i++) {
+        mh_oauth_cred *c = &user_creds->creds[i];
+        if (strcmp(c->user, user) == 0) continue;
+        if (!save_user(fp, c->user, c->access_token, c->refresh_token,
+                       c->expires_at)) {
+            goto err;
+        }
+    }
+
+    /* Write updated creds for this user. */
+    if (!save_user(fp, user, cred->access_token, cred->refresh_token,
+                   cred->expires_at)) {
+        goto err;
+    }
+
+    free_user_creds(user_creds);
+
+    return TRUE;
+
+  err:
+    free_user_creds(user_creds);
+    set_err(cred->ctx, MH_OAUTH_CRED_FILE);
+    return FALSE;
 }
 
 mh_oauth_cred *
 }
 
 mh_oauth_cred *
-mh_oauth_cred_load(FILE *fp, mh_oauth_ctx *ctx)
+mh_oauth_cred_load(FILE *fp, mh_oauth_ctx *ctx, const char *user)
 {
 {
-    mh_oauth_cred *result;
-    time_t expires_at = 0;
-    char *access, *refresh, *expire;
-
-    access = refresh = expire = NULL;
-    if (!parse_cred(&access, &refresh, &expire, fp, ctx)) {
-        free(access);
-        free(refresh);
-        free(expire);
+    mh_oauth_cred *creds, *result = NULL;
+    struct user_creds *user_creds;
+    size_t i;
+
+    if (!load_creds(&user_creds, fp, ctx)) {
         return NULL;
     }
 
         return NULL;
     }
 
-    if (expire != NULL) {
-        errno = 0;
-        expires_at = strtol(expire, NULL, 10);
-        free(expire);
-        if (errno != 0) {
-            set_err_details(ctx, MH_OAUTH_CRED_FILE, "invalid expiration time");
-            free(access);
-            free(refresh);
-            return NULL;
+    /* Search user_creds for this user.  If we don't find it, return NULL.
+     * If we do, free fields of all structs except this one, moving this one to
+     * the first struct if necessary.  When we return it, it just looks like one
+     * struct to the caller, and the whole array is freed later. */
+    creds = user_creds->creds;
+    for (i = 0; i < user_creds->len; i++) {
+        if (strcmp(creds[i].user, user) == 0) {
+            result = creds;
+            if (i > 0) {
+                result->access_token = creds[i].access_token;
+                result->refresh_token = creds[i].refresh_token;
+                result->expires_at = creds[i].expires_at;
+            }
+        } else {
+            free(creds[i].access_token);
+            free(creds[i].refresh_token);
         }
         }
+        free(creds[i].user);
+    }
+
+    if (result == NULL) {
+        set_err_details(ctx, MH_OAUTH_CRED_USER_NOT_FOUND, user);
+        return NULL;
     }
 
     }
 
-    result = mh_xmalloc(sizeof *result);
     result->ctx = ctx;
     result->ctx = ctx;
-    result->access_token = access;
-    result->refresh_token = refresh;
-    result->expires_at = expires_at;
+    result->user = NULL;
 
     return result;
 }
 
     return result;
 }
index 3f3dc598d26abaa916f948b482ad45bb91a3a7f3..ab0bad5373c0e9aa39583145004afc2d8c8f9b05 100644 (file)
@@ -180,7 +180,7 @@ check_creds() {
     # it against our "correct" output.
     f="${MHTMPDIR}/oauth-test"
 
     # it against our "correct" output.
     f="${MHTMPDIR}/oauth-test"
 
-    sed 's/^expire:.*/expire:/' "$f" > "$f".notime
+    sed 's/^\(expire.*:\).*/\1/' "$f" > "$f".notime
     check "$f".notime "${MHTMPDIR}/$$.expected-creds"
     rm "$f"
 }
     check "$f".notime "${MHTMPDIR}/$$.expected-creds"
     rm "$f"
 }
index 6fa5892ac88f5b6d0045de59f98cd6ff8a8af283..c745692892018d78fd2a541295871c5d7a31bca3 100755 (executable)
@@ -20,8 +20,8 @@ setup_pop
 start_test 'access token ready, pop server accepts message'
 
 fake_creds <<EOF
 start_test 'access token ready, pop server accepts message'
 
 fake_creds <<EOF
-access: test-access
-expire: 2000000000
+access-nobody@example.com: test-access
+expire-nobody@example.com: 2000000000
 EOF
 
 start_pop_xoauth
 EOF
 
 start_pop_xoauth
@@ -32,9 +32,9 @@ test_inc_success
 start_test 'expired access token, refresh works, pop server accepts message'
 
 fake_creds <<EOF
 start_test 'expired access token, refresh works, pop server accepts message'
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -62,8 +62,8 @@ check_http_req
 start_test 'refresh gets proper error from http'
 
 fake_creds <<EOF
 start_test 'refresh gets proper error from http'
 
 fake_creds <<EOF
-access: test-access
-refresh: test-refresh
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -87,8 +87,8 @@ check_http_req
 start_test 'pop server rejects token'
 
 fake_creds <<EOF
 start_test 'pop server rejects token'
 
 fake_creds <<EOF
-access: wrong-access
-expire: 2000000000
+access-nobody@example.com: wrong-access
+expire-nobody@example.com: 2000000000
 EOF
 
 start_pop_xoauth
 EOF
 
 start_pop_xoauth
@@ -99,8 +99,8 @@ test_inc 'inc: -ERR [AUTH] Invalid credentials.'
 start_test "pop server doesn't support oauth"
 
 fake_creds <<EOF
 start_test "pop server doesn't support oauth"
 
 fake_creds <<EOF
-access: test-access
-expire: 2000000000
+access-nobody@example.com: test-access
+expire-nobody@example.com: 2000000000
 EOF
 
 start_pop testuser testpass
 EOF
 
 start_pop testuser testpass
index 90bc0df16cd54f54904e6b88e6ab8b12fb666050..ed23e05cc60e17b8425bd1477b8682bd2b60f3d0 100755 (executable)
@@ -18,7 +18,7 @@ expect_no_creds() {
 
 test_mhlogin() {
     start_fakehttp
 
 test_mhlogin() {
     start_fakehttp
-    run_test 'eval echo code | mhlogin -saslmech xoauth2 -authservice test' \
+    run_test 'eval echo code | mhlogin -saslmech xoauth2 -authservice test -user nobody@example.com' \
 "Load the following URL in your browser and authorize nmh to access test:
 
 http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=test-scope
 "Load the following URL in your browser and authorize nmh to access test:
 
 http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=test-scope
@@ -52,8 +52,8 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-expire:
+access-nobody@example.com: test-access
+expire-nobody@example.com:
 EOF
 
 test_mhlogin
 EOF
 
 test_mhlogin
@@ -71,7 +71,7 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
+access-nobody@example.com: test-access
 EOF
 
 test_mhlogin
 EOF
 
 test_mhlogin
@@ -91,9 +91,9 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: refresh-token
-expire:
+access-nobody@example.com: test-access
+refresh-nobody@example.com: refresh-token
+expire-nobody@example.com:
 EOF
 
 test_mhlogin
 EOF
 
 test_mhlogin
@@ -111,7 +111,7 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-refresh: refresh-token
+refresh-nobody@example.com: refresh-token
 EOF
 
 test_mhlogin
 EOF
 
 test_mhlogin
@@ -152,13 +152,63 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: refresh-token
-expire:
+access-nobody@example.com: test-access
+refresh-nobody@example.com: refresh-token
+expire-nobody@example.com:
 EOF
 
 test_mhlogin
 
 EOF
 
 test_mhlogin
 
+# TEST
+start_test 'mhlogin multiple users'
+
+expect_http_post_code
+
+fake_json_response <<EOF
+{
+  "access_token": "user3-access",
+  "refresh_token": "user3-refresh",
+  "expires_in": 3600,
+  "token_type": "Bearer"
+}
+EOF
+
+expect_creds <<EOF
+access-nobody@example.com: user1-access
+refresh-nobody@example.com: user1-refresh
+expire-nobody@example.com:
+access-nobody2@example.com: user2-access
+refresh-nobody2@example.com: user2-refresh
+expire-nobody2@example.com:
+access-nobody3@example.com: user3-access
+refresh-nobody3@example.com: user3-refresh
+expire-nobody3@example.com:
+EOF
+
+fake_creds <<EOF
+access-nobody@example.com: user1-access
+refresh-nobody@example.com: user1-refresh
+expire-nobody@example.com: 100
+access-nobody2@example.com: user2-access
+refresh-nobody2@example.com: user2-refresh
+expire-nobody2@example.com: 100
+EOF
+
+start_fakehttp
+run_test 'eval echo code | mhlogin -saslmech xoauth2 -authservice test -user nobody3@example.com' \
+         "Load the following URL in your browser and authorize nmh to access test:
+
+http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=test-scope
+
+Enter the authorization code: $1"
+check_http_req
+check_creds_private
+check_creds
+
+#
+# fail cases
+#
+
 # TEST
 start_test 'mhlogin user enters bad code'
 
 # TEST
 start_test 'mhlogin user enters bad code'
 
@@ -176,10 +226,6 @@ expect_no_creds
 
 test_mhlogin 'Code rejected; try again? '
 
 
 test_mhlogin 'Code rejected; try again? '
 
-#
-# fail cases
-#
-
 # TEST
 start_test 'mhlogin response has no content-type'
 
 # TEST
 start_test 'mhlogin response has no content-type'
 
@@ -258,7 +304,7 @@ mhlogin: bad OAuth request; re-run with -snoop and send REDACTED output to nmh-w
 start_test 'mhlogin -browser'
 
 run_test "eval echo code | mhlogin -saslmech xoauth2 -authservice test\
 start_test 'mhlogin -browser'
 
 run_test "eval echo code | mhlogin -saslmech xoauth2 -authservice test\
- -browser 'echo \$@ > ${MHTMPDIR}/$$.browser'" \
+ -user nobody@example.com -browser 'echo \$@ > ${MHTMPDIR}/$$.browser'" \
 "Follow the prompts in your browser to authorize nmh to access test.
 Enter the authorization code: mhlogin: error exchanging code for OAuth2 token
 mhlogin: error making HTTP request to OAuth2 authorization endpoint: Failed to connect to 127.0.0.1 port ${http_port}: Connection refused"
 "Follow the prompts in your browser to authorize nmh to access test.
 Enter the authorization code: mhlogin: error exchanging code for OAuth2 token
 mhlogin: error making HTTP request to OAuth2 authorization endpoint: Failed to connect to 127.0.0.1 port ${http_port}: Connection refused"
index 4c7a335c8ae490fdf2c55606cf5764f303ee9447..d75901ad393a642444c5a65218d62e3fc0eb837e 100755 (executable)
@@ -29,9 +29,9 @@ start_test 'access token ready, smtp server accepts message'
 setup_draft
 
 fake_creds <<EOF
 setup_draft
 
 fake_creds <<EOF
-access: test-access
-refresh: test-refresh
-expire: 2000000000
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 2000000000
 EOF
 
 start_fakesmtp
 EOF
 
 start_fakesmtp
@@ -43,9 +43,9 @@ start_test 'expired access token, refresh works, smtp server accepts message'
 setup_draft
 
 fake_creds <<EOF
 setup_draft
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -59,9 +59,9 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: test-refresh
-expire:
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com:
 EOF
 
 test_send
 EOF
 
 test_send
@@ -75,9 +75,9 @@ start_test 'expired access token, refresh works and gets updated, smtp server ac
 setup_draft
 
 fake_creds <<EOF
 setup_draft
 
 fake_creds <<EOF
-access: old-access
-refresh: old-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: old-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_old_refresh
 EOF
 
 expect_http_post_old_refresh
@@ -91,8 +91,8 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: test-refresh
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 test_send
 EOF
 
 test_send
@@ -105,8 +105,8 @@ start_test 'access token has no expiration, refresh works, smtp server accepts m
 setup_draft
 
 fake_creds <<EOF
 setup_draft
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -119,8 +119,8 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: test-refresh
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 test_send
 EOF
 
 test_send
@@ -133,7 +133,7 @@ start_test 'no access token, refresh works, smtp server accepts message'
 setup_draft
 
 fake_creds <<EOF
 setup_draft
 
 fake_creds <<EOF
-refresh: test-refresh
+refresh-nobody@example.com: test-refresh
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -146,8 +146,8 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-refresh: test-refresh
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 test_send
 EOF
 
 test_send
@@ -177,7 +177,7 @@ start_test 'empty creds file -- should tell user to mhlogin'
 
 fake_creds < /dev/null
 
 
 fake_creds < /dev/null
 
-test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth2 -authservice test'
+test_send_no_servers 'send: user not found in cred file: nobody@example.com'
 
 # TEST
 start_test 'garbage creds file'
 
 # TEST
 start_test 'garbage creds file'
@@ -192,7 +192,7 @@ start_test 'unexpected field in creds file'
 
 fake_creds <<EOF
 bork: bork
 
 fake_creds <<EOF
 bork: bork
-access: test-access
+access-nobody@example.com: test-access
 EOF
 
 test_send_no_servers 'send: error loading cred file: unexpected field'
 EOF
 
 test_send_no_servers 'send: error loading cred file: unexpected field'
@@ -201,8 +201,8 @@ test_send_no_servers 'send: error loading cred file: unexpected field'
 start_test 'garbage expiration time'
 
 fake_creds <<EOF
 start_test 'garbage expiration time'
 
 fake_creds <<EOF
-access: test-access
-expire: 99999999999999999999999999999999
+access-nobody@example.com: test-access
+expire-nobody@example.com: 99999999999999999999999999999999
 EOF
 
 test_send_no_servers 'send: error loading cred file: invalid expiration time'
 EOF
 
 test_send_no_servers 'send: error loading cred file: invalid expiration time'
@@ -211,7 +211,7 @@ test_send_no_servers 'send: error loading cred file: invalid expiration time'
 start_test 'refresh response has no access token'
 
 fake_creds <<EOF
 start_test 'refresh response has no access token'
 
 fake_creds <<EOF
-refresh: test-refresh
+refresh-nobody@example.com: test-refresh
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -230,8 +230,8 @@ send: invalid response: no access token'
 start_test 'expired access token, no refresh token -- tell user to mhlogin'
 
 fake_creds <<EOF
 start_test 'expired access token, no refresh token -- tell user to mhlogin'
 
 fake_creds <<EOF
-access: test-access
-expire: 1414303986
+access-nobody@example.com: test-access
+expire-nobody@example.com: 1414303986
 EOF
 
 test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth2 -authservice test'
 EOF
 
 test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth2 -authservice test'
@@ -240,7 +240,7 @@ test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth
 start_test 'access token has no expiration, no refresh token -- tell user to mhlogin'
 
 fake_creds <<EOF
 start_test 'access token has no expiration, no refresh token -- tell user to mhlogin'
 
 fake_creds <<EOF
-access: test-access
+access-nobody@example.com: test-access
 EOF
 
 test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth2 -authservice test'
 EOF
 
 test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth2 -authservice test'
@@ -249,8 +249,8 @@ test_send_no_servers 'send: no valid credentials -- run mhlogin -saslmech xoauth
 start_test 'refresh finds no http server'
 
 fake_creds <<EOF
 start_test 'refresh finds no http server'
 
 fake_creds <<EOF
-access: test-access
-refresh: test-refresh
+access-nobody@example.com: test-access
+refresh-nobody@example.com: test-refresh
 EOF
 
 cat > "${testname}.expected-send-output" <<EOF
 EOF
 
 cat > "${testname}.expected-send-output" <<EOF
@@ -315,7 +315,7 @@ test_send_only_fakehttp 'send: credentials rejected -- run mhlogin -saslmech xoa
 start_test 'refresh gets response too big'
 
 fake_creds <<EOF
 start_test 'refresh gets response too big'
 
 fake_creds <<EOF
-refresh: test-refresh
+refresh-nobody@example.com: test-refresh
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -340,8 +340,8 @@ start_test 'smtp server rejects token'
 XOAUTH='not-that-one'
 
 fake_creds <<EOF
 XOAUTH='not-that-one'
 
 fake_creds <<EOF
-access: test-access
-expire: 2000000000
+access-nobody@example.com: test-access
+expire-nobody@example.com: 2000000000
 EOF
 
 test_send_only_fakesmtp 'post: problem initializing server; [BHST] Not no way, not no how!
 EOF
 
 test_send_only_fakesmtp 'post: problem initializing server; [BHST] Not no way, not no how!
index 76b0d3e2e76b549cadcf9e93c5f8ef63411fd5e1..ab89a3ff34b4980e94b1cc0e9eee8fc3a5fa6bef 100755 (executable)
@@ -29,12 +29,12 @@ fake_json_response <<EOF
 EOF
 
 expect_creds <<EOF
 EOF
 
 expect_creds <<EOF
-access: test-access
-expire:
+access-nobody@example.com: test-access
+expire-nobody@example.com:
 EOF
 
 start_fakehttp
 EOF
 
 start_fakehttp
-run_test 'eval echo code | mhlogin -saslmech xoauth2 -authservice test' \
+run_test 'eval echo code | mhlogin -user nobody@example.com -saslmech xoauth2 -authservice test' \
 "Load the following URL in your browser and authorize nmh to access test:
 
 http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=test-scope
 "Load the following URL in your browser and authorize nmh to access test:
 
 http://127.0.0.1:${http_port}/oauth/auth?response_type=code&client_id=test-id&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=test-scope
@@ -54,9 +54,9 @@ test_send_only_fakesmtp
 start_test 'inc refreshes'
 
 fake_creds <<EOF
 start_test 'inc refreshes'
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -83,9 +83,9 @@ test_send_only_fakesmtp
 start_test 'msgchck refreshes'
 
 fake_creds <<EOF
 start_test 'msgchck refreshes'
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
@@ -112,9 +112,9 @@ test_send_only_fakesmtp
 start_test 'send refreshes'
 
 fake_creds <<EOF
 start_test 'send refreshes'
 
 fake_creds <<EOF
-access: old-access
-refresh: test-refresh
-expire: 1414303986
+access-nobody@example.com: old-access
+refresh-nobody@example.com: test-refresh
+expire-nobody@example.com: 1414303986
 EOF
 
 expect_http_post_refresh
 EOF
 
 expect_http_post_refresh
index 093c4f0acd72a6043374b32b7fbff6711c72746d..88912b5ccea5166ae405923b478781acc5798ad9 100644 (file)
@@ -6,6 +6,7 @@
  * complete copyright information.
  */
 
  * complete copyright information.
  */
 
+#include <errno.h>
 #include <stdio.h>
 #include <string.h>
 
 #include <stdio.h>
 #include <string.h>
 
@@ -13,6 +14,7 @@
 #include <h/oauth.h>
 
 #define MHLOGIN_SWITCHES \
 #include <h/oauth.h>
 
 #define MHLOGIN_SWITCHES \
+    X("user username", 0, USERSW) \
     X("saslmech", 0, SASLMECHSW) \
     X("authservice", 0, AUTHSERVICESW) \
     X("browser", 0, BROWSERSW) \
     X("saslmech", 0, SASLMECHSW) \
     X("authservice", 0, AUTHSERVICESW) \
     X("browser", 0, BROWSERSW) \
@@ -44,7 +46,7 @@ geta (void)
 }
 
 static int
 }
 
 static int
-do_login(const char *svc, const char *browser, int snoop)
+do_login(const char *svc, const char *user, const char *browser, int snoop)
 {
     char *fn, *code;
     mh_oauth_ctx *ctx;
 {
     char *fn, *code;
     mh_oauth_ctx *ctx;
@@ -57,6 +59,10 @@ do_login(const char *svc, const char *browser, int snoop)
         adios(NULL, "missing -authservice switch");
     }
 
         adios(NULL, "missing -authservice switch");
     }
 
+    if (user == NULL) {
+        adios(NULL, "missing -user switch");
+    }
+
     if (!mh_oauth_new(&ctx, svc)) {
         adios(NULL, mh_oauth_get_err_string(ctx));
     }
     if (!mh_oauth_new(&ctx, svc)) {
         adios(NULL, mh_oauth_get_err_string(ctx));
     }
@@ -106,11 +112,14 @@ do_login(const char *svc, const char *browser, int snoop)
       adios(NULL, mh_oauth_get_err_string(ctx));
     }
 
       adios(NULL, mh_oauth_get_err_string(ctx));
     }
 
-    cred_file = lkfopendata(fn, "w", &failed_to_lock);
+    cred_file = lkfopendata(fn, "r+", &failed_to_lock);
+    if (cred_file == NULL && errno == ENOENT) {
+        cred_file = lkfopendata(fn, "w+", &failed_to_lock);
+    }
     if (cred_file == NULL || failed_to_lock) {
       adios(fn, "oops");
     }
     if (cred_file == NULL || failed_to_lock) {
       adios(fn, "oops");
     }
-    if (!mh_oauth_cred_save(cred_file, cred)) {
+    if (!mh_oauth_cred_save(cred_file, cred, user)) {
       adios(NULL, mh_oauth_get_err_string(ctx));
     }
     if (lkfclosedata(cred_file, fn) != 0) {
       adios(NULL, mh_oauth_get_err_string(ctx));
     }
     if (lkfclosedata(cred_file, fn) != 0) {
@@ -129,7 +138,7 @@ int
 main(int argc, char **argv)
 {
     char *cp, **argp, **arguments;
 main(int argc, char **argv)
 {
     char *cp, **argp, **arguments;
-    const char *saslmech = NULL, *svc = NULL, *browser = NULL;
+    const char *user = NULL, *saslmech = NULL, *svc = NULL, *browser = NULL;
     int snoop = 0;
 
     if (nmh_init(argv[0], 1)) { return 1; }
     int snoop = 0;
 
     if (nmh_init(argv[0], 1)) { return 1; }
@@ -156,6 +165,11 @@ main(int argc, char **argv)
                print_version(invo_name);
                done (0);
 
                print_version(invo_name);
                done (0);
 
+           case USERSW:
+               if (!(user = *argp++) || *user == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               continue;
+
            case SASLMECHSW:
                if (!(saslmech = *argp++) || *saslmech == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
            case SASLMECHSW:
                if (!(saslmech = *argp++) || *saslmech == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
@@ -186,7 +200,7 @@ main(int argc, char **argv)
     free(arguments);
 
 #ifdef OAUTH_SUPPORT
     free(arguments);
 
 #ifdef OAUTH_SUPPORT
-    return do_login(svc, browser, snoop);
+    return do_login(svc, user, browser, snoop);
 #else
     NMH_UNUSED(svc);
     NMH_UNUSED(browser);
 #else
     NMH_UNUSED(svc);
     NMH_UNUSED(browser);