/*
 * This file is part of uudeview, the simple and friendly multi-part multi-
 * file uudecoder  program  (c) 1994-2001 by Frank Pilhofer. The author may
 * be contacted at fp@fpx.de
 *
 * 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.
 */

/*
 * This file implements the externally visible functions, as declared
 * in uudeview.h, and some internal interfacing functions
 */

#ifdef HAVE_CONFIG_H
    #include "config.h"
#endif

#ifdef SYSTEM_WINDLL
    #include <windows.h>
#endif
#ifdef SYSTEM_OS2
    #include <os2.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

#ifdef HAVE_FCNTL_H
    #include <fcntl.h>
#endif

#ifdef STDC_HEADERS
    #include <stdlib.h>
    #include <stdarg.h>
    #include <string.h>
#else
    #ifdef HAVE_STDARG_H
        #include <stdarg.h>
    #else
        #ifdef HAVE_VARARGS_H
            #include <varargs.h>
        #endif
    #endif
#endif

#ifdef HAVE_UNISTD_H
    #include <unistd.h>
#endif

#ifdef TIME_WITH_SYS_TIME
    #include <sys/time.h>
    #include <time.h>
#else
    #ifdef HAVE_SYS_TIME_H
        #include <sys/time.h>
    #else
        #include <time.h>
    #endif
#endif

#ifdef HAVE_ERRNO_H
    #include <errno.h>
#endif

/* to get open() in Windows */
#ifdef HAVE_IO_H
    #include <io.h>
#endif

#include <uudeview.h>
#include <uuint.h>
#include <fptools.h>
#include <uustring.h>

char * uulib_id = "$Id$";

#ifdef SYSTEM_WINDLL
BOOL _export WINAPI
DllEntryPoint (HINSTANCE hInstance, DWORD seginfo,
               LPVOID lpCmdLine)
{
    /* Don't do anything, so just return true */
    return TRUE;
}
#endif

/*
 * In DOS, we must open the file binary, O_BINARY is defined there
 */

#ifndef O_BINARY
    #define O_BINARY      0
#endif

/* for braindead systems */
#ifndef SEEK_SET
    #ifdef L_BEGIN
        #define SEEK_SET L_BEGIN
    #else
        #define SEEK_SET 0
    #endif
#endif

/*
 * Callback functions and their opaque arguments
 */

void   (*uu_MsgCallback)  (void *, char *, int)         = NULL;
int    (*uu_BusyCallback) (void *, uuprogress *)        = NULL;
int    (*uu_FileCallback) (void *, char *, char *, int) = NULL;
char * (*uu_FNameFilter)  (void *, char *)              = NULL;

void *uu_MsgCBArg  = NULL;
void *uu_BusyCBArg = NULL;
void *uu_FileCBArg = NULL;
void *uu_FFCBArg   = NULL;

/*
 * Global variables
 */

int uu_fast_scanning = 0;   /* assumes at most 1 part per file          */
int uu_bracket_policy = 0;  /* gives part numbers in [] higher priority */
int uu_verbose = 1;     /* enables/disables messages&notes          */
int uu_desperate = 0;       /* desperate mode                           */
int uu_ignreply = 0;        /* ignore replies                           */
int uu_debug = 0;       /* debugging mode (print __FILE__/__LINE__) */
int uu_errno = 0;       /* the errno that caused this UURET_IOERR   */
int uu_dumbness = 0;        /* switch off the program's intelligence    */
int uu_overwrite = 1;       /* whether it's ok to overwrite ex. files   */
int uu_ignmode = 0;     /* ignore the original file mode            */
int uu_handletext = 0;      /* do we want text/plain messages           */
int uu_usepreamble = 0;     /* do we want Mime preambles/epilogues      */
int uu_tinyb64 = 0;     /* detect short B64 outside of MIME         */
int uu_remove_input = 0;        /* remove input files after decoding        */
int uu_more_mime = 0;           /* strictly adhere to MIME headers          */

headercount hlcount =
{
    3,                    /* restarting after a MIME body             */
    2,                            /* after useful data in freestyle mode      */
    1                             /* after useful data and an empty line      */
};

/*
 * version string
 */

char uulibversion[256] = VERSION "pl" PATCH;

/*
 * prefix to the files on disk, usually a path name to save files to
 */

char *uusavepath;

/*
 * extension to use when encoding single-part files
 */

char *uuencodeext;

