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