1 /*-
2  * Copyright 2016 Vsevolod Stakhov
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "lua_common.h"
17 #include "libserver/maps/map.h"
18 #include "libserver/maps/map_private.h"
19 
20 /***
21  * @module rspamd_logger
22  * Rspamd logger module is used to log messages from LUA API to the main rspamd logger.
23  * It supports legacy and modern interfaces allowing highly customized an convenient log functions.
24  * Here is an example of logger usage:
25  * @example
26 local rspamd_logger = require "rspamd_logger"
27 
28 local a = 'string'
29 local b = 1.5
30 local c = 1
31 local d = {
32 	'aa',
33 	1,
34 	'bb'
35 }
36 local e = {
37 	key = 'value',
38 	key2 = 1.0
39 }
40 
41 -- New extended interface
42 -- %<number> means numeric arguments and %s means the next argument
43 -- for example %1, %2, %s: %s would mean the third argument
44 
45 rspamd_logger.infox('a=%1, b=%2, c=%3, d=%4, e=%s', a, b, c, d, e)
46 -- Output: a=string, b=1.50000, c=1, d={[1] = aa, [2] = 1, [3] = bb} e={[key]=value, [key2]=1.0}
47 
48 -- Legacy interface (can handle merely strings)
49 rspamd_logger.info('Old stupid API')
50 
51 -- Create string using logger API
52 local str = rspamd_logger.slog('a=%1, b=%2, c=%3, d=%4, e=%5', a, b, c, d, e)
53 
54 print(str)
55 -- Output: a=string, b=1.50000, c=1, d={[1] = aa, [2] = 1, [3] = bb} e={[key]=value, [key2]=1.0}
56  */
57 
58 /* Logger methods */
59 /***
60  * @function logger.err(msg)
61  * Log message as an error
62  * @param {string} msg string to be logged
63  */
64 LUA_FUNCTION_DEF (logger, err);
65 /***
66  * @function logger.warn(msg)
67  * Log message as a warning
68  * @param {string} msg string to be logged
69  */
70 LUA_FUNCTION_DEF (logger, warn);
71 /***
72  * @function logger.info(msg)
73  * Log message as an informational message
74  * @param {string} msg string to be logged
75  */
76 LUA_FUNCTION_DEF (logger, info);
77 /***
78  * @function logger.message(msg)
79  * Log message as an notice message
80  * @param {string} msg string to be logged
81  */
82 LUA_FUNCTION_DEF (logger, message);
83 /***
84  * @function logger.debug(msg)
85  * Log message as a debug message
86  * @param {string} msg string to be logged
87  */
88 LUA_FUNCTION_DEF (logger, debug);
89 /***
90  * @function logger.errx(fmt[, args)
91  * Extended interface to make an error log message
92  * @param {string} fmt format string, arguments are encoded as %<number>
93  * @param {any} args list of arguments to be replaced in %<number> positions
94  */
95 LUA_FUNCTION_DEF (logger, errx);
96 /***
97  * @function logger.warn(fmt[, args)
98  * Extended interface to make a warning log message
99  * @param {string} fmt format string, arguments are encoded as %<number>
100  * @param {any} args list of arguments to be replaced in %<number> positions
101  */
102 LUA_FUNCTION_DEF (logger, warnx);
103 /***
104  * @function logger.infox(fmt[, args)
105  * Extended interface to make an informational log message
106  * @param {string} fmt format string, arguments are encoded as %<number>
107  * @param {any} args list of arguments to be replaced in %<number> positions
108  */
109 LUA_FUNCTION_DEF (logger, infox);
110 /***
111  * @function logger.infox(fmt[, args)
112  * Extended interface to make an informational log message
113  * @param {string} fmt format string, arguments are encoded as %<number>
114  * @param {any} args list of arguments to be replaced in %<number> positions
115  */
116 LUA_FUNCTION_DEF (logger, messagex);
117 /***
118  * @function logger.debugx(fmt[, args)
119  * Extended interface to make a debug log message
120  * @param {string} fmt format string, arguments are encoded as %<number>
121  * @param {any} args list of arguments to be replaced in %<number> positions
122  */
123 LUA_FUNCTION_DEF (logger, debugx);
124 
125 /***
126  * @function logger.debugm(module, id, fmt[, args)
127  * Extended interface to make a debug log message
128  * @param {string} module debug module
129  * @param {task|cfg|pool|string} id id to log
130  * @param {string} fmt format string, arguments are encoded as %<number>
131  * @param {any} args list of arguments to be replaced in %<number> positions
132  */
133 LUA_FUNCTION_DEF (logger, debugm);
134 /***
135  * @function logger.slog(fmt[, args)
136  * Create string replacing percent params with corresponding arguments
137  * @param {string} fmt format string, arguments are encoded as %<number>
138  * @param {any} args list of arguments to be replaced in %<number> positions
139  * @return {string} string with percent parameters substituted
140  */
141 LUA_FUNCTION_DEF (logger, slog);
142 
143 /***
144  * @function logger.logx(level, module, id, fmt[, args)
145  * Extended interface to make a generic log message on any level
146  * @param {number} log level as a number (see GLogLevelFlags enum for values)
147  * @param {task|cfg|pool|string} id id to log
148  * @param {string} fmt format string, arguments are encoded as %<number>
149  * @param {any} args list of arguments to be replaced in %<number> positions
150  */
151 LUA_FUNCTION_DEF (logger, logx);
152 
153 /***
154  * @function logger.log_level()
155  * Returns log level for a logger
156  * @return {string} current log level
157  */
158 LUA_FUNCTION_DEF (logger, log_level);
159 
160 static const struct luaL_reg loggerlib_f[] = {
161 		LUA_INTERFACE_DEF (logger, err),
162 		LUA_INTERFACE_DEF (logger, warn),
163 		LUA_INTERFACE_DEF (logger, message),
164 		{"msg", lua_logger_message},
165 		LUA_INTERFACE_DEF (logger, info),
166 		LUA_INTERFACE_DEF (logger, debug),
167 		LUA_INTERFACE_DEF (logger, errx),
168 		LUA_INTERFACE_DEF (logger, warnx),
169 		LUA_INTERFACE_DEF (logger, infox),
170 		LUA_INTERFACE_DEF (logger, messagex),
171 		{"msgx", lua_logger_messagex},
172 		LUA_INTERFACE_DEF (logger, debugx),
173 		LUA_INTERFACE_DEF (logger, debugm),
174 		LUA_INTERFACE_DEF (logger, slog),
175 		LUA_INTERFACE_DEF (logger, logx),
176 		LUA_INTERFACE_DEF (logger, log_level),
177 		{"__tostring", rspamd_lua_class_tostring},
178 		{NULL, NULL}
179 };
180 
181 static void
lua_common_log_line(GLogLevelFlags level,lua_State * L,const gchar * msg,const gchar * uid,const gchar * module,gint stack_level)182 lua_common_log_line (GLogLevelFlags level,
183 					 lua_State *L,
184 					 const gchar *msg,
185 					 const gchar *uid,
186 					 const gchar *module,
187 					 gint stack_level)
188 {
189 	lua_Debug d;
190 	gchar func_buf[128], *p;
191 
192 	if (lua_getstack (L, stack_level, &d) == 1) {
193 		(void) lua_getinfo (L, "Sl", &d);
194 		if ((p = strrchr (d.short_src, '/')) == NULL) {
195 			p = d.short_src;
196 		}
197 		else {
198 			p++;
199 		}
200 
201 		if (strlen (p) > 30) {
202 			rspamd_snprintf (func_buf, sizeof (func_buf), "%27s...:%d", p,
203 					d.currentline);
204 		}
205 		else {
206 			rspamd_snprintf (func_buf, sizeof (func_buf), "%s:%d", p,
207 					d.currentline);
208 		}
209 
210 		rspamd_common_log_function (NULL,
211 				level,
212 				module,
213 				uid,
214 				func_buf,
215 				"%s",
216 				msg);
217 	}
218 	else {
219 		rspamd_common_log_function (NULL,
220 				level,
221 				module,
222 				uid,
223 				G_STRFUNC,
224 				"%s",
225 				msg);
226 	}
227 }
228 
229 /*** Logger interface ***/
230 static gint
lua_logger_err(lua_State * L)231 lua_logger_err (lua_State *L)
232 {
233 	LUA_TRACE_POINT;
234 	const gchar *msg;
235 	msg = luaL_checkstring (L, 1);
236 	lua_common_log_line (G_LOG_LEVEL_CRITICAL, L, msg, NULL, NULL, 1);
237 	return 0;
238 }
239 
240 static gint
lua_logger_warn(lua_State * L)241 lua_logger_warn (lua_State *L)
242 {
243 	LUA_TRACE_POINT;
244 	const gchar *msg;
245 	msg = luaL_checkstring (L, 1);
246 	lua_common_log_line (G_LOG_LEVEL_WARNING, L, msg, NULL, NULL, 1);
247 	return 0;
248 }
249 
250 static gint
lua_logger_info(lua_State * L)251 lua_logger_info (lua_State *L)
252 {
253 	LUA_TRACE_POINT;
254 	const gchar *msg;
255 	msg = luaL_checkstring (L, 1);
256 	lua_common_log_line (G_LOG_LEVEL_INFO, L, msg, NULL, NULL, 1);
257 	return 0;
258 }
259 
260 static gint
lua_logger_message(lua_State * L)261 lua_logger_message (lua_State *L)
262 {
263 	LUA_TRACE_POINT;
264 	const gchar *msg;
265 	msg = luaL_checkstring (L, 1);
266 	lua_common_log_line (G_LOG_LEVEL_MESSAGE, L, msg, NULL, NULL, 1);
267 	return 0;
268 }
269 
270 static gint
lua_logger_debug(lua_State * L)271 lua_logger_debug (lua_State *L)
272 {
273 	LUA_TRACE_POINT;
274 	const gchar *msg;
275 	msg = luaL_checkstring (L, 1);
276 	lua_common_log_line (G_LOG_LEVEL_DEBUG, L, msg, NULL, NULL, 1);
277 	return 0;
278 }
279 
280 static inline bool
lua_logger_char_safe(int t,unsigned int esc_type)281 lua_logger_char_safe (int t, unsigned int esc_type)
282 {
283 	if (t & 0x80) {
284 		if (esc_type & LUA_ESCAPE_8BIT) {
285 			return false;
286 		}
287 
288 		return true;
289 	}
290 
291 	if (esc_type & LUA_ESCAPE_UNPRINTABLE) {
292 		if (!g_ascii_isprint (t) && !g_ascii_isspace (t)) {
293 			return false;
294 		}
295 	}
296 
297 	if (esc_type & LUA_ESCAPE_NEWLINES) {
298 		if (t == '\r' || t == '\n') {
299 			return false;
300 		}
301 	}
302 
303 	return true;
304 }
305 
306 static gsize
lua_logger_out_str(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace,enum lua_logger_escape_type esc_type)307 lua_logger_out_str (lua_State *L, gint pos,
308 					gchar *outbuf, gsize len,
309 					struct lua_logger_trace *trace,
310 					enum lua_logger_escape_type esc_type)
311 {
312 	gsize slen, flen;
313 	const gchar *str = lua_tolstring (L, pos, &slen);
314 	static const gchar hexdigests[16] = "0123456789abcdef";
315 	gsize r = 0, s;
316 
317 	if (str) {
318 		gboolean normal = TRUE;
319 		flen = MIN (slen, len - 1);
320 
321 		for (r = 0; r < flen; r ++) {
322 			if (!lua_logger_char_safe (str[r], esc_type)) {
323 				normal = FALSE;
324 				break;
325 			}
326 		}
327 
328 		if (normal) {
329 			r = rspamd_strlcpy (outbuf, str, flen + 1);
330 		}
331 		else {
332 			/* Need to escape non printed characters */
333 			r = 0;
334 			s = 0;
335 
336 			while (slen > 0 && len > 1) {
337 				if (!lua_logger_char_safe (str[s], esc_type)) {
338 					if (len >= 3) {
339 						outbuf[r++] = '\\';
340 						outbuf[r++] = hexdigests[((str[s] >> 4) & 0xF)];
341 						outbuf[r++] = hexdigests[((str[s]) & 0xF)];
342 
343 						len -= 2;
344 					}
345 					else {
346 						outbuf[r++] = '?';
347 					}
348 				}
349 				else {
350 					outbuf[r++] = str[s];
351 				}
352 
353 				s++;
354 				slen --;
355 				len --;
356 			}
357 
358 			outbuf[r] = '\0';
359 		}
360 	}
361 
362 	return r;
363 }
364 
365 static gsize
lua_logger_out_num(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace)366 lua_logger_out_num (lua_State *L, gint pos, gchar *outbuf, gsize len,
367 					struct lua_logger_trace *trace)
368 {
369 	gdouble num = lua_tonumber (L, pos);
370 	glong inum;
371 	gsize r = 0;
372 
373 	if ((gdouble) (glong) num == num) {
374 		inum = num;
375 		r = rspamd_snprintf (outbuf, len + 1, "%l", inum);
376 	}
377 	else {
378 		r = rspamd_snprintf (outbuf, len + 1, "%f", num);
379 	}
380 
381 	return r;
382 }
383 
384 static gsize
lua_logger_out_boolean(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace)385 lua_logger_out_boolean (lua_State *L, gint pos, gchar *outbuf, gsize len,
386 						struct lua_logger_trace *trace)
387 {
388 	gboolean val = lua_toboolean (L, pos);
389 	gsize r = 0;
390 
391 	r = rspamd_strlcpy (outbuf, val ? "true" : "false", len + 1);
392 
393 	return r;
394 }
395 
396 static gsize
lua_logger_out_userdata(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace)397 lua_logger_out_userdata (lua_State *L, gint pos, gchar *outbuf, gsize len,
398 						 struct lua_logger_trace *trace)
399 {
400 	gint r = 0, top;
401 	const gchar *str = NULL;
402 	gboolean converted_to_str = FALSE;
403 
404 	top = lua_gettop (L);
405 
406 	if (!lua_getmetatable (L, pos)) {
407 		return 0;
408 	}
409 
410 	lua_pushstring (L, "__index");
411 	lua_gettable (L, -2);
412 
413 	if (!lua_istable (L, -1)) {
414 
415 		if (lua_isfunction (L, -1)) {
416 			/* Functional metatable, try to get __tostring directly */
417 			lua_pushstring (L, "__tostring");
418 			lua_gettable (L, -3);
419 
420 			if (lua_isfunction (L, -1)) {
421 				lua_pushvalue (L, pos);
422 
423 				if (lua_pcall (L, 1, 1, 0) != 0) {
424 					lua_settop (L, top);
425 
426 					return 0;
427 				}
428 
429 				str = lua_tostring (L, -1);
430 
431 				if (str) {
432 					r = rspamd_snprintf (outbuf, len, "%s", str);
433 				}
434 
435 				lua_settop (L, top);
436 
437 				return r;
438 			}
439 		}
440 		lua_settop (L, top);
441 
442 		return 0;
443 	}
444 
445 	lua_pushstring (L, "__tostring");
446 	lua_gettable (L, -2);
447 
448 	if (lua_isfunction (L, -1)) {
449 		lua_pushvalue (L, pos);
450 
451 		if (lua_pcall (L, 1, 1, 0) != 0) {
452 			lua_settop (L, top);
453 
454 			return 0;
455 		}
456 
457 		str = lua_tostring (L, -1);
458 
459 		if (str) {
460 			converted_to_str = TRUE;
461 		}
462 	}
463 	else {
464 		lua_pop (L, 1);
465 		lua_pushstring (L, "class");
466 		lua_gettable (L, -2);
467 
468 		if (lua_isstring (L, -1)) {
469 			str = lua_tostring (L, -1);
470 			converted_to_str = TRUE;
471 		}
472 	}
473 
474 	if (converted_to_str) {
475 		r = rspamd_snprintf (outbuf, len, "%s", str);
476 	}
477 	else {
478 		/* Print raw pointer */
479 		r = rspamd_snprintf (outbuf, len, "%s(%p)", str, lua_touserdata (L, pos));
480 	}
481 
482 	lua_settop (L, top);
483 
484 	return r;
485 }
486 
487 #define MOVE_BUF(d, remain, r)    \
488     (d) += (r); (remain) -= (r);    \
489     if ((remain) == 0) { lua_pop (L, 1); break; }
490 
491 static gsize
lua_logger_out_table(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace,enum lua_logger_escape_type esc_type)492 lua_logger_out_table (lua_State *L, gint pos, gchar *outbuf, gsize len,
493 					  struct lua_logger_trace *trace,
494 					  enum lua_logger_escape_type esc_type)
495 {
496 	gchar *d = outbuf;
497 	gsize remain = len, r;
498 	gboolean first = TRUE;
499 	gconstpointer self = NULL;
500 	gint i, tpos, last_seq = -1;
501 
502 	if (!lua_istable (L, pos) || remain == 0) {
503 		return 0;
504 	}
505 
506 	self = lua_topointer (L, pos);
507 
508 	/* Check if we have seen this pointer */
509 	for (i = 0; i < TRACE_POINTS; i ++) {
510 		if (trace->traces[i] == self) {
511 			r = rspamd_snprintf (d, remain + 1, "ref(%p)", self);
512 
513 			d += r;
514 
515 			return (d - outbuf);
516 		}
517 	}
518 
519 	trace->traces[trace->cur_level % TRACE_POINTS] = self;
520 
521 	lua_pushvalue (L, pos);
522 	r = rspamd_snprintf (d, remain + 1, "{");
523 	remain -= r;
524 	d += r;
525 
526 	/* Get numeric keys (ipairs) */
527 	for (i = 1; ; i++) {
528 		lua_rawgeti (L, -1, i);
529 
530 		if (lua_isnil (L, -1)) {
531 			lua_pop (L, 1);
532 			break;
533 		}
534 
535 		last_seq = i;
536 
537 		if (!first) {
538 			r = rspamd_snprintf (d, remain + 1, ", ");
539 			MOVE_BUF(d, remain, r);
540 		}
541 
542 		r = rspamd_snprintf (d, remain + 1, "[%d] = ", i);
543 		MOVE_BUF(d, remain, r);
544 		tpos = lua_gettop (L);
545 
546 		if (lua_topointer (L, tpos) == self) {
547 			r = rspamd_snprintf (d, remain + 1, "__self");
548 		}
549 		else {
550 			r = lua_logger_out_type (L, tpos, d, remain, trace, esc_type);
551 		}
552 		MOVE_BUF(d, remain, r);
553 
554 		first = FALSE;
555 		lua_pop (L, 1);
556 	}
557 
558 	/* Get string keys (pairs) */
559 	for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 1)) {
560 		/* 'key' is at index -2 and 'value' is at index -1 */
561 
562 		if (lua_type (L, -2) == LUA_TNUMBER) {
563 			if (last_seq > 0) {
564 				lua_pushvalue (L, -2);
565 
566 				if (lua_tonumber (L, -1) <= last_seq + 1) {
567 					lua_pop (L, 1);
568 					/* Already seen */
569 					continue;
570 				}
571 
572 				lua_pop (L, 1);
573 			}
574 		}
575 
576 		if (!first) {
577 			r = rspamd_snprintf (d, remain + 1, ", ");
578 			MOVE_BUF(d, remain, r);
579 		}
580 
581 		/* Preserve key */
582 		lua_pushvalue (L, -2);
583 		r = rspamd_snprintf (d, remain + 1, "[%s] = ",
584 				lua_tostring (L, -1));
585 		lua_pop (L, 1); /* Remove key */
586 		MOVE_BUF(d, remain, r);
587 		tpos = lua_gettop (L);
588 
589 		if (lua_topointer (L, tpos) == self) {
590 			r = rspamd_snprintf (d, remain + 1, "__self");
591 		}
592 		else {
593 			r = lua_logger_out_type (L, tpos, d, remain, trace, esc_type);
594 		}
595 		MOVE_BUF(d, remain, r);
596 
597 		first = FALSE;
598 	}
599 
600 	lua_pop (L, 1);
601 
602 	r = rspamd_snprintf (d, remain + 1, "}");
603 	d += r;
604 
605 	return (d - outbuf);
606 }
607 
608 #undef MOVE_BUF
609 
610 gsize
lua_logger_out_type(lua_State * L,gint pos,gchar * outbuf,gsize len,struct lua_logger_trace * trace,enum lua_logger_escape_type esc_type)611 lua_logger_out_type (lua_State *L, gint pos,
612 					 gchar *outbuf, gsize len,
613 					 struct lua_logger_trace *trace,
614 					 enum lua_logger_escape_type esc_type)
615 {
616 	gint type;
617 	gsize r = 0;
618 
619 	if (len == 0) {
620 		return 0;
621 	}
622 
623 	type = lua_type (L, pos);
624 	trace->cur_level ++;
625 
626 	switch (type) {
627 	case LUA_TNUMBER:
628 		r = lua_logger_out_num (L, pos, outbuf, len, trace);
629 		break;
630 	case LUA_TBOOLEAN:
631 		r = lua_logger_out_boolean (L, pos, outbuf, len, trace);
632 		break;
633 	case LUA_TTABLE:
634 		r = lua_logger_out_table (L, pos, outbuf, len, trace, esc_type);
635 		break;
636 	case LUA_TUSERDATA:
637 		r = lua_logger_out_userdata (L, pos, outbuf, len, trace);
638 		break;
639 	case LUA_TFUNCTION:
640 		r = rspamd_snprintf (outbuf, len + 1, "function");
641 		break;
642 	case LUA_TLIGHTUSERDATA:
643 		r = rspamd_snprintf (outbuf, len + 1, "0x%p", lua_topointer (L, pos));
644 		break;
645 	case LUA_TNIL:
646 		r = rspamd_snprintf (outbuf, len + 1, "nil");
647 		break;
648 	case LUA_TNONE:
649 		r = rspamd_snprintf (outbuf, len + 1, "no value");
650 		break;
651 	default:
652 		/* Try to push everything as string using tostring magic */
653 		r = lua_logger_out_str (L, pos, outbuf, len, trace, esc_type);
654 		break;
655 	}
656 
657 	trace->cur_level --;
658 
659 	return r;
660 }
661 
662 static const gchar *
lua_logger_get_id(lua_State * L,gint pos,GError ** err)663 lua_logger_get_id (lua_State *L, gint pos, GError **err)
664 {
665 	const gchar *uid = NULL, *clsname;
666 
667 	if (lua_getmetatable (L, pos) != 0) {
668 		uid = "";
669 		lua_pushstring (L, "__index");
670 		lua_gettable (L, -2);
671 
672 		lua_pushstring (L, "class");
673 		lua_gettable (L, -2);
674 
675 		clsname = lua_tostring (L, -1);
676 
677 		if (strcmp (clsname, "rspamd{task}") == 0) {
678 			struct rspamd_task *task = lua_check_task (L, pos);
679 
680 			if (task) {
681 				uid = task->task_pool->tag.uid;
682 			}
683 			else {
684 				g_set_error (err, g_quark_from_static_string ("lua_logger"),
685 						EINVAL, "invalid rspamd{task}");
686 			}
687 		}
688 		else if (strcmp (clsname, "rspamd{mempool}") == 0) {
689 			rspamd_mempool_t  *pool;
690 
691 			pool = rspamd_lua_check_mempool (L, pos);
692 
693 			if (pool) {
694 				uid = pool->tag.uid;
695 			}
696 			else {
697 				g_set_error (err, g_quark_from_static_string ("lua_logger"),
698 						EINVAL, "invalid rspamd{mempool}");
699 			}
700 		}
701 		else if (strcmp (clsname, "rspamd{config}") == 0) {
702 			struct rspamd_config *cfg;
703 
704 			cfg = lua_check_config (L, pos);
705 
706 			if (cfg) {
707 				if (cfg->checksum) {
708 					uid = cfg->checksum;
709 				}
710 			}
711 			else {
712 				g_set_error (err, g_quark_from_static_string ("lua_logger"),
713 						EINVAL, "invalid rspamd{config}");
714 			}
715 		}
716 		else if (strcmp (clsname, "rspamd{map}") == 0) {
717 			struct rspamd_lua_map *map;
718 
719 			map = lua_check_map (L, pos);
720 
721 			if (map) {
722 				if (map->map) {
723 					uid = map->map->tag;
724 				}
725 				else {
726 					uid = "embedded";
727 				}
728 			}
729 			else {
730 				g_set_error (err, g_quark_from_static_string ("lua_logger"),
731 						EINVAL, "invalid rspamd{map}");
732 			}
733 		}
734 		else {
735 			g_set_error (err, g_quark_from_static_string ("lua_logger"),
736 					EINVAL, "unknown class: %s", clsname);
737 		}
738 
739 
740 		/* Metatable, __index, classname */
741 		lua_pop (L, 3);
742 	}
743 	else {
744 		g_set_error (err, g_quark_from_static_string ("lua_logger"),
745 				EINVAL, "no metatable found for userdata");
746 	}
747 
748 	return uid;
749 }
750 
751 static gboolean
lua_logger_log_format(lua_State * L,gint fmt_pos,gboolean is_string,gchar * logbuf,gsize remain)752 lua_logger_log_format (lua_State *L, gint fmt_pos, gboolean is_string,
753 		gchar *logbuf, gsize remain)
754 {
755 	gchar *d;
756 	const gchar *s, *c;
757 	gsize r, cpylen = 0;
758 	guint arg_num = 0, cur_arg;
759 	bool num_arg = false;
760 	struct lua_logger_trace tr;
761 	enum {
762 		copy_char = 0,
763 		got_percent,
764 		parse_arg_num
765 	} state = copy_char;
766 
767 	d = logbuf;
768 	s = lua_tostring (L, fmt_pos);
769 	c = s;
770 	cur_arg = fmt_pos;
771 
772 	if (s == NULL) {
773 		return FALSE;
774 	}
775 
776 	while (remain > 0 && *s != '\0') {
777 		switch (state) {
778 		case copy_char:
779 			if (*s == '%') {
780 				state = got_percent;
781 				s++;
782 				if (cpylen > 0) {
783 					memcpy (d, c, cpylen);
784 					d += cpylen;
785 				}
786 				cpylen = 0;
787 			}
788 			else {
789 				s++;
790 				cpylen ++;
791 				remain--;
792 			}
793 			break;
794 		case got_percent:
795 			if (g_ascii_isdigit (*s) || *s == 's') {
796 				state = parse_arg_num;
797 				c = s;
798 			}
799 			else {
800 				*d++ = *s++;
801 				c = s;
802 				state = copy_char;
803 			}
804 			break;
805 		case parse_arg_num:
806 			if (g_ascii_isdigit (*s)) {
807 				s++;
808 				num_arg = true;
809 			}
810 			else {
811 				if (num_arg) {
812 					arg_num = strtoul (c, NULL, 10);
813 					arg_num += fmt_pos - 1;
814 					/* Update the current argument */
815 					cur_arg = arg_num;
816 				}
817 				else {
818 					/* We have non numeric argument, e.g. %s */
819 					arg_num = cur_arg ++;
820 					s ++;
821 				}
822 
823 				if (arg_num < 1 || arg_num > (guint) lua_gettop (L) + 1) {
824 					msg_err ("wrong argument number: %ud", arg_num);
825 
826 					return FALSE;
827 				}
828 
829 				memset (&tr, 0, sizeof (tr));
830 				r = lua_logger_out_type (L, arg_num + 1, d, remain, &tr,
831 						is_string ? LUA_ESCAPE_UNPRINTABLE : LUA_ESCAPE_LOG);
832 				g_assert (r <= remain);
833 				remain -= r;
834 				d += r;
835 				state = copy_char;
836 				c = s;
837 			}
838 			break;
839 		}
840 	}
841 
842 	if (state == parse_arg_num) {
843 		if (num_arg) {
844 			arg_num = strtoul (c, NULL, 10);
845 			arg_num += fmt_pos - 1;
846 		}
847 		else {
848 			/* We have non numeric argument, e.g. %s */
849 			arg_num = cur_arg;
850 		}
851 
852 		if (arg_num < 1 || arg_num > (guint) lua_gettop (L) + 1) {
853 			msg_err ("wrong argument number: %ud", arg_num);
854 
855 			return FALSE;
856 		}
857 
858 		memset (&tr, 0, sizeof (tr));
859 		r = lua_logger_out_type (L, arg_num + 1, d, remain, &tr,
860 				is_string ? LUA_ESCAPE_UNPRINTABLE : LUA_ESCAPE_LOG);
861 		g_assert (r <= remain);
862 		remain -= r;
863 		d += r;
864 	}
865 	else if (state == copy_char) {
866 		if (cpylen > 0 && remain > 0) {
867 			memcpy (d, c, cpylen);
868 			d += cpylen;
869 		}
870 	}
871 
872 	*d = '\0';
873 
874 
875 	return TRUE;
876 }
877 
878 static gint
lua_logger_do_log(lua_State * L,GLogLevelFlags level,gboolean is_string,gint start_pos)879 lua_logger_do_log (lua_State *L,
880 				   GLogLevelFlags level,
881 				   gboolean is_string,
882 				   gint start_pos)
883 {
884 	gchar logbuf[RSPAMD_LOGBUF_SIZE - 128];
885 	const gchar *uid = NULL;
886 	gint fmt_pos = start_pos;
887 	gint ret;
888 	GError *err = NULL;
889 
890 	if (lua_type (L, start_pos) == LUA_TSTRING) {
891 		fmt_pos = start_pos;
892 	}
893 	else if (lua_type (L, start_pos) == LUA_TUSERDATA) {
894 		fmt_pos = start_pos + 1;
895 
896 		uid = lua_logger_get_id (L, start_pos, &err);
897 
898 		if (uid == NULL) {
899 			ret = luaL_error (L, "bad userdata for logging: %s",
900 					err ? err->message : "unknown error");
901 
902 			if (err) {
903 				g_error_free (err);
904 			}
905 
906 			return ret;
907 		}
908 	}
909 	else {
910 		/* Bad argument type */
911 		return luaL_error (L, "bad format string type: %s",
912 				lua_typename (L, lua_type (L, start_pos)));
913 	}
914 
915 	ret = lua_logger_log_format (L, fmt_pos, is_string,
916 			logbuf, sizeof (logbuf) - 1);
917 
918 	if (ret) {
919 		if (is_string) {
920 			lua_pushstring (L, logbuf);
921 			return 1;
922 		}
923 		else {
924 			lua_common_log_line (level, L, logbuf, uid, "lua", 1);
925 		}
926 	}
927 	else {
928 		if (is_string) {
929 			lua_pushnil (L);
930 
931 			return 1;
932 		}
933 	}
934 
935 	return 0;
936 }
937 
938 static gint
lua_logger_errx(lua_State * L)939 lua_logger_errx (lua_State *L)
940 {
941 	LUA_TRACE_POINT;
942 	return lua_logger_do_log (L, G_LOG_LEVEL_CRITICAL, FALSE, 1);
943 }
944 
945 static gint
lua_logger_warnx(lua_State * L)946 lua_logger_warnx (lua_State *L)
947 {
948 	LUA_TRACE_POINT;
949 	return lua_logger_do_log (L, G_LOG_LEVEL_WARNING, FALSE, 1);
950 }
951 
952 static gint
lua_logger_infox(lua_State * L)953 lua_logger_infox (lua_State *L)
954 {
955 	LUA_TRACE_POINT;
956 	return lua_logger_do_log (L, G_LOG_LEVEL_INFO, FALSE, 1);
957 }
958 
959 static gint
lua_logger_messagex(lua_State * L)960 lua_logger_messagex (lua_State *L)
961 {
962 	LUA_TRACE_POINT;
963 	return lua_logger_do_log (L, G_LOG_LEVEL_MESSAGE, FALSE, 1);
964 }
965 
966 static gint
lua_logger_debugx(lua_State * L)967 lua_logger_debugx (lua_State *L)
968 {
969 	LUA_TRACE_POINT;
970 	return lua_logger_do_log (L, G_LOG_LEVEL_DEBUG, FALSE, 1);
971 }
972 
973 static gint
lua_logger_logx(lua_State * L)974 lua_logger_logx (lua_State *L)
975 {
976 	LUA_TRACE_POINT;
977 	GLogLevelFlags flags = lua_tonumber (L, 1);
978 	const gchar *modname = lua_tostring (L, 2), *uid = NULL;
979 	gchar logbuf[RSPAMD_LOGBUF_SIZE - 128];
980 	gboolean ret;
981 	gint stack_pos = 1;
982 
983 	if (lua_type (L, 3) == LUA_TSTRING) {
984 		uid = luaL_checkstring (L, 3);
985 	}
986 	else if (lua_type (L, 3) == LUA_TUSERDATA) {
987 		uid = lua_logger_get_id (L, 3, NULL);
988 	}
989 	else {
990 		uid = "???";
991 	}
992 
993 	if (uid && modname) {
994 		if (lua_type (L, 4) == LUA_TSTRING) {
995 			ret = lua_logger_log_format (L, 4, FALSE, logbuf, sizeof (logbuf) - 1);
996 		}
997 		else if (lua_type (L, 4) == LUA_TNUMBER) {
998 			stack_pos = lua_tonumber (L, 4);
999 			ret = lua_logger_log_format (L, 5, FALSE, logbuf, sizeof (logbuf) - 1);
1000 		}
1001 		else {
1002 			return luaL_error (L, "invalid argument on pos 4");
1003 		}
1004 
1005 		if (ret) {
1006 			lua_common_log_line (flags, L, logbuf, uid, modname, stack_pos);
1007 		}
1008 	}
1009 	else {
1010 		return luaL_error (L, "invalid arguments");
1011 	}
1012 
1013 	return 0;
1014 }
1015 
1016 
1017 static gint
lua_logger_debugm(lua_State * L)1018 lua_logger_debugm (lua_State *L)
1019 {
1020 	LUA_TRACE_POINT;
1021 	gchar logbuf[RSPAMD_LOGBUF_SIZE - 128];
1022 	const gchar *uid = NULL, *module = NULL;
1023 	gint stack_pos = 1;
1024 	gboolean ret;
1025 
1026 	module = luaL_checkstring (L, 1);
1027 
1028 	if (lua_type (L, 2) == LUA_TSTRING) {
1029 		uid = luaL_checkstring (L, 2);
1030 	}
1031 	else {
1032 		uid = lua_logger_get_id (L, 2, NULL);
1033 	}
1034 
1035 	if (uid && module) {
1036 		if (lua_type (L, 3) == LUA_TSTRING) {
1037 			ret = lua_logger_log_format (L, 3, FALSE, logbuf, sizeof (logbuf) - 1);
1038 		}
1039 		else if (lua_type (L, 3) == LUA_TNUMBER) {
1040 			stack_pos = lua_tonumber (L, 3);
1041 			ret = lua_logger_log_format (L, 4, FALSE, logbuf, sizeof (logbuf) - 1);
1042 		}
1043 		else {
1044 			return luaL_error (L, "invalid argument on pos 3");
1045 		}
1046 
1047 		if (ret) {
1048 			lua_common_log_line (G_LOG_LEVEL_DEBUG, L, logbuf, uid, module, stack_pos);
1049 		}
1050 	}
1051 	else {
1052 		return luaL_error (L, "invalid arguments");
1053 	}
1054 
1055 	return 0;
1056 }
1057 
1058 
1059 
1060 static gint
lua_logger_slog(lua_State * L)1061 lua_logger_slog (lua_State *L)
1062 {
1063 	return lua_logger_do_log (L, 0, TRUE, 1);
1064 }
1065 
1066 static gint
lua_logger_log_level(lua_State * L)1067 lua_logger_log_level (lua_State *L)
1068 {
1069 	gint log_level = rspamd_log_get_log_level (NULL);
1070 
1071 	lua_pushstring (L, rspamd_get_log_severity_string(log_level));
1072 
1073 	return 1;
1074 }
1075 
1076 /*** Init functions ***/
1077 
1078 static gint
lua_load_logger(lua_State * L)1079 lua_load_logger (lua_State *L)
1080 {
1081 	lua_newtable (L);
1082 	luaL_register (L, NULL, loggerlib_f);
1083 
1084 	return 1;
1085 }
1086 
1087 void
luaopen_logger(lua_State * L)1088 luaopen_logger (lua_State *L)
1089 {
1090 	rspamd_lua_add_preload (L, "rspamd_logger", lua_load_logger);
1091 }
1092