ALT Linux Team development discussions
 help / color / mirror / Atom feed
* [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries
@ 2020-10-27 11:33 Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 1/6] Optimize the filling of the record fields Alexey Gladkov
                   ` (6 more replies)
  0 siblings, 7 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

Привет!

Этот патчсет реализовывает цепочки хэшей для записей в логе. Это позволяет
обнаружить возможное втрожение в логи.

При старте syslogd считает хэш для каждой записи используя хэш предыдущей
записи или пустой, если это первая запись в лог после старта. Цепочка не
прерывается до остановки сервера или перекофигруции.

Наследование чексумм между рестартами не сделано намеренно поскольку у сервера
нет возможности сохранить и гарантировать неприкосновенность чексуммы между
рестартами.

Рестарт syslogd, как и нарушение цепочки чексумм, считается поводом для
расследования.

Реализация sha256 (каталог block/) взята из git.

Alexey Gladkov (6):
  Optimize the filling of the record fields
  syslogd: make logerror is printf-like
  syslogd: Implement customization of log file records
  syslogd: no need to try to substitute a field in a record if it's not
    in the log_format
  Makefile: use make variables instead of a hardcoded list
  syslogd: implement checksum chains for log entries

 Makefile                         |  24 +-
 block/bswap.h                    | 217 ++++++++++++++++
 block/sha256.c                   | 202 +++++++++++++++
 block/sha256.h                   |  24 ++
 hash.h                           |  66 +++++
 syslog.conf                      |  17 ++
 syslogd.c                        | 419 +++++++++++++++++++++++++------
 tests/log-hashes/.gitignore      |   5 +
 tests/log-hashes/check           |  28 +++
 tests/log-hashes/check-hashes.sh |  28 +++
 10 files changed, 950 insertions(+), 80 deletions(-)
 create mode 100644 block/bswap.h
 create mode 100644 block/sha256.c
 create mode 100644 block/sha256.h
 create mode 100644 hash.h
 create mode 100644 tests/log-hashes/.gitignore
 create mode 100755 tests/log-hashes/check
 create mode 100755 tests/log-hashes/check-hashes.sh

-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 1/6] Optimize the filling of the record fields
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 2/6] syslogd: make logerror is printf-like Alexey Gladkov
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 syslogd.c | 84 ++++++++++++++++++++++++++++---------------------------
 1 file changed, 43 insertions(+), 41 deletions(-)

diff --git a/syslogd.c b/syslogd.c
index 0f2260d..55281bd 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -289,6 +289,16 @@ struct sourceinfo {
 	unsigned int flags;
 } sinfo;
 
+enum record_fields_type {
+	RECORD_FIELD_TIME = 0,
+	RECORD_FIELD_SEP1,
+	RECORD_FIELD_HOST,
+	RECORD_FIELD_SEP2,
+	RECORD_FIELD_MSG,
+	RECORD_FIELD_EOL,
+	RECORD_FIELD_COUNTS,
+};
+
 static int	Debug;			/* debug flag */
 static int	Compress = 1;		/* compress repeated messages flag */
 static char	LocalHostName[MAXHOSTNAMELEN+1];	/* our hostname */
@@ -332,7 +342,7 @@ void printline(const struct sourceinfo* const, char *msg);
 void logmsg(int pri, char *msg, const struct sourceinfo* const, int flags);
 void fprintlog(register struct filed *f, char *from, int flags, char *msg);
 void endtty(int);
-void wallmsg(register struct filed *f, struct iovec *iov, size_t iovsz);
+void wallmsg(register struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS]);
 void reapchild(int);
 const char *cvtaddr(struct sockaddr_storage *f, int len);
 const char *cvthname(struct sockaddr_storage *f, int len);
@@ -1443,10 +1453,16 @@ finish:
 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 }
 
+static inline void set_record_field(struct iovec iov[RECORD_FIELD_COUNTS],
+		enum record_fields_type name, char *value, size_t len)
+{
+	iov[name].iov_base = value;
+	iov[name].iov_len = len == -1 ? strlen(value) : len;
+}
+
 void fprintlog(struct filed *f, char *from, int flags, char *msg)
 {
-	struct iovec iov[6];
-	register struct iovec *v = iov;
+	struct iovec iov[RECORD_FIELD_COUNTS];
 	char repbuf[80];
 #ifdef SYSLOG_INET
 	register int l;
@@ -1458,31 +1474,20 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 
 	verbosef("Called fprintlog, ");
 
-	v->iov_base = f->f_lasttime;
-	v->iov_len = 15;
-	v++;
-	v->iov_base = " ";
-	v->iov_len = 1;
-	v++;
-	v->iov_base = f->f_prevhost;
-	v->iov_len = strlen(v->iov_base);
-	v++;
-	v->iov_base = " ";
-	v->iov_len = 1;
-	v++;
+	set_record_field(iov, RECORD_FIELD_TIME, f->f_lasttime, 15);
+	set_record_field(iov, RECORD_FIELD_SEP1, " ", 1);
+	set_record_field(iov, RECORD_FIELD_HOST, f->f_prevhost, -1);
+	set_record_field(iov, RECORD_FIELD_SEP2, " ", 1);
+
 	if (msg) {
-		v->iov_base = msg;
-		v->iov_len = strlen(msg);
+		set_record_field(iov, RECORD_FIELD_MSG, msg, -1);
 	} else if (f->f_prevcount > 1) {
 		(void) snprintf(repbuf, sizeof(repbuf), "last message repeated %d times",
 		    f->f_prevcount);
-		v->iov_base = repbuf;
-		v->iov_len = strlen(repbuf);
+		set_record_field(iov, RECORD_FIELD_MSG, repbuf, -1);
 	} else {
-		v->iov_base = f->f_prevline;
-		v->iov_len = f->f_prevlen;
+		set_record_field(iov, RECORD_FIELD_MSG, f->f_prevline, f->f_prevlen);
 	}
-	v++;
 
 	verbosef("logging to %s", TypeNames[f->f_type]);
 
@@ -1508,14 +1513,13 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 				(long)(INET_SUSPEND_TIME - fwd_suspend));
 		}
 		break;
-		
 	/*
 	 * The trick is to wait some time, then retry to get the
 	 * address. If that fails retry x times and then give up.
 	 *
 	 * You'll run into this problem mostly if the name server you
 	 * need for resolving the address is on the same machine, but
-	 * is started after syslogd. 
+	 * is started after syslogd.
 	 */
 	case F_FORW_UNKN:
 		verbosef(" %s\n", f->f_un.f_forw.f_hname);
@@ -1549,7 +1553,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		break;
 
 	case F_FORW:
-		/* 
+		/*
 		 * Don't send any message to a remote host if it
 		 * already comes from one. (we don't care 'bout who
 		 * sent the message, we don't send it anyway)  -Joey
@@ -1562,7 +1566,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 			int i;
 			f->f_time = now;
 			(void) snprintf(line, sizeof(line), "<%d>%s", f->f_prevpri, \
-				(char *) iov[4].iov_base);
+				(char *) iov[RECORD_FIELD_MSG].iov_base);
 			l = strlen(line);
 			if (l > MAXLINE)
 				l = MAXLINE;
@@ -1582,7 +1586,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 					break;
 			}
 			if (err != -1) {
-				verbosef("INET sendto error: %d = %s.\n", 
+				verbosef("INET sendto error: %d = %s.\n",
 					err, strerror(err));
 				f->f_type = F_FORW_SUSP;
 				errno = err;
@@ -1597,7 +1601,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 #ifdef UNIXPC
 		if (1) {
 #else
-		if (flags & IGN_CONS) {	
+		if (flags & IGN_CONS) {
 #endif
 			verbosef(" (ignored).\n");
 			break;
@@ -1610,11 +1614,9 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		f->f_time = now;
 		verbosef(" %s\n", f->f_un.f_fname);
 		if (f->f_type == F_TTY || f->f_type == F_CONSOLE) {
-			v->iov_base = "\r\n";
-			v->iov_len = 2;
+			set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
 		} else {
-			v->iov_base = "\n";
-			v->iov_len = 1;
+			set_record_field(iov, RECORD_FIELD_EOL, "\n", 1);
 		}
 	again:
 		/* f->f_file == -1 is an indicator that we couldn't
@@ -1622,7 +1624,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		if (f->f_file == -1)
 			break;
 
-		if (writev(f->f_file, iov, 6) < 0) {
+		if (writev(f->f_file, iov, RECORD_FIELD_COUNTS) < 0) {
 			int e = errno;
 
 			/* If a named pipe is full, just ignore it for now */
@@ -1669,9 +1671,8 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 	case F_WALL:
 		f->f_time = now;
 		verbosef("\n");
-		v->iov_base = "\r\n";
-		v->iov_len = 2;
-		wallmsg(f, iov, 6);
+		set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
+		wallmsg(f, iov);
 		break;
 	} /* switch */
 	if (f->f_type != F_FORW_UNKN)
@@ -1693,7 +1694,7 @@ void endtty(int sig)
  *	world, or a list of approved users.
  */
 
-void wallmsg(struct filed *f, struct iovec *iov, size_t iovsz)
+void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 {
 	char p[sizeof (_PATH_DEV) + UNAMESZ];
 	register int i;
@@ -1722,7 +1723,7 @@ void wallmsg(struct filed *f, struct iovec *iov, size_t iovsz)
 
 		(void) snprintf(greetings, sizeof(greetings),
 		    "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
-			(char *) iov[2].iov_base, ctime(&now));
+			(char *) iov[RECORD_FIELD_HOST].iov_base, ctime(&now));
 		len = strlen(greetings);
 
 		/* scan the user login file */
@@ -1768,9 +1769,10 @@ void wallmsg(struct filed *f, struct iovec *iov, size_t iovsz)
 				if (ttyf >= 0) {
 					struct stat statb;
 
-					if (fstat(ttyf, &statb) == 0 &&
-					    (statb.st_mode & S_IWRITE))
-						(void) writev(ttyf, iov, iovsz);
+					if (!fstat(ttyf, &statb) && (statb.st_mode & S_IWRITE)) {
+						if (writev(ttyf, iov, RECORD_FIELD_COUNTS) < 0)
+							errno = 0; /* ignore */
+					}
 					close(ttyf);
 					ttyf = -1;
 				}
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 2/6] syslogd: make logerror is printf-like
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 1/6] Optimize the filling of the record fields Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 3/6] syslogd: Implement customization of log file records Alexey Gladkov
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

If we make logerror look like a printf-like function, we can remove
intermediate buffers in calling functions.

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 syslogd.c | 41 +++++++++++++++++++++++------------------
 1 file changed, 23 insertions(+), 18 deletions(-)

diff --git a/syslogd.c b/syslogd.c
index 55281bd..d4aa9e4 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -348,7 +348,8 @@ const char *cvtaddr(struct sockaddr_storage *f, int len);
 const char *cvthname(struct sockaddr_storage *f, int len);
 void domark(int);
 void debug_switch(int);
-void logerror(const char *type);
+void logerror(const char *fmt, ...)
+	SYSKLOGD_FORMAT((__printf__, 1, 2)) SYSKLOGD_NONNULL((1));
 void die(int sig);
 void doexit(int sig);
 void init();
@@ -1927,24 +1928,34 @@ void debug_switch(int sig)
 /*
  * Print syslogd errors some place.
  */
-void logerror(const char *type)
+void logerror(const char *fmt, ...)
 {
+	va_list ap;
 	char buf[BUFSIZ];
 	struct sourceinfo source;
+	int sv_errno = errno;
+
+	strncpy(buf, "syslogd: ", sizeof(buf));
+
+	va_start(ap, fmt);
+	vsnprintf(buf + 9, sizeof(buf) - 9, fmt, ap);
+	va_end(ap);
+
+	verbosef("Called logerror: %s\n", buf);
+
+	if (sv_errno != 0) {
+		size_t bufsz = strlen(buf);
+		if (strerror_r(sv_errno, buf + bufsz, sizeof(buf) - bufsz))
+			errno = 0; // ignore
+	}
 
 	memset(&source, '\0', sizeof(source));
 
 	source.flags = SINFO_ISINTERNAL;
 	source.hostname = LocalHostName;
 
-	verbosef("Called logerr, msg: %s\n", type);
-
-	if (errno == 0)
-		(void) snprintf(buf, sizeof(buf), "syslogd: %s", type);
-	else
-		(void) snprintf(buf, sizeof(buf), "syslogd: %s: %s", type, strerror(errno));
-	errno = 0;
 	logmsg(LOG_SYSLOG|LOG_ERR, buf, &source, ADDDATE);
+	errno = 0;
 	return;
 }
 
@@ -2268,7 +2279,6 @@ void cfline(char *line, struct filed *f)
 	struct addrinfo hints, *ai;
 #endif
 	char buf[MAXLINE];
-	char xbuf[BUFSIZ];
 
 	verbosef("cfline(%s)\n", line);
 
@@ -2316,8 +2326,7 @@ void cfline(char *line, struct filed *f)
 		}
 
 		if (pri < 0) {
-			(void) snprintf(xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf);
-			logerror(xbuf);
+			logerror("unknown priority name \"%s\"", buf);
 			return;
 		}
 
@@ -2363,8 +2372,7 @@ void cfline(char *line, struct filed *f)
 				i = decode(buf, FacNames);
 				if (i < 0) {
 
-					(void) snprintf(xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf);
-					logerror(xbuf);
+					logerror("unknown facility name \"%s\"", buf);
 					return;
 				}
 
@@ -2457,8 +2465,7 @@ void cfline(char *line, struct filed *f)
 
 		if ( f->f_file < 0 ){
 			f->f_file = -1;
-			verbosef("Error opening log file: %s\n", p);
-			logerror(p);
+			logerror("Error opening log file: %s", p);
 			break;
 		}
 		if (isatty(f->f_file)) {
@@ -2558,7 +2565,6 @@ static void allocate_log(void)
 		Files = (struct filed *) malloc(sizeof(struct filed));
 		if ( Files == (void *) 0 )
 		{
-			verbosef("Cannot initialize log structure.");
 			logerror("Cannot initialize log structure.");
 			return;
 		}
@@ -2570,7 +2576,6 @@ static void allocate_log(void)
 						  sizeof(struct filed));
 		if ( Files == (struct filed *) 0 )
 		{
-			verbosef("Cannot grow log structure.");
 			logerror("Cannot grow log structure.");
 			return;
 		}
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 3/6] syslogd: Implement customization of log file records
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 1/6] Optimize the filling of the record fields Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 2/6] syslogd: make logerror is printf-like Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 4/6] syslogd: no need to try to substitute a field in a record if it's not in the log_format Alexey Gladkov
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 syslog.conf |  17 +++
 syslogd.c   | 303 ++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 277 insertions(+), 43 deletions(-)

diff --git a/syslog.conf b/syslog.conf
index 801f35f..83fa433 100644
--- a/syslog.conf
+++ b/syslog.conf
@@ -1,4 +1,21 @@
 # /etc/syslog.conf - Configuration file for syslogd(8)
+#
+# Specifies log format. The log format can contain placeholders for log message
+# elements:
+#   %t - time of last occurrence.
+#   %h - host from which this message.
+#   %m - message text.
+#   %% - '%' character.
+#   \\ - '\' character.
+#   \b - backspace character.
+#   \t - horizontal tab character.
+#   \f - form feed character.
+#   \n - new line character.
+#   \r - carriage return character.
+#
+# Default:
+#log_format: %t %h %m
+
 #
 # For info about the format of this file, see "man syslog.conf".
 #
diff --git a/syslogd.c b/syslogd.c
index d4aa9e4..214c4e9 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -289,16 +289,38 @@ struct sourceinfo {
 	unsigned int flags;
 } sinfo;
 
-enum record_fields_type {
-	RECORD_FIELD_TIME = 0,
-	RECORD_FIELD_SEP1,
-	RECORD_FIELD_HOST,
-	RECORD_FIELD_SEP2,
-	RECORD_FIELD_MSG,
-	RECORD_FIELD_EOL,
-	RECORD_FIELD_COUNTS,
+enum log_format_type {
+	LOG_FORMAT_NONE = 0,
+	LOG_FORMAT_BOL,
+	LOG_FORMAT_TIME,
+	LOG_FORMAT_HOST,
+	LOG_FORMAT_MSG,
+	LOG_FORMAT_EOL,
+	LOG_FORMAT_COUNTS,
 };
 
+#define LOG_FORMAT_REPEAT_MAX  5
+#define LOG_FORMAT_FIELDS_MAX  LOG_FORMAT_COUNTS * LOG_FORMAT_REPEAT_MAX
+#define LOG_FORMAT_IOVEC_MAX   LOG_FORMAT_FIELDS_MAX * 2 + 1
+
+struct log_format_field {
+	enum log_format_type f_type;
+	struct iovec *f_iov;
+};
+
+struct log_format {
+	char *line;
+
+	struct iovec *iov;
+	size_t iovec_nr;
+
+	struct log_format_field *fields;
+	size_t fields_nr;
+};
+
+static struct log_format log_fmt = { 0 };
+static long int iovec_max = 0;
+
 static int	Debug;			/* debug flag */
 static int	Compress = 1;		/* compress repeated messages flag */
 static char	LocalHostName[MAXHOSTNAMELEN+1];	/* our hostname */
@@ -307,6 +329,7 @@ static char	*emptystring = "";
 static int	InetInuse = 0;		/* non-zero if INET sockets are being used */
 static int	*finet = NULL;		/* Internet datagram sockets */
 static int	Initialized = 0;	/* set when we have initialized ourselves */
+static int	LogFormatInitialized = 0; /* set when we have initialized log_format */
 static int	MarkInterval = 20 * 60;	/* interval between marks in seconds */
 #ifdef INET6
 static int	family = PF_UNSPEC;	/* protocol family (IPv4, IPv6 or both) */
@@ -340,9 +363,15 @@ void untty(void);
 void printchopped(const struct sourceinfo* const, char *msg, size_t len, int fd);
 void printline(const struct sourceinfo* const, char *msg);
 void logmsg(int pri, char *msg, const struct sourceinfo* const, int flags);
+char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
+	SYSKLOGD_NONNULL((1));
+void clear_record_fields(struct log_format *log_fmt)
+	SYSKLOGD_NONNULL((1));
+void set_record_field(struct log_format *log_fmt, enum log_format_type name, char *value, ssize_t len)
+	SYSKLOGD_NONNULL((1));
 void fprintlog(register struct filed *f, char *from, int flags, char *msg);
 void endtty(int);
-void wallmsg(register struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS]);
+void wallmsg(register struct filed *f, struct log_format *log_fmt);
 void reapchild(int);
 const char *cvtaddr(struct sockaddr_storage *f, int len);
 const char *cvthname(struct sockaddr_storage *f, int len);
@@ -358,6 +387,9 @@ int decode(char *name, struct code *codetab);
 static void verbosef(char *, ...)
 	SYSKLOGD_FORMAT((__printf__, 1, 2)) SYSKLOGD_NONNULL((1));
 static void allocate_log(void);
+int set_log_format_field(struct log_format *log_fmt, size_t i, enum log_format_type t, char *s, size_t n)
+	SYSKLOGD_NONNULL((1));
+int parse_log_format(struct log_format *log_fmt, char *s);
 void sighup_handler(int);
 
 #ifdef SYSLOG_UNIXAF
@@ -648,6 +680,9 @@ int main(int argc, char **argv)
 	if (funix_dir && *funix_dir)
 		add_funix_dir(funix_dir);
 
+	if (parse_log_format(&log_fmt, "%t %h %m") < 0)
+		exit(1);
+
 	if ( !(Debug || NoFork) )
 	{
 		verbosef("Checking pidfile.\n");
@@ -1454,16 +1489,38 @@ finish:
 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 }
 
-static inline void set_record_field(struct iovec iov[RECORD_FIELD_COUNTS],
-		enum record_fields_type name, char *value, size_t len)
+char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
 {
-	iov[name].iov_base = value;
-	iov[name].iov_len = len == -1 ? strlen(value) : len;
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		if (log_fmt->fields[i].f_type == name)
+			return log_fmt->fields[i].f_iov->iov_base;
+	}
+	return NULL;
+}
+
+void set_record_field(struct log_format *log_fmt,
+		enum log_format_type name, char *value, ssize_t len)
+{
+	size_t iov_len = len == -1 ? strlen(value) : len;
+
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		if (log_fmt->fields[i].f_type == name) {
+			log_fmt->fields[i].f_iov->iov_base = value;
+			log_fmt->fields[i].f_iov->iov_len = iov_len;
+		}
+	}
+}
+
+void clear_record_fields(struct log_format *log_fmt)
+{
+	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
+		log_fmt->fields[i].f_iov->iov_base = NULL;
+		log_fmt->fields[i].f_iov->iov_len = 0;
+	}
 }
 
 void fprintlog(struct filed *f, char *from, int flags, char *msg)
 {
-	struct iovec iov[RECORD_FIELD_COUNTS];
 	char repbuf[80];
 #ifdef SYSLOG_INET
 	register int l;
@@ -1475,19 +1532,18 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 
 	verbosef("Called fprintlog, ");
 
-	set_record_field(iov, RECORD_FIELD_TIME, f->f_lasttime, 15);
-	set_record_field(iov, RECORD_FIELD_SEP1, " ", 1);
-	set_record_field(iov, RECORD_FIELD_HOST, f->f_prevhost, -1);
-	set_record_field(iov, RECORD_FIELD_SEP2, " ", 1);
+	clear_record_fields(&log_fmt);
 
+	set_record_field(&log_fmt, LOG_FORMAT_TIME, f->f_lasttime, 15);
+	set_record_field(&log_fmt, LOG_FORMAT_HOST, f->f_prevhost, -1);
 	if (msg) {
-		set_record_field(iov, RECORD_FIELD_MSG, msg, -1);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, msg, -1);
 	} else if (f->f_prevcount > 1) {
 		(void) snprintf(repbuf, sizeof(repbuf), "last message repeated %d times",
 		    f->f_prevcount);
-		set_record_field(iov, RECORD_FIELD_MSG, repbuf, -1);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, repbuf, -1);
 	} else {
-		set_record_field(iov, RECORD_FIELD_MSG, f->f_prevline, f->f_prevlen);
+		set_record_field(&log_fmt, LOG_FORMAT_MSG, f->f_prevline, f->f_prevlen);
 	}
 
 	verbosef("logging to %s", TypeNames[f->f_type]);
@@ -1567,7 +1623,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 			int i;
 			f->f_time = now;
 			(void) snprintf(line, sizeof(line), "<%d>%s", f->f_prevpri, \
-				(char *) iov[RECORD_FIELD_MSG].iov_base);
+				(char *) log_fmt.iov[LOG_FORMAT_MSG].iov_base);
 			l = strlen(line);
 			if (l > MAXLINE)
 				l = MAXLINE;
@@ -1615,9 +1671,9 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		f->f_time = now;
 		verbosef(" %s\n", f->f_un.f_fname);
 		if (f->f_type == F_TTY || f->f_type == F_CONSOLE) {
-			set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
+			set_record_field(&log_fmt, LOG_FORMAT_EOL, "\r\n", 2);
 		} else {
-			set_record_field(iov, RECORD_FIELD_EOL, "\n", 1);
+			set_record_field(&log_fmt, LOG_FORMAT_EOL, "\n", 1);
 		}
 	again:
 		/* f->f_file == -1 is an indicator that we couldn't
@@ -1625,7 +1681,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		if (f->f_file == -1)
 			break;
 
-		if (writev(f->f_file, iov, RECORD_FIELD_COUNTS) < 0) {
+		if (writev(f->f_file, log_fmt.iov, LOG_FORMAT_IOVEC_MAX) < 0) {
 			int e = errno;
 
 			/* If a named pipe is full, just ignore it for now */
@@ -1672,8 +1728,8 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 	case F_WALL:
 		f->f_time = now;
 		verbosef("\n");
-		set_record_field(iov, RECORD_FIELD_EOL, "\r\n", 2);
-		wallmsg(f, iov);
+		set_record_field(&log_fmt, LOG_FORMAT_EOL, "\r\n", 2);
+		wallmsg(f, &log_fmt);
 		break;
 	} /* switch */
 	if (f->f_type != F_FORW_UNKN)
@@ -1695,25 +1751,22 @@ void endtty(int sig)
  *	world, or a list of approved users.
  */
 
-void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
+void wallmsg(struct filed *f, struct log_format *log_fmt)
 {
 	char p[sizeof (_PATH_DEV) + UNAMESZ];
 	register int i;
-	int ttyf, len;
+	int ttyf;
 	static int reenter = 0;
 	struct utmp ut;
 	struct utmp *uptr;
 	char greetings[200];
 
-	(void) &len;
-
 	if (reenter++)
 		return;
 
 	/* open the user login file */
 	setutent();
 
-
 	/*
 	 * Might as well fork instead of using nonblocking I/O
 	 * and doing notty().
@@ -1722,10 +1775,13 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 		(void) signal(SIGTERM, SIG_DFL);
 		(void) alarm(0);
 
-		(void) snprintf(greetings, sizeof(greetings),
-		    "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
-			(char *) iov[RECORD_FIELD_HOST].iov_base, ctime(&now));
-		len = strlen(greetings);
+		if (f->f_type == F_WALL) {
+			snprintf(greetings, sizeof(greetings),
+			    "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
+				get_record_field(log_fmt, LOG_FORMAT_HOST), ctime(&now));
+
+			set_record_field(log_fmt, LOG_FORMAT_BOL, greetings, -1);
+		}
 
 		/* scan the user login file */
 		while ((uptr = getutent())) {
@@ -1757,11 +1813,6 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 			strcpy(p, _PATH_DEV);
 			strncat(p, ut.ut_line, UNAMESZ);
 
-			if (f->f_type == F_WALL) {
-				iov[0].iov_base = greetings;
-				iov[0].iov_len = len;
-				iov[1].iov_len = 0;
-			}
 			if (setjmp(ttybuf) == 0) {
 				(void) signal(SIGALRM, endtty);
 				(void) alarm(15);
@@ -1771,7 +1822,7 @@ void wallmsg(struct filed *f, struct iovec iov[RECORD_FIELD_COUNTS])
 					struct stat statb;
 
 					if (!fstat(ttyf, &statb) && (statb.st_mode & S_IWRITE)) {
-						if (writev(ttyf, iov, RECORD_FIELD_COUNTS) < 0)
+						if (writev(ttyf, log_fmt->iov, LOG_FORMAT_IOVEC_MAX) < 0)
 							errno = 0; /* ignore */
 					}
 					close(ttyf);
@@ -1924,7 +1975,6 @@ void debug_switch(int sig)
 	signal(SIGUSR1, debug_switch);
 }
 
-
 /*
  * Print syslogd errors some place.
  */
@@ -1949,6 +1999,11 @@ void logerror(const char *fmt, ...)
 			errno = 0; // ignore
 	}
 
+	if (!LogFormatInitialized) {
+		fputs(buf, stderr);
+		return;
+	}
+
 	memset(&source, '\0', sizeof(source));
 
 	source.flags = SINFO_ISINTERNAL;
@@ -2163,6 +2218,13 @@ void init(void)
 
 		*++p = '\0';
 
+
+		if (!strncmp("log_format:", cline, 11)) {
+			for (p = cline + 11; isspace(*p); ++p);
+			parse_log_format(&log_fmt, p);
+			continue;
+		}
+
 		allocate_log();
 		f = &Files[lognum++];
 
@@ -2590,6 +2652,161 @@ static void allocate_log(void)
 	return;
 }
 
+int set_log_format_field(struct log_format *log_fmt, size_t i,
+		enum log_format_type t, char *s, size_t n)
+{
+	if (i >= iovec_max) {
+		logerror("Too many parts in the log_format string");
+		return -1;
+	}
+
+	if (t != LOG_FORMAT_NONE) {
+		if (log_fmt->fields_nr >= LOG_FORMAT_FIELDS_MAX) {
+			logerror("Too many placeholders in the log_format string");
+			return -1;
+		}
+
+		log_fmt->fields[log_fmt->fields_nr].f_type = t;
+		log_fmt->fields[log_fmt->fields_nr].f_iov = log_fmt->iov + i;
+		log_fmt->fields_nr++;
+	}
+
+	log_fmt->iov[i].iov_base = s;
+	log_fmt->iov[i].iov_len = n;
+	log_fmt->iovec_nr++;
+
+	return 0;
+}
+
+int parse_log_format(struct log_format *log_fmt, char *str)
+{
+	char *ptr, *start;
+	int i, special, field_nr;
+	struct log_format new_fmt = { 0 };
+
+	iovec_max = sysconf(_SC_IOV_MAX);
+
+	new_fmt.line = calloc(1, LINE_MAX);
+	if (!new_fmt.line) {
+		logerror("Cannot allocate log_format string");
+		goto error;
+	}
+
+	new_fmt.iov = calloc(LOG_FORMAT_IOVEC_MAX, sizeof(struct iovec));
+	if (!new_fmt.iov) {
+		logerror("Cannot allocate records array for log_format string");
+		goto error;
+	}
+
+	new_fmt.fields = calloc(LOG_FORMAT_FIELDS_MAX, sizeof(struct log_format_field));
+	if (!new_fmt.fields) {
+		logerror("Cannot allocate rules array for log_format string");
+		goto error;
+	}
+
+	ptr = str;
+	i = special = 0;
+
+	while (*ptr != '\0' && i < LINE_MAX) {
+		char c = *ptr++;
+
+		switch (c) {
+			case 'b': if (special) c = '\b'; break;
+			case 'f': if (special) c = '\f'; break;
+			case 'n': if (special) c = '\n'; break;
+			case 'r': if (special) c = '\r'; break;
+			case 't': if (special) c = '\t'; break;
+			case '\\':
+				if (!special) {
+					special = 1;
+					continue;
+				}
+				break;
+		}
+		new_fmt.line[i++] = c;
+		special = 0;
+	}
+
+	field_nr = 0;
+	special = 0;
+	i = 0;
+
+	if (set_log_format_field(&new_fmt, field_nr++, LOG_FORMAT_BOL, NULL, 0) < 0)
+		goto error;
+
+	start = ptr = new_fmt.line;
+
+	while (*ptr != '\0') {
+		enum log_format_type f_type;
+
+		if (special) {
+			switch (*ptr) {
+				case 't': f_type = LOG_FORMAT_TIME; break;
+				case 'h': f_type = LOG_FORMAT_HOST; break;
+				case 'm': f_type = LOG_FORMAT_MSG;  break;
+				case '%':
+					special = 0;
+					goto create_special;
+				default:
+					logerror("unexpected special: '%%%c'", *ptr);
+					goto error;
+			}
+			special = 0;
+			goto create_field;
+
+		} else if (*ptr == '%')
+			special = 1;
+next:
+		ptr++;
+		continue;
+create_field:
+		if ((ptr - start - 1) > 0 &&
+		    set_log_format_field(&new_fmt, field_nr++,
+				LOG_FORMAT_NONE, start, (ptr - start - 1)) < 0)
+			goto error;
+
+		if (set_log_format_field(&new_fmt, field_nr++, f_type, NULL, 0) < 0)
+			goto error;
+
+		start = ptr + 1;
+		goto next;
+create_special:
+		if (set_log_format_field(&new_fmt, field_nr++,
+				LOG_FORMAT_NONE, start, (ptr - start - 1)) < 0)
+			goto error;
+
+		start = ptr;
+		goto next;
+	}
+
+	if (special) {
+		logerror("unexpected '%%' at the end of line");
+		goto error;
+	}
+
+	if (start != ptr &&
+	    set_log_format_field(&new_fmt, field_nr++,
+			LOG_FORMAT_NONE, start, (ptr - start)) < 0)
+		goto error;
+
+	if (set_log_format_field(&new_fmt, field_nr++,
+			LOG_FORMAT_EOL, NULL, 0) < 0)
+		goto error;
+
+	log_fmt->line   = new_fmt.line;
+	log_fmt->iov    = new_fmt.iov;
+	log_fmt->fields = new_fmt.fields;
+
+	LogFormatInitialized = 1;
+
+	return 0;
+error:
+	free(new_fmt.line);
+	free(new_fmt.iov);
+	free(new_fmt.fields);
+
+	return -1;
+}
 
 /*
  * The following function is resposible for handling a SIGHUP signal.  Since
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 4/6] syslogd: no need to try to substitute a field in a record if it's not in the log_format
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
                   ` (2 preceding siblings ...)
  2020-10-27 11:33 ` [devel] [PATCH 3/6] syslogd: Implement customization of log file records Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 5/6] Makefile: use make variables instead of a hardcoded list Alexey Gladkov
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 syslogd.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/syslogd.c b/syslogd.c
index 214c4e9..4c3c85f 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -314,6 +314,7 @@ struct log_format {
 	struct iovec *iov;
 	size_t iovec_nr;
 
+	unsigned int f_mask;
 	struct log_format_field *fields;
 	size_t fields_nr;
 };
@@ -1491,6 +1492,9 @@ finish:
 
 char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
 {
+	if (!(log_fmt->f_mask | (1 << name)))
+		return NULL;
+
 	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
 		if (log_fmt->fields[i].f_type == name)
 			return log_fmt->fields[i].f_iov->iov_base;
@@ -1501,7 +1505,12 @@ char *get_record_field(struct log_format *log_fmt, enum log_format_type name)
 void set_record_field(struct log_format *log_fmt,
 		enum log_format_type name, char *value, ssize_t len)
 {
-	size_t iov_len = len == -1 ? strlen(value) : len;
+	size_t iov_len;
+
+	if (!(log_fmt->f_mask | (1 << name)))
+		return;
+
+	iov_len = len == -1 ? strlen(value) : len;
 
 	for (int i = 0; i < LOG_FORMAT_FIELDS_MAX && log_fmt->fields[i].f_iov; i++) {
 		if (log_fmt->fields[i].f_type == name) {
@@ -2666,6 +2675,7 @@ int set_log_format_field(struct log_format *log_fmt, size_t i,
 			return -1;
 		}
 
+		log_fmt->f_mask |= (1 << t);
 		log_fmt->fields[log_fmt->fields_nr].f_type = t;
 		log_fmt->fields[log_fmt->fields_nr].f_iov = log_fmt->iov + i;
 		log_fmt->fields_nr++;
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 5/6] Makefile: use make variables instead of a hardcoded list
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
                   ` (3 preceding siblings ...)
  2020-10-27 11:33 ` [devel] [PATCH 4/6] syslogd: no need to try to substitute a field in a record if it's not in the log_format Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:33 ` [devel] [PATCH 6/6] syslogd: implement checksum chains for log entries Alexey Gladkov
  2020-10-27 11:44 ` [devel] [PATCH 0/6] sysklogd: implement checkum " Alexey V. Vissarionov
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 Makefile | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index b2876fd..b46969f 100644
--- a/Makefile
+++ b/Makefile
@@ -99,19 +99,19 @@ test:
 install: install_man install_exec
 
 syslogd: syslogd.o pidfile.o
-	${CC} ${LDFLAGS} -o syslogd syslogd.o pidfile.o ${LIBS}
+	${CC} ${LDFLAGS} -o $@ $^ ${LIBS}
 
 klogd:	klogd.o syslog.o pidfile.o
-	${CC} ${LDFLAGS} -o klogd klogd.o syslog.o pidfile.o ${LIBS}
+	${CC} ${LDFLAGS} -o $@ $^ ${LIBS}
 
 syslogd.o: syslogd.c version.h
-	${CC} ${SKFLAGS} ${SYSLOGD_FLAGS} $(DEB) -c syslogd.c
+	${CC} ${SKFLAGS} ${SYSLOGD_FLAGS} $(DEB) -c $<
 
 syslog.o: syslog.c
-	${CC} ${SKFLAGS} ${SYSLOG_FLAGS} -c syslog.c
+	${CC} ${SKFLAGS} ${SYSLOG_FLAGS} -c $<
 
 klogd.o: klogd.c klogd.h version.h
-	${CC} ${SKFLAGS} ${KLOGD_FLAGS} $(DEB) -c klogd.c
+	${CC} ${SKFLAGS} ${KLOGD_FLAGS} $(DEB) -c $<
 
 clean:
 	rm -f *.o *.log *~ *.orig
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [devel] [PATCH 6/6] syslogd: implement checksum chains for log entries
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
                   ` (4 preceding siblings ...)
  2020-10-27 11:33 ` [devel] [PATCH 5/6] Makefile: use make variables instead of a hardcoded list Alexey Gladkov
@ 2020-10-27 11:33 ` Alexey Gladkov
  2020-10-27 11:44 ` [devel] [PATCH 0/6] sysklogd: implement checkum " Alexey V. Vissarionov
  6 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 11:33 UTC (permalink / raw)
  To: ALT Devel discussion list

This is a mechanism for detecting intrusion into log files.

Each time the server starts, it starts creating a checksum chain for
each log file. On restart, the chain starts with an empty checksum.

A mismatch between the checkum and the server restart is a possible
intrusion and the reason for investigation.

Each entry is accompanied by a checksum of this entry with the previous
checksum. So, to check the checksum of the current record, you need to
have a previous checksum.

Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
 Makefile                         |  14 +-
 block/bswap.h                    | 217 +++++++++++++++++++++++++++++++
 block/sha256.c                   | 202 ++++++++++++++++++++++++++++
 block/sha256.h                   |  24 ++++
 hash.h                           |  66 ++++++++++
 syslogd.c                        |  41 ++++++
 tests/log-hashes/.gitignore      |   5 +
 tests/log-hashes/check           |  28 ++++
 tests/log-hashes/check-hashes.sh |  28 ++++
 9 files changed, 622 insertions(+), 3 deletions(-)
 create mode 100644 block/bswap.h
 create mode 100644 block/sha256.c
 create mode 100644 block/sha256.h
 create mode 100644 hash.h
 create mode 100644 tests/log-hashes/.gitignore
 create mode 100755 tests/log-hashes/check
 create mode 100755 tests/log-hashes/check-hashes.sh

diff --git a/Makefile b/Makefile
index b46969f..27c415b 100644
--- a/Makefile
+++ b/Makefile
@@ -53,6 +53,10 @@ MANDIR = $(prefix)/usr/share/man
 # file system standard.
 FSSTND = -DFSSTND
 
+# The following define determines whether the syslogd should create checksum
+# chains for log entries.
+USE_CHECKSUMS = -DUSE_CHECKSUMS
+
 # The following define establishes ownership for the man pages.
 # Avery tells me that there is a difference between Debian and
 # Slackware.  Rather than choose sides I am leaving it up to the user.
@@ -67,8 +71,8 @@ MAN_PERMS = 644
 # ballot below.
 SYSLOGD_PIDNAME = -DSYSLOGD_PIDNAME=\"syslogd.pid\"
 
-SYSLOGD_FLAGS= -DSYSLOG_INET -DSYSLOG_UNIXAF -DINET6 -DNO_SCCS ${FSSTND} \
-	${SYSLOGD_PIDNAME}
+SYSLOGD_FLAGS= -DSYSLOG_INET -DSYSLOG_UNIXAF -DINET6 -DNO_SCCS \
+	${USE_CHECKSUMS} ${FSSTND} ${SYSLOGD_PIDNAME}
 SYSLOG_FLAGS= -DALLOW_KERNEL_LOGGING
 KLOGD_FLAGS = ${FSSTND} ${KLOGD_START_DELAY}
 DEB =
@@ -80,6 +84,10 @@ TESTS = \
 	priority-exclamation \
 	named-pipes
 
+ifneq (${USE_CHECKSUMS},)
+TESTS += log-hashes
+endif
+
 all: syslogd klogd
 
 test:
@@ -98,7 +106,7 @@ test:
 
 install: install_man install_exec
 
-syslogd: syslogd.o pidfile.o
+syslogd: syslogd.o pidfile.o block/sha256.o
 	${CC} ${LDFLAGS} -o $@ $^ ${LIBS}
 
 klogd:	klogd.o syslog.o pidfile.o
diff --git a/block/bswap.h b/block/bswap.h
new file mode 100644
index 0000000..e4e2573
--- /dev/null
+++ b/block/bswap.h
@@ -0,0 +1,217 @@
+#ifndef COMPAT_BSWAP_H
+#define COMPAT_BSWAP_H
+
+/*
+ * Let's make sure we always have a sane definition for ntohl()/htonl().
+ * Some libraries define those as a function call, just to perform byte
+ * shifting, bringing significant overhead to what should be a simple
+ * operation.
+ */
+
+/*
+ * Default version that the compiler ought to optimize properly with
+ * constant values.
+ */
+static inline uint32_t default_swab32(uint32_t val)
+{
+	return (((val & 0xff000000) >> 24) |
+		((val & 0x00ff0000) >>  8) |
+		((val & 0x0000ff00) <<  8) |
+		((val & 0x000000ff) << 24));
+}
+
+static inline uint64_t default_bswap64(uint64_t val)
+{
+	return (((val & (uint64_t)0x00000000000000ffULL) << 56) |
+		((val & (uint64_t)0x000000000000ff00ULL) << 40) |
+		((val & (uint64_t)0x0000000000ff0000ULL) << 24) |
+		((val & (uint64_t)0x00000000ff000000ULL) <<  8) |
+		((val & (uint64_t)0x000000ff00000000ULL) >>  8) |
+		((val & (uint64_t)0x0000ff0000000000ULL) >> 24) |
+		((val & (uint64_t)0x00ff000000000000ULL) >> 40) |
+		((val & (uint64_t)0xff00000000000000ULL) >> 56));
+}
+
+#undef bswap32
+#undef bswap64
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+#define bswap32 git_bswap32
+static inline uint32_t git_bswap32(uint32_t x)
+{
+	uint32_t result;
+	if (__builtin_constant_p(x))
+		result = default_swab32(x);
+	else
+		__asm__("bswap %0" : "=r" (result) : "0" (x));
+	return result;
+}
+
+#define bswap64 git_bswap64
+#if defined(__x86_64__)
+static inline uint64_t git_bswap64(uint64_t x)
+{
+	uint64_t result;
+	if (__builtin_constant_p(x))
+		result = default_bswap64(x);
+	else
+		__asm__("bswap %q0" : "=r" (result) : "0" (x));
+	return result;
+}
+#else
+static inline uint64_t git_bswap64(uint64_t x)
+{
+	union { uint64_t i64; uint32_t i32[2]; } tmp, result;
+	if (__builtin_constant_p(x))
+		result.i64 = default_bswap64(x);
+	else {
+		tmp.i64 = x;
+		result.i32[0] = git_bswap32(tmp.i32[1]);
+		result.i32[1] = git_bswap32(tmp.i32[0]);
+	}
+	return result.i64;
+}
+#endif
+
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+
+#include <stdlib.h>
+
+#define bswap32(x) _byteswap_ulong(x)
+#define bswap64(x) _byteswap_uint64(x)
+
+#endif
+
+#if defined(bswap32)
+
+#undef ntohl
+#undef htonl
+#define ntohl(x) bswap32(x)
+#define htonl(x) bswap32(x)
+
+#endif
+
+#if defined(bswap64)
+
+#undef ntohll
+#undef htonll
+#define ntohll(x) bswap64(x)
+#define htonll(x) bswap64(x)
+
+#else
+
+#undef ntohll
+#undef htonll
+
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN)
+
+# define GIT_BYTE_ORDER __BYTE_ORDER
+# define GIT_LITTLE_ENDIAN __LITTLE_ENDIAN
+# define GIT_BIG_ENDIAN __BIG_ENDIAN
+
+#elif defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+
+# define GIT_BYTE_ORDER BYTE_ORDER
+# define GIT_LITTLE_ENDIAN LITTLE_ENDIAN
+# define GIT_BIG_ENDIAN BIG_ENDIAN
+
+#else
+
+# define GIT_BIG_ENDIAN 4321
+# define GIT_LITTLE_ENDIAN 1234
+
+# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
+#  define GIT_BYTE_ORDER GIT_BIG_ENDIAN
+# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
+#  define GIT_BYTE_ORDER GIT_LITTLE_ENDIAN
+# elif defined(__THW_BIG_ENDIAN__) && !defined(__THW_LITTLE_ENDIAN__)
+#  define GIT_BYTE_ORDER GIT_BIG_ENDIAN
+# elif defined(__THW_LITTLE_ENDIAN__) && !defined(__THW_BIG_ENDIAN__)
+#  define GIT_BYTE_ORDER GIT_LITTLE_ENDIAN
+# else
+#  error "Cannot determine endianness"
+# endif
+
+#endif
+
+#if GIT_BYTE_ORDER == GIT_BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#else
+# define ntohll(n) default_bswap64(n)
+# define htonll(n) default_bswap64(n)
+#endif
+
+#endif
+
+/*
+ * Performance might be improved if the CPU architecture is OK with
+ * unaligned 32-bit loads and a fast ntohl() is available.
+ * Otherwise fall back to byte loads and shifts which is portable,
+ * and is faster on architectures with memory alignment issues.
+ */
+
+#if !defined(NO_UNALIGNED_LOADS) && ( \
+    defined(__i386__) || defined(__x86_64__) || \
+    defined(_M_IX86) || defined(_M_X64) || \
+    defined(__ppc__) || defined(__ppc64__) || \
+    defined(__powerpc__) || defined(__powerpc64__) || \
+    defined(__s390__) || defined(__s390x__))
+
+#define get_be16(p)	ntohs(*(unsigned short *)(p))
+#define get_be32(p)	ntohl(*(unsigned int *)(p))
+#define get_be64(p)	ntohll(*(uint64_t *)(p))
+#define put_be32(p, v)	do { *(unsigned int *)(p) = htonl(v); } while (0)
+#define put_be64(p, v)	do { *(uint64_t *)(p) = htonll(v); } while (0)
+
+#else
+
+static inline uint16_t get_be16(const void *ptr)
+{
+	const unsigned char *p = ptr;
+	return	(uint16_t)p[0] << 8 |
+		(uint16_t)p[1] << 0;
+}
+
+static inline uint32_t get_be32(const void *ptr)
+{
+	const unsigned char *p = ptr;
+	return	(uint32_t)p[0] << 24 |
+		(uint32_t)p[1] << 16 |
+		(uint32_t)p[2] <<  8 |
+		(uint32_t)p[3] <<  0;
+}
+
+static inline uint64_t get_be64(const void *ptr)
+{
+	const unsigned char *p = ptr;
+	return	(uint64_t)get_be32(&p[0]) << 32 |
+		(uint64_t)get_be32(&p[4]) <<  0;
+}
+
+static inline void put_be32(void *ptr, uint32_t value)
+{
+	unsigned char *p = ptr;
+	p[0] = value >> 24;
+	p[1] = value >> 16;
+	p[2] = value >>  8;
+	p[3] = value >>  0;
+}
+
+static inline void put_be64(void *ptr, uint64_t value)
+{
+	unsigned char *p = ptr;
+	p[0] = value >> 56;
+	p[1] = value >> 48;
+	p[2] = value >> 40;
+	p[3] = value >> 32;
+	p[4] = value >> 24;
+	p[5] = value >> 16;
+	p[6] = value >>  8;
+	p[7] = value >>  0;
+}
+
+#endif
+
+#endif /* COMPAT_BSWAP_H */
diff --git a/block/sha256.c b/block/sha256.c
new file mode 100644
index 0000000..b1f02be
--- /dev/null
+++ b/block/sha256.c
@@ -0,0 +1,202 @@
+#include <arpa/inet.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "./sha256.h"
+#include "./bswap.h"
+
+#undef RND
+#undef BLKSIZE
+
+#define BLKSIZE blk_SHA256_BLKSIZE
+
+void blk_SHA256_Init(blk_SHA256_CTX *ctx)
+{
+	ctx->offset = 0;
+	ctx->size = 0;
+	ctx->state[0] = 0x6a09e667ul;
+	ctx->state[1] = 0xbb67ae85ul;
+	ctx->state[2] = 0x3c6ef372ul;
+	ctx->state[3] = 0xa54ff53aul;
+	ctx->state[4] = 0x510e527ful;
+	ctx->state[5] = 0x9b05688cul;
+	ctx->state[6] = 0x1f83d9abul;
+	ctx->state[7] = 0x5be0cd19ul;
+}
+
+static inline uint32_t ror(uint32_t x, unsigned n)
+{
+	return (x >> n) | (x << (32 - n));
+}
+
+static inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z)
+{
+	return z ^ (x & (y ^ z));
+}
+
+static inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z)
+{
+	return ((x | y) & z) | (x & y);
+}
+
+static inline uint32_t sigma0(uint32_t x)
+{
+	return ror(x, 2) ^ ror(x, 13) ^ ror(x, 22);
+}
+
+static inline uint32_t sigma1(uint32_t x)
+{
+	return ror(x, 6) ^ ror(x, 11) ^ ror(x, 25);
+}
+
+static inline uint32_t gamma0(uint32_t x)
+{
+	return ror(x, 7) ^ ror(x, 18) ^ (x >> 3);
+}
+
+static inline uint32_t gamma1(uint32_t x)
+{
+	return ror(x, 17) ^ ror(x, 19) ^ (x >> 10);
+}
+
+static void blk_SHA256_Transform(blk_SHA256_CTX *ctx, const unsigned char *buf)
+{
+
+	uint32_t S[8], W[64], t0, t1;
+	int i;
+
+	/* copy state into S */
+	for (i = 0; i < 8; i++)
+		S[i] = ctx->state[i];
+
+	/* copy the state into 512-bits into W[0..15] */
+	for (i = 0; i < 16; i++, buf += sizeof(uint32_t))
+		W[i] = get_be32(buf);
+
+	/* fill W[16..63] */
+	for (i = 16; i < 64; i++)
+		W[i] = gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16];
+
+#define RND(a,b,c,d,e,f,g,h,i,ki)                    \
+	t0 = h + sigma1(e) + ch(e, f, g) + ki + W[i];   \
+	t1 = sigma0(a) + maj(a, b, c);                  \
+	d += t0;                                        \
+	h  = t0 + t1;
+
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],0,0x428a2f98);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],1,0x71374491);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],2,0xb5c0fbcf);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],3,0xe9b5dba5);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],4,0x3956c25b);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],5,0x59f111f1);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],6,0x923f82a4);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],7,0xab1c5ed5);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],8,0xd807aa98);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],9,0x12835b01);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],10,0x243185be);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],11,0x550c7dc3);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],12,0x72be5d74);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],13,0x80deb1fe);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],14,0x9bdc06a7);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],15,0xc19bf174);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],16,0xe49b69c1);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],17,0xefbe4786);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],18,0x0fc19dc6);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],19,0x240ca1cc);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],20,0x2de92c6f);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],21,0x4a7484aa);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],22,0x5cb0a9dc);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],23,0x76f988da);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],24,0x983e5152);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],25,0xa831c66d);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],26,0xb00327c8);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],27,0xbf597fc7);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],28,0xc6e00bf3);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],29,0xd5a79147);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],30,0x06ca6351);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],31,0x14292967);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],32,0x27b70a85);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],33,0x2e1b2138);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],34,0x4d2c6dfc);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],35,0x53380d13);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],36,0x650a7354);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],37,0x766a0abb);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],38,0x81c2c92e);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],39,0x92722c85);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],40,0xa2bfe8a1);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],41,0xa81a664b);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],42,0xc24b8b70);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],43,0xc76c51a3);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],44,0xd192e819);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],45,0xd6990624);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],46,0xf40e3585);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],47,0x106aa070);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],48,0x19a4c116);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],49,0x1e376c08);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],50,0x2748774c);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],51,0x34b0bcb5);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],52,0x391c0cb3);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],53,0x4ed8aa4a);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],54,0x5b9cca4f);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],55,0x682e6ff3);
+	RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],56,0x748f82ee);
+	RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],57,0x78a5636f);
+	RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],58,0x84c87814);
+	RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],59,0x8cc70208);
+	RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],60,0x90befffa);
+	RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],61,0xa4506ceb);
+	RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],62,0xbef9a3f7);
+	RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],63,0xc67178f2);
+
+	for (i = 0; i < 8; i++)
+		ctx->state[i] += S[i];
+}
+
+void blk_SHA256_Update(blk_SHA256_CTX *ctx, const void *data, size_t len)
+{
+	unsigned int len_buf = ctx->size & 63;
+
+	ctx->size += len;
+
+	/* Read the data into buf and process blocks as they get full */
+	if (len_buf) {
+		unsigned int left = 64 - len_buf;
+		if (len < left)
+			left = len;
+		memcpy(len_buf + ctx->buf, data, left);
+		len_buf = (len_buf + left) & 63;
+		len -= left;
+		data = ((const char *)data + left);
+		if (len_buf)
+			return;
+		blk_SHA256_Transform(ctx, ctx->buf);
+	}
+	while (len >= 64) {
+		blk_SHA256_Transform(ctx, data);
+		data = ((const char *)data + 64);
+		len -= 64;
+	}
+	if (len)
+		memcpy(ctx->buf, data, len);
+}
+
+void blk_SHA256_Final(unsigned char *digest, blk_SHA256_CTX *ctx)
+{
+	static const unsigned char pad[64] = { 0x80 };
+	unsigned int padlen[2];
+	int i;
+
+	/* Pad with a binary 1 (ie 0x80), then zeroes, then length */
+	padlen[0] = htonl((uint32_t)(ctx->size >> 29));
+	padlen[1] = htonl((uint32_t)(ctx->size << 3));
+
+	i = ctx->size & 63;
+	blk_SHA256_Update(ctx, pad, 1 + (63 & (55 - i)));
+	blk_SHA256_Update(ctx, padlen, 8);
+
+	/* copy output */
+	for (i = 0; i < 8; i++, digest += sizeof(uint32_t))
+		put_be32(digest, ctx->state[i]);
+}
diff --git a/block/sha256.h b/block/sha256.h
new file mode 100644
index 0000000..5099d64
--- /dev/null
+++ b/block/sha256.h
@@ -0,0 +1,24 @@
+#ifndef SHA256_BLOCK_SHA256_H
+#define SHA256_BLOCK_SHA256_H
+
+#define blk_SHA256_BLKSIZE 64
+
+struct blk_SHA256_CTX {
+	uint32_t state[8];
+	uint64_t size;
+	uint32_t offset;
+	uint8_t buf[blk_SHA256_BLKSIZE];
+};
+
+typedef struct blk_SHA256_CTX blk_SHA256_CTX;
+
+void blk_SHA256_Init(blk_SHA256_CTX *ctx);
+void blk_SHA256_Update(blk_SHA256_CTX *ctx, const void *data, size_t len);
+void blk_SHA256_Final(unsigned char *digest, blk_SHA256_CTX *ctx);
+
+#define platform_SHA256_CTX blk_SHA256_CTX
+#define platform_SHA256_Init blk_SHA256_Init
+#define platform_SHA256_Update blk_SHA256_Update
+#define platform_SHA256_Final blk_SHA256_Final
+
+#endif
diff --git a/hash.h b/hash.h
new file mode 100644
index 0000000..6fbed5c
--- /dev/null
+++ b/hash.h
@@ -0,0 +1,66 @@
+#ifndef SYSLOGD_HASH_H_
+#define SYSLOGD_HASH_H_
+
+#include "./block/sha256.h"
+
+/* A suitably aligned type for stack allocations of hash contexts. */
+union hash_ctx {
+	platform_SHA256_CTX sha256;
+};
+typedef union hash_ctx hash_ctx_t;
+
+#ifdef USE_CHECKSUMS
+
+/* The length in bytes and in hex digits (SHA-256 value). */
+#define HASH_RAWSZ 32
+#define HASH_HEXSZ (2 * HASH_RAWSZ)
+
+#define HASH_NAME   "sha256"
+#define HASH_NAMESZ sizeof(HASH_NAME) - 1
+
+#define EMPTY_HASH_LITERAL \
+	HASH_NAME ":e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
+static inline void hash_init(hash_ctx_t *ctx)
+{
+        platform_SHA256_Init(&ctx->sha256);
+}
+
+static inline void hash_update(hash_ctx_t *ctx, const void *data, size_t len)
+{
+        platform_SHA256_Update(&ctx->sha256, data, len);
+}
+
+static inline void hash_final(unsigned char *hash, hash_ctx_t *ctx)
+{
+        platform_SHA256_Final(hash, &ctx->sha256);
+}
+
+#else /* USE_CHECKSUMS */
+
+#define HASH_RAWSZ 0
+#define HASH_HEXSZ 0
+
+#define HASH_NAME   ""
+#define HASH_NAMESZ 0
+
+#define EMPTY_HASH_LITERAL ""
+
+
+static inline void hash_init(hash_ctx_t *ctx)
+{
+	return;
+}
+
+static inline void hash_update(hash_ctx_t *ctx, const void *in, size_t len)
+{
+	return;
+}
+
+static inline void hash_final(unsigned char *hash, hash_ctx_t *ctx)
+{
+	return;
+}
+
+#endif /* USE_CHECKSUMS */
+#endif
diff --git a/syslogd.c b/syslogd.c
index 4c3c85f..dac7c37 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -61,6 +61,7 @@
 #include "pidfile.h"
 #include "version.h"
 #include "attribute.h"
+#include "hash.h"
 
 #if defined(__linux__)
 #include <paths.h>
@@ -186,6 +187,8 @@ struct filed {
 	int	f_prevcount;			/* repetition cnt of prevline */
 	int	f_repeatcount;			/* number of "repeated" msgs */
 	int	f_flags;			/* store some additional flags */
+	/* hash of last logged message */
+	char    f_prevhash[HASH_NAMESZ + 1 + HASH_HEXSZ + 1];
 };
 
 /*
@@ -292,6 +295,7 @@ struct sourceinfo {
 enum log_format_type {
 	LOG_FORMAT_NONE = 0,
 	LOG_FORMAT_BOL,
+	LOG_FORMAT_HASH,
 	LOG_FORMAT_TIME,
 	LOG_FORMAT_HOST,
 	LOG_FORMAT_MSG,
@@ -391,6 +395,7 @@ static void allocate_log(void);
 int set_log_format_field(struct log_format *log_fmt, size_t i, enum log_format_type t, char *s, size_t n)
 	SYSKLOGD_NONNULL((1));
 int parse_log_format(struct log_format *log_fmt, char *s);
+void calculate_digest(struct filed *f, struct log_format *log_fmt);
 void sighup_handler(int);
 
 #ifdef SYSLOG_UNIXAF
@@ -1528,6 +1533,36 @@ void clear_record_fields(struct log_format *log_fmt)
 	}
 }
 
+void calculate_digest(struct filed *f, struct log_format *log_fmt)
+{
+	int i, n;
+	unsigned char digest[HASH_RAWSZ];
+	hash_ctx_t hash_ctx;
+
+	if (!(log_fmt->f_mask | (1 << LOG_FORMAT_HASH)))
+		return;
+
+	digest[0] = 0;
+
+	hash_init(&hash_ctx);
+	for (i = 0; i < LOG_FORMAT_IOVEC_MAX; i++)
+		hash_update(&hash_ctx, log_fmt->iov[i].iov_base, log_fmt->iov[i].iov_len);
+	hash_final(digest, &hash_ctx);
+
+	strncpy(f->f_prevhash, HASH_NAME, sizeof(f->f_prevhash));
+	n = HASH_NAMESZ;
+
+	strncpy(f->f_prevhash + n, ":", sizeof(f->f_prevhash) - n);
+	n += 1;
+
+	for (i = 0; i < HASH_RAWSZ; i++) {
+		snprintf(f->f_prevhash + n, sizeof(f->f_prevhash) - n, "%02x", digest[i]);
+		n += 2;
+	}
+	f->f_prevhash[n] = 0;
+	return;
+}
+
 void fprintlog(struct filed *f, char *from, int flags, char *msg)
 {
 	char repbuf[80];
@@ -1545,6 +1580,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 
 	set_record_field(&log_fmt, LOG_FORMAT_TIME, f->f_lasttime, 15);
 	set_record_field(&log_fmt, LOG_FORMAT_HOST, f->f_prevhost, -1);
+	set_record_field(&log_fmt, LOG_FORMAT_HASH, f->f_prevhash, -1);
 	if (msg) {
 		set_record_field(&log_fmt, LOG_FORMAT_MSG, msg, -1);
 	} else if (f->f_prevcount > 1) {
@@ -1690,6 +1726,8 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		if (f->f_file == -1)
 			break;
 
+		calculate_digest(f, &log_fmt);
+
 		if (writev(f->f_file, log_fmt.iov, LOG_FORMAT_IOVEC_MAX) < 0) {
 			int e = errno;
 
@@ -1738,6 +1776,7 @@ void fprintlog(struct filed *f, char *from, int flags, char *msg)
 		f->f_time = now;
 		verbosef("\n");
 		set_record_field(&log_fmt, LOG_FORMAT_EOL, "\r\n", 2);
+		calculate_digest(f, &log_fmt);
 		wallmsg(f, &log_fmt);
 		break;
 	} /* switch */
@@ -2658,6 +2697,7 @@ static void allocate_log(void)
 	 */
 	++nlogs;
 	memset(&Files[nlogs], '\0', sizeof(struct filed));
+	strncpy(Files[nlogs].f_prevhash, EMPTY_HASH_LITERAL, sizeof(Files[nlogs].f_prevhash));
 	return;
 }
 
@@ -2754,6 +2794,7 @@ int parse_log_format(struct log_format *log_fmt, char *str)
 				case 't': f_type = LOG_FORMAT_TIME; break;
 				case 'h': f_type = LOG_FORMAT_HOST; break;
 				case 'm': f_type = LOG_FORMAT_MSG;  break;
+				case 'H': f_type = LOG_FORMAT_HASH; break;
 				case '%':
 					special = 0;
 					goto create_special;
diff --git a/tests/log-hashes/.gitignore b/tests/log-hashes/.gitignore
new file mode 100644
index 0000000..5d86449
--- /dev/null
+++ b/tests/log-hashes/.gitignore
@@ -0,0 +1,5 @@
+output
+log
+pipe
+syslog.conf
+*.log
diff --git a/tests/log-hashes/check b/tests/log-hashes/check
new file mode 100755
index 0000000..c8cfcbb
--- /dev/null
+++ b/tests/log-hashes/check
@@ -0,0 +1,28 @@
+#!/bin/sh -efu
+
+. "$TOPDIR/tests/common.sh"
+
+WORKDIR="$1"
+
+prepare
+
+cat > "$WORKDIR/syslog.conf" <<EOF
+log_format: %H %t %h %m
+*.*     $WORKDIR/output/everything.log
+*.info  $WORKDIR/syslog-mark.log
+EOF
+
+run_syslogd &
+sleep 3
+
+for f in $facilities; do
+	for p in $priorities; do
+		logger \
+			--socket "$WORKDIR/log" \
+			--socket-errors=on \
+			-p "$f.$p" "TEST $f.$p"
+	done
+done
+
+wait_mark
+"$WORKDIR/check-hashes.sh" "$WORKDIR/output/everything.log"
diff --git a/tests/log-hashes/check-hashes.sh b/tests/log-hashes/check-hashes.sh
new file mode 100755
index 0000000..c65c8de
--- /dev/null
+++ b/tests/log-hashes/check-hashes.sh
@@ -0,0 +1,28 @@
+#!/bin/sh -efu
+
+logfile="$1"
+
+empty_hash="sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
+lineno=1
+prevhash="$empty_hash"
+prevline=
+while read -r logline; do
+	loghash="${logline%% *}"
+	msg="${logline#$loghash }"
+
+	hash="sha256:$(printf '%s %s\n' "$prevhash" "$msg" |sha256sum)" ||:
+	hash="${hash%% *}"
+
+	if [ "$hash" != "$loghash" ]; then
+		printf >&2 'ERROR: hash chain broken at lineno=%d\n' "$lineno"
+		printf >&2 'expected hash: %s\n' "$hash"
+		printf >&2 '  logged hash: %s\n' "$loghash"
+		exit 1
+	fi
+
+	prevhash="$loghash"
+	prevline="$logline"
+
+	lineno=$(($lineno + 1))
+done < "$logfile"
-- 
2.25.4



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries
  2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
                   ` (5 preceding siblings ...)
  2020-10-27 11:33 ` [devel] [PATCH 6/6] syslogd: implement checksum chains for log entries Alexey Gladkov
@ 2020-10-27 11:44 ` Alexey V. Vissarionov
  2020-10-27 12:13   ` Alexey Gladkov
  6 siblings, 1 reply; 9+ messages in thread
From: Alexey V. Vissarionov @ 2020-10-27 11:44 UTC (permalink / raw)
  To: ALT Linux Team development discussions

On 2020-10-27 12:33:45 +0100, Alexey Gladkov wrote:

 > Этот патчсет реализовывает цепочки хэшей для записей в
 > логе. Это позволяет обнаружить возможное втрожение в логи.
 > При старте syslogd считает хэш для каждой записи используя
 > хэш предыдущей записи или пустой, если это первая запись в
 > лог после старта.

Лучше генерировать случайный IV, отбеливать его и использовать в
качестве начального значения.

 > Цепочка не прерывается до остановки сервера или перекофигруции.
 > Наследование чексумм между рестартами не сделано намеренно
 > поскольку у сервера нет возможности сохранить и гарантировать
 > неприкосновенность чексуммы между рестартами.

Это уже зона ответственности админа.

 > Рестарт syslogd, как и нарушение цепочки чексумм, считается
 > поводом для расследования.

И это тоже.

 > Реализация sha256 (каталог block/) взята из git.

Случись чего, насколько быстро можно перейти на какой-нибудь другой
полином - скажем, Skein или Стрибог?

А то уже совершеннолетний хеш (2002 года рождения) на основе дерева
Меркла может превратиться в тыкву практически в любой момент.


-- 
Alexey V. Vissarionov
gremlin ПРИ altlinux ТЧК org; +vii-cmiii-ccxxix-lxxix-xlii
GPG: 0D92F19E1C0DC36E27F61A29CD17E2B43D879005 @ hkp://keys.gnupg.net


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries
  2020-10-27 11:44 ` [devel] [PATCH 0/6] sysklogd: implement checkum " Alexey V. Vissarionov
