1 /*
2  *  main.c
3  *
4  *	Copyright 2007-13 VOSTROM Holdings, Inc.
5  *  This file is part of the Distribution.  See the file COPYING for details.
6  */
7 
8 #include <getopt.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include<sys/types.h>
13 #include<sys/socket.h>
14 #include <sys/param.h>   /* for NONFILE */
15 #include <sys/stat.h>
16 #include<netinet/in.h>
17 #include<arpa/inet.h>
18 #include <netdb.h>
19 #include <regex.h>
20 #include <time.h>
21 #include <stdio.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <ctype.h>
26 #include <unistd.h>
27 
28 #include "geounit.h"
29 #include "tst_malloc.h"
30 #include "logger.h"
31 #include "pwhois_thread.h"
32 
33 #ifdef SIGTSTP /* if System is BSD*/
34 #include<sys/file.h>
35 #include<sys/ioctl.h>
36 #endif
37 
38 #define DEFAULT_CONFIG "/usr/local/etc/pwhois/pwhoisd.conf"
39 #define DEFAULT_PIDFILE "/var/run/pwhoisd.pid"
40 #define DEFAULT_LOGFILE "/var/log/pwhois/pwhoisd.log"
41 #define DEFAULT_WHOIS_PORT 43
42 #define DEFAULT_UID 65334
43 #define DEFAULT_GID 65334
44 
45 #ifndef MAX_PATH
46 #define MAX_PATH 512
47 #endif
48 
49 #define DEFAULT_ACLDB_EXPORT_FILENAME "/var/db/pwhois/ACLDB.pwdump"
50 #define DEFAULT_ASNDB_EXPORT_FILENAME "/var/db/pwhois/ASNDB.pwdump"
51 #define DEFAULT_GEODB_EXPORT_FILENAME "/var/db/pwhois/GEODB.pwdump"
52 #define DEFAULT_NETDB_EXPORT_FILENAME "/var/db/pwhois/NETDB.pwdump"
53 #define DEFAULT_ORGDB_EXPORT_FILENAME "/var/db/pwhois/ORGDB.pwdump"
54 #define DEFAULT_POCDB_EXPORT_FILENAME "/var/db/pwhois/POCDB.pwdump"
55 #define DEFAULT_ROUDB_EXPORT_FILENAME "/var/db/pwhois/ROUDB.pwdump"
56 
57 char ACLDB_EXPORT_FILENAME[MAX_PATH];
58 char ASNDB_EXPORT_FILENAME[MAX_PATH];
59 char GEODB_EXPORT_FILENAME[MAX_PATH];
60 char NETDB_EXPORT_FILENAME[MAX_PATH];
61 char ORGDB_EXPORT_FILENAME[MAX_PATH];
62 char POCDB_EXPORT_FILENAME[MAX_PATH];
63 char ROUDB_EXPORT_FILENAME[MAX_PATH];
64 
65 int FASTLOAD=0;
66 
67 char reportpath[MAX_PATH];
68 
69 char VERSION[]="2.2.1.0";
70 char PROGNAME[]="Prefix WhoIs";
71 char PROGNAMESHORT[]="pwhoisd";
72 char COPYRIGHT[]="Copyright (c) 2005-14 VOSTROM Holdings, Inc." ;
73 
74 /* options descriptor */
75 static struct option longopts[] =
76 {
77 	{"help",		no_argument,		NULL,			'h'},
78 	{"verbose",		no_argument,		NULL,			'v'},
79 	{"version",		no_argument,		NULL,			'V'},
80 	{"logfile",		required_argument,	NULL,			'l'},
81 	{"configfile",	required_argument,	NULL,			'c'},
82 	{"daemon",		no_argument,		NULL,			'd'},
83 	{"pidfile",		required_argument,	NULL,			'0'},
84 	{"port",		required_argument,	NULL,			'p'},
85 	{"bind",		required_argument,	NULL,			'b'},
86 	{"uid",			required_argument,	NULL,			'u'},
87 	{"gid",			required_argument,	NULL,			'g'},
88 	{"limit-max-queries",required_argument,NULL,		'm'},
89 	{"no-load",		no_argument,		NULL,			'1'},
90 	{"router-id",	required_argument,	NULL,			'r'},
91 	{"report",		required_argument,	NULL,			'R'},
92 	{NULL,			0,					NULL,			0}
93 };
94 
95 static char PID_FileName[MAX_PATH]=DEFAULT_PIDFILE;
96 static int LISTEN_QUEUE_LENGTH=5;
97 static int THREADS_POOL_LENGTH=20;
98 //static pwhois_thread_cb * threads_pool=0;
99 
usage()100 void usage()
101 {
102 	printf("usage: pwhois [*options*]\n\n"
103 		   "  -h, --help         display this help and exit\n"
104 		   "  -v, --verbose      be verbose about what you do (add more -v's to increase verbosity: above v=2 is considered debug)\n"
105 		   "  -V, --version      output version information and exit\n"
106 		   "  -l, --logfile f    write misc progress output to logfile instead of stdout\n"
107 		   "  -c, --configfile f   read startup settings from configuration file: default is %s \n"
108 		   "  -d, --daemon       start in the background\n"
109 		   "  --pidfile          use alternative PID file location: default is %s\n"
110 		   "  -p, --port <n>     port number to listen on: defaults to %d\n"
111 		   "  --b|bind <ip>      the IP address to bind on: defaults to all interfaces (*)\n"
112 		   "  -u, --uid          the effective user to run as \n"
113 		   "  -g, --gid          the effective group to run as \n"
114 		   "  -R, --report path  write rotating CSV reports of activity into directory"
115 		   "  --limit-max-queries <n>  The maximum number of queries (per IP/per day) default is %d\n"
116 		   "  -r, --router-id <id>  the router id to use for this server; useful if there is more that one set of data\n"
117 		   "     in the database and the server should only serve responses from one set of data.\n"
118 		   "  --no-load          Do not load data -- for testing purposes\n",
119 		   DEFAULT_CONFIG, DEFAULT_PIDFILE, DEFAULT_WHOIS_PORT, DEFAULT_MAX_QUERIES);
120 
121 	exit(0);
122 }
123 
getVersion()124 void getVersion()
125 {
126 	printf("%s (%s) %s\n\n",PROGNAME,VERSION,COPYRIGHT);
127 	exit(0);
128 }
129 
all_digits(register char const * const s)130 static int all_digits (register char const *const s)
131 {
132 	register char const *r;
133 
134 	for (r = s; *r; r++)
135 		if (!isdigit (*r))
136 			return 0;
137 	return 1;
138 }
139 
readLine(FILE * f,char * buf,int bfsz)140 void readLine(FILE * f, char * buf, int bfsz)
141 {
142 	int i;
143 	for(i=0;i<bfsz;i++)
144 	{
145 		if(fread(buf+i,1,1,f)<1 || buf[i]=='\r' || buf[i]=='\n')
146 		{
147 			buf[i]=0;
148 			break;
149 		}
150 	}
151 }
152 
readConfigFile(char * fname)153 static void readConfigFile(char * fname)
154 {
155 	regex_t re;
156 	regex_t comment;
157 	regex_t blank;
158 	regmatch_t args[10];
159 	int status,itmp;
160 	FILE * cfgFile;
161 	char cfgLine[1024], param[512], value[512], tmp;
162 
163 	cfgLine[1023]=0;
164 
165 	status=regcomp(&re, "^ *([A-Za-z0-9_.-]+) *= *\"?([^\"]*)\"? ?$", REG_EXTENDED|REG_ICASE);
166 	status=regcomp(&comment,"^ *#.*$",REG_EXTENDED|REG_ICASE);
167 	status=regcomp(&blank,"^ +$",REG_EXTENDED|REG_ICASE);
168 	cfgFile=fopen(fname,"r");
169 	if(!cfgFile)
170 	{
171 		fprintf(stderr,"Can't open config file %s \n\n",fname);
172 		exit(0);
173 	}
174 	strcpy(ACLDB_EXPORT_FILENAME,DEFAULT_ACLDB_EXPORT_FILENAME);
175 	strcpy(ASNDB_EXPORT_FILENAME,DEFAULT_ASNDB_EXPORT_FILENAME);
176 	strcpy(GEODB_EXPORT_FILENAME,DEFAULT_GEODB_EXPORT_FILENAME);
177 	strcpy(NETDB_EXPORT_FILENAME,DEFAULT_NETDB_EXPORT_FILENAME);
178 	strcpy(ORGDB_EXPORT_FILENAME,DEFAULT_ORGDB_EXPORT_FILENAME);
179 	strcpy(POCDB_EXPORT_FILENAME,DEFAULT_POCDB_EXPORT_FILENAME);
180 	strcpy(ROUDB_EXPORT_FILENAME,DEFAULT_ROUDB_EXPORT_FILENAME);
181 	while(!feof(cfgFile))
182 	{
183 		readLine(cfgFile, cfgLine, 1023);
184 		if(!strlen(cfgLine) || !regexec(&blank, cfgLine, 10, args, 0) || !regexec(&comment, cfgLine, 10, args, 0))
185 			continue;
186 		if((status=regexec(&re, cfgLine, 10, args, 0))!=0)
187 			continue;
188 		tmp=cfgLine[args[1].rm_eo];
189 		cfgLine[args[1].rm_eo]=0;
190 		strcpy(param,cfgLine+args[1].rm_so);
191 		cfgLine[args[1].rm_eo]=tmp;
192 
193 		tmp=cfgLine[args[2].rm_eo];
194 		cfgLine[args[2].rm_eo]=0;
195 		strcpy(value,cfgLine+args[2].rm_so);
196 		cfgLine[args[2].rm_eo]=tmp;
197 
198 		if(!strcmp(param,"pwhoisd.listenq"))
199 		{
200 			itmp=atoi(value);
201 			if(itmp>=5)
202 				LISTEN_QUEUE_LENGTH=itmp;
203 		}
204 		else if(!strcmp(param,"pwhoisd.threadsq"))
205 		{
206 			itmp=atoi(value);
207 			if(itmp>=2)
208 				THREADS_POOL_LENGTH=itmp;
209 		}
210 		else if(!strcmp(param,"fastload.acldb"))
211 			strncpy(ACLDB_EXPORT_FILENAME,value,MAX_PATH);
212 		else if(!strcmp(param,"fastload.asndb"))
213 			strncpy(ASNDB_EXPORT_FILENAME,value,MAX_PATH);
214 		else if(!strcmp(param,"fastload.geodb"))
215 			strncpy(GEODB_EXPORT_FILENAME,value,MAX_PATH);
216 		else if(!strcmp(param,"fastload.netdb"))
217 			strncpy(NETDB_EXPORT_FILENAME,value,MAX_PATH);
218 		else if(!strcmp(param,"fastload.orgdb"))
219 			strncpy(ORGDB_EXPORT_FILENAME,value,MAX_PATH);
220 		else if(!strcmp(param,"fastload.pocdb"))
221 			strncpy(POCDB_EXPORT_FILENAME,value,MAX_PATH);
222 		else if(!strcmp(param,"fastload.roudb"))
223 			strncpy(ROUDB_EXPORT_FILENAME,value,MAX_PATH);
224 		else if(!strcmp(param,"fastload"))
225 		{
226 			itmp=atoi(value);
227 			if(itmp)
228 				FASTLOAD=1;
229 			else
230 				FASTLOAD=0;
231 		}
232 	}
233 	fclose(cfgFile);
234 	regfree(&re);
235 }
236 
Daemonize()237 void Daemonize()
238 {
239 	FILE *pidFp;
240 	register int childpid, fd;
241 	if(getppid()==1)
242 	{
243 		for(fd =0;fd<NOFILE; fd++)
244 		{
245 			close(fd);
246 		}
247 		errno=0;
248 		chdir("/");
249 		umask(0);
250 	}
251 	/*
252 	 Ignore the terminal signal for BSD
253 	 */
254 #ifdef SIGTTOU
255 	signal(SIGTTOU,SIG_IGN);
256 #endif
257 #ifdef SIGTTIN
258 	signal(SIGTTIN, SIG_IGN);
259 #endif
260 #ifdef SIGTSTP
261 	signal(SIGTSTP,SIG_IGN);
262 #endif
263 	/*
264 	 fork the process and exit from the parent .Let the daemon start in child
265 	 */
266 	if((childpid =fork())>0)
267 	{
268 		//Parent process
269 		pidFp= fopen(PID_FileName,	"w+");
270 		if(pidFp == NULL)
271 		{
272 			printf("Could not open file %s", PID_FileName);
273 		}
274 		else
275 		{
276 			printf("pid= %d \n",childpid);
277 			fprintf(pidFp,"%d\n",childpid);
278 			fclose(pidFp);
279 		}
280 		exit(0);
281 	}
282 	//child process
283 	if (setsid() < 0)
284 	{
285 		printf("Can't change the process group\n");
286 	}
287 }
288 
sigTermination(int stub)289 void sigTermination(int stub)
290 {
291 	(void)stub;
292 	log_printf(0, "Terminate pwhoisd\n");
293 	closeLogger();
294 	exit(0);
295 }
296 
StartServer(uint32_t bindaddr,int port,int uid,int gid)297 int StartServer(uint32_t bindaddr, int port, int uid, int gid)
298 {
299 	int listenfd;
300 	int sockopt;
301 	struct sockaddr_in serv_addr;
302 	struct linger fix_ling;
303 
304 	signal(SIGURG,SIG_IGN);
305 	signal(SIGPIPE,SIG_IGN);
306 	//Open TCPIP socket
307 	if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
308 	{
309 		log_printf(0, "Server:can't open stream socket: (errno: %d).\n", errno);
310 		return errno;
311 	}
312 	fix_ling.l_onoff=1;
313 	fix_ling.l_linger=1;
314 	if(setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&fix_ling,sizeof(fix_ling))<0)
315 	{
316 		log_printf(0,"Server can not setsockopt (SO_LINGER): (errno: %d).\n", errno);
317 		return errno;
318 	}
319 	sockopt=1;
320 	if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&sockopt,sizeof(sockopt))<0)
321 	{
322 		log_printf(0,"Server can not setsockopt (SO_REUSEADDR): (errno: %d).\n", errno);
323 		return errno;
324 	}
325 #ifdef SO_REUSEPORT
326 	sockopt=1;
327 	if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&sockopt,sizeof(sockopt))<0)
328 	{
329 		log_printf(0,"Server can not setsockopt (SO_REUSEPORT): (errno: %d).\n", errno);
330 		return errno;
331 	}
332 #endif
333 	/*sockopt=1;
334 	if(setsockopt(listenfd,SOL_SOCKET,SO_KEEPALIVE,&sockopt,sizeof(sockopt))<0)
335 	{
336 		log_printf(0,"Server can not setsockopt (SO_KEEPALIVE): (errno: %d).\n", errno);
337 		return errno;
338 	}*/
339 	bzero((char*)&serv_addr,sizeof(serv_addr));
340 	serv_addr.sin_family= AF_INET;
341 	serv_addr.sin_addr.s_addr= bindaddr;
342 	serv_addr.sin_port= htons(port);
343 	if(bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
344 	{
345 		log_printf(0,"Server can not bind to the local address: (errno: %d).\n", errno);
346 		return errno;
347 	}
348 	listen(listenfd,LISTEN_QUEUE_LENGTH);
349 	setuid(uid);
350 	setgid(gid);
351     //save port for getVersion request
352     save_listen_port(port);
353 	initialize_threads(listenfd, sizeof(serv_addr), THREADS_POOL_LENGTH);
354     return 0;
355 }
356 
main(int argc,char * argv[])357 int main (int argc, char * argv[])
358 {
359 	int ch, option_errors=0, portno=DEFAULT_WHOIS_PORT, runasdaemon=0;
360 	int get_version_request=0;
361 	int uid=DEFAULT_UID, gid=DEFAULT_GID, no_load=0, filt_by_router=0, router_id=0;
362 	char logpath[MAX_PATH];
363 	char cfgpath[MAX_PATH];
364 	//char pidpath[MAX_PATH];
365 	struct in_addr bindaddr;
366 
367 	//Initialize default values
368 	bindaddr.s_addr=INADDR_ANY;
369 	reportpath[0]='\0';
370 	strncpy(cfgpath,DEFAULT_CONFIG,MAX_PATH);
371 	strncpy(logpath,DEFAULT_LOGFILE,MAX_PATH);
372 
373     while((ch = getopt_long(argc, argv, "hvVl:R:c:dp:b:u:g:m:r:", longopts, NULL)) != -1)
374 	{
375 		switch(ch)
376 		{
377 			case 'R':
378 				strncpy(reportpath,optarg,MAX_PATH);
379 				break;
380 			case 'v':
381 				inc_verbose_level();
382 				break;
383 			case 'V':
384 				//we will execute this request later because we need get all command line arguments
385 				get_version_request=1;
386 				break;
387 			case 'l':
388 				strncpy(logpath,optarg,MAX_PATH);
389 				break;
390 			case 'c':
391 				strncpy(cfgpath,optarg,MAX_PATH);
392 				break;
393 			case 'd':
394 				runasdaemon=1;
395 				break;
396 			case '0':
397 				strncpy(PID_FileName,optarg,MAX_PATH);
398 				break;
399 			case 'p':
400 				if (strlen (optarg) == 0 || !all_digits (optarg))
401 				{
402 					fprintf(stderr, "Invalid argument for -p option: %s\n\n", optarg);
403 					option_errors++;
404 					break;
405 				}
406 				portno = atoi (optarg);
407 				if (portno <= 0 || portno >= 65536)
408 				{
409 					fprintf(stderr, "Invalid argument for -p option: %s\n\n", optarg);
410 					option_errors++;
411 				}
412 				break;
413 			case 'b':
414 				if(!inet_aton(optarg,&bindaddr))
415 				{
416 					fprintf(stderr,"Can't parse ip (%s)\n\n",optarg);
417 					option_errors++;
418 				}
419 				break;
420 			case 'u':
421 				if (strlen (optarg) == 0 || !all_digits (optarg))
422 				{
423 					fprintf(stderr, "Invalid argument for -u option: %s\n\n", optarg);
424 					option_errors++;
425 					break;
426 				}
427 				uid = atoi (optarg);
428 				if (uid == 0 || uid >= 65536)
429 				{
430 					fprintf(stderr, "Invalid argument for -u option: %s\n\n", optarg);
431 					option_errors++;
432 				}
433 				break;
434 			case 'g':
435 				if (strlen (optarg) == 0 || !all_digits (optarg))
436 				{
437 					fprintf(stderr, "Invalid argument for -g option: %s\n\n", optarg);
438 					option_errors++;
439 					break;
440 				}
441 				gid = atoi (optarg);
442 				if (gid == 0 || gid >= 65536)
443 				{
444 					fprintf(stderr, "Invalid argument for -g option: %s\n\n", optarg);
445 					option_errors++;
446 				}
447 				break;
448 			case 'm':
449 				if (strlen (optarg) == 0 || !all_digits (optarg))
450 				{
451 					fprintf(stderr, "Invalid argument for -m option: %s\n\n", optarg);
452 					option_errors++;
453 					break;
454 				}
455                 setQueriesLimit(atoi(optarg));
456 				break;
457 			case '1':
458 				no_load=1;
459 				break;
460 			case 'r':
461 				if (strlen (optarg) == 0 || !all_digits (optarg))
462 				{
463 					fprintf(stderr, "Invalid argument for -r option: %s\n\n", optarg);
464 					option_errors++;
465 					break;
466 				}
467 				router_id = atoi (optarg);
468 				filt_by_router=1;
469 				break;
470 			case 'h':
471 			default:
472 				usage();
473 				break;
474 		}
475 	}
476 	if(option_errors)
477 		usage();
478 	if(get_version_request)
479 		getVersion();
480 	readConfigFile(cfgpath);
481 	if (!FASTLOAD)
482 	{
483 		fprintf(stderr, "Fastload option MUST be set!\n");
484 		return 1;
485 	}
486 	if(runasdaemon)
487 	{
488 		//daemonize here, before database loading
489 		Daemonize();
490 	}
491 	if(initLogger(logpath)!=0)
492 	{
493 		fprintf(stderr, "Can't open logfile: %s\n\n", logpath);
494 		exit(0);
495 	}
496 	//load ACL
497 	loadACL_fromFile(ACLDB_EXPORT_FILENAME);
498 	//Start server here
499 	if(StartServer(bindaddr.s_addr, portno, uid, gid))
500 		return 0;
501 	signal(SIGINT, sigTermination);
502 	signal(SIGHUP, databaseReload);
503 	signal(SIGTERM, sigTermination);
504 	signal(SIGKILL, sigTermination);
505 	signal(SIGUSR1, ACL_Reload_fromFile);
506 	signal(SIGUSR2, ACL_Reload_fromFile);
507 	signal(SIGQUIT,SIG_IGN); // Ignore the SIGQUIT signal
508 	SetupFilterParameters(filt_by_router, router_id);
509 	if(no_load)
510 		EnableEchoServer();
511 	else
512 		databaseReload(0);
513 	//suspend main thread
514 	while(1)
515 		pause();
516     return 0;
517 }
518