/*
 *  Copyright (C) 2003 Tomasz Kojm <zolw@konarski.edu.pl>
 *
 *  untgz() is based on public domain minitar utility by Charles G. Waldman
 *
 *  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 Mass Ave, Cambridge, MA 02139, USA.
 */


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

#include "clamav.h"

#define TAR_BLOCKSIZE 512

int cli_untgz(int fd, const char *destdir)
{
	char *fullname, osize[13], name[101], type;
	char block[TAR_BLOCKSIZE];
	int nbytes, nread, nwritten, in_block = 0;
	unsigned int size;
	FILE *outfile = NULL;
	gzFile *infile;

    cli_dbgmsg("in cli_untgz()\n");

    if((infile = gzdopen(fd, "rb")) == NULL) {
	cli_errmsg("Can't gzdopen() descriptor %d\n", fd);
	return -1;
    }


    fullname = (char *) calloc(sizeof(char), strlen(destdir) + 100 + 5);

    while(1) {

	nread = gzread(infile, block, TAR_BLOCKSIZE);

	if(!in_block && nread == 0)
	    break;

	if(nread != TAR_BLOCKSIZE) {
	    cli_errmsg("Incomplete block read.\n");
	    free(fullname);
	    return -1;
	}

	if(!in_block) {
	    if (block[0] == '\0')  /* We're done */
		break;

	    strncpy(name, block, 100);
	    name[100] = '\0';
	    strcpy(fullname, destdir);
	    strcat(fullname, "/");
	    strcat(fullname, name);
	    cli_dbgmsg("Unpacking %s\n",fullname);
	    type = block[156];

	    switch(type) {
		case '0':
		case '\0':
		    break;
		case '5':
		    cli_errmsg("Directories in CVD are not supported.\n");
		    free(fullname);
		    return -1;
		default:
		    cli_errmsg("Unknown type flag %c.\n",type);
		    free(fullname);
		    return -1;
	    }

	    in_block = 1;

	    if(outfile) {
		if(fclose(outfile)) {
		    cli_errmsg("Cannot close file %s.\n", fullname);
		    free(fullname);
		    return -1;
		}
		outfile = NULL;
	    }

	    if(!(outfile = fopen(fullname, "wb"))) {
		cli_errmsg("Cannot create file %s.\n", fullname);
		free(fullname);
		return -1;
	    }

	    strncpy(osize, block + 124, 12);
	    osize[12] = '\0';
	    size = -1;
	    sscanf(osize, "%o", &size);

	    if(size < 0) {
		cli_errmsg("Invalid size in header.\n");
		free(fullname);
		return -1;
	    }

	} else { /* write or continue writing file contents */
	    nbytes = size > TAR_BLOCKSIZE ? TAR_BLOCKSIZE : size;
	    nwritten = fwrite(block, 1, nbytes, outfile);

	    if(nwritten != nbytes) {
		cli_errmsg("Wrote %d instead of %d (%s).\n", nwritten, nbytes, fullname);
		free(fullname);
		return -1;
	    }

	    size -= nbytes;
	    if(size == 0)
		in_block = 0;
	}
    }

    return 0;
}

char *cli_cut(const char *line, int field)
{
        int length, counter = 0, i, j = 0, k;
        char *buffer;

    length = strlen(line);
    buffer = (char *) cli_calloc(length, sizeof(char));

    for(i = 0; i < length; i++) {
        if(line[i] == ':') {
            counter++;
            if(counter == field) {
		break;
	    } else {
		memset(buffer, 0, length);
		j = 0;
	    }
        } else {
            buffer[j++] = line[i];
        }
    }

    return (char *) cli_realloc(buffer, strlen(buffer) + 1);
}

struct cl_cvd *cli_cvdhead(const char *head)
{
	char *pt;
	struct cl_cvd *cvd;

    cvd = (struct cl_cvd *) cli_calloc(1, sizeof(struct cl_cvd));
    cvd->time = cli_cut(head, 2);

