view src/secure_save.c @ 1743:d8e9d0cc640e

Allow to switch to fullscreen mode using LIRC. Imagine the following situation (which happened to me several times) : you want to see photos from your bed or your sofa so you launch geeqie and go to the right directory, then you take your remote control and sit comfortably far from your keyboard and mouse. And when you want to begin to watch photos, you realize you forgot to enable full screen! You have to stand up and to go until your computer and come back, whereas you could have done it with your remote control. Patch by Bernard Massot.
author zas_
date Tue, 05 Jan 2010 17:49:50 +0000
parents a6f9ba6fd751
children
line wrap: on
line source

/*
 * Geeqie
 * Copyright (C) 2008 - 2009 The Geeqie Team
 *
 * based on the code developped for ELinks by Laurent Monin
 *
 * This software is released under the GNU General Public License (GNU GPL).
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at your own risk!
 */

#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <utime.h>

#include "main.h"
#include "secure_save.h"


/* ABOUT SECURE SAVE */
/* This code was borrowed from the ELinks project (http://elinks.cz)
 * It was originally written by me (Laurent Monin aka Zas) and heavily
 * modified and improved by all ELinks contributors.
 * This code was released under the GPLv2 licence.
 * It was modified to be included in geeqie on 2008/04/05 */

/* If ssi->secure_save is TRUE:
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * A call to secure_open("/home/me/.confdir/filename", mask) will open a file
 * named "filename.tmp_XXXXXX" in /home/me/.confdir/ and return a pointer to a
 * structure SecureSaveInfo on success or NULL on error.
 *
 * filename.tmp_XXXXXX can't conflict with any file since it's created using
 * mkstemp(). XXXXXX is a random string.
 *
 * Subsequent write operations are done using returned SecureSaveInfo FILE *
 * field named fp.
 *
 * If an error is encountered, SecureSaveInfo int field named err is set
 * (automatically if using secure_fp*() functions or by programmer)
 *
 * When secure_close() is called, "filename.tmp_XXXXXX" is flushed and closed,
 * and if SecureSaveInfo err field has a value of zero, "filename.tmp_XXXXXX"
 * is renamed to "filename". If this succeeded, then secure_close() returns 0.
 *
 * WARNING: since rename() is used, any symlink called "filename" may be
 * replaced by a regular file. If destination file isn't a regular file,
 * then secsave is disabled for that file.
 *
 * If ssi->secure_save is FALSE:
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * No temporary file is created, "filename" is truncated, all operations are
 * done on it, no rename nor flush occur, symlinks are preserved.
 *
 * In both cases:
 * ~~~~~~~~~~~~~
 *
 * Access rights are affected by secure_open() mask parameter.
 */

/* FIXME: locking system on files about to be rewritten ? */
/* FIXME: Low risk race conditions about ssi->file_name. */

SecureSaveErrno secsave_errno = SS_ERR_NONE;


/** Open a file for writing in a secure way. @returns a pointer to a
 * structure secure_save_info on success, or NULL on failure. */
