/* Title:	SETTIME.C	Set system date and time.
 *
 * Version:	1-001
 *
 * Facility:	None.
 *
 * Abstract:
 *
 * This program first attempts to start a server program on a 
 *  remote DECnet node and retrieve the current date and time 
 *  from it.  This presupposes, of course, that the remote 
 *  system is up, running some flavor of DECnet, and knows the
 *  correct time.  At our installation the remote node is a 
 *  VAX 11/780, running VMS and DECnet-VAX, which is more likely 
 *  to be up than our MicroVAX I's (where this program is running).
 *  The remote node must have a DECnet object named SENDTIME to
 *  kick off the server program which sends the current datetime 
 *  to us.  This program sends the string "ULTRIX" to the server
 *  program before making its request, thus allowing a single
 *  server program to handle requests from both Ultrix and VMS
 *  clients.  (Our server program is a simple VMS command procedure.)
 *
 * Environment:
 *
 * Must be run during startup but after DECnet is started.  Call from 
 *  rc.local.
 *
 * Author:	David E. Smith		Date:	25-NOV-1987
 *
 * Modified by:
 * 1-001 - Original. DES 25-NOV-1987
 */


#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netdnet/dn.h>
#include <time.h>
#include <sgtty.h>

#define BUFFSIZE 1024
#define NECHO 0xfffffff7			/* do not echo input */

char opsys[] = { "ULTRIX" }, date_buff[BUFFSIZE];
int  monthdays[13] = {0,32,30,32,31,32,31,32,32,31,32,31,32};
extern int errno;
int  nyr,nmth,nday,nhr,nmin;	/* Declared global to make it easier 
				   to get values back from CHECKTIME()	*/

/*
 * Module: MAIN
 *
 * Functional Description:
 *
 * This module first checks for the correct number of passed parameters
 *  and then initializes the time structures from time.h by calling the
 *  gettimeofday() function.  This module calls procedures to get a date 
 *  string and then validate it (i.e. ensure it is in the proper format, 
 *  yymmddHHMM, and that it represents some real date since 1-JAN-1970.)
 *  Once a good date is received, it converts the string into the number
 *  of seconds since 1-JAN-1970 00:00 GMT.  This integer value is used
 *  to set the system time using the settimeofday() function.
 *
 * Formal Arguments:
 *
 * 	argv[1] = character string representation of DECnet server node.
 *
 * Implicit Inputs:	None.
 *
 * Implicit Outputs:	None.
 *
 * Completion Codes:
 *
 *	0	Procedure successfully completed.
 *	errno	Index into system error messages.
 *
 * Side Effects:
 *
 * If used to change the date of a system running multiuser, this
 * procedure may crash the system and bring it down to single user.
 *
 */

main(argc, argv)
int argc;
char *argv[];
{
int  v = 0,i,getnettime(),asktime(),checktime();
unsigned long ndt;
struct timeval tim;
struct timezone timz;

	/* Check for correct number of passed arguments.	*/

if (argc < 2)
	{
	printf("Usage: %s remotenode\n", argv[0]);
	exit(-1);
	}

	/* Fill time-of-day structures with current system time. */

if (gettimeofday (&tim, &timz) == -1) 
	{
	exit(errno);
	}

while (v <= 0)
	{
	if (v == 0)
		if ((v = getnettime(argv[1], date_buff)) == -1)
			continue;
		else;
	else
		v = asktime(date_buff);

	if ((v = checktime(date_buff)) <= 0) continue;

	/* Calculate elapsed seconds since midnight, 1 Jan 1970.	*/

	ndt = 60*nmin + 3600*nhr + 86400*(nday - 1); /* Start with the number
							of seconds since the
							beginning of this 
							month. 		    */

	for (i=1;i<nmth;i++)	/* Add the number of seconds for each of the
				   past months since the beginning of this
				   year.				    */

		switch(i)
		{
		case 2: ndt = ndt + 2419200;
			continue;

		case 4: ndt = ndt + 2592000;
			continue;

		case 6: ndt = ndt + 2592000;
			continue;

		case 9: ndt = ndt + 2592000;
			continue;

		case 11: ndt = ndt + 2592000;
			 continue;

		default: ndt = ndt + 2678400;
		}
	if 		/* Add an extra day if this is a leap year and
			   February is past.				*/
	((1970 + nyr)%4 == 0 && (1970 + nyr)%100 != 0 || (1970 + nyr)%400 == 0)
		if (nmth>2)
			ndt = ndt + 86400;

	for (i=1970;i<(1970 + nyr);i++)
		{
		ndt = ndt + 31536000;	/* Add the seconds for each year from
					   1970 through last year.	    */

		if (i%4 == 0 && i%100 != 0 || i%400 == 0)
			ndt = ndt + 86400; /* Leap year - add an extra day. */
		}

	v = ndt = ndt + (60 * timz.tz_minuteswest);	/* correction for GMT */

	} /* while v <= 0 */

tim.tv_sec = ndt;	/* Load new value into time structure.		*/
tim.tv_usec = 0;	/* Zero microsecond counter.			*/

	/* Set new system date.		*/

if (settimeofday(&tim,&timz) == -1)
	{
	exit(errno);
	}

exit(0);
} /* End of main */

/*
 *	Module: GETNETTIME
 *
 * Functional Description:
 *
 * This procedure establishes a DECnet link with a server node.
 *  It passes the character string "Ultrix" the the server process
 *  on the remote node.  In return it receives a string which is the
 *  ultrix formatted system date and time from that remote node.
 *  The datetime string is returned through the "buff" argument and 
 *  the length of buff is returned as the function value.  A -1 is
 *  returned in the case of any error.
 *
 * Formal Arguments:
 *
 *	node		character string name of DECnet server node.
 *	buff		character buffer to return datetime string.
 *
 * Implicit Inputs:	None.
 *
 * Implicit Outputs:	None.
 *
 * Function Value:
 *
 *	-1		any error condition.
 *	length of buff	successfully procedure completion.
 *
 * Side Effects:	None that I know of.
 *
 */

