/*
 * tools.c
 *
 * This file is part of msmtp, an SMTP client.
 *
 * Copyright (C) 2004, 2005, 2006, 2007, 2009
 * Martin Lambers <marlam@marlam.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 3 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, see <http://www.gnu.org/licenses/>.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#ifdef W32_NATIVE
# include <windows.h>
# include <io.h>
# include <lmcons.h>
# include <sys/locking.h>
# include <limits.h>
#else
# include <pwd.h>
#endif

#include "gettext.h"
#include "xalloc.h"

#include "tools.h"


/*
 * get_prgname()
 *
 * see tools.h
 */

const char *get_prgname(const char *argv0)
{
    const char *prgname;

    if (argv0)
    {
        prgname = strrchr(argv0, PATH_SEP);
        if (!prgname)
        {
            prgname = argv0;
        }
        else
        {
            prgname++;
        }
    }
    else
    {
        prgname = "";
    }

    return prgname;
}


/*
 * get_sysconfdir()
 *
 * see tools.h
 */

char *get_sysconfdir(void)
{
#ifdef W32_NATIVE

    BYTE sysconfdir[MAX_PATH + 1];
    HKEY hkey;
    DWORD len;
    DWORD type;
    long l;

    l = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
            "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"
            "Shell Folders", 0, KEY_READ, &hkey);
    if (l != ERROR_SUCCESS)
    {
        return xstrdup("C:");
    }
    len = MAX_PATH;
    l = RegQueryValueEx(hkey, "Common AppData", NULL, &type, sysconfdir, &len);
    if (l != ERROR_SUCCESS || len >= MAX_PATH)
    {
        if (l != ERROR_SUCCESS || len >= MAX_PATH)
        {
            return xstrdup("C:");
        }
    }
    RegCloseKey(hkey);
    return xstrdup((char *)sysconfdir);

#else /* UNIX or DJGPP */

#ifdef SYSCONFDIR
    return xstrdup(SYSCONFDIR);
#else
    return xstrdup("/etc");
#endif

#endif
}


/*
 * get_username()
 *
 * see tools.h
 */

char *get_username(void)
{
    char *username;
#ifdef W32_NATIVE
    DWORD size = UNLEN + 1;
    TCHAR buf[UNLEN + 1];
#elif defined DJGPP
#else /* UNIX */
    struct passwd *pw;
#endif

    username = getenv("USER");
    if (username)
    {
        username = xstrdup(username);
    }
    else
    {
        username = getenv("LOGNAME");
        if (username)
        {
            username = xstrdup(username);
        }
        else
        {
#ifdef W32_NATIVE
            if (GetUserName(buf, &size))
            {
                username = xstrdup((char *)buf);
            }
            else
            {
                /* last resort */
                username = xstrdup("unknown");
            }
#elif defined DJGPP
            /* DJGPP's getlogin() checks USER, then LOGNAME, and then uses
             * "dosuser" as a last resort. We already checked USER and LOGNAME
             * and choose "unknown" as a last resort to be consistent with the
             * other systems. */
            username = xstrdup("unknown");
#else /* UNIX */
            username = getlogin();
            if (username)
            {
                username = xstrdup(username);
            }
            else
            {
                pw = getpwuid(getuid());
                if (pw && pw->pw_name)
                {
                    username = xstrdup(pw->pw_name);
                }
                else
                {
                    /* last resort */
                    username = xstrdup("unknown");
                }
            }
#endif
        }
    }

    return username;
}


/*
 * get_homedir()
 *
 * see tools.h
 */

char *get_homedir(void)
{
#ifdef W32_NATIVE

    char *home;
    BYTE homebuf[MAX_PATH + 1];
    HKEY hkey;
    DWORD len;
    DWORD type;
    long l;

    if ((home = getenv("HOME")))
    {
        home = xstrdup(home);
    }
    else
    {
        home = NULL;
        l = RegOpenKeyEx(HKEY_CURRENT_USER,
                "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"
                "Shell Folders", 0, KEY_READ, &hkey);
        if (l == ERROR_SUCCESS)
        {
            len = MAX_PATH;
            l = RegQueryValueEx(hkey, "AppData", NULL, &type, homebuf, &len);
            if (l == ERROR_SUCCESS && len < MAX_PATH)
            {
                RegCloseKey(hkey);
                home = xstrdup((char *)homebuf);
            }
        }
        if (!home)
        {
            home = xstrdup("C:");
        }
    }

    return home;

#elif defined DJGPP

    char *home;

    if ((home = getenv("HOME")))
    {
        home = xstrdup(home);
    }
    else
    {
        home = xstrdup("C:");
    }

    return home;

#else /* UNIX */

    char *home;
    struct passwd *pw;

    if ((home = getenv("HOME")))
    {
        home = xstrdup(home);
    }
    else
    {
        pw = getpwuid(getuid());
        if (pw && pw->pw_dir)
        {
            home = xstrdup(pw->pw_dir);
        }
        else
        {
            home = xstrdup("");
        }
    }

    return home;

#endif
}