/*
 * areas to malloc
 */

char *uulib_msgstring;
char *uugen_inbuffer;
char *uugen_fnbuffer;

/*
 * The Global List of Files
 */

uulist *UUGlobalFileList = NULL;

/*
 * time values for BusyCallback. msecs is MILLIsecs here
 */

static long uu_busy_msecs = 0;  /* call callback function each msecs */
static long uu_last_secs  = 0;  /* secs  of last call to callback */
static long uu_last_usecs = 0;  /* usecs of last call to callback */

/*
 * progress information
 */

uuprogress progress;

/*
 * Linked list of files we want to delete after decoding
 */

typedef struct _itbd
{
    char *fname;
    struct _itbd *NEXT;
} itbd;
static itbd * ftodel = NULL;

/*
 * for the busy poll
 */

unsigned long uuyctr;

/*
 * Areas to allocate. Instead of using static memory areas, we malloc()
 * the memory in UUInitialize() and release them in UUCleanUp to prevent
 * blowing up of the binary size
 * This is a table with the pointers to allocate and required sizes.
 * They are guaranteed to be never NULL.
 */

typedef struct
{
    char **ptr;
    size_t size;
} allomap;

static allomap toallocate[] =
{
    { &uugen_fnbuffer,    1024 },  /* generic filename buffer */
    { &uugen_inbuffer,    1024 },  /* generic input data buffer */
    { &uucheck_lastname,   256 },  /* from uucheck.c */
    { &uucheck_tempname,   256 },
    { &uuestr_itemp,       256 },  /* from uuencode.c:UUEncodeStream() */
    { &uuestr_otemp,      1024 },
    { &uulib_msgstring,   1024 },  /* from uulib.c:UUMessage() */
    { &uuncdl_fulline,     300 },  /* from uunconc.c:UUDecodeLine() */
    { &uuncdp_oline,      1200 },  /* from uunconc.c:UUDecodePart() */
    { &uunconc_UUxlat,     256 * sizeof (int) },  /* from uunconc.c:toplevel */
    { &uunconc_UUxlen,      64 * sizeof (int) },
    { &uunconc_B64xlat,    256 * sizeof (int) },
    { &uunconc_XXxlat,     256 * sizeof (int) },
    { &uunconc_BHxlat,     256 * sizeof (int) },
    { &uunconc_save,     3*300 },  /* from uunconc.c:decoding buffer */
    { &uuscan_shlline,    1024 },  /* from uuscan.c:ScanHeaderLine() */
    { &uuscan_pvvalue,     300 },  /* from uuscan.c:ParseValue() */
    { &uuscan_phtext,      300 },  /* from uuscan.c:ParseHeader() */
    { &uuscan_sdline,      300 },  /* from uuscan.c:ScanData() */
    { &uuscan_sdbhds1,     300 },
    { &uuscan_sdbhds2,     300 },
    { &uuscan_spline,      300 },  /* from uuscan.c:ScanPart() */
    { &uuutil_bhwtmp,      300 },  /* from uuutil.c:UUbhwrite() */
    { NULL, 0 }
};

/*
 * Handle the printing of messages. Works like printf.
 */

#if defined(STDC_HEADERS) || defined(HAVE_STDARG_H)
    int
    UUMessage (char *file, int line, int level, char *format, ...)
#else
    int
    UUMessage (va_alist)
    va_dcl
#endif
{
    char *msgptr;
#if defined(STDC_HEADERS) || defined(HAVE_STDARG_H)
    va_list ap;

    va_start (ap, format);
#else
    char *file, *format;
    int   line, level;
    va_list ap;

    va_start (ap);
    file   = va_arg (ap, char *);
    line   = va_arg (ap, int);
    level  = va_arg (ap, int);
    format = va_arg (ap, char *);
#endif

    if (uu_debug)
    {
        sprintf (uulib_msgstring, "%s(%d): %s", file, line, msgnames[level]);
        msgptr = uulib_msgstring + strlen (uulib_msgstring);
    }
    else
    {
        sprintf (uulib_msgstring, "%s", msgnames[level]);
        msgptr = uulib_msgstring + strlen (uulib_msgstring);
    }

    if (uu_MsgCallback && (level>UUMSG_NOTE || uu_verbose))
    {
        vsprintf (msgptr, format, ap);

        (*uu_MsgCallback) (uu_MsgCBArg, uulib_msgstring, level);
    }

    va_end (ap);

    return UURET_OK;
}

