/*===========================================================================
  Copyright (C) 1995-2007 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/* ++++++++++++++++++++++++++++++++++++++++++++++++++

.LANGUAGE: C
.AUTHOR:
  Klaus Banse ESO - Garching
.IDENTIFICATION
.KEYWORDS
  MD5, signature
.PURPOSE
  calculate the MD5 signature from the data of a FITS file

.ALGORITHM
  code is taken over from module fitsmd5.c of NDevillard 
  as copied into /home/code_other/ND/fitsmd5/fitsmd5

.RETURNS
  return status, = 0 o.k., else not o.k.

.VERSIONS
 070702		last modif

-------------------------------------------------- */



/*----------------------------------------------------------------------------
   
   File name    :   fitsmd5.c
   Author       :	N. Devillard
   Created on   :	May 2001
   Description  :	Display/Add/Update the DATAMD5 keyword/value

 ---------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>


/* Definitions related to FITS */
#define FITSLINESZ		80	/* a FITS line is 80 chars 	*/
#define FITSCARDS		36	/* 36 cards per block		*/
#define FITSBLOCKSZ		(FITSLINESZ*FITSCARDS)	/* FITS block size=2880 */

/* Definitions related to MD5 */
#define MD5HASHSZ		32 /* an MD5 key length is 32 bytes = 128 bits */

/* FITS keyword used to store MD5 key */
#define FITSMD5KEY		"DATAMD5 "
 
/* The following types defined for MD5 computation only */
typedef unsigned int word32 ;

struct MD5Context {
	word32 buf[4];
	word32 bits[2];
	unsigned char in[64];
};

int  SCETER(), CGN_INDEXC(), SCTPUT();


/*----------------------------------------------------------------------------
							Function prototypes
 ---------------------------------------------------------------------------*/
static void MD5Init(
#ifdef __STDC__
	struct MD5Context *
#endif
);
static void MD5Update(
#ifdef __STDC__
	struct MD5Context *, unsigned char *, unsigned
#endif
);
static void MD5Final(
#ifdef __STDC__
	unsigned char *, struct MD5Context *
#endif
);
static void MD5Transform(
#ifdef __STDC__
	word32 *, word32 *
#endif
);
static void byteReverse(
#ifdef __STDC__
	unsigned char *, unsigned
#endif
);

static int    fits_md5_check(
#ifdef __STDC__
	char *, int
#endif
);
static char * fits_pretty_string(
#ifdef __STDC__
	char *
#endif
);
static char * fits_getvalue(
#ifdef __STDC__
	char *
#endif
);


static char * pname = NULL ;
static int silent_process=0 ;



/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

/* Midas wrapper for the top MD5 function:

   a) declare the signature string `datamd5' as static to have access
      to it from the wrapper routine
      and remove the corresponding declaration inside `fits_md5_check'
      
      inside `fits_md5_check' at section
	if (!silent_process) {
      declare char. array texxt[80] and switch from printf to sprintf + SCTPUT

   b) call the original routine `fits_md5_check' to fill the static string
      which is then copied and passed back to the calling routine
   c) for VMS we have to isolate the call of sync()
   d) finally the "main" code in the end of the original file has to be 
      taken out

   this section of code (Midas_md5() has to be inserted into the 
   original code
   after the section:
				Global variables

   and before section:
				MD5 function code
*/

/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */



static char  datamd5[MD5HASHSZ+1];



int Midas_md5(file, upda, mymd5)
char *file, *mymd5;
int upda;

{
int stat;

stat = fits_md5_check(file,upda);

(void) strcpy(mymd5,datamd5);

return stat;
}

/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */




/*----------------------------------------------------------------------------
							MD5 function code
 ---------------------------------------------------------------------------*/

/*
 * Reverse bytes in a 32-bit word. This code is harmless on little endian
 * machines.
 */
static void byteReverse( buf, longs)
     unsigned char *buf;
     unsigned longs;
{
    word32 t;
    do {
	t = (word32) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
	    ((unsigned) buf[1] << 8 | buf[0]);
	*(word32 *) buf = t;
	buf += 4;
    } while (--longs);
}

/*
 * Start MD5 accumulation.  Set bit count to 0 and buffer to MD5
 * initialization constants.
 */