int getnettime(node, buff)
char node[], buff[];
{
int socket;			/* socket for connection */
int length = strlen(opsys);	/* length of data to send*/

	/* connect to remote node	*/

if ((socket = dnet_conn(node, "sendtime", 0, 0, 0, 0, 0)) == -1)
	return(-1);

	/* send opsys name to Gettime. */

if ((write(socket, opsys, length) == -1) ||
	((length = read(socket, buff, BUFFSIZE)) == -1))
	{
	close(socket);
	return(-1);
	}
buff[length] = '\0';

	/* program done */

close(socket);
if (length == 0)
	return(-1);
else
	return(length);
} /* End of getnettime */

/*
 *	Module: ASKTIME
 *
 * Functional Description:
 *
 * Called by main() if getnettime() returns an error code.  Prompts
 *  for an ultrix datetime string from standard input.  The terminal
 *  mode is set to CBREAK and ECHO is turned off so that each character
 *  may be checked as it is pressed.  Only numerals, the return/enter
 *  key, and the delete key are accepted as input; all other keypresses
 *  are ignored.  If more or less than 10 digits are typed, an invalid
 *  date message results and a value of zero (0) is returned to the
 *  calling procedure.  Otherwise the length of buff, which will always 
 *  be 10 on success, is returned.  Before returning the terminal is
 *  restored to its original mode, which was saved in the local variable
 *  save_flags.
 *
 * Formal Arguments:
 *
 *	buff		character buffer to return datetime string.
 *
 * Implicit Inputs:	None.
 *
 * Implicit Outputs:	None.
 *
 * Routine Value:
 *
 *	0		any error condition.
 *	length of buff	successfully procedure completion.
 *
 * Side Effects:	None that I know of.
 *
 */
int asktime(buff)
char buff[];
{
int v = 0;
short save_flags;
struct sgttyb ttytab;

	/* Set terminal mode to "half-cooked" (CBREAK) */

ioctl(stdin->_file,TIOCGETP,&ttytab);
save_flags = ttytab.sg_flags;
ttytab.sg_flags |= CBREAK;
ttytab.sg_flags &= NECHO;
ioctl(stdin->_file,TIOCSETP,&ttytab);

	/* Begin date entry and checking ... */

while (v == 0)
	{

printf("\n\nEnter date and time in the following format.");
printf(" (yymmddHHMM): ");

	/* Fill date string array (buff[])	*/

while ((buff[v] = getchar()) != '\n' && v < 10)
	{
	if (isdigit(buff[v]) == 0)
		{
		if (buff[v] == '\177' && v > 0)
			{
			v--;
			printf("\033[D \033[D"); /* Only works on vt100
							or compatible. */
			continue;
			}
		else continue;
		}
	printf("%c",buff[v]);
	v++;
	} /* While getting characters */

printf("\n");
if (v < 10 || buff[10] != '\n')
	{
	printf("\n\nInvalid date and time. Try again.");
	v = 0;
	}
	} /* while v = 0 */

buff[10] = '\0';

	/* Set terminal mode to "cooked" (not CBREAK) */

	ttytab.sg_flags = save_flags;
	ioctl(stdin->_file,TIOCSETP,&ttytab);

return(v);
} /* End of asktime */

/*
 *	Module: CHECKTIME()
 *
 * Functional Description:
 *
 * This routine receives the argument buff which contains the datetime
 *  string.  It first breaks the string down into the five component
 *  strings year (yr), month (mth), day, hour (hr), and minute (min).
 *  Each of these substrings are converted to their integer value and
 *  checked to see if the fit into a valid date (e.g. month must not be
 *  greater than twelve).  The procedure returns the length of buff on 
 *  success and -1 to indicate failure.
 *
 * Formal Arguments:
 *
 *	buff		character buffer to return datetime string.
 *
 * Implicit Inputs:	None.
 *
 * Implicit Outputs:
 *
 * The following globally declared integer variables are used to pass
 *  the values of year, month, day, hour, and minute back to main().
 *
 *	nyr, nmth, nday, nhr, nmin
 *
 * Routine Value:
 *
 *	-1		any error condition.
 *	length of buff	successfully procedure completion.
 *
 * Side Effects:	None that I know of.
 *
 */

int checktime(buff)
char buff[];
{
	/* Parse date string into year, month, date, hour, & minute.	*/
	/* Integer variables nyr, nmth, nday, nhr, and nmin are declared */
	/* globally above.						*/

int v=strlen(buff);
char yr[3],mth[3],day[3],hr[3],min[3];

strncpy(yr, &buff[0],2);
strncpy(mth,&buff[2],2);
strncpy(day,&buff[4],2);
strncpy(hr, &buff[6],2);
strncpy(min,&buff[8],2);

	/* Convert strings to integers for the calculation.	*/

if ((nyr = atoi(yr) - 70) < 0) v = -1;
if ((nmth = atoi(mth)) > 12) v = -1;
if ((nday = atoi(day)) >= monthdays[nmth]) v = -1;
if (nday == 0) v = -1;
if ((nhr = atoi(hr)) > 23) v = -1;
if ((nmin = atoi(min)) > 59) v = -1;

	/* End date entry and checking. */

if (v == -1) printf("\n\nInvalid date and time. Try again.");

return(v);
} /* End of checktime */
