/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,®ion0))) 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",®ion); | 
| 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 |  |  |