Commit message: Check types to avoid invalid reads/writes.
CVE: CVE-2017-6309
CWE: CWE-125
Vulnerability description: An issue was discovered in tnef before 1.4.13. Two type confusions have been identified in the parse_file() function. These might lead to invalid read and write operations, controlled by an attacker.

Project: tnef

confirm_action (const char *prompt, ...)
{
    if (INTERACTIVE)
    {
	int confirmed = 0;
	char buf[BUFSIZ + 1];
	va_list args;
	va_start (args, prompt);

	VPRINTF(stdout, prompt, args);
	fgets (buf, BUFSIZ, stdin);
	if (buf[0] == 'y' || buf[0] == 'Y') confirmed = 1;

	va_end (args);

	return confirmed;
    }
    return 1;
}

Project: tnef

file_add_attr (File* file, Attr* attr)
{
    assert (file && attr);
    if (!(file && attr)) return;

    /* we only care about some things... we will skip most attributes */
    switch (attr->name)
    {
    case attATTACHMODIFYDATE:
	copy_date_from_attr (attr, &file->dt);
	break;

    case attATTACHMENT:
    {
	MAPI_Attr **mapi_attrs = mapi_attr_read (attr->len, attr->buf);
	if (mapi_attrs)
	{
	    file_add_mapi_attrs (file, mapi_attrs);
	    mapi_attr_free_list (mapi_attrs);
	    XFREE (mapi_attrs);
	}
    }
    break;

    case attATTACHTITLE:
	file->name = strdup( (char*)attr->buf );
	break;

    case attATTACHDATA:
	file->len = attr->len;
	file->data = CHECKED_XMALLOC(unsigned char, attr->len);
	memmove (file->data, attr->buf, attr->len);
	break;

    default:
	break;
    }
}

Project: tnef

file_free (File *file)
{
    if (file)
    {
	XFREE (file->name);
	XFREE (file->data);
	XFREE (file->mime_type);
        XFREE (file->content_id);
        XFREE (file->path);
	memset (file, '\0', sizeof (File));
    }
}

Project: tnef

file_write (File *file, const char* directory)
{
    char *path = NULL;

    assert (file);
    if (!file) return;

    if (file->name == NULL)
    {
	file->name = strdup( TNEF_DEFAULT_FILENAME );
	debug_print ("No file name specified, using default %s.\n", TNEF_DEFAULT_FILENAME);
    }

    if ( file->path == NULL )
    {
	file->path = munge_fname( file->name );

	if (file->path == NULL)
	{
	    file->path = strdup( TNEF_DEFAULT_FILENAME );
	    debug_print ("No path name available, using default %s.\n", TNEF_DEFAULT_FILENAME);
	}
    }

    path = concat_fname( directory, file->path );

    if (path == NULL)
    {
	path = strdup( TNEF_DEFAULT_FILENAME );
	debug_print ("No path generated, using default %s.\n", TNEF_DEFAULT_FILENAME);
    }

    debug_print ("%sWRITING\t|\t%s\t|\t%s\n",
		 ((LIST_ONLY==0)?"":"NOT "), file->name, path);

    if (!LIST_ONLY)
    {
	FILE *fp = NULL;

	if (!confirm_action ("extract %s?", file->name)) return;
	if (!OVERWRITE_FILES)
	{
	    if (file_exists (path))
	    {
		if (!NUMBER_FILES)
		{
		    fprintf (stderr,
			     "tnef: %s: Could not create file: File exists\n",
			     path);
		    return;
		}
		else
		{
		    char *tmp = find_free_number (path);
		    debug_print ("Renaming %s to %s\n", path, tmp);
		    XFREE (path);
		    path = tmp;
		}
	    }
	}

	fp = fopen (path, "wb");
	if (fp == NULL)
	{
	    perror (path);
	    exit (1);
	}
	if (fwrite (file->data, 1, file->len, fp) != file->len)
	{
	    perror (path);
	    exit (1);
	}
	fclose (fp);
    }

    if (LIST_ONLY || VERBOSE_ON)
    {
	if (LIST_ONLY && VERBOSE_ON)
	{
	    /* FIXME: print out date and stuff */
	    const char *date_str = date_to_str(&file->dt);
	    fprintf (stdout, "%11lu\t|\t%s\t|\t%s\t|\t%s", 
		     (unsigned long)file->len,
		     date_str+4, /* skip the day of week */
		     file->name,
		     path);
	}
	else
	{
            fprintf (stdout, "%s\t|\t%s", file->name, path);
	}
	if ( SHOW_MIME )
	{
	    fprintf (stdout, "\t|\t%s", file->mime_type ? file->mime_type : "unknown");
            fprintf (stdout, "\t|\t%s", file->content_id ? file->content_id : "");
	}
        fprintf (stdout, "\n");
    }
    XFREE(path);
}

