summaryrefslogtreecommitdiff
path: root/src/logger/win32/syslog_win32.c
blob: e94bb9e7e64eb99d2fb3b6ce65e22a3ee3f6b841 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/************************************************************************

 Copyright (C) 2011, 2012 Project Wolframe.
 All rights reserved.

 This file is part of Project Wolframe.

 Commercial Usage
    Licensees holding valid Project Wolframe Commercial licenses may
    use this file in accordance with the Project Wolframe
    Commercial License Agreement provided with the Software or,
    alternatively, in accordance with the terms contained
    in a written agreement between the licensee and Project Wolframe.

 GNU General Public License Usage
    Alternatively, you can redistribute this file and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Wolframe is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Wolframe.  If not, see <http://www.gnu.org/licenses/>.

 If you have questions regarding the use of this file, please contact
 Project Wolframe.

************************************************************************/

/*
 * syslog-client.c - syslog client implementation for windows
 *
 * Created by Alexander Yaworsky
 *
 * THIS SOFTWARE IS NOT COPYRIGHTED
 *
 * This source code is offered for use in the public domain. You may
 * use, modify or distribute it freely.
 *
 * This code is distributed in the hope that it will be useful but
 * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
 * DISCLAIMED. This includes but is not limited to warranties of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

/**
 * \file syslog_win32.c
 * \brief implementation of a syslog client on Windows
 *
 * It is roughly based on the one of http://syslog-win32.sourceforge.net,
 * changed in the way it's configured (we can't have a /etc/syslog.conf
 * on Windows, host and port must be injectable through a function from
 * the Wolframe configuration), in the way the messages are composed using
 * newer string functions, using WSock2 funtions like getaddrinfo and
 * some small other things like added documentation.
*/

/* TODOS:
 * - reset logmask in openlog?
 * - handle more options
 */

#include "syslog_win32.h"

#include <Winsock2.h>
#include <ws2tcpip.h>
#include <string.h>
#include <stdio.h>

static BOOL initialized = FALSE;
static BOOL wsa_initialized = FALSE;

static int log_mask = 0xFF;
static const char *syslog_ident = NULL;
static int syslog_facility = 0;
static char str_pid[40];
static char local_hostname[MAX_COMPUTERNAME_LENGTH+4];

static char syslog_hostname[256] = "localhost";
static char syslog_service[256] = "514";

/* UDP socket and data to use for sending messages */
static SOCKADDR_STORAGE sa_logger;
static SOCKET sock = INVALID_SOCKET;
#define SYSLOG_DGRAM_SIZE 1024
static char datagram[SYSLOG_DGRAM_SIZE];
static int datagram_size = SYSLOG_DGRAM_SIZE;

void openlog( const char* ident, int option, int facility )
{
	BOOL failed = TRUE;
	WSADATA wsd;
	struct addrinfo hints;
	struct addrinfo *result = NULL;
	SOCKADDR_IN sa_local;
	int size;
	DWORD n;
	
	/* allow idempotent calls, first options are taken, use closelog
	 * before openlog if you want to reconfigure the syslog interface
	 */
	if( initialized ) return;

	/* Initialize Windows Socket DLL */
	if( WSAStartup( MAKEWORD( 2, 2 ), &wsd ) != 0 ) goto DONE;
	wsa_initialized = TRUE;

	/* tell getaddrinfo what we want */
	memset( &hints, 0, sizeof( struct addrinfo ) );
	hints.ai_flags = 0;
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;
 
	/* resolve the domain name into a list of addresses */
	if( getaddrinfo( syslog_hostname, syslog_service, &hints, &result ) != 0 ) goto DONE;

	/* Compose the socket address and port of the logging destination */
	memset( &sa_logger, 0, sizeof( SOCKADDR_IN ) );
	memcpy( &sa_logger, result->ai_addr, result->ai_addrlen );

	/* Create a UDP socket */
	sock = socket( result->ai_family, result->ai_socktype, result->ai_protocol );
	if( sock == INVALID_SOCKET ) goto DONE;
	
	/* from RFC 3164: The client should connect from a constant port, if possible
	 * also port 514.
	 *
	 * But if we are on the same machine with loopback the port is taken?
	 * Should we try 514 and if it is taken take a fixed different one?
	 */
	
	/* bind to the socket */
	memset( &sa_local, 0, sizeof( SOCKADDR_IN ) );
	sa_local.sin_family = AF_INET;
	if( bind( sock, (SOCKADDR *)&sa_local, sizeof( SOCKADDR_IN ) ) != 0 ) goto DONE;

	/* determine the maximal size of a datagram packet we can send */
	size = sizeof( datagram_size );
	if( getsockopt( sock, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *)&datagram_size, &size ) != 0 ) goto DONE;
	if( datagram_size > sizeof( datagram ) ) datagram_size = sizeof( datagram );
	
	/* set global facility and ident */
	syslog_facility = facility ? facility : LOG_USER;
	syslog_ident = ident;

	/* by RFC 3164 we should put here the name of the local machine */
	n = sizeof( local_hostname );
	if( !GetComputerName( local_hostname, &n ) ) goto DONE;
	
	/* interpret options, currently we use only LOG_PID */
	if( option & LOG_PID )
		_snprintf_s( str_pid, sizeof( str_pid ), _TRUNCATE, "[%lu]", GetCurrentProcessId( ) );
	else
		str_pid[0] = '\0';
	
	/* install C cleanup function */
	if( atexit( closelog ) ) goto DONE;
	
	/* if we get here, everything's peachy */
	failed = FALSE;
	