/*
 * Call the Busy Callback from time to time. This function must be
 * polled from the Busy loops.
 */

int
UUBusyPoll (void)
{
#ifdef HAVE_GETTIMEOFDAY
    struct timeval tv;
    long msecs;

    if (uu_BusyCallback)
    {
        (void) gettimeofday (&tv, NULL);

        msecs = 1000*(tv.tv_sec-uu_last_secs)+(tv.tv_usec-uu_last_usecs)/1000;

        if (uu_last_secs==0 || msecs > uu_busy_msecs)
        {
            uu_last_secs  = tv.tv_sec;
            uu_last_usecs = tv.tv_usec;

            return (*uu_BusyCallback) (uu_BusyCBArg, &progress);
        }
    }
#else
    time_t now;
    long msecs;

    if (uu_BusyCallback)
    {
        now = time(NULL);
        if (uu_busy_msecs <= 0)
        {
            msecs = 1;
        }
        else
        {
            msecs = 1000 * (now - uu_last_secs);
        }

        if (uu_last_secs==0 || msecs > uu_busy_msecs)
        {
            uu_last_secs  = now;
            uu_last_usecs = 0;

            return (*uu_BusyCallback) (uu_BusyCBArg, &progress);
        }
    }
#endif

    return 0;
}

/*
 * Initialization function
 */

int UUEXPORT
UUInitialize (void)
{
    allomap *aiter;

    progress.action     = 0;
    progress.curfile[0] = '\0';

    ftodel = NULL;

    uusavepath  = NULL;
    uuencodeext = NULL;

    mssdepth = 0;
    memset (&localenv, 0, sizeof (headers));
    memset (&sstate,   0, sizeof (scanstate));

    nofnum    = 0;
    mimseqno  = 0;
    lastvalid = 0;
    lastenc   = 0;
    uuyctr    = 0;

    /*
     * Allocate areas
     */

    for (aiter=toallocate; aiter->ptr; aiter++)
        *(aiter->ptr) = NULL;

    for (aiter=toallocate; aiter->ptr; aiter++)
    {
        if ((*(aiter->ptr) = (char *) malloc (aiter->size)) == NULL)
        {
            /*
             * oops. we may not print a message here, because we need these
             * areas (uulib_msgstring) in UUMessage()
             */
            for (aiter=toallocate; aiter->ptr; aiter++)
            {
                _FP_free (*(aiter->ptr));
            }
            return UURET_NOMEM;
        }
    }

    /*
     * Must be called after areas have been malloced
     */

    UUInitConc ();

    return UURET_OK;
}

/*
 * Set and get Options
 */

int UUEXPORT
UUGetOption (int option, int *ivalue, char *cvalue, int clength)
{
    int result;

    switch (option)
    {
    case UUOPT_VERSION:
        _FP_strncpy (cvalue, uulibversion, clength);
        result = 0;
        break;
    case UUOPT_FAST:
        if (ivalue) *ivalue = uu_fast_scanning;
        result = uu_fast_scanning;
        break;
    case UUOPT_DUMBNESS:
        if (ivalue) *ivalue = uu_dumbness;
        result = uu_dumbness;
        break;
    case UUOPT_BRACKPOL:
        if (ivalue) *ivalue = uu_bracket_policy;
        result = uu_bracket_policy;
        break;
    case UUOPT_VERBOSE:
        if (ivalue) *ivalue = uu_verbose;
        result = uu_verbose;
        break;
    case UUOPT_DESPERATE:
        if (ivalue) *ivalue = uu_desperate;
        result = uu_desperate;
        break;
    case UUOPT_IGNREPLY:
        if (ivalue) *ivalue = uu_ignreply;
        result = uu_ignreply;
        break;
    case UUOPT_DEBUG:
        if (ivalue) *ivalue = uu_debug;
        result = uu_debug;
        break;
    case UUOPT_ERRNO:
        if (ivalue) *ivalue = uu_errno;
        result = uu_errno;
        break;
    case UUOPT_OVERWRITE:
        if (ivalue) *ivalue = uu_overwrite;
        result = uu_overwrite;
        break;
    case UUOPT_SAVEPATH:
        _FP_strncpy (cvalue, uusavepath, clength);
        result = 0;
        break;
    case UUOPT_PROGRESS:
        if (clength==sizeof(uuprogress))
        {
            memcpy (cvalue, &progress, sizeof (uuprogress));
            result = 0;
        }
        else
            result = -1;
        break;
    case UUOPT_IGNMODE:
        if (ivalue) *ivalue = uu_ignmode;
        result = uu_ignmode;
        break;
    case UUOPT_USETEXT:
        if (ivalue) *ivalue = uu_handletext;
        result = uu_handletext;
        break;
    case UUOPT_PREAMB:
        if (ivalue) *ivalue = uu_usepreamble;
        result = uu_usepreamble;
        break;
    case UUOPT_TINYB64:
        if (ivalue) *ivalue = uu_tinyb64;
        result = uu_tinyb64;
        break;
    case UUOPT_ENCEXT:
        _FP_strncpy (cvalue, uuencodeext, clength);
        result = 0;
        break;
    case UUOPT_REMOVE:
        if (ivalue) *ivalue = uu_remove_input;
        result = uu_remove_input;
        break;
    case UUOPT_MOREMIME:
        if (ivalue) *ivalue = uu_more_mime;
        result = uu_more_mime;
        break;
    default:
        return -1;
    }
    return result;
}

