1 /* This file is part of Eclat.
2 Copyright (C) 2012-2018 Sergey Poznyakoff.
3
4 Eclat is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 Eclat is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Eclat. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "eclat.h"
18 #include "grecs/json.h"
19 #include <termios.h>
20 #include <sys/ioctl.h>
21 #include <sys/stat.h>
22
23 int translation_enabled;
24 char *custom_map;
25
26 void
translate_ids(int argc,char ** argv,const char * mapname)27 translate_ids(int argc, char **argv, const char *mapname)
28 {
29 int i;
30 struct eclat_map *map;
31 char *val;
32 char *q, *realname;
33 int dir;
34 int rc;
35
36 if (!translation_enabled || argc == 0)
37 return;
38 if (custom_map)
39 mapname = custom_map;
40
41 dir = eclat_map_name_split(mapname, &realname, &q);
42 if (dir == -1)
43 die(EX_USAGE, "bad qualifier: %s", q);
44
45 map = eclat_map_lookup(realname);
46 if (!map) {
47 debug(ECLAT_DEBCAT_MAIN, 1,
48 ("no such map: %s", realname));
49 return;
50 }
51
52 if (eclat_map_open(map) != eclat_map_ok)
53 die(EX_UNAVAILABLE, "failed to open map %s", realname);
54
55 free(realname);
56
57 for (i = 0; i < argc; i++) {
58 if (!strchr(argv[i], '=')) {
59 switch (rc = eclat_map_get(map, dir, argv[i], &val)) {
60 case eclat_map_ok:
61 argv[i] = val;
62 break;
63
64 case eclat_map_not_found:
65 debug(ECLAT_DEBCAT_MAIN, 1,
66 ("%s not found in map %s",
67 argv[i], mapname));
68 break;
69
70 default:
71 die(EX_UNAVAILABLE, "cannot translate %s: %s",
72 argv[i], eclat_map_strerror(rc));
73 }
74 }
75 }
76 }
77
78 char *
eclat_stpcpy(char * p,char * q)79 eclat_stpcpy(char *p, char *q)
80 {
81 while ((*p = *q++))
82 ++p;
83 return p;
84 }
85
86 #define RESOURCE_ID_PFX "resource-id="
87 #define RESOURCE_ID_LEN (sizeof(RESOURCE_ID_PFX) - 1)
88
89 void
translate_resource_ids(int argc,char ** argv)90 translate_resource_ids(int argc, char **argv)
91 {
92 int i, j, rc, ecnt;
93 size_t len;
94 struct eclat_map *map;
95 char *val, *p;
96 struct wordsplit ws;
97 int wsflags = WRDSF_DEFFLAGS|WRDSF_DELIM;
98
99 if (!translation_enabled || argc == 0)
100 return;
101 ws.ws_delim = ",";
102 for (i = 0; i < argc; i++) {
103 if (strncmp(argv[i], RESOURCE_ID_PFX, RESOURCE_ID_LEN))
104 continue;
105 if (wordsplit(argv[i] + RESOURCE_ID_LEN, &ws, wsflags))
106 die(EX_SOFTWARE,
107 "error expanding argument %s: %s",
108 argv[i] + RESOURCE_ID_LEN,
109 wordsplit_strerror(&ws));
110 wsflags |= WRDSF_REUSE;
111 for (j = 0, ecnt = 0; j < ws.ws_wordc; j++) {
112 if (!(p = strchr(ws.ws_wordv[j], ':')))
113 continue;
114 *p++ = 0;
115 map = eclat_map_lookup(ws.ws_wordv[j]);
116 if (!map)
117 die(EX_UNAVAILABLE, "no such map: %s",
118 ws.ws_wordv[j]);
119 if (eclat_map_open(map) != eclat_map_ok)
120 die(EX_UNAVAILABLE,
121 "failed to open map %s", ws.ws_wordv[j]);
122 rc = eclat_map_get(map, MAP_DIR, p, &val);
123 if (rc != eclat_map_ok) {
124 die(EX_UNAVAILABLE,
125 "cannot translate %s using map %s: %s",
126 p, ws.ws_wordv[j], eclat_map_strerror(rc));
127 }
128 free(ws.ws_wordv[j]);
129 ws.ws_wordv[j] = val;
130 ecnt++;
131 }
132 if (ecnt == 0)
133 continue;
134 len = RESOURCE_ID_LEN + ws.ws_wordc - 1;
135 for (j = 0; j < ws.ws_wordc; j++)
136 len += strlen(ws.ws_wordv[j]);
137 val = grecs_malloc(len + 1);
138 strcpy(val, RESOURCE_ID_PFX);
139 p = val + RESOURCE_ID_LEN;
140 for (j = 0; j < ws.ws_wordc; j++) {
141 if (j)
142 *p++ = ',';
143 p = eclat_stpcpy(p, ws.ws_wordv[j]);
144 }
145 argv[i] = val;
146 }
147 if (wsflags & WRDSF_REUSE)
148 wordsplit_free(&ws);
149 }
150
151 int
get_scr_cols()152 get_scr_cols()
153 {
154 struct winsize ws;
155
156 ws.ws_col = ws.ws_row = 0;
157 if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0) {
158 const char *p = getenv ("COLUMNS");
159 if (p)
160 ws.ws_col = strtol(p, NULL, 10);
161 }
162 return ws.ws_col ? ws.ws_col : 80;
163 }
164
165
166 void
describe_request_update(eclat_command_env_t * env,int argc,char ** argv,const char * uparm,int n_in,int * n_out)167 describe_request_update(eclat_command_env_t *env, int argc, char **argv,
168 const char *uparm, int n_in, int *n_out)
169 {
170 int i, j, k;
171 struct ec2_request *q = env->request;
172 char *bufptr = NULL;
173 size_t bufsize = 0;
174 struct wordsplit ws;
175 int wsflags;
176 int upn = 0;
177
178 ws.ws_delim = ",";
179 wsflags = WRDSF_DEFFLAGS | WRDSF_DELIM;
180 for (i = 0, j = n_in; i < argc; i++) {
181 char *p = strchr(argv[i], '=');
182 if (!p) {
183 if (uparm) {
184 grecs_asprintf(&bufptr, &bufsize,
185 "%s.%d", uparm, upn++);
186 eclat_request_add_param(q, bufptr, argv[i]);
187 continue;
188 }
189 die(EX_USAGE, "malformed filter: %s", argv[i]);
190 }
191 *p++ = 0;
192 grecs_asprintf(&bufptr, &bufsize, "Filter.%d.Name", j);
193 eclat_request_add_param(q, bufptr, argv[i]);
194
195 if (wordsplit(p, &ws, wsflags))
196 die(EX_SOFTWARE, "wordsplit failed at \"%s\": %s",
197 p, wordsplit_strerror(&ws));
198 wsflags |= WRDSF_REUSE;
199
200 for (k = 0; k < ws.ws_wordc; k++) {
201 grecs_asprintf(&bufptr, &bufsize, "Filter.%d.Value.%d",
202 j, k+1);
203 eclat_request_add_param(q, bufptr, ws.ws_wordv[k]);
204 }
205 ++j;
206 }
207 if (wsflags & WRDSF_REUSE)
208 wordsplit_free(&ws);
209 free(bufptr);
210 if (n_out)
211 *n_out = j;
212 }
213
214 void
describe_request_create(eclat_command_env_t * env,int argc,char ** argv,const char * uparm)215 describe_request_create(eclat_command_env_t *env, int argc, char **argv,
216 const char *uparm)
217 {
218 describe_request_update(env, argc, argv, uparm, 1, NULL);
219 }
220
221 unsigned long max_retry_sleep = 600;
222 unsigned long max_retry_time = 1800;
223
224 int
eclat_send_request(struct ec2_request * orig,struct grecs_node ** ret_tree)225 eclat_send_request(struct ec2_request *orig, struct grecs_node **ret_tree)
226 {
227 char *url;
228 CURLcode res;
229 int ret = 0;
230 struct curl_slist *headers;
231 struct eclat_io *io;
232 time_t endtime;
233 unsigned long tts, t;
234 struct grecs_node *xmltree = NULL;
235
236 endtime = time(NULL) + max_retry_time;
237 tts = 2;
238 while (1) {
239 struct grecs_node *node;
240 struct ec2_request *req;
241
242 io = eclat_io_init(0);
243 if (!io) {
244 err("cannot initialize IO structure");
245 return -1;
246 }
247
248 /* Prepare the request */
249 req = eclat_request_dup(orig);
250 if (req->flags & EC2_RF_POST) {
251 eclat_request_finalize(orig);
252 curl_easy_setopt(io->curl, CURLOPT_POST, 1);
253 curl_easy_setopt(io->curl, CURLOPT_POSTFIELDS,
254 req->postdata);
255 curl_easy_setopt(io->curl, CURLOPT_POSTFIELDSIZE,
256 strlen(req->postdata));
257 }
258 eclat_request_sign(req, secret_key, signature_version);
259 url = eclat_request_to_url(req);
260 curl_easy_setopt(io->curl, CURLOPT_URL, url);
261 debug(ECLAT_DEBCAT_MAIN, 1, ("using URL: %s", url));
262 free(url);
263 headers = NULL;
264 if (req->headers) {
265 struct grecs_list_entry *ep;
266 struct grecs_txtacc *acc;
267 int rc;
268
269 acc = grecs_txtacc_create();
270
271 for (ep = req->headers->head; ep; ep = ep->next) {
272 struct ec2_param *p = ep->data;
273 char *str;
274
275 grecs_txtacc_grow_string(acc, p->name);
276 grecs_txtacc_grow_char(acc, ':');
277 grecs_txtacc_grow_string(acc, p->value);
278 grecs_txtacc_grow_char(acc, 0);
279 str = grecs_txtacc_finish(acc, 0);
280 debug(ECLAT_DEBCAT_MAIN, 1, ("HDR: %s", str));
281
282 headers = curl_slist_append(headers, str);
283 grecs_txtacc_free_string(acc, str);
284 }
285
286 rc = curl_easy_setopt(io->curl, CURLOPT_HTTPHEADER,
287 headers);
288 grecs_txtacc_free(acc);
289
290 if (rc)
291 die(EX_SOFTWARE,
292 "failed to add headers: %s",
293 curl_easy_strerror(rc));
294 }
295
296 if (req->flags & EC2_RF_POST)
297 debug(ECLAT_DEBCAT_MAIN, 1,
298 ("DATA: %s", req->postdata));
299
300 if (dry_run_mode)
301 debug(ECLAT_DEBCAT_MAIN, 1, ("not sending request"));
302 else {
303 grecs_tree_free(xmltree);
304
305 res = curl_easy_perform(io->curl);
306 if (res == CURLE_OK) {
307 xmltree = eclat_io_finish(io);
308 } else {
309 err("CURL: %s", curl_easy_strerror(res));
310 xmltree = NULL;
311 ret = 1;
312 }
313 }
314 eclat_io_free(io);
315
316 curl_slist_free_all(headers);
317 eclat_request_free(req);
318
319 if (ret || time(NULL) >= endtime)
320 break;
321
322 node = grecs_find_node(xmltree, ".Response.Errors.Error.Code");
323 if (!node)
324 break;
325 if (node->type != grecs_node_stmt
326 || node->v.value->type != GRECS_TYPE_STRING) {
327 err("unexpectedly formatted error code");
328 break;
329 }
330
331 if (strcmp(node->v.value->v.string, "RequestLimitExceeded"))
332 break;
333
334 t = random() % tts + 1;
335 warn("request limit exceeded; sleeping for %lu seconds", t);
336 sleep(t);
337 if (tts < max_retry_sleep) {
338 tts <<= 1;
339 if (tts == 0 || tts > max_retry_sleep)
340 tts = max_retry_sleep;
341 }
342 }
343
344 *ret_tree = xmltree;
345 return ret;
346 }
347
348 int
eclat_actcmp(const char * a,const char * b)349 eclat_actcmp(const char *a, const char *b)
350 {
351 int rc = 0;
352 enum { cmp_init, cmp_norm, cmp_upca, cmp_upcb } state = cmp_init;
353
354 while (rc == 0) {
355 if (!*a)
356 return *b ? - *b : 0;
357 if (!*b)
358 return *a ? *a : 0;
359 if (*a == '-') {
360 a++;
361 if (state == cmp_norm)
362 state = cmp_upca;
363 else
364 state = cmp_norm;
365 } else if (*b == '-') {
366 b++;
367 if (state == cmp_norm)
368 state = cmp_upcb;
369 else
370 state = cmp_norm;
371 } else {
372 switch (state) {
373 case cmp_init:
374 rc = toupper(*a) - toupper(*b);
375 break;
376
377 case cmp_norm:
378 rc = *a - *b;
379 break;
380
381 case cmp_upca:
382 rc = toupper(*a) - *b;
383 break;
384
385 case cmp_upcb:
386 rc = *a - toupper(*b);
387 break;
388 }
389 a++;
390 b++;
391 state = cmp_norm;
392 }
393 }
394 return rc;
395 }
396
397 char **available_attrs;
398
399 void
list_attrs(FILE * fp)400 list_attrs(FILE *fp)
401 {
402 size_t ncols = get_scr_cols();
403 int i, col, len;
404 char *delim = "";
405
406 fprintf(fp, "Available attributes are:\n");
407 col = 0;
408 for (i = 0; available_attrs[i]; i++) {
409 len = strlen(delim) + strlen(available_attrs[i]);
410 if (col + len > ncols) {
411 fprintf(fp, ",\n%s", available_attrs[i]);
412 col = strlen(available_attrs[i]);
413 } else
414 col += fprintf(fp, "%s%s", delim, available_attrs[i]);
415 delim = ", ";
416 }
417 fputc('\n', fp);
418 fputc('\n', fp);
419 }
420
421 char *
canonattrname(char ** attrs,const char * arg,char * delim,size_t * plen)422 canonattrname(char **attrs, const char *arg, char *delim, size_t *plen)
423 {
424 size_t len = strlen(arg);
425 int i;
426
427 for (i = 0; attrs[i]; i++) {
428 size_t alen = delim ? strcspn(attrs[i], delim)
429 : strlen(attrs[i]);
430 if (alen == len && strncasecmp(arg, attrs[i], len) == 0) {
431 if (plen)
432 *plen = len;
433 return attrs[i];
434 }
435 }
436 return NULL;
437 }
438
439 char *
read_file(const char * file)440 read_file(const char *file)
441 {
442 char *buf = NULL;
443
444 if (strcmp(file, "-") == 0) {
445 struct grecs_txtacc *acc = grecs_txtacc_create();
446 char inbuf[4096];
447 size_t n;
448
449 while ((n = fread(inbuf, 1, sizeof(inbuf), stdin)) > 0)
450 grecs_txtacc_grow(acc, inbuf, n);
451 grecs_txtacc_grow_char(acc, 0);
452 if (ferror(stdin))
453 die(EX_NOINPUT, "read error");
454 grecs_txtacc_grow_char(acc, 0);
455 buf = grecs_txtacc_finish(acc, 1);
456 grecs_txtacc_free(acc);
457 } else {
458 struct stat st;
459 FILE *fp;
460
461 if (stat(file, &st))
462 die(EX_USAGE, "cannot stat file %s: %s", file,
463 strerror(errno));
464
465 /* FIXME: Use limits.h to check st.st_size */
466 buf = grecs_malloc(st.st_size+1);
467 fp = fopen(file, "r");
468 if (!fp)
469 die(EX_NOINPUT, "cannot open file %s: %s", file,
470 strerror(errno));
471 if (fread(buf, st.st_size, 1, fp) != 1)
472 die(EX_NOINPUT, "error reading from %s: %s", file,
473 strerror(errno));
474 fclose(fp);
475 buf[st.st_size] = 0;
476 }
477
478 return buf;
479 }
480
481 char *instance_store_base_url = "http://169.254.169.254/latest";
482 unsigned short instance_store_port;
483 char *instance_store_document_path = "dynamic/instance-identity/document";
484 char *instance_store_credentials_path = "meta-data/iam/security-credentials";
485
486 static CURL *
get_curl(struct grecs_txtacc * acc)487 get_curl(struct grecs_txtacc *acc)
488 {
489 CURL *curl = instance_store_curl_new(acc);
490
491 eclat_set_curl_trace(curl, debug_level(ECLAT_DEBCAT_CURL));
492 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
493 if (instance_store_port)
494 curl_easy_setopt(curl, CURLOPT_PORT,
495 (long) instance_store_port);
496 return curl;
497 }
498
499 char *
eclat_get_instance_zone()500 eclat_get_instance_zone()
501 {
502 char *doc;
503 struct json_value *obj, *p;
504 char *retval = NULL;
505 CURL *curl;
506 char *url;
507 struct grecs_txtacc *acc;
508
509 acc = grecs_txtacc_create();
510 curl = get_curl(acc);
511 url = path_concat(instance_store_base_url,
512 instance_store_document_path);
513 if (instance_store_read(url, curl))
514 doc = NULL;
515 else {
516 grecs_txtacc_grow_char(acc, 0);
517 doc = grecs_txtacc_finish(acc, 0);
518 }
519 free(url);
520 curl_easy_cleanup(curl);
521 if (!doc)
522 return NULL;
523 obj = json_parse_string(doc, strlen(doc));
524 if (!obj) {
525 char *str = NULL;
526 size_t len = 0;
527 grecs_asprint_locus(&str, &len, &json_err_locus);
528 die(EX_DATAERR, "%s: %s", str, json_err_diag);
529 }
530 p = json_value_lookup(obj, "region");
531 if (p && p->type == json_string)
532 retval = grecs_strdup(p->v.s);
533 grecs_txtacc_free(acc);
534 json_value_free(obj);
535 return retval;
536 }
537
538 void
eclat_get_instance_creds(char * id,char ** access_key_ptr,char ** secret_key_ptr,char ** token_ptr)539 eclat_get_instance_creds(char *id, char **access_key_ptr, char **secret_key_ptr,
540 char **token_ptr)
541 {
542 CURL *curl;
543 char *url = NULL;
544 char *s;
545 char *doc;
546 struct json_value *obj, *p;
547 int err = 0;
548 struct grecs_txtacc *acc;
549
550 acc = grecs_txtacc_create();
551 curl = get_curl(acc);
552 url = path_concat(instance_store_base_url,
553 instance_store_credentials_path);
554 if (id) {
555 s = url;
556 url = path_concat(s, id);
557 free(s);
558 }
559 if (instance_store_read(url, curl))
560 die(EX_UNAVAILABLE, "url %s: not found ", url);
561 else {
562 grecs_txtacc_grow_char(acc, 0);
563 doc = grecs_txtacc_finish(acc, 0);
564 }
565 if (!id) {
566 size_t len = strcspn(doc, "\r\n");
567 doc[len] = 0;
568 s = url;
569 url = path_concat(s, doc);
570 free(s);
571 if (instance_store_read(url, curl))
572 die(EX_UNAVAILABLE, "url %s: not found ", url);
573 else {
574 grecs_txtacc_grow_char(acc, 0);
575 doc = grecs_txtacc_finish(acc, 0);
576 }
577 }
578 free(url);
579
580 obj = json_parse_string(doc, strlen(doc));
581 if (!obj) {
582 char *str = NULL;
583 size_t len = 0;
584 grecs_asprint_locus(&str, &len, &json_err_locus);
585 die(EX_DATAERR, "%s: %s", str, json_err_diag);
586 }
587
588 p = json_value_lookup(obj, "AccessKeyId");
589 if (p && p->type == json_string)
590 *access_key_ptr = grecs_strdup(p->v.s);
591 else
592 err = 1;
593
594 p = json_value_lookup(obj, "SecretAccessKey");
595 if (p && p->type == json_string)
596 *secret_key_ptr = grecs_strdup(p->v.s);
597 else
598 err = 1;
599
600 p = json_value_lookup(obj, "Token");
601 if (p && p->type == json_string)
602 *token_ptr = grecs_strdup(p->v.s);
603 else
604 err = 1;
605
606 grecs_txtacc_free(acc);
607 json_value_free(obj);
608
609 if (err)
610 die(EX_DATAERR, "security credentials missing");
611 }
612