DONE:
	if( failed ) {
		if( sock != INVALID_SOCKET ) {
			closesocket( sock );
			sock = INVALID_SOCKET;
		}
		if( wsa_initialized ) {
			wsa_initialized = FALSE;
			WSACleanup( );
		}
	}
	
	initialized = !failed;
}

extern int setlogmask( int mask )
{
	/* set log mask, return old one */
	int old_mask = log_mask;
	
	if( mask ) log_mask = mask;	
	
	return old_mask;
}

void syslog( int pri, char* fmt, ... )
{
	va_list ap;
	va_start( ap, fmt );
	vsyslog( pri, fmt, ap );
	va_end( ap );
}
	
void vsyslog( int pri, char* fmt, va_list ap )
{
	/* from RFC 3164: Mmm is the English language abbreviation for the month of the
	 * year with the first character in uppercase and the other two
	 * characters in lowercase.  The following are the only acceptable
	 * values:
	 *
	 * Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
	 */
	static char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

	int len;
	SYSTEMTIME t;

	/* do not log if we are not inside the logging mask */
	if( !( LOG_MASK( LOG_PRI( pri ) ) & log_mask ) ) return;

	/* open default logger, will return immediatelly if already
	 * registered before */
	openlog( NULL, 0, pri & LOG_FACMASK );

	/* do not log if not initialized after opening a default log */
	if( !initialized ) return;

	if( !( pri & LOG_FACMASK ) ) pri |= syslog_facility;

	/* from RFC 3164: .. hh:mm:ss is the local time. .. */
	GetLocalTime( &t );
	
	/* the PRI and the HEADER with TIMESTAMP and HOSTNAME as
	 * well as the TAG field of the MSG part
	 */
	len = _snprintf_s( datagram, datagram_size, _TRUNCATE, "<%d>%s %2d %02d:%02d:%02d %s %s%s: ",
		pri, month[t.wMonth-1], t.wDay, t.wHour, t.wMinute, t.wSecond,
		local_hostname, syslog_ident ? syslog_ident : "", str_pid );

	/* append now the formatted user message */
	(void)_vsnprintf_s( datagram + len, datagram_size - len, _TRUNCATE, fmt, ap );

	/* send as datagram, we are not really interested in errors here */
	(void)sendto( sock, datagram, strlen( datagram ), 0, (SOCKADDR *)&sa_logger, sizeof( SOCKADDR_IN ) );
}

void closelog( )
{
	if( !initialized ) return;
	
	if( sock != INVALID_SOCKET ) (void)closesocket( sock );
	if( wsa_initialized ) WSACleanup( );

	sock = INVALID_SOCKET;
	wsa_initialized	= FALSE;
	initialized = FALSE;	
}

void set_syslogd_destination( const char *hostname, const char *service )
{
	strncpy_s( syslog_hostname, 255, hostname, _TRUNCATE );
	strncpy_s( syslog_service, 255, service, _TRUNCATE );
}