/*
 * get_filename()
 *
 * see tools.h
 */

char *get_filename(const char *directory, const char *name)
{
    char *path;
    size_t dirlen;

    dirlen = strlen(directory);
    path = xmalloc((dirlen + strlen(name) + 2) * sizeof(char));
    strcpy(path, directory);
    if (dirlen == 0 || path[dirlen - 1] != PATH_SEP)
    {
        path[dirlen++] = PATH_SEP;
    }
    strcpy(path + dirlen, name);

    return path;
}


/*
 * expand_tilde()
 *
 * see tools.h
 */

char *expand_tilde(const char *filename)
{
    char *new_filename;
    size_t homedirlen;

    if (filename[0] == '~')
    {
        new_filename = get_homedir();
        homedirlen = strlen(new_filename);
        new_filename = xrealloc(new_filename,
                (homedirlen + strlen(filename)) * sizeof(char));
        strcpy(new_filename + homedirlen, filename + 1);
        return new_filename;
    }
    else
    {
        return xstrdup(filename);
    }
}


/*
 * check_secure()
 *
 * see tools.h
 */

int check_secure(const char *pathname)
{
#if defined W32_NATIVE || defined DJGPP || defined __CYGWIN__

    return 0;

#else /* UNIX */

    struct stat statbuf;

    if (stat(pathname, &statbuf) < 0)
    {
        return 3;
    }

    if (statbuf.st_uid != geteuid())
    {
        return 1;
    }
    if (statbuf.st_mode & (S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP
                | S_IROTH | S_IWOTH | S_IXOTH))
    {
        return 2;
    }

    return 0;

#endif /* UNIX */
}


/*
 * [DJGPP and Windows only] mkstemp_unlink()
 *
 * This function does on DOS/Windows what mkstemp() followed by unlink() do on
 * UNIX.
 *
 * 1. unlink() on DOS and Windows is not POSIX conformant: it does not wait
 *    until the last file descriptor is closed before unlinking the file.
 *    Instead, it fails (Windows) or may even mess up the file system (DOS).
 * 2. Windows does not have mkstemp.
 * 3. If a file is opened with O_TEMPORARY on Windows or DOS, it will be deleted
 *    after the last file descriptor is closed. This is what this function does.
 *
 * Return value: file descriptor, or -1 on error (errno will be set).
 */

#if defined(W32_NATIVE) || defined(DJGPP)
int mkstemp_unlink(char *template)
{
    size_t templatelen;
    char *X;
    int i;
    int try;
    int ret;
    const char alnum[]
        = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    templatelen = strlen(template);
    if (templatelen < 6)
    {
        errno = EINVAL;
        return -1;
    }
    X = template + templatelen - 6;
    if (strcmp(X, "XXXXXX") != 0)
    {
        errno = EINVAL;
        return -1;
    }

    srand((unsigned int)time(NULL));

    /* We have 62^6 possible filenames. We try 62^2=3844 times. */
    ret = -1;
    for (try = 0; ret == -1 && try < 3844; try++)
    {
        for (i = 0; i < 6; i++)
        {
            X[i] = alnum[rand() % 36];
        }
#ifdef W32_NATIVE
        ret = _open(template, _O_CREAT | _O_EXCL | _O_RDWR
                | _O_TEMPORARY | _O_BINARY,
                _S_IREAD | _S_IWRITE);
#else /* DJGPP */
        ret = open(template, O_CREAT | O_EXCL | O_RDWR
                | O_TEMPORARY | _O_BINARY,
                S_IRUSR | S_IWUSR);
#endif /* DJGPP */
    }

    return ret;
}
#endif /* W32_NATIVE or DJGPP */


/*
 * tempfile()
 *
 * see tools.h
 */

