Skip to content

Commit e9fcc57

Browse files
committed
Support S3 config files and temporary credentials
Extend the URL authority part parsing to "[ID:SECRET:TOKEN@]BUCKET". The latter fields are optional, and if only a username is provided, interpret it as the profile to look for in the configuration files. Check the conventional AWS environment variables for session token, profile, and credentials config file location. Pick up credential settings from ~/.aws/credentials (standard shared AWS credentials file), ~/.s3cfg (as per s3tools), and ~/.awssecret (as per Tim Kay's aws and Heng Li's kurl.c), in that order. Check for $AWS_SESSION_TOKEN etc and propagate any temporary credentials found to X-Amz-Security-Token header. Hat tip @DonFreed.
1 parent 6462e34 commit e9fcc57

File tree

1 file changed

+130
-7
lines changed

1 file changed

+130
-7
lines changed

hfile_libcurl.c

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ DEALINGS IN THE SOFTWARE. */
2525
#include <config.h>
2626

2727
#include <ctype.h>
28+
#include <stdarg.h>
29+
#include <stdio.h>
2830
#include <stdlib.h>
2931
#include <string.h>
3032
#include <time.h>
@@ -697,6 +699,89 @@ static int is_dns_compliant(const char *s0, const char *slim)
697699
return has_nondigit && len >= 3 && len <= 63;
698700
}
699701

