1 /*	$CoreSDI: im_tcp.c,v 1.35 2002/03/01 07:31:02 alejo Exp $	*/
2 
3 /*
4  * Copyright (c) 2001, Core SDI S.A., Argentina
5  * All rights reserved
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither name of the Core SDI S.A. nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * im_tcp -- input from INET using TCP
34  *
35  * Author: Alejo Sanchez for Core SDI S.A.
36  *
37  * This input module is a bit tricky, because of the nature of TCP
38  * connections, and the use of poll() for I/O on syslogd
39  *
40  * The main idea is that first a im_tcp module will be called
41  * and it will bind to a port and wait for connections to it.
42  *
43  * Whenever a conection is established it will add it to an
44  * array of file descriptors of connections.
45  *
46  */
47 
48 #include "config.h"
49 
50 
51 #if TIME_WITH_SYS_TIME
52 # include <sys/time.h>
53 # include <time.h>
54 #else
55 # if HAVE_SYS_TIME_H
56 #  include <sys/time.h>
57 # else
58 #  include <time.h>
59 # endif
60 #endif
61 #include <sys/types.h>
62 #include <sys/socket.h>
63 #include <sys/uio.h>
64 #include <ctype.h>
65 #include <stdlib.h>
66 #include <unistd.h>
67 #include <errno.h>
68 #include <signal.h>
69 #include <string.h>
70 #include <stdio.h>
71 #include <syslog.h>
72 
73 #include "../modules.h"
74 #include "../syslogd.h"
75 
76 /* recvfrom() and others like socklen_t, Irix doesn't provide it */
77 #ifndef HAVE_SOCKLEN_T
78 
79 #endif
80 
81 struct tcp_conn {
82 	struct tcp_conn *next;
83 	int		 fd;
84 	char		 name[MAXHOSTNAMELEN + 1];
85 	char		 port[20];
86 	char		 saveline[MAXLINE + 3]; /* maxline + cr lf */
87 };
88 
89 struct im_tcp_ctx {
90 	socklen_t	 addrlen;
91 	struct tcp_conn	*first;
92 	int		flags;
93 };
94 
95 #define	M_USEMSGHOST	0x01
96 #define	M_NOTFQDN	0x02
97 
98 
99 void printline(char *, char *, size_t, int);
100 int listen_tcp(char *host, char *port, socklen_t *);
101 int accept_tcp(int, socklen_t, char *, int, char *, int);
102 
103 
104 /*
105  * initialize tcp input
106  *
107  * this module takes a host argument (ie. 0.0.0.0, 0::0, server.example.com)
108  * and a port/service ('syslog' or numerical)
109  *
110  */
111 
112 int
im_tcp_init(struct i_module * I,char ** argv,int argc)113 im_tcp_init(struct i_module *I, char **argv, int argc)
114 {
115 	struct im_tcp_ctx	*c;
116 	char			*host, *port;
117 	int			ch, argcnt;
118 
119 	dprintf(MSYSLOG_INFORMATIVE, "im_tcp_init: entering\n");
120 
121 	if ( (I->im_ctx = calloc(1, sizeof(struct im_tcp_ctx))) == NULL) {
122 		dprintf(MSYSLOG_SERIOUS, "om_tcp_init: cant alloc memory");
123 		return (-1);
124 	}
125 
126 	c = (struct im_tcp_ctx *) I->im_ctx;
127 
128 	host = "0.0.0.0";
129 	port = "syslog";
130 
131 	argcnt = 1; /* skip module name */
132 
133 	while ((ch = getxopt(argc, argv, "h!host: p!port: a!addhost q!nofqdn",
134 	    &argcnt)) != -1) {
135 
136 		switch (ch) {
137 		case 'h':
138 			/* get addr to bind */
139 			host = argv[argcnt];
140 			break;
141 		case 'p':
142 			/* get remote host port */
143 			port = argv[argcnt];
144 			break;
145 		case 'a':
146 			c->flags |= M_USEMSGHOST;
147 			break;
148 		case 'q':
149 			/* don't use domain in hostname (FQDN) */
150 			c->flags |= M_NOTFQDN;
151 			break;
152 		default:
153 			dprintf(MSYSLOG_SERIOUS, "om_tcp_init: parsing error"
154 			    " [%c]\n", ch);
155 			free(c);
156 			return (-1);
157 		}
158 		argcnt++;
159 	}
160 
161 	if ( (I->im_fd = listen_tcp(host, port, &c->addrlen)) < 0) {
162 		dprintf(MSYSLOG_SERIOUS, "im_tcp_init: error with listen_tcp() %s\n",
163 		    strerror(errno));
164 		free(c);
165 		return (-1);
166 	}
167 
168 	I->im_path = NULL;
169 
170 	add_fd_input(I->im_fd , I);
171 
172 	dprintf(MSYSLOG_INFORMATIVE, "im_tcp_init: running\n");
173 
174 	return (1);
175 }
176 
177 
178 /*
179  * im_tcp_read: accept a connection and add it to the queue
180  *
181  */
182 
183 int
im_tcp_read(struct i_module * im,int infd,struct im_msg * ret)184 im_tcp_read(struct i_module *im, int infd, struct im_msg *ret)
185 {
186 	struct im_tcp_ctx *c;
187 	struct tcp_conn *con;
188 	int n;
189 
190 	dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: entering...\n");
191 
192 	if (im == NULL || ret == NULL) {
193 		dprintf(MSYSLOG_SERIOUS, "im_tcp_read: arg %s%s is null\n",
194 		    ret? "ret":"", im? "im" : "");
195 		return (-1);
196 	}
197 
198 	if ((c = (struct im_tcp_ctx *) im->im_ctx) == NULL) {
199 		dprintf(MSYSLOG_SERIOUS, "im_tcp_read: null context\n");
200 		return (-1);
201 	}
202 
203 	if (infd == im->im_fd) {
204 
205 		dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: new connection\n");
206 
207 		/* create a new connection */
208 		if ((con = (struct tcp_conn *) calloc(1, sizeof(*con)))
209 		    == NULL) {
210 			dprintf(MSYSLOG_SERIOUS, "im_tcp_read: "
211 			    "error allocating conn struct\n");
212 			return (-1);
213 		}
214 
215 		/* accept it and add to queue */
216 		if ((con->fd = accept_tcp(infd, c->addrlen, con->name,
217 		    sizeof(con->name), con->port, sizeof(con->port))) < 0) {
218 			dprintf(MSYSLOG_SERIOUS, "im_tcp_read: couldn't accept\n");
219 			free (con);
220 			return (-1);
221 		}
222 
223 		/* add to queue */
224 		con->next = c->first;
225 		c->first = con;
226 
227 
228 		dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: new conection from"
229 		    " %s with fd %d\n", con->name, con->fd);
230 
231 		/* add to inputs list */
232 		add_fd_input(con->fd , im);
233 
234 		return (0); /* 0 because there is no line to log */
235 
236 	}
237 
238 	/* read connected socket */
239 
240 	dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: reading connection fd %d\n",
241 	    infd);
242 
243 	/* find connection */
244 	for (con = c->first; con && con->fd != infd; con = con->next);
245 
246 	if (con == NULL || con->fd != infd) {
247 		dprintf(MSYSLOG_SERIOUS, "im_tcp_read: no such connection "
248 		    "fd %d !\n", infd);
249 		remove_fd_input(infd);
250 		return (-1);
251 	}
252 
253 	n = read(con->fd, im->im_buf, sizeof(im->im_buf) - 1);
254 	if (n == 0) {
255 		struct tcp_conn **prev;
256 
257 		dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: conetion from %s"
258 		    " closed\n", con->name);
259 
260 		remove_fd_input(con->fd);
261 
262 		/* connection closed, remove its tcp_con struct */
263 		close (con->fd);
264 
265 		/* remove node */
266 		for (prev = &c->first; *prev != NULL ; prev = &(*prev)->next) {
267 
268 			if (*prev == con) {
269 
270 				*prev = con->next;
271 				break;
272 			}
273 		}
274 
275 		if (con->saveline[0] != '\0')
276 			printline(ret->im_host, con->saveline,
277 			    strlen(con->saveline),  0);
278 
279 		free(con);
280 
281 		return (0);
282 
283 	} else if (n < 0 && errno != EINTR) {
284 		dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: conetion from %s"
285 		    " closed with error [%s]\n", con->name, strerror(errno));
286 		logerror("im_tcp_read");
287 		con->fd = -1;
288 		remove_fd_input(con->fd);
289 		return (0);
290 	} else {
291 		char	*p, *nextline, *cr;
292 
293 		/* terminate it */
294 		(im->im_buf)[n] = '\0';
295 		p = &im->im_buf[0];
296 
297 		dprintf(MSYSLOG_INFORMATIVE, "im_tcp_read: read: %s [%s]",
298 		    con->name, im->im_buf);
299 
300 		/* change non printable chars to X, just in case */
301 		for(p = im->im_buf; *p != '\0'; p++)
302 			if (!isprint((unsigned int) *p) && *p != '\n')
303 				*p = 'X';
304 		p = im->im_buf;
305 
306 		do {
307 			char	*msg;
308 
309 			msg = p;
310 
311 			/* multiple lines ? */
312 			if((nextline = strchr(p, '\n')) != NULL) {
313 				/* terminate this line and advance */
314 				*nextline++ = '\0';
315 				if (*nextline == '\0')
316 					nextline = NULL; /* no more lines */
317 			} else {
318 				/* save this partial line and return */
319 				strncat(con->saveline, p,
320 				    sizeof(con->saveline) - 1
321 				    - strlen(con->saveline));
322 			}
323 
324 			/* remove trailing carriage returns */
325 			if ((cr = strchr(p, '\r')) != NULL)
326 				*cr = '\0';
327 
328 			if (*p == '\0') {
329 				if (nextline != NULL) {
330 					p = nextline;
331 					continue;
332 				} else
333 					return (0);
334 			}
335 
336 			if (c->flags & M_USEMSGHOST) {
337 				char	host[90];
338 				int	n1, n2;
339 
340 				if (con->saveline[0] != '\0') {
341 					strncat(con->saveline, p,
342 					    sizeof(con->saveline) - 1
343 					    - strlen(con->saveline));
344 					msg = con->saveline;
345 				} else {
346 					msg = p;
347 				}
348 
349 				/* extract hostname from message */
350 				if ((sscanf(msg, "<%*d>%*3s %*i %*i:%*i:%*i %n%89s"
351 				    " %n", &n1, host, &n2) != 1 &&
352 				    sscanf(msg, "%*3s %*i %*i:%*i:%*i %n%89s %n",
353 				    &n1, host, &n2) != 1 &&
354 				    sscanf(msg, "%n%89s %n", &n1,
355 				    host, &n2) != 1)
356 				    || im->im_buf[n2] == '\0') {
357 					dprintf(MSYSLOG_INFORMATIVE,
358 					    "im_tcp_read: ignoring invalid "
359 					    "message [%s]\n", msg);
360 					if (nextline != NULL) {
361 						p = nextline;
362 						continue;
363 					} else {
364 						return (0);
365 						con->saveline[0] = '\0';
366 					}
367 				}
368 
369 				/* remove host from message */
370 				while (im->im_buf[n2] != '\0')
371 					im->im_buf[n1++] = im->im_buf[n2++];
372 				im->im_buf[n1] = '\0';
373 
374 				strncpy(ret->im_host, host,
375 				    sizeof(ret->im_host) - 1);
376 				ret->im_host[sizeof(ret->im_host) - 1] = '\0';
377 
378 			} else {
379 
380 				/* get hostname from originating addr */
381 				strncpy(ret->im_host, con->name,
382 				    sizeof(ret->im_host) - 1);
383 				ret->im_host[sizeof(ret->im_host) - 1] = '\0';
384 			}
385 
386 			if (c->flags & M_NOTFQDN) {
387 				char	*dot;
388 
389 				if ((dot = strchr(ret->im_host, '.')) != NULL)
390 					*dot = '\0';
391 			}
392 
393 			printline(ret->im_host, msg,  strlen(msg),  0);
394 			*msg = '\0';
395 
396 			p = nextline;
397 
398 		} while (nextline != NULL);
399 	}
400 
401 	return (0); /* we already logged the lines */
402 }
403 
404 int
im_tcp_close(struct i_module * im)405 im_tcp_close(struct i_module *im)
406 {
407 	struct im_tcp_ctx *c;
408 	struct tcp_conn *con, *cnext;
409 
410 	c = (struct im_tcp_ctx *) im->im_ctx;
411 
412 	/* close all connections */
413 	for (con = c->first; con; con = cnext) {
414 		if (con->fd > -1)
415 			close(con->fd);
416 		cnext = con->next;
417 		free(con);
418 	}
419 
420 	im->im_ctx = NULL;
421 
422 	/* close listening socket */
423 	return (close(im->im_fd));
424 }
425