FILE *tempfile(const char *base)
{
    FILE *f;
    size_t baselen;
    const char *dir;
    size_t dirlen;
    char *template = NULL;
    size_t templatelen;
    int fd = -1;
    int saved_errno;

    if (!base || (*base == '\0'))
    {
        base = "tmp";
    }
    /* the directory for the temp file */
    if (!(dir = getenv("TMPDIR")))
    {
        /* system dependent default location */
#ifdef W32_NATIVE
        /* there is no registry key for this (?) */
        if (!(dir = getenv("TEMP")))
        {
            if (!(dir = getenv("TMP")))
            {
                dir = "C:";
            }
        }
#elif defined DJGPP
        dir = "C:";
#else /* UNIX */
#ifdef P_tmpdir
        dir = P_tmpdir;
#else
        dir = "/tmp";
#endif
#endif /* UNIX */
    }
    dirlen = strlen(dir);

    /* the proposed file name */
    baselen = strlen(base);
#ifdef DJGPP
    /* shorten the base to two characters because of 8.3 filenames */
    if (baselen > 2)
    {
        baselen = 2;
    }
#endif

    /* build the template */
    templatelen = dirlen + 1 + baselen + 6;
    template = xmalloc((templatelen + 1) * sizeof(char));
    strncpy(template, dir, dirlen);
    if (dirlen == 0 || template[dirlen - 1] != PATH_SEP)
    {
        template[dirlen++] = PATH_SEP;
    }
    /* template is long enough */
    strncpy(template + dirlen, base, baselen);
    strcpy(template + dirlen + baselen, "XXXXXX");

    /* create the file */
#if defined(W32_NATIVE) || defined(DJGPP)
    if ((fd = mkstemp_unlink(template)) == -1)
#else /* UNIX */
    if ((fd = mkstemp(template)) == -1)
#endif /* UNIX */
    {
        goto error_exit;
    }

    /* UNIX only: set the permissions (not every mkstemp() sets them to 0600)
     * and unlink the file so that it gets deleted when the caller closes it */
#ifndef DJGPP
#ifndef W32_NATIVE
    if (fchmod(fd, S_IRUSR | S_IWUSR) == -1)
    {
        goto error_exit;
    }
    if (unlink(template) != 0)
    {
        goto error_exit;
    }
#endif /* not W32_NATIVE */
#endif /* not DJGPP */

    /* get the stream from the filedescriptor */
    if (!(f = fdopen(fd, "w+")))
    {
        goto error_exit;
    }
    free(template);

    return f;

error_exit:
    saved_errno = errno;
    if (fd >= 0)
    {
        close(fd);
    }
    if (template)
    {
        (void)remove(template);
        free(template);
    }
    errno = saved_errno;
    return NULL;
}


/*
 * lock_file()
 *
 * see tools.h
 */

int lock_file(FILE *f, int lock_type, int timeout)
{
    int fd;
    int lock_success;
    int tenth_seconds;
    struct timespec tenth_second = { 0, 100000000 };
#ifndef W32_NATIVE
    struct flock lock;
#endif /* not W32_NATIVE */

    fd = fileno(f);
#ifndef W32_NATIVE
    lock.l_type = (lock_type == TOOLS_LOCK_WRITE) ? F_WRLCK : F_RDLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
#endif /* not W32_NATIVE */
    tenth_seconds = 0;
    for (;;)
    {
        errno = 0;
#ifdef W32_NATIVE
        lock_success = (_locking(fd, _LK_NBLCK, LONG_MAX) != -1);
#else /* UNIX, DJGPP */
        lock_success = (fcntl(fd, F_SETLK, &lock) != -1);
#endif
        if (lock_success || (errno != EACCES && errno != EAGAIN)
            || tenth_seconds / 10 >= timeout)
        {
            break;
        }
        else
        {
            nanosleep(&tenth_second, NULL);
            tenth_seconds++;
        }
    }
    return (lock_success ? 0 : (tenth_seconds / 10 >= timeout ? 1 : 2));
}


/*
 * string_replace()
 *
 * see tools.h
 */

char *string_replace(char *str, const char *s, const char *r)
{
    char *p, *new_str;
    size_t next_pos = 0;
    size_t slen = strlen(s);
    size_t rlen = strlen(r);

    while ((p = strstr(str + next_pos, s)))
    {
        new_str = xmalloc((strlen(str) + rlen - 1) * sizeof(char));
        strncpy(new_str, str, (size_t)(p - str));
        strcpy(new_str + (size_t)(p - str), r);
        strcpy(new_str + (size_t)(p - str) + rlen,
                str + (size_t)(p - str) + slen);
        next_pos = (size_t)(p - str) + rlen;
        free(str);
        str = new_str;
    }
    return str;
}