int UUEXPORT
UUSetOption (int option, int ivalue, char *cvalue)
{
    switch (option)
    {
    case UUOPT_FAST:
        uu_fast_scanning  = ivalue;
        break;
    case UUOPT_DUMBNESS:
        uu_dumbness       = ivalue;
        break;
    case UUOPT_BRACKPOL:
        uu_bracket_policy = ivalue;
        break;
    case UUOPT_VERBOSE:
        uu_verbose        = ivalue;
        break;
    case UUOPT_DESPERATE:
        uu_desperate      = ivalue;
        break;
    case UUOPT_IGNREPLY:
        uu_ignreply       = ivalue;
        break;
    case UUOPT_DEBUG:
        uu_debug          = ivalue;
        break;
    case UUOPT_OVERWRITE:
        uu_overwrite      = ivalue;
        break;
    case UUOPT_SAVEPATH:
        _FP_free (uusavepath);
        uusavepath = _FP_strdup (cvalue);
        break;
    case UUOPT_IGNMODE:
        uu_ignmode = ivalue;
        break;
    case UUOPT_USETEXT:
        uu_handletext = ivalue;
        break;
    case UUOPT_PREAMB:
        uu_usepreamble = ivalue;
        break;
    case UUOPT_TINYB64:
        uu_tinyb64 = ivalue;
        break;
    case UUOPT_ENCEXT:
        _FP_free (uuencodeext);
        uuencodeext = _FP_strdup (cvalue);
        break;
    case UUOPT_REMOVE:
        uu_remove_input = ivalue;
        break;
    case UUOPT_MOREMIME:
        uu_more_mime = ivalue;
        break;
    default:
        return UURET_ILLVAL;
    }
    return UURET_OK;
}

char * UUEXPORT
UUstrerror (int code)
{
    return uuretcodes[code];
}

/*
 * Set the various Callback functions
 */

int UUEXPORT
UUSetMsgCallback (void *opaque, void (*func) (void *, char *, int))
{
    uu_MsgCallback = func;
    uu_MsgCBArg    = opaque;

    return UURET_OK;
}

int UUEXPORT
UUSetBusyCallback (void *opaque, int (*func)(void *, uuprogress *), long msecs)
{
    uu_BusyCallback = func;
    uu_BusyCBArg    = opaque;
    uu_busy_msecs   = msecs;

    return UURET_OK;
}

int UUEXPORT
UUSetFileCallback (void *opaque, int (*func)(void *, char *, char *, int))
{
    uu_FileCallback = func;
    uu_FileCBArg    = opaque;

    return UURET_OK;
}

int UUEXPORT
UUSetFNameFilter (void *opaque, char * (*func)(void *, char *))
{
    uu_FNameFilter = func;
    uu_FFCBArg     = opaque;

    return UURET_OK;
}

/*
 * Return a pointer to the nth element of the GlobalFileList
 * zero-based, returns NULL if item is too large.
 */

uulist * UUEXPORT
UUGetFileListItem (int item)
{
    uulist *iter=UUGlobalFileList;

    if (item < 0)
        return NULL;
    while (item && iter)
    {
        iter = iter->NEXT;
        item--;
    }
    return iter;
}

/*
 * call the current filter
 */

