Coverage Report

Created: 2025-10-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/netcdf-c/libdispatch/ds3util.c
Line
Count
Source
1
/*********************************************************************
2
 *   Copyright 2018, UCAR/Unidata
3
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
4
 *********************************************************************/
5
6
#include "config.h"
7
#include <stdlib.h>
8
#include <string.h>
9
#include <stdio.h>
10
#include <assert.h>
11
#ifdef HAVE_UNISTD_H
12
#include <unistd.h>
13
#endif
14
#ifdef HAVE_SYS_STAT_H
15
#include <sys/stat.h>
16
#endif
17
#ifdef HAVE_FCNTL_H
18
#include <fcntl.h>
19
#endif
20
#ifdef _MSC_VER
21
#include <io.h>
22
#endif
23
24
#include "netcdf.h"
25
#include "nc4internal.h"
26
#include "ncuri.h"
27
#include "nclist.h"
28
#include "ncbytes.h"
29
#include "ncrc.h"
30
#include "nclog.h"
31
#include "ncs3sdk.h"
32
#include "ncutil.h"
33
34
#undef AWSDEBUG
35
36
/* Alternate .aws directory location */
37
1
#define NC_TEST_AWS_DIR "NC_TEST_AWS_DIR"
38
39
enum URLFORMAT {UF_NONE=0, UF_VIRTUAL=1, UF_PATH=2, UF_S3=3, UF_OTHER=4};
40
41
/* Read these files in order and later overriding earlier */
42
static const char* awsconfigfiles[] = {".aws/config",".aws/credentials",NULL};
43
0
#define NCONFIGFILES (sizeof(awsconfigfiles)/sizeof(char*))
44
45
/**************************************************/
46
/* Forward */
47
48
static int endswith(const char* s, const char* suffix);
49
static void freeprofile(struct AWSprofile* profile);
50
static void freeentry(struct AWSentry* e);
51
static int awsparse(const char* text, NClist* profiles);
52
53
extern void awsprofiles(void);
54
55
/**************************************************/
56
/* Capture environmental Info */
57
58
EXTERNL void
59
NC_s3sdkenvironment(void)
60
0
{
61
    /* Get various environment variables as defined by the AWS sdk */
62
0
    NCglobalstate* gs = NC_getglobalstate();
63
0
    if(getenv("AWS_REGION")!=NULL)
64
0
        gs->aws.default_region = nulldup(getenv("AWS_REGION"));
65
0
    else if(getenv("AWS_DEFAULT_REGION")!=NULL)
66
0
        gs->aws.default_region = nulldup(getenv("AWS_DEFAULT_REGION"));
67
0
    else if(gs->aws.default_region == NULL)
68
0
        gs->aws.default_region = nulldup(AWS_GLOBAL_DEFAULT_REGION);
69
0
    gs->aws.access_key_id = nulldup(getenv("AWS_ACCESS_KEY_ID"));
70
0
    gs->aws.config_file = nulldup(getenv("AWS_CONFIG_FILE"));
71
0
    gs->aws.profile = nulldup(getenv("AWS_PROFILE"));
72
0
    gs->aws.secret_access_key = nulldup(getenv("AWS_SECRET_ACCESS_KEY"));
73
0
}
74
75
/**************************************************/
76
/* Generic S3 Utilities */
77
78
/*
79
Rebuild an S3 url into a canonical path-style url.
80
If region is not in the host, then use specified region
81
if provided, otherwise us-east-1.
82
@param url      (in) the current url
83
@param s3       (in/out) NCS3INFO structure
84
@param pathurlp (out) the resulting pathified url string
85
*/
86
87
int
88
NC_s3urlrebuild(NCURI* url, NCS3INFO* s3, NCURI** newurlp)
89
0
{
90
0
    size_t i;
91
0
    int stat = NC_NOERR;
92
0
    NClist* hostsegments = NULL;
93
0
    NClist* pathsegments = NULL;
94
0
    NCbytes* buf = ncbytesnew();
95
0
    NCURI* newurl = NULL;
96
0
    char* bucket = NULL;
97
0
    char* host = NULL;
98
0
    char* path = NULL;
99
0
    char* region = NULL;
100
0
    NCS3SVC svc = NCS3UNK;
101
    
102
0
    if(url == NULL)
103
0
        {stat = NC_EURL; goto done;}
104
105
    /* Parse the hostname */
106
0
    hostsegments = nclistnew();
107
    /* split the hostname by "." */
108
0
    if((stat = NC_split_delim(url->host,'.',hostsegments))) goto done;
109
110
    /* Parse the path*/
111
0
    pathsegments = nclistnew();
112
    /* split the path by "/" */
113
0
    if((stat = NC_split_delim(url->path,'/',pathsegments))) goto done;
114
115
  /* Distinguish path-style from virtual-host style from s3: and from other.
116
  Virtual:
117
    (1) https://<bucket-name>.s3.<region>.amazonaws.com/<path>
118
    (2) https://<bucket-name>.s3.amazonaws.com/<path> -- region defaults (to us-east-1)
119
  Path:
120
    (3) https://s3.<region>.amazonaws.com/<bucket-name>/<path>
121
    (4) https://s3.amazonaws.com/<bucket-name>/<path> -- region defaults to us-east-1
122
  S3:
123
    (5) s3://<bucket-name>/<path>
124
  Google:
125
    (6) https://storage.googleapis.com/<bucket-name>/<path>
126
    (7) gs3://<bucket-name>/<path>
127
  Other:
128
    (8) https://<host>/<bucket-name>/<path>
129
    (9) https://<bucket-name>.s3.<region>.domain.example.com/<path>
130
    (10)https://s3.<region>.example.com/<bucket>/<path>
131
  */
132
0
  if(url->host == NULL || strlen(url->host) == 0)
133
0
        {stat = NC_EURL; goto done;}
134
135
    /* Reduce the host to standard form such as s3.amazonaws.com by pulling out the
136
       region and bucket from the host */
137
0
    if(strcmp(url->protocol,"s3")==0 && nclistlength(hostsegments)==1) { /* Format (5) */
138
0
  bucket = nclistremove(hostsegments,0);
139
  /* region unknown at this point */
140
  /* Host will be set to canonical form later */
141
0
  svc = NCS3;
142
0
    } else if(strcmp(url->protocol,"gs3")==0 && nclistlength(hostsegments)==1) { /* Format (7) */
143
0
  bucket = nclistremove(hostsegments,0);
144
  /* region unknown at this point */
145
  /* Host will be set to canonical form later */
146
0
  svc = NCS3GS;
147
0
    } else if(endswith(url->host,AWSHOST)) { /* Virtual or path */
148
0
  svc = NCS3;
149
  /* If we find a bucket as part of the host, then remove it */
150
0
  switch (nclistlength(hostsegments)) {
151
0
  default: stat = NC_EURL; goto done;
152
0
  case 3: /* Format (4) */ 
153
      /* region unknown at this point */
154
          /* bucket unknown at this point */
155
0
      break;
156
0
  case 4: /* Format (2) or (3) */
157
0
            if(strcasecmp(nclistget(hostsegments,0),"s3")!=0) { /* Presume format (2) */
158
          /* region unknown at this point */
159
0
          bucket = nclistremove(hostsegments,0); /* Make canonical */
160
0
            } else if(strcasecmp(nclistget(hostsegments,0),"s3")==0) { /* Format (3) */
161
0
          region = nclistremove(hostsegments,1); /* Make canonical */
162
          /* bucket unknown at this point */
163
0
      } else /* ! Format (2) and ! Format (3) => error */
164
0
          {stat = NC_EURL; goto done;}
165
0
      break;
166
0
  case 5: /* Format (1) */
167
0
            if(strcasecmp(nclistget(hostsegments,1),"s3")!=0)
168
0
          {stat = NC_EURL; goto done;}
169
      /* Make canonical */
170
0
      region = nclistremove(hostsegments,2);
171
0
          bucket = nclistremove(hostsegments,0);
172
0
      break;
173
0
  }
174
0
    } else if(strcasecmp(url->host,GOOGLEHOST)==0) { /* Google (6) */
175
0
        if((host = strdup(url->host))==NULL)
176
0
      {stat = NC_ENOMEM; goto done;}
177
        /* region is unknown */
178
  /* bucket is unknown at this point */
179
0
  svc = NCS3GS;
180
0
    } else { /* Presume Formats (8),(9),(10) */
181
0
    if (nclistlength(hostsegments) > 3 && strcasecmp(nclistget(hostsegments, 1), "s3") == 0){
182
0
      bucket = nclistremove(hostsegments, 0);
183
0
      region = nclistremove(hostsegments, 2);
184
0
      host = strdup(url->host + sizeof(bucket) + 1);
185
0
    }else{
186
0
      if (nclistlength(hostsegments) > 2 && strcasecmp(nclistget(hostsegments, 0), "s3") == 0){
187
0
        region = nclistremove(hostsegments, 1);
188
0
      }
189
0
      if ((host = strdup(url->host)) == NULL){
190
0
        stat = NC_ENOMEM;
191
0
        goto done;
192
0
      }
193
0
    }
194
0
  }
195
196
    /* region = (1) from url, (2) s3->region, (3) default */
197
0
    if(region == NULL && s3 != NULL)
198
0
  region = nulldup(s3->region);
199
0
    if(region == NULL) {
200
0
        const char* region0 = NULL;
201
  /* Get default region */
202
0
  if((stat = NC_getdefaults3region(url,&region0))) goto done;
203
0
  region = (char*)nulldup(region0);
204
0
    }
205
0
    if(region == NULL) {stat = NC_ES3; goto done;}
206
207
    /* bucket = (1) from url, (2) s3->bucket */
208
0
    if(bucket == NULL && nclistlength(pathsegments) > 0) {
209
0
  bucket = nclistremove(pathsegments,0); /* Get from the URL path; will reinsert below */
210
0
    }
211
0
    if(bucket == NULL && s3 != NULL)
212
0
  bucket = nulldup(s3->bucket);
213
0
    if(bucket == NULL) {stat = NC_ES3; goto done;}
214
215
0
    if(svc == NCS3) {
216
        /* Construct the revised host */
217
0
  ncbytesclear(buf);
218
0
        ncbytescat(buf,"s3");
219
0
  assert(region != NULL);
220
0
        ncbytescat(buf,".");
221
0
  ncbytescat(buf,region);
222
0
        ncbytescat(buf,AWSHOST);
223
0
  nullfree(host);
224
0
        host = ncbytesextract(buf);
225
0
    } else if(svc == NCS3GS) {
226
0
  nullfree(host);
227
0
  host = strdup(GOOGLEHOST);
228
0
    }
229
230
0
    ncbytesclear(buf);
231
232
    /* Construct the revised path */
233
0
    if(bucket != NULL) {
234
0
        ncbytescat(buf,"/");
235
0
        ncbytescat(buf,bucket);
236
0
    }
237
0
    for(i=0;i<nclistlength(pathsegments);i++) {
238
0
  ncbytescat(buf,"/");
239
0
  ncbytescat(buf,nclistget(pathsegments,i));
240
0
    }
241
0
    path = ncbytesextract(buf);
242
243
    /* clone the url so we can modify it*/
244
0
    if((newurl=ncuriclone(url))==NULL) {stat = NC_ENOMEM; goto done;}
245
246
    /* Modify the URL to canonical form */
247
0
    ncurisetprotocol(newurl,"https");
248
0
    assert(host != NULL);
249
0
    ncurisethost(newurl,host);
250
0
    assert(path != NULL);
251
0
    ncurisetpath(newurl,path);
252
253
    /* Add "s3" to the mode list */
254
0
    NC_addmodetag(newurl,"s3");
255
256
    /* Rebuild the url->url */
257
0
    ncurirebuild(newurl);
258
    /* return various items */
259
#ifdef AWSDEBUG
260
    fprintf(stderr,">>> NC_s3urlrebuild: final=%s bucket=|%s| region=|%s|\n",newurl->uri,bucket,region);
261
#endif
262
0
    if(newurlp) {*newurlp = newurl; newurl = NULL;}
263
0
    if(s3 != NULL) {
264
0
        s3->bucket = bucket; bucket = NULL;
265
0
        s3->region = region; region = NULL;
266
0
        s3->svc = svc;
267
0
    }
268
0
done:
269
0
    nullfree(region);
270
0
    nullfree(bucket)
271
0
    nullfree(host)
272
0
    nullfree(path)
273
0
    ncurifree(newurl);
274
0
    ncbytesfree(buf);
275
0
    nclistfreeall(hostsegments);
276
0
    nclistfreeall(pathsegments);
277
0
    return stat;
278
0
}
279
280
static int
281
endswith(const char* s, const char* suffix)
282
0
{
283
0
    if(s == NULL || suffix == NULL) return 0;
284
0
    size_t ls = strlen(s);
285
0
    size_t lsf = strlen(suffix);
286
0
    ssize_t delta = (ssize_t)(ls - lsf);
287
0
    if(delta < 0) return 0;
288
0
    if(memcmp(s+delta,suffix,lsf)!=0) return 0;
289
0
    return 1;
290
0
}
291
292
/**************************************************/
293
/* S3 utilities */
294
295
EXTERNL int
296
NC_s3urlprocess(NCURI* url, NCS3INFO* s3, NCURI** newurlp)
297
0
{
298
0
    int stat = NC_NOERR;
299
0
    NCURI* url2 = NULL;
300
0
    NClist* pathsegments = NULL;
301
0
    const char* profile0 = NULL;
302
303
0
    if(url == NULL || s3 == NULL)
304
0
        {stat = NC_EURL; goto done;}
305
    /* Get current profile */
306
0
    if((stat = NC_getactives3profile(url,&profile0))) goto done;
307
0
    if(profile0 == NULL) profile0 = "no";
308
0
    s3->profile = strdup(profile0);
309
310
    /* Rebuild the URL to path format and get a usable region and optional bucket*/
311
0
    if((stat = NC_s3urlrebuild(url,s3,&url2))) goto done;
312
0
    if(url2->port){
313
0
  char hostport[8192];
314
0
  snprintf(hostport,sizeof(hostport),"%s:%s",url2->host,url2->port);
315
0
  s3->host = strdup(hostport);
316
0
    }else{
317
0
        s3->host = strdup(url2->host);
318
0
    }
319
    /* construct the rootkey minus the leading bucket */
320
0
    pathsegments = nclistnew();
321
0
    if((stat = NC_split_delim(url2->path,'/',pathsegments))) goto done;
322
0
    if(nclistlength(pathsegments) > 0) {
323
0
  char* seg = nclistremove(pathsegments,0);
324
0
        nullfree(seg);
325
0
    }
326
0
    if((stat = NC_join(pathsegments,&s3->rootkey))) goto done;
327
0
    if(newurlp) {*newurlp = url2; url2 = NULL;}
328
329
0
done:
330
0
    ncurifree(url2);
331
0
    nclistfreeall(pathsegments);
332
0
    return stat;
333
0
}
334
335
int
336
NC_s3clone(NCS3INFO* s3, NCS3INFO** news3p)
337
0
{
338
0
    NCS3INFO* news3 = NULL;
339
0
    if(s3 && news3p) {
340
0
  if((news3 = (NCS3INFO*)calloc(1,sizeof(NCS3INFO)))==NULL)
341
0
           return NC_ENOMEM;
342
0
  if((news3->host = nulldup(s3->host))==NULL) return NC_ENOMEM;
343
0
  if((news3->region = nulldup(s3->region))==NULL) return NC_ENOMEM;
344
0
  if((news3->bucket = nulldup(s3->bucket))==NULL) return NC_ENOMEM;
345
0
  if((news3->rootkey = nulldup(s3->rootkey))==NULL) return NC_ENOMEM;
346
0
  if((news3->profile = nulldup(s3->profile))==NULL) return NC_ENOMEM;
347
0
    }
348
0
    if(news3p) {*news3p = news3; news3 = NULL;}
349
0
    else {NC_s3clear(news3); nullfree(news3);}
350
0
    return NC_NOERR;
351
0
}
352
353
int
354
NC_s3clear(NCS3INFO* s3)
355
0
{
356
0
    if(s3) {
357
0
  nullfree(s3->host); s3->host = NULL;
358
0
  nullfree(s3->region); s3->region = NULL;
359
0
  nullfree(s3->bucket); s3->bucket = NULL;
360
0
  nullfree(s3->rootkey); s3->rootkey = NULL;
361
0
  nullfree(s3->profile); s3->profile = NULL;
362
0
    }
363
0
    return NC_NOERR;
364
0
}
365
366
/*
367
Check if a url has indicators that signal an S3 or Google S3 url or ZoH S3 url.
368
The rules are as follows:
369
1. If the protocol is "s3" or "gs3" or "zoh", then return (true,s3|gs3|zoh).
370
2. If the mode contains "s3" or "gs3" or "zoh", then return (true,s3|gs3|zoh).
371
3. Check the host name:
372
3.1 If the host ends with ".amazonaws.com", then return (true,s3).
373
3.1 If the host is "storage.googleapis.com", then return (true,gs3).
374
4. Otherwise return (false,unknown).
375
*/
376
377
int
378
NC_iss3(NCURI* uri, NCS3SVC* svcp)
379
0
{
380
0
    int iss3 = 0;
381
0
    NCS3SVC svc = NCS3UNK;
382
383
0
    if(uri == NULL) goto done; /* not a uri */
384
    /* is the protocol "s3" or "gs3" or "zoh" ? */
385
0
    if(strcasecmp(uri->protocol,"s3")==0) {iss3 = 1; svc = NCS3; goto done;}
386
0
    if(strcasecmp(uri->protocol,"gs3")==0) {iss3 = 1; svc = NCS3GS; goto done;}
387
#ifdef NETCDF_ENABLE_ZOH
388
    if(strcasecmp(uri->protocol,"zoh")==0) {iss3 = 1; svc = NCS3ZOH; goto done;}
389
#endif
390
    /* Is "s3" or "gs3" in the mode list? */
391
0
    if(NC_testmode(uri,"s3")) {iss3 = 1; svc = NCS3; goto done;}
392
0
    if(NC_testmode(uri,"gs3")) {iss3 = 1; svc = NCS3GS; goto done;}    
393
    /* Last chance; see if host looks s3'y */
394
0
    if(uri->host != NULL) {
395
0
        if(endswith(uri->host,AWSHOST)) {iss3 = 1; svc = NCS3; goto done;}
396
0
        if(strcasecmp(uri->host,GOOGLEHOST)==0) {iss3 = 1; svc = NCS3GS; goto done;}
397
0
    }    
398
0
    if(svcp) *svcp = svc;
399
0
done:
400
0
    return iss3;
401
0
}
402
403
/**************************************************/
404
/**
405
The .aws/config and .aws/credentials files
406
are in INI format (https://en.wikipedia.org/wiki/INI_file).
407
This format is not well defined, so the grammar used
408
here is restrictive. Here, the term "profile" is the same
409
as the INI term "section".
410
411
The grammar used is as follows:
412
413
Grammar:
414
415
inifile: profilelist ;
416
profilelist: profile | profilelist profile ;
417
profile: '[' profilename ']' EOL entries ;
418
entries: empty | entries entry ;
419
entry:  WORD = WORD EOL ;
420
profilename: WORD ;
421
Lexical:
422
WORD    sequence of printable characters - [ \[\]=]+
423
EOL '\n' | ';'
424
425
Note:
426
1. The semicolon at beginning of a line signals a comment.
427
2. # comments are not allowed
428
3. Duplicate profiles or keys are ignored.
429
4. Escape characters are not supported.
430
*/
431
432
#define AWS_EOF (-1)
433
#define AWS_ERR (0)
434
#define AWS_WORD (0x10001)
435
#define AWS_EOL (0x10002)
436
437
typedef struct AWSparser {
438
    char* text;
439
    char* pos;
440
    size_t yylen; /* |yytext| */
441
    NCbytes* yytext;
442
    int token; /* last token found */
443
    int pushback; /* allow 1-token pushback */
444
} AWSparser;
445
446
#ifdef LEXDEBUG
447
static const char*
448
tokenname(int token)
449
{
450
    static char num[32];
451
    switch(token) {
452
    case AWS_EOF: return "EOF";
453
    case AWS_ERR: return "ERR";
454
    case AWS_WORD: return "WORD";
455
    default: snprintf(num,sizeof(num),"%d",token); return num;
456
    }
457
    return "UNKNOWN";
458
}
459
#endif
460
461
/*
462
@param text of the aws credentials file
463
@param profiles list of form struct AWSprofile (see ncauth.h)
464
*/
465
466
#define LBR '['
467
#define RBR ']'
468
469
static void
470
freeprofile(struct AWSprofile* profile)
471
1
{
472
1
    if(profile) {
473
#ifdef AWSDEBUG
474
fprintf(stderr,">>> freeprofile: %s\n",profile->name);
475
#endif
476
1
  for(size_t i=0;i<nclistlength(profile->entries);i++) {
477
0
      struct AWSentry* e = (struct AWSentry*)nclistget(profile->entries,i);
478
0
      freeentry(e);
479
0
  }
480
1
        nclistfree(profile->entries);
481
1
  nullfree(profile->name);
482
1
  nullfree(profile);
483
1
    }
484
1
}
485
486
void
487
NC_s3freeprofilelist(NClist* profiles)
488
3
{
489
3
    if(profiles) {
490
3
  for(size_t i=0;i<nclistlength(profiles);i++) {
491
1
      struct AWSprofile* p = (struct AWSprofile*)nclistget(profiles,i);
492
1
      freeprofile(p);
493
1
  }
494
2
  nclistfree(profiles);
495
2
    }
496
3
}
497
498
const char*
499
NC_s3dumps3info(NCS3INFO* info)
500
0
{
501
0
    static char text[8192];
502
0
    snprintf(text,sizeof(text),"host=%s region=%s bucket=%s rootkey=%s profile=%s",
503
0
    (info->host?info->host:"null"),
504
0
    (info->region?info->region:"null"),
505
0
    (info->bucket?info->bucket:"null"),
506
0
    (info->rootkey?info->rootkey:"null"),
507
0
    (info->profile?info->profile:"null"));
508
0
    return text;
509
0
}
510
511
/* Find, load, and parse the aws config &/or credentials file */
512
int
513
NC_aws_load_credentials(NCglobalstate* gstate)
514
1
{
515
1
    int stat = NC_NOERR;
516
1
    NClist* profiles = nclistnew();
517
1
    NCbytes* buf = ncbytesnew();
518
1
    char path[8192];
519
1
    const char* aws_root = getenv(NC_TEST_AWS_DIR);
520
1
    const char* awscfg_local[NCONFIGFILES + 1]; /* +1 for the env variable */
521
1
    const char** awscfg = NULL;
522
523
    /* add a "no" credentials */
524
1
    {
525
1
  struct AWSprofile* noprof = (struct AWSprofile*)calloc(1,sizeof(struct AWSprofile));
526
1
  noprof->name = strdup("no");
527
1
  noprof->entries = nclistnew();
528
1
  nclistpush(profiles,noprof); noprof = NULL;
529
1
    }
530
531
1
    awscfg = awsconfigfiles;
532
1
    if((awscfg_local[0] = NC_getglobalstate()->aws.config_file)!=NULL) {
533
0
  memcpy(&awscfg_local[1],awsconfigfiles,sizeof(char*)*NCONFIGFILES);
534
0
  awscfg = awscfg_local;
535
0
    }
536
3
    for(;*awscfg;awscfg++) {
537
        /* Construct the path ${HOME}/<file> or Windows equivalent. */
538
2
  const char* cfg = *awscfg;
539
540
2
        snprintf(path,sizeof(path),"%s%s%s",
541
2
      (aws_root?aws_root:gstate->home),
542
2
      (*cfg == '/'?"":"/"),
543
2
      cfg);
544
2
  ncbytesclear(buf);
545
2
        if((stat=NC_readfile(path,buf))) {
546
2
            nclog(NCLOGWARN, "Could not open file: %s",path);
547
2
        } else {
548
            /* Parse the credentials file */
549
0
      const char* text = ncbytescontents(buf);
550
0
            if((stat = awsparse(text,profiles))) goto done;
551
0
  }
552
2
    }
553
  
554
    /* If there is no default credentials, then try to synthesize one
555
       from various environment variables */
556
1
    {
557
1
  size_t i;
558
1
        struct AWSprofile* dfalt = NULL;
559
1
        struct AWSentry* entry = NULL;
560
1
        NCglobalstate* gs = NC_getglobalstate();
561
  /* Verify that we can build a default */
562
1
        if(gs->aws.access_key_id != NULL && gs->aws.secret_access_key != NULL) {
563
      /* Kill off any previous default profile */
564
0
      for(i=nclistlength(profiles)-1;i>=0;i--) {/* walk backward because we are removing entries */
565
0
    struct AWSprofile* prof = (struct AWSprofile*)nclistget(profiles,i);
566
0
    if(strcasecmp(prof->name,"default")==0) {
567
0
        nclistremove(profiles,i);
568
0
        freeprofile(prof);
569
0
    }
570
0
      }
571
      /* Build new default profile */
572
0
      if((dfalt = (struct AWSprofile*)calloc(1,sizeof(struct AWSprofile)))==NULL) {stat = NC_ENOMEM; goto done;}
573
0
      dfalt->name = strdup("default");
574
0
      dfalt->entries = nclistnew();
575
      /* Save the new default profile */
576
0
      nclistpush(profiles,dfalt); dfalt = NULL;
577
      /* Create the entries for default */
578
0
      if((entry = (struct AWSentry*)calloc(1,sizeof(struct AWSentry)))==NULL) {stat = NC_ENOMEM; goto done;}
579
0
      entry->key = strdup("aws_access_key_id");
580
0
      entry->value = strdup(gs->aws.access_key_id);
581
0
      nclistpush(dfalt->entries,entry); entry = NULL;
582
0
      if((entry = (struct AWSentry*)calloc(1,sizeof(struct AWSentry)))==NULL) {stat = NC_ENOMEM; goto done;}
583
0
      entry->key = strdup("aws_secret_access_key");
584
0
      entry->value = strdup(gs->aws.secret_access_key);
585
0
      nclistpush(dfalt->entries,entry); entry = NULL;
586
0
  }
587
1
    }
588
589
1
    if(gstate->rcinfo->s3profiles)
590
1
        NC_s3freeprofilelist(gstate->rcinfo->s3profiles);
591
1
    gstate->rcinfo->s3profiles = profiles; profiles = NULL;
592
593
#ifdef AWSDEBUG
594
    awsprofiles();
595
#endif
596
597
1
done:
598
1
    ncbytesfree(buf);
599
1
    NC_s3freeprofilelist(profiles);
600
1
    return stat;
601
1
}
602
603
/* Lookup a profile by name;
604
@param profilename to lookup
605
@param profilep return the matching profile; null if profile not found
606
@return NC_NOERR if no error
607
@return other error
608
*/
609
610
int
611
NC_authgets3profile(const char* profilename, struct AWSprofile** profilep)
612
0
{
613
0
    int stat = NC_NOERR;
614
0
    NCglobalstate* gstate = NC_getglobalstate();
615
616
0
    for(size_t i=0;i<nclistlength(gstate->rcinfo->s3profiles);i++) {
617
0
  struct AWSprofile* profile = (struct AWSprofile*)nclistget(gstate->rcinfo->s3profiles,i);
618
0
  if(strcmp(profilename,profile->name)==0)
619
0
      {if(profilep) {*profilep = profile; goto done;}}
620
0
    }
621
0
    if(profilep) *profilep = NULL; /* not found */
622
0
done:
623
0
    return stat;
624
0
}
625
626
/**
627
@param profile name of profile
628
@param key key to search for in profile
629
@param value place to store the value if key is found; NULL if not found
630
@return NC_NOERR if key is found, Some other error otherwise.
631
*/
632
633
int
634
NC_s3profilelookup(const char* profile, const char* key, const char** valuep)
635
0
{
636
0
    int stat = NC_NOERR;
637
0
    struct AWSprofile* awsprof = NULL;
638
0
    const char* value = NULL;
639
640
0
    if(profile == NULL) return NC_ES3;
641
0
    if((stat=NC_authgets3profile(profile,&awsprof))==NC_NOERR && awsprof != NULL) {
642
0
        for(size_t i=0;i<nclistlength(awsprof->entries);i++) {
643
0
      struct AWSentry* entry = (struct AWSentry*)nclistget(awsprof->entries,i);
644
0
      if(strcasecmp(entry->key,key)==0) {
645
0
    value = entry->value;
646
0
          break;
647
0
      }
648
0
  }
649
0
    }
650
0
    if(valuep) *valuep = value;
651
0
    return stat;
652
0
}
653
/**
654
 * Get the credentials for a given profile or load them from environment.
655
 @param profile name to use to look for credentials
656
 @param region return region from profile or env
657
 @param accessid return accessid from progile or env
658
 @param accesskey return accesskey from profile or env
659
 */