static SecureSaveInfo *
secure_open_umask(const gchar *file_name)
{
	struct stat st;
	SecureSaveInfo *ssi;

	secsave_errno = SS_ERR_NONE;

	ssi = g_new0(SecureSaveInfo, 1);
	if (!ssi) {
		secsave_errno = SS_ERR_OUT_OF_MEM;
		goto end;
	}

	ssi->secure_save = TRUE;
	ssi->preserve_perms = TRUE;
	ssi->unlink_on_error = TRUE;

	ssi->file_name = g_strdup(file_name);
	if (!ssi->file_name) {
		secsave_errno = SS_ERR_OUT_OF_MEM;
		goto free_f;
	}

	/* Check properties of final file. */
#ifndef NO_UNIX_SOFTLINKS
	if (lstat(ssi->file_name, &st)) {
#else
	if (stat(ssi->file_name, &st)) {
#endif
		/* We ignore error caused by file inexistence. */
		if (errno != ENOENT) {
			/* lstat() error. */
			ssi->err = errno;
			secsave_errno = SS_ERR_STAT;
			goto free_file_name;
		}
	} else {
		if (!S_ISREG(st.st_mode)) {
			/* Not a regular file, secure_save is disabled. */
			ssi->secure_save = FALSE;
		} else {
#ifdef HAVE_ACCESS
			/* XXX: access() do not work with setuid programs. */
			if (access(ssi->file_name, R_OK | W_OK) < 0) {
				ssi->err = errno;
				secsave_errno = SS_ERR_ACCESS;
				goto free_file_name;
			}
#else
			FILE *f1;

			/* We still have a race condition here between
			 * [l]stat() and fopen() */

			f1 = fopen(ssi->file_name, "rb+");
			if (f1) {
				fclose(f1);
			} else {
				ssi->err = errno;
				secsave_errno = SS_ERR_OPEN_READ;
				goto free_file_name;
			}
#endif
		}
	}

	if (ssi->secure_save) {
		/* We use a random name for temporary file, mkstemp() opens
		 * the file and return a file descriptor named fd, which is
		 * then converted to FILE * using fdopen().
		 */
		gint fd;
		gchar *randname = g_strconcat(ssi->file_name, ".tmp_XXXXXX", NULL);

		if (!randname) {
			secsave_errno = SS_ERR_OUT_OF_MEM;
			goto free_file_name;
		}

		/* No need to use safe_mkstemp() here. --Zas */
		fd = g_mkstemp(randname);
		if (fd == -1) {
			secsave_errno = SS_ERR_MKSTEMP;
			g_free(randname);
			goto free_file_name;
		}

		ssi->fp = fdopen(fd, "wb");
		if (!ssi->fp) {
			secsave_errno = SS_ERR_OPEN_WRITE;
			ssi->err = errno;
			g_free(randname);
			goto free_file_name;
		}

		ssi->tmp_file_name = randname;
	} else {
		/* No need to create a temporary file here. */
		ssi->fp = fopen(ssi->file_name, "wb");
		if (!ssi->fp) {
			secsave_errno = SS_ERR_OPEN_WRITE;
			ssi->err = errno;
			goto free_file_name;
		}
	}

	return ssi;

free_file_name:
	g_free(ssi->file_name);
	ssi->file_name = NULL;

free_f:
	g_free(ssi);
	ssi = NULL;

end:
	return NULL;
}

SecureSaveInfo *
secure_open(const gchar *file_name)
{
	SecureSaveInfo *ssi;
	mode_t saved_mask;
#ifdef CONFIG_OS_WIN32
	/* There is neither S_IRWXG nor S_IRWXO under crossmingw32-gcc */
	const mode_t mask = 0177;
#else
	const mode_t mask = S_IXUSR | S_IRWXG | S_IRWXO;
#endif

	saved_mask = umask(mask);
	ssi = secure_open_umask(file_name);
	umask(saved_mask);

	return ssi;
}

/** Close a file opened with secure_open(). Rreturns 0 on success,
 * errno or -1 on failure.
 */
gint
secure_close(SecureSaveInfo *ssi)
{
	gint ret = -1;

	if (!ssi) return ret;
	if (!ssi->fp) goto free;

	if (ssi->err) {	/* Keep previous errno. */
		ret = ssi->err;
		fclose(ssi->fp); /* Close file */
		goto free;
	}

	/* Ensure data is effectively written to disk, we first flush libc buffers
	 * using fflush(), then fsync() to flush kernel buffers, and finally call
	 * fclose() (which call fflush() again, but the first one is needed since
	 * it doesn't make much sense to flush kernel buffers and then libc buffers,
	 * while closing file releases file descriptor we need to call fsync(). */
#if defined(HAVE_FFLUSH) || defined(HAVE_FSYNC)
	if (ssi->secure_save) {
		gboolean fail = FALSE;

#ifdef HAVE_FFLUSH
		fail = (fflush(ssi->fp) == EOF);
#endif

#ifdef HAVE_FSYNC
		if (!fail) fail = fsync(fileno(ssi->fp));
#endif

		if (fail) {
			ret = errno;
			secsave_errno = SS_ERR_OTHER;

			fclose(ssi->fp); /* Close file, ignore errors. */
			goto free;
		}
	}
#endif

	/* Close file. */
	if (fclose(ssi->fp) == EOF) {
		ret = errno;
		secsave_errno = SS_ERR_OTHER;
		goto free;
	}

	if (ssi->secure_save && ssi->file_name && ssi->tmp_file_name) {
		struct stat st;

		/* FIXME: Race condition on ssi->file_name. The file
		 * named ssi->file_name may have changed since
		 * secure_open() call (where we stat() file and
		 * more..).  */
#ifndef NO_UNIX_SOFTLINKS
		if (lstat(ssi->file_name, &st) == 0)
#else
		if (stat(ssi->file_name, &st) == 0)
#endif
			{
			/* set the dest file attributes to that of source (ignoring errors) */
			if (ssi->preserve_perms)
				{
				if (chown(ssi->tmp_file_name, st.st_uid, st.st_gid) != 0) log_printf("chown('%s', %d, %d) failed", ssi->tmp_file_name, st.st_uid, st.st_gid);
				if (chmod(ssi->tmp_file_name, st.st_mode) != 0) log_printf("chmod('%s', %o) failed", ssi->tmp_file_name, st.st_mode);
				}

			if (ssi->preserve_mtime)
				{
				struct utimbuf tb;

				tb.actime = st.st_atime;
				tb.modtime = st.st_mtime;
				utime(ssi->tmp_file_name, &tb);
				}
			}
		DEBUG_3("rename %s -> %s", ssi->tmp_file_name, ssi->file_name);
		if (rename(ssi->tmp_file_name, ssi->file_name) == -1) {
			ret = errno;
			secsave_errno = SS_ERR_RENAME;
			goto free;
		}
	}

	ret = 0;	/* Success. */

free:
	if (ssi->tmp_file_name)
		{
		if (ret && ssi->unlink_on_error) unlink(ssi->tmp_file_name);
		g_free(ssi->tmp_file_name);
		}
	if (ssi->file_name) g_free(ssi->file_name);
	if (ssi) g_free(ssi);

	return ret;
}


/** fputs() wrapper, set ssi->err to errno on error. If ssi->err is set when
 * called, it immediatly returns EOF.
 */
gint
secure_fputs(SecureSaveInfo *ssi, const gchar *s)
{
	gint ret;

	if (!ssi || !ssi->fp || ssi->err) return EOF;

	ret = fputs(s, ssi->fp);
	if (ret == EOF) {
		secsave_errno = SS_ERR_OTHER;
		ssi->err = errno;
	}

	return ret;
}


/** fputc() wrapper, set ssi->err to errno on error. If ssi->err is set when
 * called, it immediatly returns EOF.
 */
gint
secure_fputc(SecureSaveInfo *ssi, gint c)
{
	gint ret;

	if (!ssi || !ssi->fp || ssi->err) return EOF;

	ret = fputc(c, ssi->fp);
	if (ret == EOF) {
		ssi->err = errno;
		secsave_errno = SS_ERR_OTHER;
	}

	return ret;
}

/** fprintf() wrapper, set ssi->err to errno on error and return a negative
 * value. If ssi->err is set when called, it immediatly returns -1.
 */
gint
secure_fprintf(SecureSaveInfo *ssi, const gchar *format, ...)
{
	va_list ap;
	gint ret;

	if (!ssi || !ssi->fp || ssi->err) return -1;

	va_start(ap, format);
	ret = g_vfprintf(ssi->fp, format, ap);
	va_end(ap);

	return ret;
}

/** fwrite() wrapper, set ssi->err to errno on error and return a value less than
 * the number of elements to write. If ssi->err is set when called, it immediatly returns 0.
 */
size_t
secure_fwrite(gconstpointer ptr, size_t size, size_t nmemb, SecureSaveInfo *ssi)
{
	size_t ret;

	if (!ssi || !ssi->fp || ssi->err) return 0;

	ret = fwrite(ptr, size, nmemb, ssi->fp);
	if (ret < nmemb)
		{
		ssi->err = errno;
		secsave_errno = SS_ERR_OTHER;
		}

	return ret;
}

gchar *
secsave_strerror(SecureSaveErrno secsave_error)
{
	switch (secsave_error) {
	case SS_ERR_OPEN_READ:
		return _("Cannot read the file");
	case SS_ERR_STAT:
		return _("Cannot get file status");
	case SS_ERR_ACCESS:
		return _("Cannot access the file");
	case SS_ERR_MKSTEMP:
		return _("Cannot create temp file");
	case SS_ERR_RENAME:
		return _("Cannot rename the file");
	case SS_ERR_DISABLED:
		return _("File saving disabled by option");
	case SS_ERR_OUT_OF_MEM:
		return _("Out of memory");
	case SS_ERR_OPEN_WRITE:
		return _("Cannot write the file");
	case SS_ERR_NONE: /* Impossible. */
	case SS_ERR_OTHER:
	default:
		return _("Secure file saving error");
	}
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */