1 #if HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #include "shm.h"
6 #include "shm_client.h"
7 #include "util.h"
8 
9 #include <argp.h>
10 #include <mysql/mysql.h>
11 
12 #include <netinet/in.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 
19 #define HOSTNAME_LEN 255
20 #define STRINGIFY(s) #s
21 #define STR(s) STRINGIFY(s)
22 
23 const char *argp_program_version = PACKAGE_STRING;
24 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
25 
26 struct ctx_s {
27 	int foreground;
28 	char *config_file;
29 	char *prefix;
30 	MYSQL *dbh;
31 	MYSQL_STMT *stmt;
32 	MYSQL_BIND bind[7];
33 	struct {
34 		long long int timestamp;
35 		char hostname[HOSTNAME_LEN];
36 		unsigned long hostname_len;
37 		char iface[IFNAMSIZ];
38 		unsigned long iface_len;
39 		int vlan_tag;
40 		char mac[ETHER_ADDR_LEN];
41 		unsigned long mac_len;
42 		char ip[16];
43 		unsigned long ip_len;
44 		int origin;
45 	} bind_data;
46 };
47 
48 static const char sql_create_log_template[] = "\
49 CREATE TABLE IF NOT EXISTS `%slog` (\
50 	`tstamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\
51 	`hostname` varchar(" STR(HOSTNAME_LEN) ") NOT NULL DEFAULT \"localhost\",\
52 	`interface` varchar(16) NOT NULL,\
53 	`vlan_tag` int(11) NOT NULL DEFAULT 0,\
54 	`mac_address` BINARY(6) NOT NULL,\
55 	`ip_address` VARBINARY(16) NOT NULL,\
56 	`origin_id` INT(11) NOT NULL,\
57 \
58 	KEY `interface` (`interface`),\
59 	KEY `vlan_tag` (`vlan_tag`),\
60 	KEY `mac_address` (`mac_address`),\
61 	KEY `interface_vlan_tag` (`interface`,`vlan_tag`)\
62 )";
63 
64 static const char sql_create_origin_template[] = "\
65 CREATE TABLE IF NOT EXISTS `%sorigin` (\
66 	`id` INT(11) NOT NULL,\
67 	`name` VARCHAR(16) NOT NULL,\
68 	`description` VARCHAR(255) NOT NULL,\
69 \
70 	PRIMARY KEY (`id`)\
71 )";
72 
73 static const char sql_create_plaintext_template[] = "\
74 CREATE OR REPLACE VIEW `%slog_plaintext` AS \
75 SELECT \
76 	l.`tstamp`, \
77 	l.`hostname`, \
78 	l.`interface`, \
79 	l.`vlan_tag`, \
80 	HEX(l.`mac_address`) AS `mac_address`, \
81 	HEX(l.`ip_address`) AS `ip_address`, \
82 	o.`name` AS `origin` \
83 FROM `%slog` AS l \
84 INNER JOIN `%sorigin` as o \
85 	ON o.`id` = l.`origin_id`";
86 
87 static const char sql_insert_log_template[] = "\
88 INSERT INTO `%slog` (\
89 	`tstamp`, `hostname`, `interface`, `vlan_tag`, `mac_address`, `ip_address`, `origin_id`\
90 ) \
91 VALUES(\
92 	FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?\
93 )";
94 
95 static const char sql_insert_origin_template[] = "\
96 INSERT INTO `%sorigin` (\
97 	`id`, `name`, `description`\
98 ) \
99 VALUES(\
100 	%u, '%s', '%s'\
101 )";
102 
malloc_c(size_t size)103 static inline void *malloc_c(size_t size)
104 {
105 	void *data = malloc(size);
106 	if (!data) {
107 		log_msg(LOG_ERR, "Error allocating memory");
108 	}
109 	return data;
110 }
111 
parse_opt(int key,char * arg,struct argp_state * state)112 static error_t parse_opt(int key, char *arg, struct argp_state *state)
113 {
114 	struct ctx_s *ctx;
115 
116 	ctx = (struct ctx_s *)state->input;
117 	switch (key) {
118 	case 'f':
119 		ctx->foreground = 1;
120 		break;
121 	case 'p':
122 		ctx->prefix = arg;
123 		break;
124 	case 'c':
125 		ctx->config_file = arg;
126 		break;
127 	default:
128 		return ARGP_ERR_UNKNOWN;
129 		break;
130 	}
131 	return 0;
132 }
133 
mysql_simple_query(MYSQL * dbh,const char * format,...)134 static void mysql_simple_query(MYSQL *dbh, const char *format, ...)
135 {
136 	va_list pvar;
137 	char buf[BUFSIZ];
138 
139 	va_start(pvar, format);
140 	vsnprintf(buf, sizeof(buf), format, pvar);
141 	va_end(pvar);
142 
143 	if (mysql_query(dbh, buf)) {
144 		log_msg(LOG_ERR, "Error executing query: %s", mysql_error(dbh));
145 	}
146 }
147 
mysql_init_tables(MYSQL * dbh,char * prefix)148 static void mysql_init_tables(MYSQL *dbh, char *prefix)
149 {
150 	int i;
151 
152 	mysql_simple_query(dbh, sql_create_log_template, prefix);
153 	mysql_simple_query(dbh, sql_create_origin_template, prefix);
154 
155 	if (!mysql_warning_count(dbh)) {
156 		for (i = 0; pkt_origin_str[i]; i++) {
157 			mysql_simple_query(dbh, sql_insert_origin_template,
158 				prefix, i, pkt_origin_str[i], pkt_origin_desc[i]);
159 		}
160 	}
161 
162 	mysql_simple_query(dbh, sql_create_plaintext_template, prefix, prefix, prefix);
163 }
164 
stmt_init(struct ctx_s * data)165 void stmt_init(struct ctx_s *data)
166 {
167 	int rc;
168 	int len;
169 	char *buf;
170 
171 	data->stmt = mysql_stmt_init(data->dbh);
172 	if (!data->stmt) {
173 		log_msg(LOG_ERR, "Error allocating MySQL statement object");
174 	}
175 
176 	len = sizeof(sql_insert_log_template) + strlen(data->prefix);
177 	buf = (char *)malloc_c(len);
178 	snprintf(buf, len, sql_insert_log_template, data->prefix);
179 
180 	rc = mysql_stmt_prepare(data->stmt, buf, strnlen(buf, len));
181 	if (rc) {
182 		log_msg(LOG_ERR, "Error preparing MySQL statement object: %s",
183 			mysql_stmt_error(data->stmt));
184 	}
185 	free(buf);
186 
187 	if (mysql_stmt_bind_param(data->stmt, data->bind)) {
188 		log_msg(LOG_ERR, "Error binding MySQL statement object: %s",
189 			mysql_stmt_error(data->stmt));
190 	}
191 }
192 
db_connect(struct ctx_s * data)193 int db_connect(struct ctx_s *data)
194 {
195 	int rc;
196 
197 	data->dbh = mysql_init(data->dbh);
198 	if (!data->dbh) {
199 		log_msg(LOG_ERR, "Error allocating MySQL object");
200 	}
201 
202 	if (data->config_file) {
203 		rc = mysql_options(data->dbh, MYSQL_READ_DEFAULT_FILE, data->config_file);
204 		if (rc) {
205 			log_msg(LOG_ERR, "Failed to read config file %s: %s",
206 				data->config_file, mysql_error(data->dbh));
207 		}
208 	}
209 
210 	rc = mysql_options(data->dbh, MYSQL_READ_DEFAULT_GROUP, PACKAGE);
211 	if (rc) {
212 		log_msg(LOG_ERR, "Failed to read [" PACKAGE "] section from my.cnf: %s",
213 			mysql_error(data->dbh));
214 	}
215 
216 	if (!mysql_real_connect(data->dbh, NULL, NULL, NULL, NULL, 0, NULL, 0)) {
217 		log_msg(LOG_WARNING, "Failed to connect to database: %s",
218 			mysql_error(data->dbh));
219 		return -1;
220 	}
221 
222 	mysql_init_tables(data->dbh, data->prefix);
223 	stmt_init(data);
224 
225 	return 0;
226 }
227 
db_disconnect(struct ctx_s * data)228 void db_disconnect(struct ctx_s *data)
229 {
230 	if (data->stmt) {
231 		mysql_stmt_close(data->stmt);
232 		data->stmt = NULL;
233 	}
234 	mysql_close(data->dbh);
235 	data->dbh = NULL;
236 }
237 
db_reconnect(struct ctx_s * data)238 static inline void db_reconnect(struct ctx_s *data)
239 {
240 	while (1) {
241 		if (data->dbh) {
242 			db_disconnect(data);
243 		}
244 		if (!db_connect(data)) {
245 			break;
246 		}
247 		sleep(1);
248 	}
249 }
250 
bind_init(struct ctx_s * data)251 void bind_init(struct ctx_s *data)
252 {
253 	memset(data->bind, 0, sizeof(data->bind));
254 
255 	data->bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
256 	data->bind[0].buffer = &data->bind_data.timestamp;
257 
258 	data->bind[1].buffer_type = MYSQL_TYPE_STRING;
259 	data->bind[1].buffer = &data->bind_data.hostname;
260 	data->bind[1].length = &data->bind_data.hostname_len;
261 
262 	data->bind[2].buffer_type = MYSQL_TYPE_STRING;
263 	data->bind[2].buffer = &data->bind_data.iface;
264 	data->bind[2].length = &data->bind_data.iface_len;
265 
266 	data->bind[3].buffer_type = MYSQL_TYPE_LONG;
267 	data->bind[3].buffer = &data->bind_data.vlan_tag;
268 
269 	data->bind[4].buffer_type = MYSQL_TYPE_BLOB;
270 	data->bind[4].buffer = &data->bind_data.mac;
271 	data->bind[4].length = &data->bind_data.mac_len;
272 
273 	data->bind[5].buffer_type = MYSQL_TYPE_BLOB;
274 	data->bind[5].buffer = &data->bind_data.ip;
275 	data->bind[5].length = &data->bind_data.ip_len;
276 
277 	data->bind[6].buffer_type = MYSQL_TYPE_LONG;
278 	data->bind[6].buffer = &data->bind_data.origin;
279 }
280 
process_entry(struct shm_log_entry * e,void * arg)281 void process_entry(struct shm_log_entry *e, void *arg)
282 {
283 	struct ctx_s *data;
284 
285 	data = (struct ctx_s *)arg;
286 
287 	data->bind_data.timestamp = e->timestamp;
288 	memcpy(data->bind_data.iface, e->interface, sizeof(data->bind_data.iface));
289 	data->bind_data.iface_len =
290 		strnlen(data->bind_data.iface, sizeof(data->bind_data.iface));
291 	data->bind_data.vlan_tag = e->vlan_tag;
292 	memcpy(data->bind_data.mac, e->mac_address, sizeof(e->mac_address));
293 	data->bind_data.mac_len = sizeof(data->bind_data.mac);
294 	memcpy(data->bind_data.ip, e->ip_address, e->ip_len);
295 	data->bind_data.ip_len = e->ip_len;
296 	data->bind_data.origin = e->origin;
297 
298 	while (1) {
299 		if (!mysql_stmt_execute(data->stmt)) {
300 			return;
301 		}
302 		log_msg(LOG_WARNING, "Error inserting data to MySQL database: %s\n",
303 			mysql_stmt_error(data->stmt));
304 
305 		db_reconnect(data);
306 	}
307 }
308 
get_hostname(char * hostname,unsigned long * len)309 static void get_hostname(char *hostname, unsigned long *len)
310 {
311 	if (gethostname(hostname, *len)) {
312 		log_msg(LOG_ERR, "Error gethostbyname failed");
313 	}
314 
315 	*len = strnlen(hostname, *len);
316 }
317 
main(int argc,char * argv[])318 int main(int argc, char *argv[])
319 {
320 	int rc;
321 	struct ctx_s ctx;
322 	struct argp_option options[] = {
323 		{ "foreground", 'f', 0, 0, "Start as a foreground process", 0 },
324 		{ "prefix", 'p', "STR", 0, "Prepend STR_ prefix to table names", 0 },
325 		{ "config", 'c', "FILE", 0, "Use FILE for MySQL config", 0 }, { 0 }
326 	};
327 	char doc[] = "FIXME\vFIXME";
328 	struct argp argp = { options, parse_opt, NULL, doc, NULL, NULL, NULL };
329 
330 	memset(&ctx, 0, sizeof(ctx));
331 	ctx.prefix = "";
332 
333 	log_open("addrwatch_mysql");
334 
335 	argp_parse(&argp, argc, argv, 0, NULL, &ctx);
336 
337 	ctx.bind_data.hostname_len = sizeof(ctx.bind_data.hostname);
338 	get_hostname(ctx.bind_data.hostname, &ctx.bind_data.hostname_len);
339 
340 	rc = mysql_library_init(0, NULL, NULL);
341 	if (rc) {
342 		log_msg(LOG_ERR, "Error initializing MySQL library (%d)", rc);
343 	}
344 
345 	bind_init(&ctx);
346 	db_reconnect(&ctx);
347 
348 	if (!ctx.foreground) {
349 		log_syslog_only(1);
350 		if (daemon(0, 0)) {
351 			log_msg(LOG_ERR, "Failed to become daemon: %s", strerror(errno));
352 		}
353 	}
354 
355 	main_loop(process_entry, &ctx);
356 
357 	mysql_library_end();
358 
359 	log_close();
360 }
361