static void MD5Init( ctx )
     struct MD5Context *ctx;
{
    ctx->buf[0] = 0x67452301;
    ctx->buf[1] = 0xefcdab89;
    ctx->buf[2] = 0x98badcfe;
    ctx->buf[3] = 0x10325476;

    ctx->bits[0] = 0;
    ctx->bits[1] = 0;
}

/*
 * Update context to reflect the concatenation of another buffer full
 * of bytes.
 */
static void MD5Update( ctx, buf, len )
     struct MD5Context *ctx;
     unsigned char *buf;
     unsigned len;
{
    register word32 t;

    /* Update bitcount */
    t = ctx->bits[0];
    if ((ctx->bits[0] = t + ((word32) len << 3)) < t)
	ctx->bits[1]++;		/* Carry from low to high */
    ctx->bits[1] += len >> 29;

    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */

    /* Handle any leading odd-sized chunks */
    if (t) {
	unsigned char *p = (unsigned char *) ctx->in + t;

	t = 64 - t;
	if (len < t) {
	    memmove(p, buf, len);
	    return;
	}
	memmove(p, buf, t);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (word32 *) ctx->in);
	buf += t;
	len -= t;
    }
    /* Process data in 64-byte chunks */
    while (len >= 64) {
	memmove(ctx->in, buf, 64);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (word32 *) ctx->in);
	buf += 64;
	len -= 64;
    }

    /* Handle any remaining bytes of data. */
    memmove(ctx->in, buf, len);
}

/*
 * Final wrapup - pad to 64-byte boundary with the bit pattern 
 * 1 0* (64-bit count of bits processed, MSB-first)
 */
static void MD5Final( digest, ctx )
unsigned char digest[16];
struct MD5Context *ctx;
{
    unsigned int count;
    unsigned char *p;

    /* Compute number of bytes mod 64 */
    count = (ctx->bits[0] >> 3) & 0x3F;

    /* Set the first char of padding to 0x80.  This is safe since there is
       always at least one byte free */
    p = ctx->in + count;
    *p++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = 64 - 1 - count;

    /* Pad out to 56 mod 64 */
    if (count < 8) {
	/* Two lots of padding:  Pad the first block to 64 bytes */
	memset(p, 0, count);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (word32 *) ctx->in);

	/* Now fill the next block with 56 bytes */
	memset(ctx->in, 0, 56);
    } else {
	/* Pad block to 56 bytes */
	memset(p, 0, count - 8);
    }
	byteReverse(ctx->in, 14);

    /* Append length in bits and transform */
    ((word32 *) ctx->in)[14] = ctx->bits[0];
    ((word32 *) ctx->in)[15] = ctx->bits[1];

    MD5Transform(ctx->buf, (word32 *) ctx->in);
	byteReverse((unsigned char *) ctx->buf, 4);
    memmove(digest, ctx->buf, 16);
    memset(ctx, 0, sizeof(*ctx));	/* In case it's sensitive */
}

/* The four core functions - F1 is optimized somewhat */

/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s) \
	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )

/*
 * The core of the MD5 algorithm, this alters an existing MD5 hash to
 * reflect the addition of 16 longwords of new data.  MD5Update blocks
 * the data and converts bytes into longwords for this routine.
 */
static void MD5Transform( buf, in )
     word32 *buf, *in;
{
    register word32 a, b, c, d;

    a = buf[0];
    b = buf[1];
    c = buf[2];
    d = buf[3];

    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

    buf[0] += a;
    buf[1] += b;
    buf[2] += c;
    buf[3] += d;
}


/*----------------------------------------------------------------------------
							FITS-related functions
 ---------------------------------------------------------------------------*/

/* Pretty-print a FITS string value */
static char * fits_pretty_string( s )
     char * s;
{
    static char     pretty[FITSLINESZ+1] ;
    int             i,j ;

    /* bulletproof */
    if (s==NULL) return NULL ;

    pretty[0] = (char)0 ;
    if (s[0]!='\'') return s ;

    /* skip first quote */
    i=1 ;
    j=0 ;
    /* trim left-side blanks */
    while (s[i]==' ') {
        if (i==(int)strlen(s)) break ;
        i++ ;
    }
    if (i>=(int)(strlen(s)-1)) return pretty ;
    /* copy string, changing double quotes to single ones */
    while (i<(int)strlen(s)) {
        if (s[i]=='\'') {
            i++ ;
        }
        pretty[j]=s[i];
        i++ ;
        j++ ;
    }
    /* NULL-terminate the pretty string */
    pretty[j+1]=(char)0;
    /* trim right-side blanks */
    j = (int)strlen(pretty)-1;
    while (pretty[j]==' ') j-- ;
    pretty[j+1]=(char)0;
    return pretty;
}


