1 /**
2  * logctl - a tool to access lumberjack logs in MongoDB
3  *  ... and potentially other sources in the future.
4  *
5  * Copyright 2012 Ulrike Gerhards and Adiscon GmbH.
6  *
7  * Copyright 2017 Hugo Soszynski and aDvens
8  *
9  * long		short
10 
11  * level	l	read records with level x
12  * severity	s	read records with severity x
13  * ret		r	number of records to return
14  * skip		k	number of records to skip
15  * sys		y	read records of system x
16  * msg		m	read records with message containing x
17  * datef	f	read records starting on time received x
18  * dateu	u	read records until time received x
19  *
20  * examples:
21  *
22  * logctl -f 15/05/2012-12:00:00 -u 15/05/2012-12:37:00
23  * logctl -s 50 --ret 10
24  * logctl -m "closed"
25  * logctl -l "INFO"
26  * logctl -s 3
27  * logctl -y "ubuntu"
28  *
29  * This file is part of rsyslog.
30  *
31  * Licensed under the Apache License, Version 2.0 (the "License");
32  * you may not use this file except in compliance with the License.
33  * You may obtain a copy of the License at
34  *
35  *       http://www.apache.org/licenses/LICENSE-2.0
36  *       -or-
37  *       see COPYING.ASL20 in the source distribution
38  *
39  * Unless required by applicable law or agreed to in writing, software
40  * distributed under the License is distributed on an "AS IS" BASIS,
41  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42  * See the License for the specific language governing permissions and
43  * limitations under the License.
44  */
45 #include "config.h"
46 #define _XOPEN_SOURCE 700 /* Need to define POSIX version to use strptime() */
47 #include <stdio.h>
48 #include <errno.h>
49 #include <string.h>
50 #include <stdlib.h>
51 #include <time.h>
52 #include <getopt.h>
53 #include <unistd.h>
54 
55 /* we need this to avoid issues with older versions of libbson */
56 #ifdef __GNUC__
57 #pragma GCC diagnostic push
58 #pragma GCC diagnostic ignored "-Wpragmas"
59 #pragma GCC diagnostic ignored "-Wunknown-attributes"
60 #pragma GCC diagnostic ignored "-Wexpansion-to-defined"
61 #endif
62 #include <mongoc.h>
63 #include <bson.h>
64 #ifdef __GNUC__
65 #pragma GCC diagnostic pop
66 #endif
67 
68 #define N 80
69 
70 static struct option long_options[] =
71 	{
72 		{"level",    required_argument, NULL, 'l'},
73 		{"severity", required_argument, NULL, 's'},
74 		{"ret",      required_argument, NULL, 'r'},
75 		{"skip",     required_argument, NULL, 'k'},
76 		{"sys",      required_argument, NULL, 'y'},
77 		{"msg",      required_argument, NULL, 'm'},
78 		{"datef",    required_argument, NULL, 'f'},
79 		{"dateu",    required_argument, NULL, 'u'},
80 		{NULL, 0,                       NULL, 0}
81 	};
82 
83 struct queryopt
84 {
85 	int32_t e_sever;
86 	int32_t e_ret;
87 	int32_t e_skip;
88 	char* e_date;
89 	char* e_level;
90 	char* e_msg;
91 	char* e_sys;
92 	char* e_dateu;
93 	int bsever;
94 	int blevel;
95 	int bskip;
96 	int bret;
97 	int bsys;
98 	int bmsg;
99 	int bdate;
100 	int bdatef;
101 	int bdateu;
102 };
103 
104 struct ofields
105 {
106 	const char* msg;
107 	const char* syslog_tag;
108 	const char* prog;
109 	char* date;
110 	int64_t date_r;
111 };
112 
113 struct query_doc
114 {
115 	bson_t* query;
116 };
117 
118 struct select_doc
119 {
120 	bson_t* select;
121 };
122 
123 struct db_connect
124 {
125 	mongoc_client_t* conn;
126 };
127 
128 struct db_collection
129 {
130 	mongoc_collection_t* collection;
131 };
132 
133 struct db_cursor
134 {
135 	mongoc_cursor_t* cursor;
136 };
137 
138 struct results
139 {
140 	const bson_t* result;
141 };
142 
143 
formater(struct ofields * fields)144 static void formater(struct ofields* fields)
145 {
146 	char str[N];
147 	time_t rtime;
148 	struct tm now;
149 
150 	rtime = (time_t) (fields->date_r / 1000);
151 	strftime (str, N, "%b %d %H:%M:%S", gmtime_r (&rtime, &now));
152 	printf ("%s  %s %s %s\n", str, fields->prog, fields->syslog_tag,
153 		fields->msg);
154 }
155 
get_data(struct results * res)156 static struct ofields* get_data(struct results* res)
157 {
158 	struct ofields* fields;
159 	const char* msg;
160 	const char* prog;
161 	const char* syslog_tag;
162 	int64_t date_r;
163 	bson_iter_t c;
164 
165 	fields = malloc (sizeof (struct ofields));
166 	bson_iter_init_find (&c, res->result, "msg");
167 	if (!(msg = bson_iter_utf8 (&c, NULL)))
168 	{
169 		perror ("bson_cursor_get_string()");
170 		exit (1);
171 	}
172 
173 	bson_iter_init_find (&c, res->result, "sys");
174 	if (!(prog = bson_iter_utf8 (&c, NULL)))
175 	{
176 		perror ("bson_cursor_get_string()");
177 		exit (1);
178 	}
179 
180 	bson_iter_init_find (&c, res->result, "syslog_tag");
181 	if (!(syslog_tag = bson_iter_utf8 (&c, NULL)))
182 	{
183 		perror ("bson_cursor_get_string()");
184 		exit (1);
185 	}
186 
187 	bson_iter_init_find (&c, res->result, "time_rcvd");
188 	if (!(date_r = bson_iter_date_time (&c)))
189 	{
190 		perror ("bson_cursor_get_utc_datetime()");
191 		exit (1);
192 	}
193 
194 	fields->msg = msg;
195 	fields->prog = prog;
196 	fields->syslog_tag = syslog_tag;
197 	fields->date_r = date_r;
198 
199 	return fields;
200 }
201 
getoptions(int argc,char * argv[],struct queryopt * opt)202 static void getoptions(int argc, char* argv[], struct queryopt* opt)
203 {
204 	int iarg;
205 
206 	while ((iarg = getopt_long (argc, argv, "l:s:r:k:y:f:u:m:",
207 				    long_options, NULL)) != -1)
208 	{
209 		/* check to see if a single character or long option came through */
210 		switch (iarg)
211 		{
212 			/* short option 's' */
213 			case 's':
214 				opt->bsever = 1;
215 				opt->e_sever = atoi (optarg);
216 				break;
217 				/* short option 'r' */
218 			case 'r':
219 				opt->bret = 1;
220 				opt->e_ret = atoi (optarg);
221 				break;
222 				/* short option 'f' : date from */
223 			case 'f':
224 				opt->bdate = 1;
225 				opt->bdatef = 1;
226 				opt->e_date = optarg;
227 				break;
228 				/* short option 'u': date until */
229 			case 'u':
230 				opt->bdate = 1;
231 				opt->bdateu = 1;
232 				opt->e_dateu = optarg;
233 				break;
234 				/* short option 'k' */
235 			case 'k':
236 				opt->bskip = 1;
237 				opt->e_skip = atoi (optarg);
238 				break;
239 				/* short option 'l' */
240 			case 'l':
241 				opt->blevel = 1;
242 				opt->e_level = optarg;
243 				break;
244 				/* short option 'm' */
245 			case 'm':
246 				opt->bmsg = 1;
247 				opt->e_msg = optarg;
248 				break;
249 				/* short option 'y' */
250 			case 'y':
251 				opt->bsys = 1;
252 				opt->e_sys = optarg;
253 				break;
254 			default:
255 				break;
256 		}                                /* end switch iarg */
257 	}                                        /* end while */
258 
259 }                                                /* end void getoptions */
260 
create_select(void)261 static struct select_doc* create_select(void)
262 /* BSON object indicating the fields to return */
263 {
264 	struct select_doc* s_doc;
265 
266 	s_doc = malloc (sizeof (struct select_doc));
267 	s_doc->select = bson_new ();
268 	bson_append_utf8 (s_doc->select, "syslog_tag", 10, "s", 1);
269 	bson_append_utf8 (s_doc->select, "msg", 3, "ERROR", 5);
270 	bson_append_utf8 (s_doc->select, "sys", 3, "sys", 3);
271 	bson_append_date_time (s_doc->select, "time_rcvd", 9, 1ll);
272 	return s_doc;
273 }
274 
create_query(struct queryopt * opt)275 static struct query_doc* create_query(struct queryopt* opt)
276 {
277 	struct query_doc* qu_doc;
278 	bson_t* query_what, * order_what, * msg_what, * date_what;
279 	struct tm tm;
280 	time_t t;
281 	int64_t ts;
282 
283 	qu_doc = malloc (sizeof (struct query_doc));
284 	qu_doc->query = bson_new ();
285 	query_what = bson_new ();
286 	bson_init (query_what);
287 	bson_append_document_begin (qu_doc->query, "$query", 6, query_what);
288 	if (opt->bsever == 1)
289 	{
290 		bson_append_int32 (query_what, "syslog_sever", 12,
291 				   opt->e_sever);
292 	}
293 	if (opt->blevel == 1)
294 	{
295 		bson_append_utf8 (query_what, "level", 5, opt->e_level, -1);
296 	}
297 
298 	if (opt->bmsg == 1)
299 	{
300 		msg_what = bson_new ();
301 		bson_init (msg_what);
302 		bson_append_document_begin (query_what, "msg", 3, msg_what);
303 		bson_append_utf8 (msg_what, "$regex", 6, opt->e_msg, -1);
304 		bson_append_utf8 (msg_what, "$options", 8, "i", 1);
305 		bson_append_document_end (query_what, msg_what);
306 	}
307 
308 	if (opt->bdate == 1)
309 	{
310 		date_what = bson_new ();
311 		bson_init (date_what);
312 		bson_append_document_begin (query_what, "time_rcvd", 9,
313 					    date_what);
314 		if (opt->bdatef == 1)
315 		{
316 			tm.tm_isdst = -1;
317 			strptime (opt->e_date, "%d/%m/%Y-%H:%M:%S", &tm);
318 			tm.tm_hour = tm.tm_hour + 1;
319 			t = mktime (&tm);
320 			ts = 1000 * (int64_t) t;
321 			bson_append_date_time (date_what, "$gt", 3, ts);
322 		}
323 
324 		if (opt->bdateu == 1)
325 		{
326 			tm.tm_isdst = -1;
327 			strptime (opt->e_dateu, "%d/%m/%Y-%H:%M:%S", &tm);
328 			tm.tm_hour = tm.tm_hour + 1;
329 			t = mktime (&tm);
330 			ts = 1000 * (int64_t) t;
331 			bson_append_date_time (date_what, "$lt", 3, ts);
332 		}
333 		bson_append_document_end (query_what, date_what);
334 	}
335 
336 	if (opt->bsys == 1)
337 	{
338 		bson_append_utf8 (query_what, "sys", 3, opt->e_sys, -1);
339 	}
340 
341 	bson_append_document_end (qu_doc->query, query_what);
342 
343 	order_what = bson_new ();
344 	bson_init (order_what);
345 	bson_append_document_begin (qu_doc->query, "$orderby", 8, order_what);
346 	bson_append_date_time (order_what, "time_rcvd", 9, 1ll);
347 	bson_append_document_end (qu_doc->query, order_what);
348 
349 	bson_free (order_what);
350 	return qu_doc;
351 }
352 
create_conn(void)353 static struct db_connect* create_conn(void)
354 {
355 	struct db_connect* db_conn;
356 
357 	db_conn = malloc (sizeof (struct db_connect));
358 	db_conn->conn = mongoc_client_new ("mongodb://localhost:27017");
359 	if (!db_conn->conn)
360 	{
361 		perror ("mongo_sync_connect()");
362 		exit (1);
363 	}
364 	return db_conn;
365 }
366 
close_conn(struct db_connect * db_conn)367 static void close_conn(struct db_connect* db_conn)
368 {
369 	mongoc_client_destroy (db_conn->conn);
370 	free (db_conn);
371 }
372 
free_cursor(struct db_cursor * db_c)373 static void free_cursor(struct db_cursor* db_c)
374 {
375 	mongoc_cursor_destroy (db_c->cursor);
376 	free (db_c);
377 }
378 
launch_query(struct queryopt * opt,struct select_doc * s_doc,struct query_doc * qu_doc,struct db_collection * db_coll)379 static struct db_cursor* launch_query(struct queryopt* opt,
380 				      __attribute__((unused)) struct select_doc* s_doc,
381 				      struct query_doc* qu_doc,
382 				      struct db_collection* db_coll)
383 {
384 	struct db_cursor* out;
385 #if MONGOC_CHECK_VERSION (1, 5, 0) /* Declaration before code (ISO C90) */
386 	const bson_t* opts = BCON_NEW (
387 		"skip", BCON_INT32 (opt->e_skip),
388 		"limit", BCON_INT32 (opt->e_ret)
389 	);
390 #endif /* MONGOC_CHECK_VERSION (1, 5, 0) */
391 
392 	out = malloc (sizeof (struct db_cursor));
393 	if (!out)
394 	{
395 		perror ("mongo_sync_cmd_query()");
396 		printf ("malloc failed\n");
397 		exit (1);
398 	}
399 #if MONGOC_CHECK_VERSION (1, 5, 0)
400 	out->cursor = mongoc_collection_find_with_opts (db_coll->collection,
401 							qu_doc->query, opts,
402 							NULL);
403 #else /* !MONGOC_CHECK_VERSION (1, 5, 0) */
404 	out->cursor = mongoc_collection_find (db_coll->collection,
405 					      MONGOC_QUERY_NONE,
406 					      (uint32_t)opt->e_skip,
407 					      (uint32_t)opt->e_ret, 0,
408 					      qu_doc->query, s_doc->select,
409 					      NULL);
410 #endif /* MONGOC_CHECK_VERSION (1, 5, 0) */
411 	if (!out->cursor)
412 	{
413 		perror ("mongo_sync_cmd_query()");
414 		printf ("no records found\n");
415 		exit (1);
416 	}
417 	return out;
418 }
419 
cursor_next(struct db_cursor * db_c,struct results * res)420 static int cursor_next(struct db_cursor* db_c, struct results* res)
421 {
422 	if (mongoc_cursor_next (db_c->cursor, &res->result))
423 		return true;
424 	return false;
425 }
426 
get_collection(struct db_connect * db_conn)427 static struct db_collection* get_collection(struct db_connect* db_conn)
428 {
429 	struct db_collection* coll;
430 
431 	coll = malloc (sizeof (struct db_collection));
432 	coll->collection = mongoc_client_get_collection (db_conn->conn,
433 							 "syslog", "log");
434 	return coll;
435 }
436 
release_collection(struct db_collection * db_coll)437 static void release_collection(struct db_collection* db_coll)
438 {
439 	mongoc_collection_destroy (db_coll->collection);
440 	free (db_coll);
441 }
442 
main(int argc,char * argv[])443 int main(int argc, char* argv[])
444 {
445 
446 	struct queryopt opt;
447 	struct ofields* fields;
448 	struct select_doc* s_doc;
449 	struct query_doc* qu_doc;
450 	struct db_connect* db_conn;
451 	struct db_cursor* db_c;
452 	struct db_collection* db_coll;
453 	struct results* res;
454 
455 	memset (&opt, 0, sizeof (struct queryopt));
456 
457 	mongoc_init (); /* Initialisation of mongo-c-driver */
458 
459 	getoptions (argc, argv, &opt);
460 	qu_doc = create_query (&opt); /* create query */
461 	s_doc = create_select ();
462 	db_conn = create_conn (); /* create connection */
463 	db_coll = get_collection (db_conn); /* Get the collection to perform query on */
464 	db_c = launch_query (&opt, s_doc, qu_doc, db_coll); /* launch the query and get the related cursor */
465 
466 	res = malloc (sizeof (struct results));
467 	while (cursor_next (db_c, res)) /* Move cursor & get pointed data */
468 	{
469 		fields = get_data (res);
470 		formater (fields); /* format output */
471 		free (fields);
472 	}
473 
474 	free (res);
475 	free_cursor (db_c);
476 	release_collection (db_coll);
477 	close_conn (db_conn);
478 	free (s_doc);
479 	free (qu_doc);
480 
481 	mongoc_cleanup (); /* Cleanup of mongo-c-driver */
482 
483 	return (0);
484 }
485