char * UUEXPORT
UUFNameFilter (char *fname)
{
    if (uu_FNameFilter)
        return (*uu_FNameFilter) (uu_FFCBArg, fname);

    return fname;
}

/*
 * Load a File. We call ScanPart repeatedly until at EOF and
 * add the parts to UUGlobalFileList
 */

int UUEXPORT
UULoadFile (char *filename, char *fileid, int delflag)
{
    int res, sr, count=0;
    struct stat finfo;
    fileread *loaded;
    uufile *fload;
    itbd *killem;
    FILE *datei;

    if ((datei = fopen (filename, "rb")) == NULL)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_OPEN_SOURCE),
                   filename, strerror (uu_errno = errno));
        return UURET_IOERR;
    }

    if (fstat (fileno(datei), &finfo) == -1)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_STAT_FILE),
                   filename, strerror (uu_errno = errno));
        fclose (datei);
        return UURET_IOERR;
    }

    /*
     * schedule for destruction
     */

    if (delflag && fileid==NULL)
    {
        if ((killem = (itbd *) malloc (sizeof (itbd))) == NULL)
        {
            UUMessage (uulib_id, __LINE__, UUMSG_WARNING,
                       uustring (S_OUT_OF_MEMORY), sizeof (itbd));
        }
        else if ((killem->fname = _FP_strdup (filename)) == NULL)
        {
            UUMessage (uulib_id, __LINE__, UUMSG_WARNING,
                       uustring (S_OUT_OF_MEMORY), strlen(filename)+1);
            _FP_free (killem);
        }
        else
        {
            killem->NEXT = ftodel;
            ftodel = killem;
        }
    }

    progress.action   = 0;
    progress.partno   = 0;
    progress.numparts = 1;
    progress.fsize    = (long) ((finfo.st_size>0)?finfo.st_size:-1);
    progress.percent  = 0;
    progress.foffset  = 0;
    _FP_strncpy (progress.curfile,
                 (strlen(filename)>255)?
                 (filename+strlen(filename)-255):filename,
                 256);
    progress.action   = UUACT_SCANNING;

    if (fileid == NULL)
        fileid = filename;

    while (!feof (datei) && !ferror (datei))
    {
        /*
         * Peek file, or some systems won't detect EOF
         */
        res = fgetc (datei);
        if (feof (datei) || ferror (datei))
            break;
        else
            ungetc (res, datei);

        if ((loaded = ScanPart (datei, fileid, &sr)) == NULL)
        {
            if (sr != UURET_NODATA && sr != UURET_OK && sr != UURET_CONT)
            {
                UUkillfread (loaded);
                if (sr != UURET_CANCEL)
                {
                    UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                               uustring (S_READ_ERROR), filename,
                               strerror (uu_errno));
                }
                UUCheckGlobalList ();
                progress.action = 0;
                fclose (datei);
                return sr;
            }
            continue;
        }

        if (ferror (datei))
        {
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_READ_ERROR), filename,
                       strerror (uu_errno = errno));
            UUCheckGlobalList ();
            progress.action = 0;
            fclose (datei);
            return UURET_IOERR;
        }

        if ((loaded->uudet == QP_ENCODED || loaded->uudet == PT_ENCODED) &&
                (loaded->filename == NULL || *(loaded->filename) == '\0') &&
                !uu_handletext && (loaded->flags&FL_PARTIAL)==0)
        {
            /*
             * Don't want text
             */
            UUkillfread (loaded);
            continue;
        }

        if ((loaded->subject == NULL || *(loaded->subject) == '\0') &&
                (loaded->mimeid  == NULL || *(loaded->mimeid)  == '\0') &&
                (loaded->filename== NULL || *(loaded->filename)== '\0') &&
                (loaded->uudet   == 0))
        {
            /*
             * no useful data here
             */
            UUkillfread (loaded);
            if (uu_fast_scanning && sr != UURET_CONT) break;
            continue;
        }

        if ((fload = UUPreProcessPart (loaded, &res)) == NULL)
        {
            /*
             * no useful data found
             */
            if (res != UURET_NODATA)
            {
                UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                           uustring (S_READ_ERROR), filename,
                           (res==UURET_IOERR)?strerror(uu_errno):UUstrerror(res));
            }
            UUkillfread (loaded);
            if (uu_fast_scanning && sr != UURET_CONT) break;
            continue;
        }

        if ((loaded->subject && *(loaded->subject)) ||
                (loaded->mimeid  && *(loaded->mimeid))  ||
                (loaded->filename&& *(loaded->filename))||
                (loaded->uudet))
        {
            UUMessage (uulib_id, __LINE__, UUMSG_MESSAGE,
                       uustring (S_LOADED_PART),
                       filename,
                       (loaded->subject)  ? loaded->subject  : "",
                       (fload->subfname)  ? fload->subfname  : "",
                       (loaded->filename) ? loaded->filename : "",
                       fload->partno,
                       (loaded->begin)    ? "begin" : "",
                       (loaded->end)      ? "end"   : "",
                       codenames[loaded->uudet]);
        }

        if ((res = UUInsertPartToList (fload)))
        {
            /*
             * couldn't use the data
             */
            UUkillfile (fload);

            if (res != UURET_NODATA)
            {
                UUCheckGlobalList ();
                progress.action = 0;
                fclose (datei);
                return res;
            }
            if (uu_fast_scanning && sr != UURET_CONT)
                break;

            continue;
        }

        /*
         * if in fast mode, we don't look any further, because we're told
         * that each source file holds at most one encoded part
         */

        if (uu_fast_scanning && sr != UURET_CONT)
            break;

        if (loaded->uudet)
            count++;
    }
    fclose (datei);

    if (!uu_fast_scanning && count==0)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_NOTE,
                   uustring (S_NO_DATA_FOUND), filename);
    }

    progress.action = 0;
    UUCheckGlobalList ();

    return UURET_OK;
}