    pt = cli_cut(head, 3);
    cvd->version = atoi(pt);
    free(pt);

    pt = cli_cut(head, 4);
    cvd->sigs = atoi(pt);
    free(pt);

    pt = cli_cut(head, 5);
    cvd->fl = (short int) atoi(pt);
    free(pt);

    cvd->md5 = cli_cut(head, 6);
    cvd->dsig = cli_cut(head, 7);
    cvd->builder = cli_cut(head, 8);

    return cvd;
}

struct cl_cvd *cl_cvdhead(const char *file)
{
	char head[257];
	FILE *fd;
	int i;


    if((fd = fopen(file, "rb")) == NULL) {
	cli_errmsg("Can't open CVD file %s\n", file);
	return NULL;
    }

    if(fread(head, 1, 256, fd) != 256) {
	cli_errmsg("Can't read CVD head from %s\n", file);
	return NULL;
    }
    head[256] = 0;

    for(i = 255; i > 0 && !isalnum(head[i]); head[i] = 0, i--);

    if(strncmp(head, "ClamAV-VDB:", 11)) {
	cli_errmsg("%s is not a CVD file.\n");
	return NULL;
    }

    return cli_cvdhead(head);
}

void cl_cvdfree(struct cl_cvd *cvd)
{
    free(cvd->time);
    free(cvd->md5);
    free(cvd->dsig);
    free(cvd->builder);
    free(cvd);
}

int cli_cvdload(FILE *fd, struct cl_node **root, int *virnum)
{
        char head[257], *dir, *tmp, buffer[BUFFSIZE];
	int bytes;
	struct cl_cvd *cvd;
	const char *tmpdir;
	FILE *tmpd;

    cli_dbgmsg("in cli_cvdload()\n");

    if(fread(head, 1, 256, fd) != 256) {
	cli_errmsg("Can't read CVD head.\n");
	return -1;
    }
    head[256] = 0;

    cvd = cli_cvdhead(head);

    /* verify md5/dsig */


    /* unpack */

    tmpdir = getenv("TMPDIR");

    if(tmpdir == NULL)
#ifdef P_tmpdir
	tmpdir = P_tmpdir;
#else
	tmpdir = "/tmp";
#endif

    dir = cl_gentemp(tmpdir);
    if(mkdir(dir, 0700)) {
	cli_errmsg("cli_cvdload():  Can't create temporary directory %s\n", dir);
	return CL_ETMPDIR;
    }

    /* 
    if(cli_untgz(fileno(fd), dir)) {
	cli_errmsg("cli_cvdload(): Can't unpack CVD file.\n");
	return CL_ECVDEXTR;
    }
    */

    /* FIXME: it seems there is some problem with current position after
     * gzdopen() in cli_untgz(). Temporarily we need this wrapper:
     */

	    /* start */

	    tmp = cl_gentemp(tmpdir);
	    if((tmpd = fopen(tmp, "wb+")) == NULL) {
		cli_errmsg("Can't create temporary file %s\n", tmp);
		free(dir);
		free(tmp);
		return -1;
	    }
	    while((bytes = fread(buffer, 1, BUFFSIZE, fd)) > 0)
		fwrite(buffer, 1, bytes, tmpd);

	    fflush(tmpd);
	    fseek(tmpd, 0L, SEEK_SET);

	    if(cli_untgz(fileno(tmpd), dir)) {
		cli_errmsg("cli_cvdload(): Can't unpack CVD file.\n");
		cli_rmdirs(dir);
		free(dir);
		unlink(tmp);
		free(tmp);
		return CL_ECVDEXTR;
	    }

	    fclose(tmpd);
	    unlink(tmp);
	    free(tmp);

	    /* end */

    /* load extracted directory */
    cl_loaddbdir(dir, root, virnum);

    cli_rmdirs(dir);
    free(dir);

    return 0;
}