@ 2020-10-27 12:13   ` Alexey Gladkov
  0 siblings, 0 replies; 9+ messages in thread
From: Alexey Gladkov @ 2020-10-27 12:13 UTC (permalink / raw)
  To: ALT Linux Team development discussions

On Tue, Oct 27, 2020 at 02:44:16PM +0300, Alexey V. Vissarionov wrote:
> On 2020-10-27 12:33:45 +0100, Alexey Gladkov wrote:
> 
>  > Этот патчсет реализовывает цепочки хэшей для записей в
>  > логе. Это позволяет обнаружить возможное втрожение в логи.
>  > При старте syslogd считает хэш для каждой записи используя
>  > хэш предыдущей записи или пустой, если это первая запись в
>  > лог после старта.
> 
> Лучше генерировать случайный IV, отбеливать его и использовать в
> качестве начального значения.

В этом случае будет нельзя проверить, что это именно первая запись. Какой
смысл в этом случайном начальном хэше ?

>  > Цепочка не прерывается до остановки сервера или перекофигруции.
>  > Наследование чексумм между рестартами не сделано намеренно
>  > поскольку у сервера нет возможности сохранить и гарантировать
>  > неприкосновенность чексуммы между рестартами.
> 
> Это уже зона ответственности админа.

Это зона ответственности сервера. Иначе этот функционал не нужен так как
неприкосновенность логов тоже зона ответственности админа.

>  > Рестарт syslogd, как и нарушение цепочки чексумм, считается
>  > поводом для расследования.
> 
> И это тоже.

Если админ не проверяет чексуммы в логах или не реагирует на их
несовпадения, то в активации этой проверки нет смысла.

>  > Реализация sha256 (каталог block/) взята из git.
> 
> Случись чего, насколько быстро можно перейти на какой-нибудь другой
> полином - скажем, Skein или Стрибог?

Нет ничего невозможного, но я не вижу в этом практического смысла.

> А то уже совершеннолетний хеш (2002 года рождения) на основе дерева
> Меркла может превратиться в тыкву практически в любой момент.

Даже sha256 тут избыточна. Размер сообщения ограничен 1024, пользователь
не контролирует полностью хэшируемое сообщение. Получить коллизию тут даже
с sha1 будет практически невозможно.

-- 
Rgrds, legion



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2020-10-27 12:13 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-27 11:33 [devel] [PATCH 0/6] sysklogd: implement checkum chains for log entries Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 1/6] Optimize the filling of the record fields Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 2/6] syslogd: make logerror is printf-like Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 3/6] syslogd: Implement customization of log file records Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 4/6] syslogd: no need to try to substitute a field in a record if it's not in the log_format Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 5/6] Makefile: use make variables instead of a hardcoded list Alexey Gladkov
2020-10-27 11:33 ` [devel] [PATCH 6/6] syslogd: implement checksum chains for log entries Alexey Gladkov
2020-10-27 11:44 ` [devel] [PATCH 0/6] sysklogd: implement checkum " Alexey V. Vissarionov
2020-10-27 12:13   ` Alexey Gladkov

ALT Linux Team development discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror http://lore.altlinux.org/devel/0 devel/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 devel devel/ http://lore.altlinux.org/devel \
		devel@altlinux.org devel@altlinux.ru devel@lists.altlinux.org devel@lists.altlinux.ru devel@linux.iplabs.ru mandrake-russian@linuxteam.iplabs.ru sisyphus@linuxteam.iplabs.ru
	public-inbox-index devel

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://lore.altlinux.org/org.altlinux.lists.devel


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git