1    	/*
2    	 * Copyright (C) 2015 Dejan Muhamedagic <dejan@hello-penguin.com>
3    	 *
4    	 * This program is free software; you can redistribute it and/or
5    	 * modify it under the terms of the GNU General Public
6    	 * License as published by the Free Software Foundation; either
7    	 * version 2 of the License, or (at your option) any later version.
8    	 *
9    	 * This software 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 GNU
12   	 * General Public License for more details.
13   	 *
14   	 * You should have received a copy of the GNU General Public License along
15   	 * with this program; if not, write to the Free Software Foundation, Inc.,
16   	 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17   	 */
18   	
19   	#include <stdio.h>
20   	#include <string.h>
21   	#include "attr.h"
22   	#include "booth.h"
23   	#include "ticket.h"
24   	#include "pacemaker.h"
25   	
26   	void
27   	print_geostore_usage(void)
28   	{
29   		printf(
30   		"Usage:\n"
31   		"  geostore {list|set|get|delete} [-t ticket] [options] attr [value]\n"
32   		"\n"
33   		"  list:	     List all attributes\n"
34   		"  set:          Set attribute to a value\n"
35   		"  get:          Get attribute's value\n"
36   		"  delete:       Delete attribute\n"
37   		"\n"
38   		"  -t <ticket>   Ticket where attribute resides\n"
39   		"                (required, if more than one ticket is configured)\n"
40   		"\n"
41   		"Options:\n"
42   		"  -c FILE       Specify config file [default " BOOTH_DEFAULT_CONF "]\n"
43   		"                Can be a path or just a name without \".conf\" suffix\n"
44   		"  -s <site>     Connect to a different site\n"
45   		"  -h            Print this help\n"
46   		"\n"
47   		"Examples:\n"
48   		"\n"
49   		"  # geostore list -t ticket-A -s 10.121.8.183\n"
50   		"  # geostore set -s 10.121.8.183 sr_status ACTIVE\n"
51   		"  # geostore get -t ticket-A -s 10.121.8.183 sr_status\n"
52   		"  # geostore delete -s 10.121.8.183 sr_status\n"
53   		"\n"
54   		"See the geostore(8) man page for more details.\n"
55   		);
56   	}
57   	
58   	/*
59   	 * the client side
60   	 */
61   	
62   	/* cl has all the input parameters:
63   	 * ticket, attr name, attr value
64   	 */
65   	
66   	int
67   	test_attr_reply(cmd_result_t reply_code, cmd_request_t cmd)
68   	{
69   		int rv = 0;
70   		const char *op_str = NULL;
71   	
72   		switch (cmd) {
73   		case ATTR_SET:	op_str = "set";		break;
74   		case ATTR_GET:	op_str = "get";		break;
75   		case ATTR_LIST:	op_str = "list";	break;
76   		case ATTR_DEL:	op_str = "delete";	break;
77   		default:
78   			log_error("internal error reading reply result!");
79   			return -1;
80   		}
81   	
82   		switch (reply_code) {
83   		case RLT_ASYNC:
84   			log_info("%s command sent, result will be returned "
85   				 "asynchronously.", op_str);
86   			rv = 0;
87   			break;
88   	
89   		case RLT_SYNC_SUCC:
90   		case RLT_SUCCESS:
91   			if (cmd == ATTR_SET)
92   				log_info("%s succeeded!", op_str);
93   			rv = 0;
94   			break;
95   	
96   		case RLT_SYNC_FAIL:
97   			log_info("%s failed!", op_str);
98   			rv = -1;
99   			break;
100  	
101  		case RLT_INVALID_ARG:
102  			log_error("ticket \"%s\" does not exist",
103  					cl.attr_msg.attr.tkt_id);
104  			rv = 1;
105  			break;
106  	
107  		case RLT_NO_SUCH_ATTR:
108  			log_error("attribute \"%s\" not set",
109  					cl.attr_msg.attr.name);
110  			rv = 1;
111  			break;
112  	
113  		case RLT_AUTH:
114  			log_error("authentication error");
115  			rv = -1;
116  			break;
117  	
118  		default:
119  			log_error("got an error code: %x", rv);
120  			rv = -1;
121  		}
122  		return rv;
123  	}
124  	
125  	/* read the server's reply
126  	 * need to first get the header which contains the length of the
127  	 * reply
128  	 * return codes:
129  	 *   -2: header not received
130  	 *   -1: header received, but message too short
131  	 *   >=0: success
132  	 */
133  	static int
134  	read_server_reply(struct booth_transport const *tpt, struct booth_site *site,
135  	                  char *msg)
136  	{
137  		struct boothc_header *header;
138  		int rv;
139  		int len;
140  	
141  		header = (struct boothc_header *)msg;
142  		rv = tpt->recv(site, header, sizeof(*header));
143  		if (rv < 0) {
144  			return -2;
145  		}
146  		len = ntohl(header->length);
147  		rv = tpt->recv(site, msg+sizeof(*header), len-sizeof(*header));
148  		if (rv < 0) {
149  			return -1;
150  		}
151  		return rv;
152  	}
153  	
154  	int
155  	do_attr_command(struct booth_config *conf, cmd_request_t cmd)
156  	{
157  		struct booth_site *site = NULL;
158  		struct boothc_header *header;
159  		struct booth_transport const *tpt = NULL;
160  		int len, rv = -1;
161  		char *msg = NULL;
162  	
163  		if (!*cl.site)
164  			site = local;
165  		else {
166  			if (!find_site_by_name(conf, cl.site, &site, 1)) {
167  				log_error("Site \"%s\" not configured.", cl.site);
168  				goto out_close;
169  			}
170  		}
171  	
172  		if (site->type == ARBITRATOR) {
173  			if (site == local) {
174  				log_error("We're just an arbitrator, no attributes here.");
175  			} else {
176  				log_error("%s is just an arbitrator, no attributes there.", cl.site);
177  			}
178  			goto out_close;
179  		}
180  	
181  		tpt = booth_transport + TCP;
182  	
183  		init_header(&cl.attr_msg.header, cmd, 0, cl.options, 0, 0,
184  			sizeof(cl.attr_msg));
185  	
186  		rv = tpt->open(site);
187  		if (rv < 0)
188  			goto out_close;
189  	
190  		rv = tpt->send(conf, site, &cl.attr_msg, sendmsglen(&cl.attr_msg));
191  		if (rv < 0) {
192  			goto out_close;
193  		}
194  	
195  		msg = malloc(MAX_MSG_LEN);
196  		if (!msg) {
197  			log_error("out of memory");
198  			rv = -1;
199  			goto out_close;
200  		}
201  	
202  		rv = read_server_reply(tpt, site, msg);
203  		header = (struct boothc_header *)msg;
204  		if (rv < 0) {
205  			if (rv == -1)
206  				(void)test_attr_reply(ntohl(header->result), cmd);
207  			goto out_close;
208  		}
209  		len = ntohl(header->length);
210  	
211  		if (check_boothc_header(header, len) < 0) {
212  			log_error("message from %s receive error", site_string(site));
213  			rv = -1;
214  			goto out_close;
215  		}
216  	
217  		if (check_auth(conf, site, msg, len)) {
218  			log_error("%s failed to authenticate", site_string(site));
219  			rv = -1;
220  			goto out_close;
221  		}
222  		rv = test_attr_reply(ntohl(header->result), cmd);
223  	
224  	out_close:
225  		if (tpt && site)
226  			tpt->close(site);
227  		if (msg)
228  			free(msg);
229  		return rv;
230  	}
231  	
232  	/*
233  	 * the server side
234  	 */
235  	
236  	/* need to invert gboolean, our success is 0
237  	 */
238  	#define gbool2rlt(i) (i ? RLT_SUCCESS : RLT_SYNC_FAIL)
239  	
240  	static void
241  	free_geo_attr(gpointer data)
242  	{
243  		struct geo_attr *a = (struct geo_attr *)data;
244  	
245  		if (!a)
246  			return;
247  		g_free(a->val);
248  		g_free(a);
249  	}
250  	
251  	int
252  	store_geo_attr(struct ticket_config *tk, const char *name, const char *val,
253  	               int notime)
254  	{
255  		struct geo_attr *a;
256  		GDestroyNotify free_geo_attr_notify = free_geo_attr;
257  	
258  		if (!tk)
259  			return -1;
260  		/*
261  		 * allocate new, if attr doesn't already exist
262  		 * copy the attribute value
263  		 * send status
264  		 */
265  		if (!tk->attr)
266  			tk->attr = g_hash_table_new_full(g_str_hash, g_str_equal,
267  				g_free, free_geo_attr_notify);
268  		if (!tk->attr) {
269  			log_error("out of memory");
270  			return -1;
271  		}
272  	
273  		if (strnlen(name, BOOTH_NAME_LEN) == BOOTH_NAME_LEN)
274  			tk_log_warn("name of the attribute too long (%d+ bytes), skipped",
275  				 BOOTH_NAME_LEN);
276  		else if (strnlen(val, BOOTH_ATTRVAL_LEN) == BOOTH_ATTRVAL_LEN)
277  			tk_log_warn("value of the attribute too long (%d+ bytes), skipped",
278  				 BOOTH_ATTRVAL_LEN);
279  		else {
280  			a = (struct geo_attr *)calloc(1, sizeof(struct geo_attr));
281  			if (!a) {
282  				log_error("out of memory");
283  				return -1;
284  			}
285  	
286  			a->val = g_strdup(val);
287  			if (!notime)
288  				get_time(&a->update_ts);
289  	
290  			g_hash_table_insert(tk->attr,
291  				g_strdup(name), a);
292  		}
293  	
294  		return 0;
295  	}
296  	
297  	static cmd_result_t
298  	attr_set(struct ticket_config *tk, struct boothc_attr_msg *msg)
299  	{
300  		int rc;
301  	
302  		rc = store_geo_attr(tk, msg->attr.name, msg->attr.val, 0);
303  		if (rc) {
304  			return RLT_SYNC_FAIL;
305  		}
306  		(void)pcmk_handler.set_attr(tk, msg->attr.name, msg->attr.val);
307  		return RLT_SUCCESS;
308  	}
309  	
310  	static cmd_result_t
311  	attr_del(struct ticket_config *tk, struct boothc_attr_msg *msg)
312  	{
313  		gboolean rv;
314  		gpointer orig_key, value;
315  	
316  		/*
317  		 * lookup attr
318  		 * deallocate, if found
319  		 * send status
320  		 */
321  		if (!tk->attr)
322  			return RLT_NO_SUCH_ATTR;
323  	
324  		rv = g_hash_table_lookup_extended(tk->attr, msg->attr.name,
325  				&orig_key, &value);
326  		if (!rv)
327  			return RLT_NO_SUCH_ATTR;
328  	
329  		rv = g_hash_table_remove(tk->attr, msg->attr.name);
330  	
331  		(void)pcmk_handler.del_attr(tk, msg->attr.name);
332  	
333  		return gbool2rlt(rv);
334  	}
335  	
336  	static void
337  	append_attr(gpointer key, gpointer value, gpointer user_data)
338  	{
339  		char *attr_name = (char *)key;
340  		struct geo_attr *a = (struct geo_attr *)value;
341  		GString *data = (GString *)user_data;
342  		char time_str[64];
343  		time_t ts;
344  	
345  		if (is_time_set(&a->update_ts)) {
346  			ts = wall_ts(&a->update_ts);
347  			strftime(time_str, sizeof(time_str), "%F %T",
348  					localtime(&ts));
349  		} else {
350  			time_str[0] = '\0';
351  		}
352  		g_string_append_printf(data, "%s %s %s\n",
353  			attr_name, a->val, time_str);
354  	}
355  	
356  	
357  	static cmd_result_t
358  	attr_get(struct booth_config *conf, struct ticket_config *tk, int fd,
359  	         struct boothc_attr_msg *msg)
360  	{
361  		cmd_result_t rv = RLT_SUCCESS;
362  		struct boothc_hdr_msg hdr;
363  		struct geo_attr *a;
364  		GString *attr_val;
365  	
366  		/*
367  		 * lookup attr
368  		 * send value
369  		 */
370  		if (!tk->attr) {
371  			return RLT_NO_SUCH_ATTR;
372  		}
373  	
374  		a = (struct geo_attr *)g_hash_table_lookup(tk->attr, msg->attr.name);
375  		if (!a) {
376  			return RLT_NO_SUCH_ATTR;
377  		}
378  	
379  		attr_val = g_string_new(NULL);
380  		if (!attr_val) {
381  			log_error("out of memory");
382  			return RLT_SYNC_FAIL;
383  		}
384  		g_string_printf(attr_val, "%s\n", a->val);
385  		init_header(&hdr.header, ATTR_GET, 0, 0, RLT_SUCCESS, 0,
386  			sizeof(hdr) + attr_val->len);
387  	
388  		if (send_header_plus(conf, fd, &hdr, attr_val->str, attr_val->len)) {
389  			rv = RLT_SYNC_FAIL;
390  		}
391  	
392  		if (attr_val) {
393  			g_string_free(attr_val, TRUE);
394  		}
395  	
396  		return rv;
397  	}
398  	
399  	static cmd_result_t
400  	attr_list(struct booth_config *conf, struct ticket_config *tk, int fd,
401  	          struct boothc_attr_msg *msg)
402  	{
403  		GString *data;
404  		cmd_result_t rv;
405  		struct boothc_hdr_msg hdr;
406  	
407  		/*
408  		 * list all attributes for the ticket
409  		 * send the list
410  		 */
411  		data = g_string_sized_new(512);
(1) Event cond_false: Condition "!data", taking false branch.
412  		if (!data) {
413  			log_error("out of memory");
414  			return RLT_SYNC_FAIL;
(2) Event if_end: End of if statement.
415  		}
(3) Event cond_true: Condition "tk->attr", taking true branch.
416  		if (tk->attr) {
417  			g_hash_table_foreach(tk->attr, append_attr, data);
418  		}
419  	
420  		init_header(&hdr.header, ATTR_LIST, 0, 0, RLT_SUCCESS, 0,
421  			sizeof(hdr) + data->len);
(4) Event tainted_data_return: Called function "send_header_plus(conf, fd, &hdr, data->str, data->len)", and a possible return value may be less than zero.
(5) Event cast_underflow: An assign of a possibly negative number to an unsigned type, which might trigger an underflow.
Also see events: [return_overflow]
422  		rv = send_header_plus(conf, fd, &hdr, data->str, data->len);
423  	
(6) Event cond_true: Condition "data", taking true branch.
424  		if (data) {
(7) Event cond_true: Condition "1", taking true branch.
(8) Event cond_true: Condition "1 /* !0 */", taking true branch.
425  			g_string_free(data, TRUE);
426  		}
427  	
(9) Event return_overflow: "rv", which might have underflowed, is returned from the function.
Also see events: [tainted_data_return][cast_underflow]
428  		return rv;
429  	}
430  	
431  	int
432  	process_attr_request(struct booth_config *conf, struct client *req_client,
433  	                     void *buf)
434  	{
435  		cmd_result_t rv = RLT_SYNC_FAIL;
436  		struct ticket_config *tk;
437  		int cmd;
438  		struct boothc_attr_msg *msg;
439  		struct boothc_hdr_msg hdr;
440  	
441  		msg = (struct boothc_attr_msg *)buf;
442  		cmd = ntohl(msg->header.cmd);
443  		if (!check_ticket(conf, msg->attr.tkt_id, &tk)) {
444  			log_warn("client referenced unknown ticket %s",
445  					msg->attr.tkt_id);
446  			rv = RLT_INVALID_ARG;
447  			goto reply_now;
448  		}
449  	
450  		switch (cmd) {
451  		case ATTR_LIST:
452  			rv = attr_list(conf, tk, req_client->fd, msg);
453  			if (rv) {
454  				goto reply_now;
455  			}
456  	
457  			return 1;
458  		case ATTR_GET:
459  			rv = attr_get(conf, tk, req_client->fd, msg);
460  			if (rv) {
461  				goto reply_now;
462  			}
463  	
464  			return 1;
465  		case ATTR_SET:
466  			rv = attr_set(tk, msg);
467  			break;
468  		case ATTR_DEL:
469  			rv = attr_del(tk, msg);
470  			break;
471  		}
472  	
473  	reply_now:
474  		init_header(&hdr.header, CL_RESULT, 0, 0, rv, 0, sizeof(hdr));
475  		send_header_plus(conf, req_client->fd, &hdr, NULL, 0);
476  		return 1;
477  	}
478  	
479  	/* read attr message from another site */
480  	
481  	/* this is a NOOP and it should never be invoked
482  	 * only clients retrieve/manage attributes and they connect
483  	 * directly to the target site
484  	 */
485  	int
486  	attr_recv(struct booth_config *conf, void *buf, struct booth_site *source)
487  	{
488  		struct boothc_attr_msg *msg;
489  		struct ticket_config *tk;
490  	
491  		msg = (struct boothc_attr_msg *)buf;
492  	
493  		log_warn("unexpected attribute message from %s",
494  				site_string(source));
495  	
496  		if (!check_ticket(conf, msg->attr.tkt_id, &tk)) {
497  			log_warn("got invalid ticket name %s from %s",
498  					msg->attr.tkt_id, site_string(source));
499  			source->invalid_cnt++;
500  			return -1;
501  		}
502  	
503  		return 0;
504  	}
505