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