1/*
2 * Copyright (c) 1987, 1993, 1994
3 *      The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *      This product includes software developed by the University of
16 *      California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifdef HAVE_CONFIG_H
35#include <config.h>
36#endif
37
38#include "ftmacros.h"
39
40#include <stdio.h>
41#include <string.h>
42#include <signal.h>
43#include <pcap.h>		// for PCAP_ERRBUF_SIZE
44
45#include "portability.h"
46#include "rpcapd.h"
47#include "config_params.h"	// configuration file parameters
48#include "fileconf.h"
49#include "rpcap-protocol.h"
50#include "log.h"
51
52//
53// Parameter names.
54//
55#define PARAM_ACTIVECLIENT	"ActiveClient"
56#define PARAM_PASSIVECLIENT	"PassiveClient"
57#define PARAM_NULLAUTHPERMIT	"NullAuthPermit"
58
59static char *skipws(char *ptr);
60
61/*
62 * Locale-independent version checks for alphabetical and alphanumerical
63 * characters that also can handle being handed a char value that might
64 * be negative.
65 */
66#define FILECONF_ISALPHA(c) \
67	(((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
68#define FILECONF_ISALNUM(c) \
69	(FILECONF_ISALPHA(c) || ((c) >= '0' && (c) <= '9'))
70
71void fileconf_read(void)
72{
73	FILE *fp;
74	unsigned int num_active_clients;
75
76	if ((fp = fopen(loadfile, "r")) != NULL)
77	{
78		char line[MAX_LINE + 1];
79		unsigned int lineno;
80
81		hostlist[0] = 0;
82		num_active_clients = 0;
83		lineno = 0;
84
85		while (fgets(line, MAX_LINE, fp) != NULL)
86		{
87			size_t linelen;
88			char *ptr;
89			char *param;
90			size_t result;
91			size_t toklen;
92
93			lineno++;
94
95			linelen = strlen(line);
96			if (line[linelen - 1] != '\n')
97			{
98				int c;
99
100				//
101				// Either the line doesn't fit in
102				// the buffer, or we got an EOF
103				// before the EOL.  Assume it's the
104				// former.
105				//
106				rpcapd_log(LOGPRIO_ERROR,
107				    "%s, line %u is longer than %u characters",
108				    loadfile, lineno, MAX_LINE);
109
110				//
111				// Eat characters until we get an NL.
112				//
113				while ((c = getc(fp)) != '\n')
114				{
115					if (c == EOF)
116						goto done;
117				}
118
119				//
120				// Try the next line.
121				//
122				continue;
123			}
124			ptr = line;
125
126			//
127			// Skip leading white space, if any.
128			//
129			ptr = skipws(ptr);
130			if (ptr == NULL)
131			{
132				// Blank line.
133				continue;
134			}
135
136			//
137			// Is the next character a "#"?  If so, this
138			// line is a comment; skip to the next line.
139			//
140			if (*ptr == '#')
141				continue;
142
143			//
144			// Is the next character alphabetic?  If not,
145			// this isn't a valid parameter name.
146			//
147			if (FILECONF_ISALPHA(*ptr))
148			{
149				rpcapd_log(LOGPRIO_ERROR,
150				    "%s, line %u doesn't have a valid parameter name",
151				    loadfile, lineno);
152				continue;
153			}
154
155			//
156			// Grab the first token, which is made of
157			// alphanumerics, underscores, and hyphens.
158			// That's the name of the parameter being set.
159			//
160			param = ptr;
161			while (FILECONF_ISALNUM(*ptr) || *ptr == '-' || *ptr == '_')
162				ptr++;
163
164			//
165			// Skip over white space, if any.
166			//
167			ptr = skipws(ptr);
168			if (ptr == NULL || *ptr != '=')
169			{
170				//
171				// We hit the end of the line before
172				// finding a non-white space character,
173				// or we found one but it's not an "=".
174				// That means there's no "=", so this
175				// line is invalid.  Complain and skip
176				// this line.
177				//
178				rpcapd_log(LOGPRIO_ERROR,
179				    "%s, line %u has a parameter but no =",
180				    loadfile, lineno);
181				continue;
182			}
183
184			//
185			// We found the '='; set it to '\0', and skip
186			// past it.
187			//
188			*ptr++ = '\0';
189
190			//
191			// Skip past any white space after the "=".
192			//
193			ptr = skipws(ptr);
194			if (ptr == NULL)
195			{
196				//
197				// The value is empty.
198				//
199				rpcapd_log(LOGPRIO_ERROR,
200				    "%s, line %u has a parameter but no value",
201				    loadfile, lineno);
202				continue;
203			}
204
205			//
206			// OK, what parameter is this?
207			//
208			if (strcmp(param, PARAM_ACTIVECLIENT) == 0) {
209				//
210				// Add this to the list of active clients.
211				//
212				char *address, *port;
213
214				//
215				// We can't have more than MAX_ACTIVE_LIST
216				// active clients.
217				//
218				if (num_active_clients >= MAX_ACTIVE_LIST)
219				{
220					//
221					// Too many entries for the active
222					// client list.  Complain and
223					// ignore it.
224					//
225					rpcapd_log(LOGPRIO_ERROR,
226					    "%s, line %u has an %s parameter, but we already have %u active clients",
227					    loadfile, lineno, PARAM_ACTIVECLIENT,
228					    MAX_ACTIVE_LIST);
229					continue;
230				}
231
232				//
233				// Get the address.
234				// It's terminated by a host list separator
235				// *or* a #; there *shouldn't* be a #, as
236				// that starts a comment, and that would
237				// mean that we have no port.
238				//
239				address = ptr;
240				toklen = strcspn(ptr, RPCAP_HOSTLIST_SEP "#");
241				ptr += toklen;	// skip to the terminator
242				if (toklen == 0)
243				{
244					if (*ptr == ' ' || *ptr == '\t' ||
245					    *ptr == '\r' || *ptr == '\n' ||
246					    *ptr == '#' || *ptr == '\0')
247					{
248						//
249						// The first character it saw
250						// was a whitespace character
251						// or a comment character,
252						// or we ran out of characters.
253						// This means that there's
254						// no value.
255						//
256						rpcapd_log(LOGPRIO_ERROR,
257						    "%s, line %u has a parameter but no value",
258						    loadfile, lineno);
259					}
260					else
261					{
262						//
263						// This means that the first
264						// character it saw was a
265						// separator.  This means that
266						// there's no address in the
267						// value, just a port.
268						//
269						rpcapd_log(LOGPRIO_ERROR,
270						    "%s, line %u has an %s parameter with a value containing no address",
271						    loadfile, lineno, PARAM_ACTIVECLIENT);
272					}
273					continue;
274				}
275
276				//
277				// Null-terminate the address, and skip past
278				// it.
279				//
280				*ptr++ = '\0';
281
282				//
283				// Skip any white space following the
284				// separating character.
285				//
286				ptr = skipws(ptr);
287				if (ptr == NULL)
288				{
289					//
290					// The value is empty, so there's
291					// no port in the value.
292					//
293					rpcapd_log(LOGPRIO_ERROR,
294					    "%s, line %u has an %s parameter with a value containing no port",
295					    loadfile, lineno, PARAM_ACTIVECLIENT);
296					continue;
297				}
298
299				//
300				// Get the port.
301				// We look for a white space character
302				// or a # as a terminator; the # introduces
303				// a comment that runs to the end of the
304				// line.
305				//
306				port = ptr;
307				toklen = strcspn(ptr, " \t#\r\n");
308				ptr += toklen;
309				if (toklen == 0)
310				{
311					//
312					// The value is empty, so there's
313					// no port in the value.
314					//
315					rpcapd_log(LOGPRIO_ERROR,
316					    "%s, line %u has an %s parameter with a value containing no port",
317					    loadfile, lineno, PARAM_ACTIVECLIENT);
318					continue;
319				}
320
321				//
322				// Null-terminate the port, and skip past
323				// it.
324				//
325				*ptr++ = '\0';
326				result = pcap_strlcpy(activelist[num_active_clients].address, address, sizeof(activelist[num_active_clients].address));
327				if (result >= sizeof(activelist[num_active_clients].address))
328				{
329					//
330					// It didn't fit.
331					//
332					rpcapd_log(LOGPRIO_ERROR,
333					    "%s, line %u has an %s parameter with an address with more than %u characters",
334					    loadfile, lineno, PARAM_ACTIVECLIENT,
335					    (unsigned int)(sizeof(activelist[num_active_clients].address) - 1));
336					continue;
337				}
338				if (strcmp(port, "DEFAULT") == 0) // the user choose a custom port
339					result = pcap_strlcpy(activelist[num_active_clients].port, RPCAP_DEFAULT_NETPORT_ACTIVE, sizeof(activelist[num_active_clients].port));
340				else
341					result = pcap_strlcpy(activelist[num_active_clients].port, port, sizeof(activelist[num_active_clients].port));
342				if (result >= sizeof(activelist[num_active_clients].address))
343				{
344					//
345					// It didn't fit.
346					//
347					rpcapd_log(LOGPRIO_ERROR,
348					    "%s, line %u has an %s parameter with an port with more than %u characters",
349					    loadfile, lineno, PARAM_ACTIVECLIENT,
350					    (unsigned int)(sizeof(activelist[num_active_clients].port) - 1));
351					continue;
352				}
353
354				num_active_clients++;
355			}
356			else if (strcmp(param, PARAM_PASSIVECLIENT) == 0)
357			{
358				char *eos;
359				char *host;
360
361				//
362				// Get the host.
363				// We look for a white space character
364				// or a # as a terminator; the # introduces
365				// a comment that runs to the end of the
366				// line.
367				//
368				host = ptr;
369				toklen = strcspn(ptr, " \t#\r\n");
370				if (toklen == 0)
371				{
372					//
373					// The first character it saw
374					// was a whitespace character
375					// or a comment character.
376					// This means that there's
377					// no value.
378					//
379					rpcapd_log(LOGPRIO_ERROR,
380					    "%s, line %u has a parameter but no value",
381					    loadfile, lineno);
382					continue;
383				}
384				ptr += toklen;
385				*ptr++ = '\0';
386
387				//
388				// Append this to the host list.
389				// Save the current end-of-string for the
390				// host list, in case the new host doesn't
391				// fit, so that we can discard the partially-
392				// copied host name.
393				//
394				eos = hostlist + strlen(hostlist);
395				if (eos != hostlist)
396				{
397					//
398					// The list is not empty, so prepend
399					// a comma before adding this host.
400					//
401					result = pcap_strlcat(hostlist, ",", sizeof(hostlist));
402					if (result >= sizeof(hostlist))
403					{
404						//
405						// It didn't fit.  Discard
406						// the comma (which wasn't
407						// added, but...), complain,
408						// and ignore this line.
409						//
410						*eos = '\0';
411						rpcapd_log(LOGPRIO_ERROR,
412						    "%s, line %u has a %s parameter with a host name that doesn't fit",
413						    loadfile, lineno, PARAM_PASSIVECLIENT);
414						continue;
415					}
416				}
417				result = pcap_strlcat(hostlist, host, sizeof(hostlist));
418				if (result >= sizeof(hostlist))
419				{
420					//
421					// It didn't fit.  Discard the comma,
422					// complain, and ignore this line.
423					//
424					*eos = '\0';
425					rpcapd_log(LOGPRIO_ERROR,
426					    "%s, line %u has a %s parameter with a host name that doesn't fit",
427					    loadfile, lineno, PARAM_PASSIVECLIENT);
428					continue;
429				}
430			}
431			else if (strcmp(param, PARAM_NULLAUTHPERMIT) == 0)
432			{
433				char *setting;
434
435				//
436				// Get the setting.
437				// We look for a white space character
438				// or a # as a terminator; the # introduces
439				// a comment that runs to the end of the
440				// line.
441				//
442				setting = ptr;
443				toklen = strcspn(ptr, " \t#\r\n");
444				ptr += toklen;
445				if (toklen == 0)
446				{
447					//
448					// The first character it saw
449					// was a whitespace character
450					// or a comment character.
451					// This means that there's
452					// no value.
453					//
454					rpcapd_log(LOGPRIO_ERROR,
455					    "%s, line %u has a parameter but no value",
456					    loadfile, lineno);
457					continue;
458				}
459				*ptr++ = '\0';
460
461				//
462				// XXX - should we complain if it's
463				// neither "yes" nor "no"?
464				//
465				if (strcmp(setting, "YES") == 0)
466					nullAuthAllowed = 1;
467				else
468					nullAuthAllowed = 0;
469			}
470			else
471			{
472				rpcapd_log(LOGPRIO_ERROR,
473				    "%s, line %u has an unknown parameter %s",
474				    loadfile, lineno, param);
475				continue;
476			}
477		}
478
479done:
480		// clear the remaining fields of the active list
481		for (int i = num_active_clients; i < MAX_ACTIVE_LIST; i++)
482		{
483			activelist[i].address[0] = 0;
484			activelist[i].port[0] = 0;
485			num_active_clients++;
486		}
487
488		rpcapd_log(LOGPRIO_DEBUG, "New passive host list: %s", hostlist);
489		fclose(fp);
490	}
491}
492
493int fileconf_save(const char *savefile)
494{
495	FILE *fp;
496
497	if ((fp = fopen(savefile, "w")) != NULL)
498	{
499		char *token; /*, *port;*/					// temp, needed to separate items into the hostlist
500		char temphostlist[MAX_HOST_LIST + 1];
501		int i = 0;
502		char *lasts;
503
504		fprintf(fp, "# Configuration file help.\n\n");
505
506		// Save list of clients which are allowed to connect to us in passive mode
507		fprintf(fp, "# Hosts which are allowed to connect to this server (passive mode)\n");
508		fprintf(fp, "# Format: PassiveClient = <name or address>\n\n");
509
510		pcap_strlcpy(temphostlist, hostlist, sizeof (temphostlist));
511
512		token = pcap_strtok_r(temphostlist, RPCAP_HOSTLIST_SEP, &lasts);
513		while(token != NULL)
514		{
515			fprintf(fp, "%s = %s\n", PARAM_PASSIVECLIENT, token);
516			token = pcap_strtok_r(NULL, RPCAP_HOSTLIST_SEP, &lasts);
517		}
518
519
520		// Save list of clients which are allowed to connect to us in active mode
521		fprintf(fp, "\n\n");
522		fprintf(fp, "# Hosts to which this server is trying to connect to (active mode)\n");
523		fprintf(fp, "# Format: ActiveClient = <name or address>, <port | DEFAULT>\n\n");
524
525
526		while ((i < MAX_ACTIVE_LIST) && (activelist[i].address[0] != 0))
527		{
528			fprintf(fp, "%s = %s, %s\n", PARAM_ACTIVECLIENT,
529			    activelist[i].address, activelist[i].port);
530			i++;
531		}
532
533		// Save if we want to permit NULL authentication
534		fprintf(fp, "\n\n");
535		fprintf(fp, "# Permit NULL authentication: YES or NO\n\n");
536
537		fprintf(fp, "%s = %s\n", PARAM_NULLAUTHPERMIT,
538		    nullAuthAllowed ? "YES" : "NO");
539
540		fclose(fp);
541		return 0;
542	}
543	else
544	{
545		return -1;
546	}
547
548}
549
550//
551// Skip over white space.
552// If we hit a CR or LF, return NULL, otherwise return a pointer to
553// the first non-white space character.  Replace white space characters
554// other than CR or LF with '\0', so that, if we're skipping white space
555// after a token, the token is null-terminated.
556//
557static char *skipws(char *ptr)
558{
559	while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
560		if (*ptr == '\r' || *ptr == '\n')
561			return NULL;
562		*ptr++ = '\0';
563	}
564	return ptr;
565}
566