660
0
void NC_s3getcredentials(const char *profile, const char **region, const char** accessid, const char** accesskey) {
661
0
    if(profile != NULL && strcmp(profile,"no") != 0) {
662
0
        NC_s3profilelookup(profile, "aws_access_key_id", accessid);
663
0
        NC_s3profilelookup(profile, "aws_secret_access_key", accesskey);
664
0
        NC_s3profilelookup(profile, "region", region);
665
0
    }
666
0
    else
667
0
    { // We load from env if not in profile
668
0
        NCglobalstate* gstate = NC_getglobalstate();
669
0
        if(gstate->aws.access_key_id != NULL && accessid){
670
0
            *accessid = gstate->aws.access_key_id;
671
0
        }
672
0
        if (gstate->aws.secret_access_key != NULL && accesskey){
673
0
            *accesskey = gstate->aws.secret_access_key;
674
0
        }
675
0
        if(gstate->aws.default_region != NULL && region){
676
0
            *region = gstate->aws.default_region;
677
0
        }
678
0
    }
679
0
}
680
681
682
/**************************************************/
683
/*
684
Get the current active profile. The priority order is as follows:
685
1. aws.profile key in mode flags
686
2. aws.profile in .rc entries
687
3. AWS_PROFILE env variable
688
4. "default"
689
5. "no" -- meaning do not use any profile => no secret key
690
691
@param uri uri with mode flags, may be NULL
692
@param profilep return profile name here or NULL if none found
693
@return NC_NOERR if no error.
694
@return NC_EINVAL if something else went wrong.
695
*/
696
697
int
698
NC_getactives3profile(NCURI* uri, const char** profilep)
699
0
{
700
0
    int stat = NC_NOERR;
701
0
    const char* profile = NULL;
702
0
    struct AWSprofile* ap = NULL;
703
0
    struct NCglobalstate* gs = NC_getglobalstate();
704
705
0
    if (uri != NULL) {
706
0
  profile = ncurifragmentlookup(uri,"aws.profile");
707
0
  if(profile == NULL)
708
0
    profile = NC_rclookupx(uri,"AWS.PROFILE");
709
0
    }
710
711
0
    if(profile == NULL && gs->aws.profile != NULL) {
712
0
        if((stat=NC_authgets3profile(gs->aws.profile,&ap))) goto done;
713
0
  if(ap) profile = nulldup(gs->aws.profile);
714
0
    }
715
716
0
    if(profile == NULL) {
717
0
        if((stat=NC_authgets3profile("default",&ap))) goto done;
718
0
  if(ap) profile = "default";
719
0
    }
720
721
0
    if(profile == NULL) {
722
0
        if((stat=NC_authgets3profile("no",&ap))) goto done;
723
0
  if(ap) profile = "no";
724
0
    }
725
726
#ifdef AWSDEBUG
727
    fprintf(stderr,">>> activeprofile = %s\n",(profile?profile:"null"));
728
#endif
729
0
    if(profilep) *profilep = profile;
730
0
done:
731
0
    return stat;
732
0
}
733
734
/*
735
Get the current default region. The search order is as follows:
736
1. aws.region key in mode flags
737
2. aws.region in .rc entries
738
3. aws_region key in current profile (only if profiles are being used)
739
4. NCglobalstate.aws.default_region
740
741
@param uri uri with mode flags, may be NULL
742
@param regionp return region name here or NULL if none found
743
@return NC_NOERR if no error.
744
@return NC_EINVAL if something else went wrong.
745
*/
746
747
int
748
NC_getdefaults3region(NCURI* uri, const char** regionp)
749
0
{
750
0
    int stat = NC_NOERR;
751
0
    const char* region = NULL;
752
0
    const char* profile = NULL;
753
754
0
    region = ncurifragmentlookup(uri,"aws.region");
755
0
    if(region == NULL)
756
0
        region = NC_rclookupx(uri,"AWS.REGION");
757
0
    if(region == NULL) {/* See if we can find a profile */
758
0
        if(NC_getactives3profile(uri,&profile)==NC_NOERR) {
759
0
      if(profile)
760
0
          (void)NC_s3profilelookup(profile,"aws_region",&region);
761
0
  }
762
0
    }
763
0
    if(region == NULL)
764
0
  region = (NC_getglobalstate()->aws.default_region ? NC_getglobalstate()->aws.default_region : "us-east-1"); /* Force use of the Amazon default */
765
#ifdef AWSDEBUG
766
    fprintf(stderr,">>> activeregion = |%s|\n",region);
767
#endif
768
0
    if(regionp) *regionp = region;
769
0
    return stat;
770
0
}
771
772
/**
773
The .aws/config and .aws/credentials files
774
are in INI format (https://en.wikipedia.org/wiki/INI_file).
775
This format is not well defined, so the grammar used
776
here is restrictive. Here, the term "profile" is the same
777
as the INI term "section".
778
779
The grammar used is as follows:
780
781
Grammar:
782
783
inifile: profilelist ;
784
profilelist: profile | profilelist profile ;
785
profile: '[' profilename ']' EOL entries ;
786
entries: empty | entries entry ;
787
entry:  WORD = WORD EOL ;
788
profilename: WORD ;
789
Lexical:
790
WORD    sequence of printable characters - [ \[\]=]+
791
EOL '\n' | ';'
792
793
Note:
794
1. The semicolon at beginning of a line signals a comment.
795
2. # comments are not allowed
796
3. Duplicate profiles or keys are ignored.
797
4. Escape characters are not supported.
798
*/
799
800
0
#define AWS_EOF (-1)
801
0
#define AWS_ERR (0)
802
0
#define AWS_WORD (0x10001)
803
0
#define AWS_EOL (0x10002)
804
805
#ifdef LEXDEBUG
806
static const char*
807
tokenname(int token)
808
{
809
    static char num[32];
810
    switch(token) {
811
    case AWS_EOF: return "EOF";
812
    case AWS_ERR: return "ERR";
813
    case AWS_WORD: return "WORD";
814
    default: snprintf(num,sizeof(num),"%d",token); return num;
815
    }
816
    return "UNKNOWN";
817
}
818
#endif
819
820
static int
821
awslex(AWSparser* parser)
822
0
{
823
0
    int token = 0;
824
0
    char* start;
825
0
    size_t count;
826
827
0
    parser->token = AWS_ERR;
828
0
    ncbytesclear(parser->yytext);
829
0
    ncbytesnull(parser->yytext);
830
831
0
    if(parser->pushback != AWS_ERR) {
832
0
  token = parser->pushback;
833
0
  parser->pushback = AWS_ERR;
834
0
  goto done;
835
0
    }
836
837
0
    while(token == 0) { /* avoid need to goto when retrying */
838
0
  char c = *parser->pos;
839
0
  if(c == '\0') {
840
0
      token = AWS_EOF;
841
0
  } else if(c == '\n') {
842
0
      parser->pos++;
843
0
      token = AWS_EOL;
844
0
  } else if(c <= ' ' || c == '\177') {
845
0
      parser->pos++;
846
0
      continue; /* ignore whitespace */
847
0
  } else if(c == ';') {
848
0
      char* p = parser->pos - 1;
849
0
      if(*p == '\n') {
850
          /* Skip comment */
851
0
          do {p++;} while(*p != '\n' && *p != '\0');
852
0
          parser->pos = p;
853
0
          token = (*p == '\n'?AWS_EOL:AWS_EOF);
854
0
      } else {
855
0
          token = ';';
856
0
          ncbytesappend(parser->yytext,';');
857
0
    parser->pos++;
858
0
      }
859
0
  } else if(c == '[' || c == ']' || c == '=') {
860
0
      ncbytesappend(parser->yytext,c);
861
0
          ncbytesnull(parser->yytext);
862
0
      token = c;
863
0
      parser->pos++;
864
0
  } else { /*Assume a word*/
865
0
      start = parser->pos;
866
0
      for(;;) {
867
0
    c = *parser->pos++;
868
0
          if(c <= ' ' || c == '\177' || c == '[' || c == ']' || c == '=') break; /* end of word */
869
0
      }
870
      /* Pushback last char */
871
0
      parser->pos--;
872
0
      count = (size_t)(parser->pos - start);
873
0
      ncbytesappendn(parser->yytext,start,count);
874
0
      ncbytesnull(parser->yytext);
875
0
      token = AWS_WORD;
876
0
  }
877
#ifdef LEXDEBUG
878
fprintf(stderr,"%s(%d): |%s|\n",tokenname(token),token,ncbytescontents(parser->yytext));
879
#endif
880
0
    } /*for(;;)*/
881
882
0
done:
883
0
    parser->token = token;
884
0
    return token;
885
0
}
886
887
/*
888
@param text of the aws credentials file
889
@param profiles list of form struct AWSprofile (see ncauth.h)
890
*/
891
892
0
#define LBR '['
893
0
#define RBR ']'
894
895
static int
896
awsparse(const char* text, NClist* profiles)
897
0
{
898
0
    int stat = NC_NOERR;
899
0
    size_t len;
900
0
    AWSparser* parser = NULL;
901
0
    struct AWSprofile* profile = NULL;
902
0
    int token;
903
0
    char* key = NULL;
904
0
    char* value = NULL;
905
906
0
    if(text == NULL) text = "";
907
908
0
    parser = calloc(1,sizeof(AWSparser));
909
0
    if(parser == NULL)
910
0
  {stat = (NC_ENOMEM); goto done;}
911
0
    len = strlen(text);
912
0
    parser->text = (char*)malloc(len+1+1+1); /* double nul term plus leading EOL */
913
0
    if(parser->text == NULL)
914
0
  {stat = (NCTHROW(NC_EINVAL)); goto done;}
915
0
    parser->pos = parser->text;
916
0
    parser->pos[0] = '\n'; /* So we can test for comment unconditionally */
917
0
    parser->pos++;
918
0
    strcpy(parser->text+1,text);
919
0
    parser->pos += len;
920
    /* Double nul terminate */
921
0
    parser->pos[0] = '\0';
922
0
    parser->pos[1] = '\0';
923
0
    parser->pos = &parser->text[0]; /* reset */
924
0
    parser->yytext = ncbytesnew();
925
0
    parser->pushback = AWS_ERR;
926
927
    /* Do not need recursion, use simple loops */
928
0
    for(;;) {
929
0
        token = awslex(parser); /* make token always be defined */
930
0
  if(token ==  AWS_EOF) break; /* finished */
931
0
  if(token ==  AWS_EOL) {continue;} /* blank line */
932
0
  if(token != LBR) {stat = NCTHROW(NC_EINVAL); goto done;}
933
  /* parse [profile name] or [name] */
934
0
        token = awslex(parser);
935
0
  if(token != AWS_WORD) {stat = NCTHROW(NC_EINVAL); goto done;}
936
0
  assert(profile == NULL);
937
0
  if((profile = (struct AWSprofile*)calloc(1,sizeof(struct AWSprofile)))==NULL)
938
0
      {stat = NC_ENOMEM; goto done;}
939
0
  profile->name = ncbytesextract(parser->yytext);
940
0
  if(strncmp("profile", profile->name, sizeof("profile")) == 0 ) {
941
0
    token =  awslex(parser);
942
0
    if(token != AWS_WORD) {stat = NCTHROW(NC_EINVAL); goto done;}
943
0
    nullfree(profile->name);
944
0
    profile->name = ncbytesextract(parser->yytext);
945
0
  }
946
0
  profile->entries = nclistnew();
947
0
        token = awslex(parser);
948
0
  if(token != RBR) {stat = NCTHROW(NC_EINVAL); goto done;}
949
#ifdef PARSEDEBUG
950
fprintf(stderr,">>> parse: profile=%s\n",profile->name);
951
#endif
952
  /* The fields can be in any order */
953
0
  for(;;) {
954
0
      struct AWSentry* entry = NULL;
955
0
            token = awslex(parser);
956
0
      if(token == AWS_EOL) {
957
0
          continue; /* ignore empty lines */
958
0
      } else if(token == AWS_EOF) {
959
0
          break;
960
0
      } else if(token == LBR) {/* start of next profile */
961
0
          parser->pushback = token;
962
0
    break;
963
0
      } else if(token ==  AWS_WORD) {
964
0
        key = ncbytesextract(parser->yytext);
965
0
    token = awslex(parser);
966
0
          if(token != '=') {stat = NCTHROW(NC_EINVAL); goto done;}
967
0
          token = awslex(parser);
968
0
    if(token != AWS_EOL && token != AWS_WORD) {stat = NCTHROW(NC_EINVAL); goto done;}
969
0
          value = ncbytesextract(parser->yytext);
970
0
          if((entry = (struct AWSentry*)calloc(1,sizeof(struct AWSentry)))==NULL)
971
0
              {stat = NC_ENOMEM; goto done;}
972
0
          entry->key = key; key = NULL;
973
0
              entry->value = value; value = NULL;
974
#ifdef PARSEDEBUG
975
fprintf(stderr,">>> parse: entry=(%s,%s)\n",entry->key,entry->value);
976
#endif
977
0
    nclistpush(profile->entries,entry); entry = NULL;
978
0
    if(token == AWS_WORD) token = awslex(parser); /* finish the line */
979
0
      } else
980
0
          {stat = NCTHROW(NC_EINVAL); goto done;}
981
0
  }
982
983
  /* If this profile already exists, then overwrite old one */
984
0
  for(size_t i=0;i<nclistlength(profiles);i++) {
985
0
      struct AWSprofile* p = (struct AWSprofile*)nclistget(profiles,i);
986
0
      if(strcasecmp(p->name,profile->name)==0) {
987
    // Keep unique parameters from previous (incomplete!?) profile
988
0
    for (size_t j=0;j<nclistlength(p->entries);j++){
989
0
      struct AWSentry* old = (struct AWSentry*)nclistget(p->entries,j);
990
0
      int add = 1;
991
0
      for (size_t z=0;z<nclistlength(profile->entries);z++){
992
0
        struct AWSentry* new = (struct AWSentry*)nclistget(profile->entries,z);
993
0
        add &= (strcasecmp(old->key,new->key)!=0);
994
0
      }
995
0
      if(add){
996
0
          nclistpush(profile->entries, nclistremove(p->entries,j--));
997
0
      }
998
0
    }
999
0
    nclistset(profiles,i,profile);
1000
0
                profile = NULL;
1001
    /* reclaim old one */
1002
0
    freeprofile(p);
1003
0
    break;
1004
0
      }
1005
0
  }
1006
0
  if(profile) nclistpush(profiles,profile);
1007
0
  profile = NULL;
1008
0
    }
1009
1010
0
done:
1011
0
    if(profile) freeprofile(profile);
1012
0
    nullfree(key);
1013
0
    nullfree(value);
1014
0
    if(parser != NULL) {
1015
0
  nullfree(parser->text);
1016
0
  ncbytesfree(parser->yytext);
1017
0
  free(parser);
1018
0
    }
1019
0
    return (stat);
1020
0
}
1021
1022
static void
1023
freeentry(struct AWSentry* e)
1024
0
{
1025
0
    if(e) {
1026
#ifdef AWSDEBUG
1027
fprintf(stderr,">>> freeentry: key=%s value=%s\n",e->key,e->value);
1028
#endif
1029
0
        nullfree(e->key);
1030
0
        nullfree(e->value);
1031
0
        nullfree(e);
1032
0
    }
1033
0
}
1034
1035
/* Provide profile-related  dumper(s) */
1036
void
1037
awsdumpprofile(struct AWSprofile* p)
1038
0
{
1039
0
    size_t j;
1040
0
    if(p == NULL) {
1041
0
        fprintf(stderr,"    <NULL>");
1042
0
  goto done;
1043
0
    }
1044
0
    fprintf(stderr,"    [%s]",p->name);
1045
0
    if(p->entries == NULL) {
1046
0
  fprintf(stderr,"<NULL>");
1047
0
  goto done;
1048
0
    }
1049
0
    for(j=0;j<nclistlength(p->entries);j++) {
1050
0
        struct AWSentry* e = (struct AWSentry*)nclistget(p->entries,j);
1051
0
  fprintf(stderr," %s=%s",e->key,e->value);
1052
0
    }
1053
0
done:
1054
0
    fprintf(stderr,"\n");
1055
0
}
1056
1057
void
1058
awsdumpprofiles(NClist* profiles)
1059
0
{
1060
0
    size_t i;
1061
0
    NCglobalstate* gs = NC_getglobalstate();
1062
0
    for(i=0;i<nclistlength(gs->rcinfo->s3profiles);i++) {
1063
0
  struct AWSprofile* p = (struct AWSprofile*)nclistget(profiles,i);
1064
0
  awsdumpprofile(p);
1065
0
    }
1066
0
}
1067
1068
void
1069
awsprofiles(void)
1070
0
{
1071
0
    NCglobalstate* gs = NC_getglobalstate();
1072
    fprintf(stderr,">>> profiles from global->rcinfo->s3profiles:\n");
1073
0
    awsdumpprofiles(gs->rcinfo->s3profiles);
1074
0
}
1075