/* Get the FITS value in a FITS card */
static char * fits_getvalue( line )
     char *line;
{
    static char value[FITSLINESZ+1] ;
    int     i ;
    int     from, to ;
    int     inq ;

    if (line==NULL) {
        return NULL ;
    }
    memset(value, 0, FITSLINESZ+1);
    /*
     * Get past the keyword
     */
    i=0 ;
    while (line[i]!='=' && i<FITSLINESZ) i++ ;
    if (i>FITSLINESZ) {
        return NULL ;
    }
    i++ ;
    while (line[i]==' ' && i<FITSLINESZ) i++ ;
    if (i>FITSLINESZ) {
        return NULL ;
    }
    from=i;
    /*
     * Now in the value section
     * Look for the first slash '/' outside of a string
     */
    inq = 0 ;
    while (i<FITSLINESZ) {
        if (line[i]=='\'')
            inq=!inq ;
        if (line[i]=='/')
            if (!inq)
                break ;
        i++;
    }
    i-- ;
    /* Backtrack on blanks */
    while (line[i]==' ' && i>=0) i-- ;
    if (i<0) {
        return NULL ;
    }
    to=i ;
    if (to<from) {
        return NULL ;
    }
    /* Copy relevant characters into output buffer */
    strncpy(value, line+from, to-from+1);
    /* Null-terminate the string */
    value[to-from+1] = (char)0;
    /*
     * Make it pretty: remove head and tail quote, change double
     * quotes to simple ones.
     */
    strcpy(value, fits_pretty_string(value));
    return value ;
}


/* Replace the MD5 card in the input header */
static int fits_replace_card( filename, off_md5, datamd5 )
     char *filename, *datamd5;
     int off_md5;
{
	char	*	buf ;
	int			fd ;
	struct stat	sta ;
	char		card[FITSLINESZ];
	int			i ;
	int			err ;

	/* Get file size */
	if (stat(filename, &sta)==-1) {
		fprintf(stderr, "%s: cannot stat file [%s]: no update done\n",
				pname,
				filename);
		return 1 ;
	}
	/* Open file */
	fd = open(filename, O_RDWR);
	if (fd==-1) {
		fprintf(stderr,
				"%s: cannot open file [%s] for modification: no update done\n",
				pname,
				filename);
		return 1 ;
	}
	/* Memory-map the file */
	buf = (char*)mmap(0,
					  sta.st_size,
					  PROT_READ | PROT_WRITE,
					  MAP_SHARED,
					  fd,
					  0);
	if (buf==(char*)-1) {
		perror("mmap");
		close(fd);
		return 1 ;
	}
	/* sprintf should be safe, the MD5 signature size is 32 chars */
	sprintf(card, "%s= '%s' / data MD5 signature", FITSMD5KEY, datamd5);
	i=FITSLINESZ-1 ;
	while (card[i]!='e') {
		card[i]=' ';
		i-- ;
	}
	/* Copy card into file */
	memcpy(buf+off_md5, card, FITSLINESZ);
	/* flush output, unmap buffer, close file and quit */
	err=0 ;
#if vms
        ;				/* no such call for VMS */
#else
	sync();
#endif
	if (close(fd)==-1) {
		fprintf(stderr, "%s: error closing modified file [%s]",
				pname,
				filename);
		err++ ;
	}
	if (munmap(buf, sta.st_size)==-1) {
		perror("munmap");
		err++ ;
	}
	return err;
}
/*

*/

/* Display or modify the DATAMD5 value. Returns the number of errors. */

static int fits_md5_check(filename, update_header)
char *filename;
int   update_header;

