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