/*
 * decode to a temporary file. this is well handled by uudecode()
 */

int UUEXPORT
UUDecodeToTemp (uulist *thefile)
{
    return UUDecode (thefile);
}

/*
 * decode file first to temp file, then copy it to a final location
 */

int UUEXPORT
UUDecodeFile (uulist *thefile, char *destname)
{
    FILE *target, *source;
    struct stat finfo;
    int fildes, res;
    size_t bytes;

    if (thefile == NULL)
        return UURET_ILLVAL;

    if ((res = UUDecode (thefile)) != UURET_OK)
        if (res != UURET_NOEND || !uu_desperate)
            return res;

    if (thefile->binfile == NULL)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NO_BIN_FILE));
        return UURET_IOERR;
    }

    if ((source = fopen (thefile->binfile, "rb")) == NULL)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_OPEN_FILE),
                   thefile->binfile, strerror (uu_errno = errno));
        return UURET_IOERR;
    }

    /*
     * for system security, strip setuid/setgid bits from mode
     */

    if ((thefile->mode & 0777) != thefile->mode)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_NOTE,
                   uustring (S_STRIPPED_SETUID),
                   destname, (int)thefile->mode);
        thefile->mode &= 0777;
    }

    /*
     * Determine the name of the target file according to the rules:
     *
     * IF (destname!=NULL) THEN filename=destname;
     * ELSE
     *   filename = thefile->filename
     *   IF (FilenameFilter!=NULL) THEN filename=FilenameFilter(filename);
     *   filename = SaveFilePath + filename
     * END
     */

    if (destname)
        strcpy (uugen_fnbuffer, destname);
    else
    {
        char unknownfn[] = "unknown.xxx";
        sprintf(uugen_fnbuffer, "%s%s", (uusavepath) ? uusavepath : "",
                UUFNameFilter(thefile->filename ? thefile->filename : unknownfn));
    }

    /*
     * if we don't want to overwrite existing files, check if it's there
     */

    if (!uu_overwrite)
    {
        if (stat (uugen_fnbuffer, &finfo) == 0)
        {
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_TARGET_EXISTS), uugen_fnbuffer);
            fclose (source);
            return UURET_EXISTS;
        }
    }

    if (fstat (fileno(source), &finfo) == -1)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_STAT_FILE),
                   thefile->binfile, strerror (uu_errno = errno));
        fclose (source);
        return UURET_IOERR;
    }

    progress.action   = 0;
    _FP_strncpy (progress.curfile,
                 (strlen(uugen_fnbuffer)>255)?
                 (uugen_fnbuffer+strlen(uugen_fnbuffer)-255):uugen_fnbuffer,
                 256);
    progress.partno   = 0;
    progress.numparts = 1;
    progress.fsize    = (long) ((finfo.st_size)?finfo.st_size:-1);
    progress.foffset  = 0;
    progress.percent  = 0;
    progress.action   = UUACT_COPYING;

    if ((fildes = open (uugen_fnbuffer,
                        O_WRONLY | O_CREAT | O_BINARY | O_TRUNC,
                        (uu_ignmode)?0666:thefile->mode)) == -1)
    {
        progress.action = 0;
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_OPEN_TARGET),
                   uugen_fnbuffer, strerror (uu_errno = errno));
        fclose (source);
        return UURET_IOERR;
    }

    if ((target = fdopen (fildes, "wb")) == NULL)
    {
        progress.action = 0;
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_IO_ERR_TARGET),
                   uugen_fnbuffer, strerror (uu_errno = errno));
        fclose (source);
        close  (fildes);
        return UURET_IOERR;
    }

    while (!feof (source))
    {

        if (UUBUSYPOLL(ftell(source),progress.fsize))
        {
            UUMessage (uulib_id, __LINE__, UUMSG_NOTE,
                       uustring (S_DECODE_CANCEL));
            fclose (source);
            fclose (target);
            unlink (uugen_fnbuffer);
            return UURET_CANCEL;
        }

        bytes = fread (uugen_inbuffer, 1, 1024, source);

        if (ferror (source) || (bytes == 0 && !feof (source)))
        {
            progress.action = 0;
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_READ_ERROR),
                       thefile->binfile, strerror (uu_errno = errno));
            fclose (source);
            fclose (target);
            unlink (uugen_fnbuffer);
            return UURET_IOERR;
        }
        if (fwrite (uugen_inbuffer, 1, bytes, target) != bytes)
        {
            progress.action = 0;
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_WR_ERR_TARGET),
                       uugen_fnbuffer, strerror (uu_errno = errno));
            fclose (source);
            fclose (target);
            unlink (uugen_fnbuffer);
            return UURET_IOERR;
        }
    }

    fclose (target);
    fclose (source);

    /*
     * after a successful decoding run, we delete the temporary file
     */

    if (unlink (thefile->binfile))
    {
        UUMessage (uulib_id, __LINE__, UUMSG_WARNING,
                   uustring (S_TMP_NOT_REMOVED),
                   thefile->binfile,
                   strerror (uu_errno = errno));
    }
    _FP_free (thefile->binfile);
    thefile->binfile = NULL;
    thefile->state  &= ~UUFILE_TMPFILE;
    thefile->state  |=  UUFILE_DECODED;
    progress.action  = 0;

    return UURET_OK;
}

