/* Copyright (C) 1999-2014
* Smithsonian Astrophysical Observatory, Cambridge, MA, USA
* For conditions of distribution and use, see copyright notice in "copyright"
*/

/* linux:  gcc -Wall -o checkdns -g checkdns.c */

#define HAVE_TCL 1

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#if HAVE_TCL
#include <tcl.h>
#else
#include <stdio.h>
#include <stdlib.h>
extern char *optarg;
extern int optind;
#endif

#ifndef SZ_LINE
#define SZ_LINE 2048
#endif

static int debug=0;

/* shared between main routine and signal handler */
static int dns_alarm_flag=0;

/* alarm signal handler */
static void
dns_alarm(int signo)
{
 dns_alarm_flag = 1;
}

/* check whether we can get info in an ip in a finite amount of time */
static int checkdns(char *name, int delay, int cflag)
{
 int fd;
 int got;
 int flag=0;
 unsigned int ip;
 unsigned int port=80;
 char *s=NULL;
 char host[SZ_LINE];
 char tbuf[SZ_LINE];
 struct hostent *h;
 struct sockaddr_in sock_in;
 struct sigaction act1, oact1;

 /* get temp name, look for port */
 if( name && *name ){
   strncpy(tbuf, name, SZ_LINE-1);
   if( (s=strchr(tbuf, ':')) ){
     *s = '\0';
     port = atoi(s+1);
   }
 } else {
   *tbuf = '\0';
 }

 /* start the alarm, if necessary */
 if( delay > 0 ){
   /* set up alarm signal handler */
   act1.sa_handler = dns_alarm;
   sigemptyset(&act1.sa_mask);
   act1.sa_flags = 0;
#ifdef SA_INTERRUPT
   act1.sa_flags |= SA_INTERRUPT;
#endif
   if( sigaction(SIGALRM, &act1, &oact1) < 0 ){
     flag = 1;
     goto done;
   }
   /* start the alarm */
   alarm(delay);
 }

 /* get name of host we are interested in */
 if( *tbuf )
   strncpy(host, tbuf, SZ_LINE-1);
 else{
   /* use current host */
   if( gethostname(host, SZ_LINE) < 0 ){
     if( errno != EINTR ){
	if( debug ) perror("gethostname");
	flag = 2;
	goto done;
     }
   } else if( debug ){
     fprintf(stderr, "gethostname: %s\n", host);
   }
 }

 /* if the alarm went off, we are done */
 if( delay && dns_alarm_flag ){
   if( debug ) fprintf(stderr, "alarm after gethostname()\n");
   goto done;
 }

 /* try to get info on this host */
 if( !(h=gethostbyname(host)) ){
   if( errno != EINTR ){
     if( debug ) perror("gethostbyname");
     flag = 3;
     goto done;
   }
 } else if( debug ){
   fprintf(stderr, "gethostbyname: %s (%d)\n", h->h_name, port);
 }

 /* if the alarm went off, we are done */
 if( delay && dns_alarm_flag ){
   if( debug ) fprintf(stderr, "alarm after gethostbyname()\n");
   flag = 4;
   goto done;
 }

 /* connect, if necessary */
 if( cflag ){
   memcpy(&ip, h->h_addr_list[0], (size_t)h->h_length);
   ip = ntohl(ip);
   if( (fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
     if( errno != EINTR ){
	if( debug ) perror("socket");
	flag = 5;
	goto done;
     }
   }
   memset((char *)&sock_in, 0, sizeof(sock_in));
   sock_in.sin_family = AF_INET;
   sock_in.sin_addr.s_addr = htonl(ip);
   sock_in.sin_port = htons(port);
   got = connect(fd, (struct sockaddr *)&sock_in, sizeof(sock_in));
   close(fd);
   if( got < 0 ){
     if( errno != EINTR ){
	perror("connect");
	flag = 6;
	goto done;
     }
   } else if( debug ){
     fprintf(stderr, "connect succeeded\n");
   }
 }

 /* if the alarm went off, we are done */
 if( delay && dns_alarm_flag ){
   if( debug ) fprintf(stderr, "alarm after connect()\n");
   flag = 100;
   goto done;
 }

 /* stop the alarm, if necessary */
 if( delay ){
   /* turn off the alarm */
   alarm(0);
   /* reset up alarm signal handler */
   act1.sa_handler = oact1.sa_handler;
   sigemptyset(&act1.sa_mask);
   act1.sa_flags = 0;
#ifdef SA_INTERRUPT
   act1.sa_flags |= SA_INTERRUPT;
#endif
   sigaction(SIGALRM, &act1, &oact1);
 }

done:
 /* if alarm went off, set return status flag */
 if( dns_alarm_flag ){
   if( !flag ) flag = dns_alarm_flag;
 }

 /* return status flag */
 return(flag);
}

#if HAVE_TCL

static int TclcheckdnsCmd(ClientData clientData, Tcl_Interp *interp,
	     int objc, Tcl_Obj *CONST objv[])
{
 int got;
 int delay=5;
 int cflag=0;
 char *s, *t;
 char *host=NULL;
 char tbuf[SZ_LINE];

 /* make sure argument count is correct */
 if( objc < 3 ){
   Tcl_WrongNumArgs(interp, 1, objv, "host delay [connect]");
   return(TCL_ERROR);
 }

 /* get host */
 s = Tcl_GetStringFromObj(objv[1], NULL);
 if( s && (*s != '\0') ){
   host = s;
 }

 /* get delay */
 t = Tcl_GetStringFromObj(objv[2], NULL);
 if( t && (*t != '\0') ){
   delay = atoi(t);
 }

 if( objc >= 4 ){
   /* get connect flag */
   t = Tcl_GetStringFromObj(objv[3], NULL);
   if( t && (*t != '\0') ){
     cflag = atoi(t);
   }
 }

 /* reset error/result condition */
 Tcl_ResetResult(interp);

 /* check the DNS */
 got = checkdns(host, delay, cflag);

 /* set return value */
 sprintf(tbuf, "%d", got);
 Tcl_SetResult(interp, tbuf, TCL_VOLATILE);

 /* tcl command complete */
 return TCL_OK;
}

int Tclcheckdns_Init (void *vinterp)
{
 Tcl_Interp *interp = (Tcl_Interp *)vinterp;

 /* add xpa commands to this interpreter */
 Tcl_CreateObjCommand(interp, "checkdns", TclcheckdnsCmd,
		       (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
 Tcl_PkgProvide(interp, "Tclcheckdns", "1.1");
 return(TCL_OK);
}

#else

int main(int argc, char **argv)
{
 int c;
 int cflag=0;
 int delay=5;
 int got;
 char *name;

 /* process switch arguments */
 while ((c = getopt(argc, argv, "cd:v")) != -1){
   switch(c){
   case 'c':
     cflag = 1;
     break;
   case 'd':
     delay = atoi(optarg);
     break;
   case 'v':
     debug = 1;
     break;
   }
 }

 /* sanity check */
 if( (argc - optind) < 1 ){
   fprintf(stderr, "usage: %s -c -d delay host\n", argv[0]);
   return 1;
 }

 /* get arguments */
 name = argv[optind+0];

 /* make the call */
 got = checkdns(name, delay, cflag);

 /* output results */
 fprintf(stderr, "return code: %d\n", got);
 return got;
}

#endif