{
FILE *in ;

char buf[FITSBLOCKSZ], *buf_c;
char *hdrmd5;
char datpath[192], *nampntr;
extern char DATA_PATH[328];             /* that's inside scs.c (SCSPRO) */


unsigned char digest[16] ;

struct MD5Context ctx ;
 
int  n, i, err, opencount;
int  in_header, off_md5, cur_off, md5keysz ;




/* Open input file */

opencount = 0;
nampntr = filename;

open_file:
if ((in=fopen(nampntr, "r"))==NULL) 
   {
   if (opencount < 4)
      {
      n = opencount*80;
      (void) strncpy(datpath,&DATA_PATH[n],80);
      if (datpath[0] != '^')
         {
         datpath[80] = ' ';
         n = CGN_INDEXC(datpath,' ');
         (void) strcpy(&datpath[n],filename);          /* use new path */
         nampntr = datpath;
         opencount ++;
         goto open_file;
         }
      }
   SCETER(11,"Cannot open FITS file ...");
   }



/* Initialize all variables */
MD5Init(&ctx);
in_header=1 ;
hdrmd5=NULL ;
off_md5=0;
cur_off=0;
md5keysz = (int)strlen(FITSMD5KEY) ;

/* Loop over input file */
while (fread(buf, 1, FITSBLOCKSZ, in)==FITSBLOCKSZ) 
   {
   if (in_header) 
      {			/* If current block is a header block */
      buf_c = buf ;
      for (i=0; i<FITSCARDS; i++) 
         {
	 /* Try to locate MD5 keyword if not located already */
	 if (hdrmd5==NULL) 
            {
	    if (!strncmp(buf_c, FITSMD5KEY, md5keysz)) 
               {
	       hdrmd5 = fits_getvalue(buf_c);
	       off_md5 = cur_off ;
	       }
	    }

         /* Try to locate an END key */
         if (buf_c[0]=='E' && buf_c[1]=='N' &&
             buf_c[2]=='D' && buf_c[3]==' ') 
            {
	    in_header=0 ;
	    break ;
	    }

         buf_c += FITSLINESZ ;
         cur_off += FITSLINESZ ;
         }
      } 
   else 	/* If current block is a data block, try to locate */
      {		/* an extension header */
      if (buf[0]=='X' && buf[1]=='T' && buf[2]=='E' &&
          buf[3]=='N' && buf[4]=='S' && buf[5]=='I' &&
          buf[6]=='O' && buf[7]=='N' && buf[8]=='=') 
         {
	 in_header=1 ;
	 buf_c = buf ;
         for (i=0 ; i<FITSCARDS ; i++)
            {		/* Try to find an END marker in this block */
	    if (buf_c[0]=='E' && buf_c[1]=='N' &&
	        buf_c[2]=='D' && buf_c[3]==' ') 
               {		/* Found END marker in same block as XTENSION */
	       in_header=0 ;
	       break ;
               }
            buf_c += FITSLINESZ ;
	    }
	 }
      else 
         {			/* Data block: accumulate for MD5 */
	 MD5Update(&ctx, (unsigned char *)buf, FITSBLOCKSZ);
         }
      }
   }
fclose(in);


/* Got to the end of file: summarize */

MD5Final(digest, &ctx);


/* Write digest into a string */

(void) sprintf(datamd5,
    "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
    digest[ 0], digest[ 1], digest[ 2], digest[ 3], digest[ 4], digest[ 5],
    digest[ 6], digest[ 7], digest[ 8], digest[ 9], digest[10], digest[11],
    digest[12], digest[13], digest[14], digest[15]);

if (!silent_process) 
   {
   char  texxt[80];

   (void) sprintf(texxt,"filename    : %s",filename);
   SCTPUT(texxt);
   (void) sprintf(texxt,"MD5 declared: %s",hdrmd5 ? hdrmd5 : "none");
   SCTPUT(texxt);
   (void) sprintf(texxt,"MD5 computed: %s",datamd5);
   SCTPUT(texxt);
   }

if (update_header)		/* Update header if requested */
   {
   if (hdrmd5==NULL) 
      {
      fprintf(stderr, "%s: cannot update header: missing %s\n",pname,
	      FITSMD5KEY);
      return 1 ;
      }
   err = fits_replace_card(nampntr, off_md5, datamd5);
   }
else
   err=0 ;

return err ;
}