/*
 * Calls a function repeatedly with all the info we have for a file
 * If the function returns non-zero, we break and don't send any more
 */

int UUEXPORT
UUInfoFile (uulist *thefile, void *opaque, int (*func)(void *, char *))
{
    int errflag=0, res, bhflag=0, dd;
    long maxpos;
    FILE *inpfile;

    /*
     * We might need to ask our callback function to download the file
     */

    if (uu_FileCallback)
    {
        if ((res = (*uu_FileCallback) (uu_FileCBArg,
                                       thefile->thisfile->data->sfname,
                                       uugen_fnbuffer,
                                       1)) != UURET_OK)
            return res;
        if ((inpfile = fopen (uugen_fnbuffer, "rb")) == NULL)
        {
            (*uu_FileCallback) (uu_FileCBArg, thefile->thisfile->data->sfname,
                                uugen_fnbuffer, 0);
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_FILE), uugen_fnbuffer,
                       strerror (uu_errno = errno));
            return UURET_IOERR;
        }
    }
    else
    {
        if ((inpfile = fopen (thefile->thisfile->data->sfname, "rb")) == NULL)
        {
            UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_FILE),
                       thefile->thisfile->data->sfname,
                       strerror (uu_errno=errno));
            return UURET_IOERR;
        }
        _FP_strncpy (uugen_fnbuffer, thefile->thisfile->data->sfname, 1024);
    }

    /*
     * seek to beginning of info
     */

    fseek (inpfile, thefile->thisfile->data->startpos, SEEK_SET);
    maxpos = thefile->thisfile->data->startpos + thefile->thisfile->data->length;

    while (!feof (inpfile) &&
            (uu_fast_scanning || ftell(inpfile) < maxpos))
    {
        if (_FP_fgets (uugen_inbuffer, 511, inpfile) == NULL)
            break;
        uugen_inbuffer[511] = '\0';

        if (ferror (inpfile))
            break;

        dd = UUValidData (uugen_inbuffer, 0, &bhflag);

        if (thefile->uudet == B64ENCODED && dd == B64ENCODED)
            break;
        else if (thefile->uudet == BH_ENCODED && bhflag)
            break;
        else if ((thefile->uudet == UU_ENCODED || thefile->uudet == XX_ENCODED) &&
                 strncmp (uugen_inbuffer, "begin ", 6) == 0)
            break;
        else if (thefile->uudet == YENC_ENCODED &&
                 strncmp (uugen_inbuffer, "=ybegin ", 8) == 0)
            break;

        if ((*func) (opaque, uugen_inbuffer))
            break;
    }

    if (ferror (inpfile))
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_READ_ERROR),
                   uugen_fnbuffer, strerror (uu_errno = errno));
        errflag = 1;
    }

    fclose (inpfile);

    if (uu_FileCallback)
        (*uu_FileCallback) (uu_FileCBArg,
                            thefile->thisfile->data->sfname,
                            uugen_fnbuffer, 0);

    if (errflag)
        return UURET_IOERR;

    return UURET_OK;
}