702+
static FILE *expand_tilde_open(const char *fname, const char *mode)
703+
{
704+
FILE *fp;
705+
706+
if (strncmp(fname, "~/", 2) == 0) {
707+
kstring_t full_fname = { 0, 0, NULL };
708+
const char *home = getenv("HOME");
709+
if (! home) return NULL;
710+
711+
kputs(home, &full_fname);
712+
kputs(&fname[1], &full_fname);
713+
714+
fp = fopen(full_fname.s, mode);
715+
free(full_fname.s);
716+
}
717+
else
718+
fp = fopen(fname, mode);
719+
720+
return fp;
721+
}
722+
723+
static void parse_ini(const char *fname, const char *section, ...)
724+
{
725+
kstring_t line = { 0, 0, NULL };
726+
int active = 1; // Start active, so global properties are accepted
727+
char *s;
728+
729+
FILE *fp = expand_tilde_open(fname, "r");
730+
if (fp == NULL) return;
731+
732+
while (line.l = 0, kgetline(&line, (kgets_func *) fgets, fp) >= 0)
733+
if (line.s[0] == '[' && (s = strchr(line.s, ']')) != NULL) {
734+
*s = '\0';
735+
active = (strcmp(&line.s[1], section) == 0);
736+
}
737+
else if (active && (s = strpbrk(line.s, ":=")) != NULL) {
738+
const char *key = line.s, *value = &s[1], *akey;
739+
va_list args;
740+
741+
while (isspace(*key)) key++;
742+
while (s > key && isspace(s[-1])) s--;
743+
*s = '\0';
744+
745+
while (isspace(*value)) value++;
746+
while (line.l > 0 && isspace(line.s[line.l-1]))
747+
line.s[--line.l] = '\0';
748+
749+
va_start(args, section);
750+
while ((akey = va_arg(args, const char *)) != NULL) {
751+
kstring_t *avar = va_arg(args, kstring_t *);
752+
if (strcmp(key, akey) == 0) { kputs(value, avar); break; }
753+
}
754+
va_end(args);
755+
}
756+
757+
fclose(fp);
758+
free(line.s);
759+
}
760+
761+
static void parse_simple(const char *fname, kstring_t *id, kstring_t *secret)
762+
{
763+
kstring_t text = { 0, 0, NULL };
764+
char *s;
765+
size_t len;
766+
767+
FILE *fp = expand_tilde_open(fname, "r");
768+
if (fp == NULL) return;
769+
770+
while (kgetline(&text, (kgets_func *) fgets, fp) >= 0)
771+
kputc(' ', &text);
772+
fclose(fp);
773+
774+
s = text.s;
775+
while (isspace(*s)) s++;
776+
kputsn(s, len = strcspn(s, " \t"), id);
777+
778+
s += len;
779+
while (isspace(*s)) s++;
780+
kputsn(s, strcspn(s, " \t"), secret);
781+
782+
free(text.s);
783+
}
784+
700785
static int
701786
add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
702787
{
@@ -706,8 +791,11 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
706791
CURLcode err;
707792

708793
kstring_t url = { 0, 0, NULL };
794+
kstring_t profile = { 0, 0, NULL };
709795
kstring_t id = { 0, 0, NULL };
710796
kstring_t secret = { 0, 0, NULL };
797+
kstring_t token = { 0, 0, NULL };
798+
kstring_t token_hdr = { 0, 0, NULL };
711799
kstring_t auth_hdr = { 0, 0, NULL };
712800

713801
time_t now = time(NULL);
@@ -723,7 +811,7 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
723811
kputs(&date_hdr[6], message);
724812
kputc('\n', message);
725813

726-
// Our S3 URL format is s3[+SCHEME]://[ID[:SECRET]@]BUCKET/PATH
814+
// Our S3 URL format is s3[+SCHEME]://[ID[:SECRET[:TOKEN]]@]BUCKET/PATH
727815

728816
if (s3url[2] == '+') {
729817
bucket = strchr(s3url, ':') + 1;
@@ -737,10 +825,17 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
737825

738826
path = bucket + strcspn(bucket, "/?#@");
739827
if (*path == '@') {
740-
const char *colon = bucket + strcspn(bucket, ":@");
741-
urldecode_kput(bucket, colon - bucket, fp, &id);
742-
if (*colon == ':')
743-
urldecode_kput(&colon[1], path - &colon[1], fp, &secret);
828+
const char *colon = strpbrk(bucket, ":@");
829+
if (*colon != ':') {
830+
urldecode_kput(bucket, colon - bucket, fp, &profile);
831+
}
832+
else {
833+
const char *colon2 = strpbrk(&colon[1], ":@");
834+
urldecode_kput(bucket, colon - bucket, fp, &id);
835+
urldecode_kput(&colon[1], colon2 - &colon[1], fp, &secret);
836+
if (*colon2 == ':')
837+
urldecode_kput(&colon2[1], path - &colon2[1], fp, &token);
838+
}
744839

745840
bucket = &path[1];
746841
path = bucket + strcspn(bucket, "/?#");
@@ -750,6 +845,11 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
750845
const char *v;
751846
if ((v = getenv("AWS_ACCESS_KEY_ID")) != NULL) kputs(v, &id);
752847
if ((v = getenv("AWS_SECRET_ACCESS_KEY")) != NULL) kputs(v, &secret);
848+
if ((v = getenv("AWS_SESSION_TOKEN")) != NULL) kputs(v, &token);
849+
850+
if ((v = getenv("AWS_DEFAULT_PROFILE")) != NULL) kputs(v, &profile);
851+
else if ((v = getenv("AWS_PROFILE")) != NULL) kputs(v, &profile);
852+
else kputs("default", &profile);
753853
}
754854

755855
// Use virtual hosted-style access if possible, otherwise path-style.
@@ -763,14 +863,34 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
763863
}
764864
kputs(path, &url);
765865

866+
if (id.l == 0) {
867+
const char *v = getenv("AWS_SHARED_CREDENTIALS_FILE");
868+
parse_ini(v? v : "~/.aws/credentials", profile.s,
869+
"aws_access_key_id", &id, "aws_secret_access_key", &secret,
870+
"aws_session_token", &token, NULL);
871+
}
872+
if (id.l == 0)
873+
parse_ini("~/.s3cfg", profile.s, "access_key", &id,
874+
"secret_key", &secret, "access_token", &token, NULL);
875+
if (id.l == 0)
876+
parse_simple("~/.awssecret", &id, &secret);
877+
878+
if (token.l > 0) {
879+
kputs("x-amz-security-token:", message);
880+
kputs(token.s, message);
881+
kputc('\n', message);
882+
883+
kputs("X-Amz-Security-Token: ", &token_hdr);
884+
kputs(token.s, &token_hdr);
885+
if (add_header(fp, token_hdr.s) < 0) goto error;
886+
}
887+
766888
kputc('/', message);
767889
kputs(bucket, message); // CanonicalizedResource is '/' + bucket + path
768890

769891
err = curl_easy_setopt(fp->easy, CURLOPT_URL, url.s);
770892
if (err != CURLE_OK) { errno = easy_errno(fp->easy, err); goto error; }
771893

772-
// TODO Read id and secret from config files
773-
774894
// If we have no id/secret, we can't sign the request but will
775895
// still be able to access public data sets.
776896
if (id.l > 0 && secret.l > 0) {
@@ -794,8 +914,11 @@ add_s3_settings(hFILE_libcurl *fp, const char *s3url, kstring_t *message)
794914
free_and_return:
795915
save = errno;
796916
free(url.s);
917+
free(profile.s);
797918
free(id.s);
798919
free(secret.s);
920+
free(token.s);
921+
free(token_hdr.s);
799922
free(auth_hdr.s);
800923
free(message->s);
801924
errno = save;

0 commit comments

Comments
 (0)