Project: tnef

data_left (FILE* input_file)
{
    int retval = 1;
    
    if (feof(input_file)) retval = 0;
    else if (input_file != stdin)
    {
	/* check if there is enough data left */
	struct stat statbuf;
	size_t pos, data_left;
	fstat (fileno(input_file), &statbuf);
	pos = ftell(input_file);
	data_left = (statbuf.st_size - pos);

	if (data_left > 0 && data_left < MINIMUM_ATTR_LENGTH) 
	{
	    if ( CRUFT_SKIP )
	    {
		/* look for specific flavor of cruft -- trailing "\r\n" */

		if ( data_left == 2 )
		{
		    int c = fgetc( input_file );

		    if ( c < 0 )	/* this should never happen */
		    {
			fprintf( stderr, "ERROR: confused beyond all redemption.\n" );
			exit (1);
		    }

		    ungetc( c, input_file );

		    if ( c == 0x0d )		/* test for "\r" part of "\r\n" */
		    {
			/* "trust" that next char is 0x0a and ignore this cruft */

			if ( VERBOSE_ON )
			    fprintf( stderr, "WARNING: garbage at end of file (ignored)\n" );

			if ( DEBUG_ON )
			    debug_print( "!!garbage at end of file (ignored)\n" );
		    }
		    else
		    {
			fprintf( stderr, "ERROR: garbage at end of file.\n" );
		    }
		}
		else
		{
		    fprintf (stderr, "ERROR: garbage at end of file.\n");
		}
	    }
	    else
	    {
		fprintf (stderr, "ERROR: garbage at end of file.\n");
	    }

	    retval = 0;
	}
    }
    return retval;
}

Project: tnef

free_bodies(VarLenData **bodies, int len)
{
    while (len--)
    {
        XFREE(bodies[len]->data);
        XFREE(bodies[len]);
    }
}

Project: tnef

get_body_files (const char* filename,
		const char pref,
		const MessageBody* body)
{
    File **files = NULL;
    VarLenData **data;
    char *ext = "";
    char *type = "unknown";
    int i;

    switch (pref)
    {
    case 'r':
	data = body->rtf_bodies;
	ext = ".rtf";
            type = "text/rtf";
	break;
    case 'h':
	data = body->html_bodies;
	ext = ".html";
            type = "text/html";
	break;
    case 't':
	data = body->text_body;
	ext = ".txt";
            type = "text/plain";
	break;
    default:
	data = NULL;
	break;
    }

    if (data)
    {
	int count = 0;
	char *tmp 
	    = CHECKED_XCALLOC(char, 
			      strlen(filename) + strlen(ext) + 1);
	strcpy (tmp, filename);
	strcat (tmp, ext);

        char *mime = CHECKED_XCALLOC(char, strlen(type) + 1);
        strcpy (mime, type);

	/* first get a count */
	while (data[count++]);

	files = (File**)XCALLOC(File*, count + 1);
	for (i = 0; data[i]; i++)
	{
	    files[i] = (File*)XCALLOC(File, 1);
	    files[i]->name = tmp;
            files[i]->mime_type = mime;
	    files[i]->len = data[i]->len;
	    files[i]->data 
		= CHECKED_XMALLOC(unsigned char, data[i]->len);
	    memmove (files[i]->data, data[i]->data, data[i]->len);
	}
    }
    return files;
}

Project: tnef

get_text_data (Attr *attr)
{
    VarLenData **body = XCALLOC(VarLenData*, 2);

    body[0] = XCALLOC(VarLenData, 1);
    body[0]->len = attr->len;
    body[0]->data = CHECKED_XCALLOC(unsigned char, attr->len);
    memmove (body[0]->data, attr->buf, attr->len);
    return body;
}

Project: tnef

read_object (FILE *in)
{
    Attr *attr = NULL;

    /* peek to see if there is more to read from this stream */
    int tmp_char = fgetc(in);
    if (tmp_char == -1) return NULL;
    ungetc(tmp_char, in);

    attr = attr_read (in);

    return attr;
}