int UUEXPORT
UURenameFile (uulist *thefile, char *newname)
{
    char *oldname;

    if (thefile == NULL)
        return UURET_ILLVAL;

    oldname = thefile->filename;

    if ((thefile->filename = _FP_strdup (newname)) == NULL)
    {
        UUMessage (uulib_id, __LINE__, UUMSG_ERROR,
                   uustring (S_NOT_RENAME),
                   oldname, newname);
        thefile->filename = oldname;
        return UURET_NOMEM;
    }
    _FP_free (oldname);
    return UURET_OK;
}

int UUEXPORT
UURemoveTemp (uulist *thefile)
{
    if (thefile == NULL)
        return UURET_ILLVAL;

    if (thefile->binfile)
    {
        if (unlink (thefile->binfile))
        {
            UUMessage (uulib_id, __LINE__, UUMSG_WARNING,
                       uustring (S_TMP_NOT_REMOVED),
                       thefile->binfile,
                       strerror (uu_errno = errno));
        }
        _FP_free (thefile->binfile);
        thefile->binfile = NULL;
        thefile->state  &= ~UUFILE_TMPFILE;
    }
    return UURET_OK;
}

int UUEXPORT
UUCleanUp (void)
{
    itbd *iter=ftodel, *ptr;
    uulist *liter;
    uufile *fiter;
    allomap *aiter;

    /*
     * delete temporary input files (such as the copy of stdin)
     */

    while (iter)
    {
        if (unlink (iter->fname))
        {
            UUMessage (uulib_id, __LINE__, UUMSG_WARNING,
                       uustring (S_TMP_NOT_REMOVED),
                       iter->fname, strerror (uu_errno = errno));
        }
        _FP_free (iter->fname);
        ptr  = iter;
        iter = iter->NEXT;
        _FP_free (ptr);
    }

    ftodel = NULL;

    /*
     * Delete input files after successful decoding
     */

    if (uu_remove_input)
    {
        liter = UUGlobalFileList;
        while (liter)
        {
            if (liter->state & UUFILE_DECODED)
            {
                fiter = liter->thisfile;
                while (fiter)
                {
                    if (fiter->data && fiter->data->sfname)
                    {
                        /*
                         * Error code ignored. We might want to delete a file multiple
                         * times
                         */
                        unlink (fiter->data->sfname);
                    }
                    fiter = fiter->NEXT;
                }
            }
            liter = liter->NEXT;
        }
    }

    UUkilllist (UUGlobalFileList);
    UUGlobalFileList = NULL;

    _FP_free (uusavepath);
    _FP_free (uuencodeext);
    _FP_free (sstate.source);

    uusavepath  = NULL;
    uuencodeext = NULL;

    UUkillheaders (&localenv);
    UUkillheaders (&sstate.envelope);
    memset (&localenv, 0, sizeof (headers));
    memset (&sstate,   0, sizeof (scanstate));

    while (mssdepth)
    {
        mssdepth--;
        UUkillheaders (&(multistack[mssdepth].envelope));
        _FP_free (multistack[mssdepth].source);
    }

    /*
     * clean up the malloc'ed stuff
     */

    for (aiter=toallocate; aiter->ptr; aiter++)
    {
        _FP_free (*(aiter->ptr));
        *(aiter->ptr) = NULL;
    }

    return UURET_OK;
}
