Remove commented out code.
[geeqie.git] / src / secure_save.c
1 /*
2  * Geeqie
3  * Copyright (C) 2008 - 2012 The Geeqie Team
4  *
5  * based on the code developped for ELinks by Laurent Monin
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include <glib/gprintf.h>
13 #include <glib/gstdio.h>
14 #include <errno.h>
15 #include <utime.h>
16
17 #include "main.h"
18 #include "secure_save.h"
19
20
21 /* ABOUT SECURE SAVE */
22 /* This code was borrowed from the ELinks project (http://elinks.cz)
23  * It was originally written by me (Laurent Monin aka Zas) and heavily
24  * modified and improved by all ELinks contributors.
25  * This code was released under the GPLv2 licence.
26  * It was modified to be included in geeqie on 2008/04/05 */
27
28 /* If ssi->secure_save is TRUE:
29  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30  *
31  * A call to secure_open("/home/me/.confdir/filename", mask) will open a file
32  * named "filename.tmp_XXXXXX" in /home/me/.confdir/ and return a pointer to a
33  * structure SecureSaveInfo on success or NULL on error.
34  *
35  * filename.tmp_XXXXXX can't conflict with any file since it's created using
36  * mkstemp(). XXXXXX is a random string.
37  *
38  * Subsequent write operations are done using returned SecureSaveInfo FILE *
39  * field named fp.
40  *
41  * If an error is encountered, SecureSaveInfo int field named err is set
42  * (automatically if using secure_fp*() functions or by programmer)
43  *
44  * When secure_close() is called, "filename.tmp_XXXXXX" is flushed and closed,
45  * and if SecureSaveInfo err field has a value of zero, "filename.tmp_XXXXXX"
46  * is renamed to "filename". If this succeeded, then secure_close() returns 0.
47  *
48  * WARNING: since rename() is used, any symlink called "filename" may be
49  * replaced by a regular file. If destination file isn't a regular file,
50  * then secsave is disabled for that file.
51  *
52  * If ssi->secure_save is FALSE:
53  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54  *
55  * No temporary file is created, "filename" is truncated, all operations are
56  * done on it, no rename nor flush occur, symlinks are preserved.
57  *
58  * In both cases:
59  * ~~~~~~~~~~~~~
60  *
61  * Access rights are affected by secure_open() mask parameter.
62  */
63
64 /* FIXME: locking system on files about to be rewritten ? */
65 /* FIXME: Low risk race conditions about ssi->file_name. */
66
67 SecureSaveErrno secsave_errno = SS_ERR_NONE;
68
69
70 /** Open a file for writing in a secure way. @returns a pointer to a
71  * structure secure_save_info on success, or NULL on failure. */
72 static SecureSaveInfo *
73 secure_open_umask(const gchar *file_name)
74 {
75         struct stat st;
76         SecureSaveInfo *ssi;
77
78         secsave_errno = SS_ERR_NONE;
79
80         ssi = g_new0(SecureSaveInfo, 1);
81         if (!ssi) {
82                 secsave_errno = SS_ERR_OUT_OF_MEM;
83                 goto end;
84         }
85
86         ssi->secure_save = TRUE;
87         ssi->preserve_perms = TRUE;
88         ssi->unlink_on_error = TRUE;
89
90         ssi->file_name = g_strdup(file_name);
91         if (!ssi->file_name) {
92                 secsave_errno = SS_ERR_OUT_OF_MEM;
93                 goto free_f;
94         }
95
96         /* Check properties of final file. */
97 #ifndef NO_UNIX_SOFTLINKS
98         if (lstat(ssi->file_name, &st)) {
99 #else
100         if (stat(ssi->file_name, &st)) {
101 #endif
102                 /* We ignore error caused by file inexistence. */
103                 if (errno != ENOENT) {
104                         /* lstat() error. */
105                         ssi->err = errno;
106                         secsave_errno = SS_ERR_STAT;
107                         goto free_file_name;
108                 }
109         } else {
110                 if (!S_ISREG(st.st_mode)) {
111                         /* Not a regular file, secure_save is disabled. */
112                         ssi->secure_save = FALSE;
113                 } else {
114 #ifdef HAVE_ACCESS
115                         /* XXX: access() do not work with setuid programs. */
116                         if (access(ssi->file_name, R_OK | W_OK) < 0) {
117                                 ssi->err = errno;
118                                 secsave_errno = SS_ERR_ACCESS;
119                                 goto free_file_name;
120                         }
121 #else
122                         FILE *f1;
123
124                         /* We still have a race condition here between
125                          * [l]stat() and fopen() */
126
127                         f1 = fopen(ssi->file_name, "rb+");
128                         if (f1) {
129                                 fclose(f1);
130                         } else {
131                                 ssi->err = errno;
132                                 secsave_errno = SS_ERR_OPEN_READ;
133                                 goto free_file_name;
134                         }
135 #endif
136                 }
137         }
138
139         if (ssi->secure_save) {
140                 /* We use a random name for temporary file, mkstemp() opens
141                  * the file and return a file descriptor named fd, which is
142                  * then converted to FILE * using fdopen().
143                  */
144                 gint fd;
145                 gchar *randname = g_strconcat(ssi->file_name, ".tmp_XXXXXX", NULL);
146
147                 if (!randname) {
148                         secsave_errno = SS_ERR_OUT_OF_MEM;
149                         goto free_file_name;
150                 }
151
152                 /* No need to use safe_mkstemp() here. --Zas */
153                 fd = g_mkstemp(randname);
154                 if (fd == -1) {
155                         secsave_errno = SS_ERR_MKSTEMP;
156                         g_free(randname);
157                         goto free_file_name;
158                 }
159
160                 ssi->fp = fdopen(fd, "wb");
161                 if (!ssi->fp) {
162                         secsave_errno = SS_ERR_OPEN_WRITE;
163                         ssi->err = errno;
164                         g_free(randname);
165                         goto free_file_name;
166                 }
167
168                 ssi->tmp_file_name = randname;
169         } else {
170                 /* No need to create a temporary file here. */
171                 ssi->fp = fopen(ssi->file_name, "wb");
172                 if (!ssi->fp) {
173                         secsave_errno = SS_ERR_OPEN_WRITE;
174                         ssi->err = errno;
175                         goto free_file_name;
176                 }
177         }
178
179         return ssi;
180
181 free_file_name:
182         g_free(ssi->file_name);
183         ssi->file_name = NULL;
184
185 free_f:
186         g_free(ssi);
187         ssi = NULL;
188
189 end:
190         return NULL;
191 }
192
193 SecureSaveInfo *
194 secure_open(const gchar *file_name)
195 {
196         SecureSaveInfo *ssi;
197         mode_t saved_mask;
198 #ifdef CONFIG_OS_WIN32
199         /* There is neither S_IRWXG nor S_IRWXO under crossmingw32-gcc */
200         const mode_t mask = 0177;
201 #else
202         const mode_t mask = S_IXUSR | S_IRWXG | S_IRWXO;
203 #endif
204
205         saved_mask = umask(mask);
206         ssi = secure_open_umask(file_name);
207         umask(saved_mask);
208
209         return ssi;
210 }
211
212 /** Close a file opened with secure_open(). Rreturns 0 on success,
213  * errno or -1 on failure.
214  */
215 gint
216 secure_close(SecureSaveInfo *ssi)
217 {
218         gint ret = -1;
219
220         if (!ssi) return ret;
221         if (!ssi->fp) goto free;
222
223         if (ssi->err) { /* Keep previous errno. */
224                 ret = ssi->err;
225                 fclose(ssi->fp); /* Close file */
226                 goto free;
227         }
228
229         /* Ensure data is effectively written to disk, we first flush libc buffers
230          * using fflush(), then fsync() to flush kernel buffers, and finally call
231          * fclose() (which call fflush() again, but the first one is needed since
232          * it doesn't make much sense to flush kernel buffers and then libc buffers,
233          * while closing file releases file descriptor we need to call fsync(). */
234 #if defined(HAVE_FFLUSH) || defined(HAVE_FSYNC)
235         if (ssi->secure_save) {
236                 gboolean fail = FALSE;
237
238 #ifdef HAVE_FFLUSH
239                 fail = (fflush(ssi->fp) == EOF);
240 #endif
241
242 #ifdef HAVE_FSYNC
243                 if (!fail) fail = fsync(fileno(ssi->fp));
244 #endif
245
246                 if (fail) {
247                         ret = errno;
248                         secsave_errno = SS_ERR_OTHER;
249
250                         fclose(ssi->fp); /* Close file, ignore errors. */
251                         goto free;
252                 }
253         }
254 #endif
255
256         /* Close file. */
257         if (fclose(ssi->fp) == EOF) {
258                 ret = errno;
259                 secsave_errno = SS_ERR_OTHER;
260                 goto free;
261         }
262
263         if (ssi->secure_save && ssi->file_name && ssi->tmp_file_name) {
264                 struct stat st;
265
266                 /* FIXME: Race condition on ssi->file_name. The file
267                  * named ssi->file_name may have changed since
268                  * secure_open() call (where we stat() file and
269                  * more..).  */
270 #ifndef NO_UNIX_SOFTLINKS
271                 if (lstat(ssi->file_name, &st) == 0)
272 #else
273                 if (stat(ssi->file_name, &st) == 0)
274 #endif
275                         {
276                         /* set the dest file attributes to that of source (ignoring errors) */
277                         if (ssi->preserve_perms)
278                                 {
279                                 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);
280                                 if (chmod(ssi->tmp_file_name, st.st_mode) != 0) log_printf("chmod('%s', %o) failed", ssi->tmp_file_name, st.st_mode);
281                                 }
282
283                         if (ssi->preserve_mtime)
284                                 {
285                                 struct utimbuf tb;
286
287                                 tb.actime = st.st_atime;
288                                 tb.modtime = st.st_mtime;
289                                 utime(ssi->tmp_file_name, &tb);
290                                 }
291                         }
292                 DEBUG_3("rename %s -> %s", ssi->tmp_file_name, ssi->file_name);
293                 if (rename(ssi->tmp_file_name, ssi->file_name) == -1) {
294                         ret = errno;
295                         secsave_errno = SS_ERR_RENAME;
296                         goto free;
297                 }
298         }
299
300         ret = 0;        /* Success. */
301
302 free:
303         if (ssi->tmp_file_name)
304                 {
305                 if (ret && ssi->unlink_on_error) unlink(ssi->tmp_file_name);
306                 g_free(ssi->tmp_file_name);
307                 }
308         if (ssi->file_name) g_free(ssi->file_name);
309         if (ssi) g_free(ssi);
310
311         return ret;
312 }
313
314
315 /** fputs() wrapper, set ssi->err to errno on error. If ssi->err is set when
316  * called, it immediatly returns EOF.
317  */
318 gint
319 secure_fputs(SecureSaveInfo *ssi, const gchar *s)
320 {
321         gint ret;
322
323         if (!ssi || !ssi->fp || ssi->err) return EOF;
324
325         ret = fputs(s, ssi->fp);
326         if (ret == EOF) {
327                 secsave_errno = SS_ERR_OTHER;
328                 ssi->err = errno;
329         }
330
331         return ret;
332 }
333
334
335 /** fputc() wrapper, set ssi->err to errno on error. If ssi->err is set when
336  * called, it immediatly returns EOF.
337  */
338 gint
339 secure_fputc(SecureSaveInfo *ssi, gint c)
340 {
341         gint ret;
342
343         if (!ssi || !ssi->fp || ssi->err) return EOF;
344
345         ret = fputc(c, ssi->fp);
346         if (ret == EOF) {
347                 ssi->err = errno;
348                 secsave_errno = SS_ERR_OTHER;
349         }
350
351         return ret;
352 }
353
354 /** fprintf() wrapper, set ssi->err to errno on error and return a negative
355  * value. If ssi->err is set when called, it immediatly returns -1.
356  */
357 gint
358 secure_fprintf(SecureSaveInfo *ssi, const gchar *format, ...)
359 {
360         va_list ap;
361         gint ret;
362
363         if (!ssi || !ssi->fp || ssi->err) return -1;
364
365         va_start(ap, format);
366         ret = g_vfprintf(ssi->fp, format, ap);
367         va_end(ap);
368
369         return ret;
370 }
371
372 /** fwrite() wrapper, set ssi->err to errno on error and return a value less than
373  * the number of elements to write. If ssi->err is set when called, it immediatly returns 0.
374  */
375 size_t
376 secure_fwrite(gconstpointer ptr, size_t size, size_t nmemb, SecureSaveInfo *ssi)
377 {
378         size_t ret;
379
380         if (!ssi || !ssi->fp || ssi->err) return 0;
381
382         ret = fwrite(ptr, size, nmemb, ssi->fp);
383         if (ret < nmemb)
384                 {
385                 ssi->err = errno;
386                 secsave_errno = SS_ERR_OTHER;
387                 }
388
389         return ret;
390 }
391
392 gchar *
393 secsave_strerror(SecureSaveErrno secsave_error)
394 {
395         switch (secsave_error) {
396         case SS_ERR_OPEN_READ:
397                 return _("Cannot read the file");
398         case SS_ERR_STAT:
399                 return _("Cannot get file status");
400         case SS_ERR_ACCESS:
401                 return _("Cannot access the file");
402         case SS_ERR_MKSTEMP:
403                 return _("Cannot create temp file");
404         case SS_ERR_RENAME:
405                 return _("Cannot rename the file");
406         case SS_ERR_DISABLED:
407                 return _("File saving disabled by option");
408         case SS_ERR_OUT_OF_MEM:
409                 return _("Out of memory");
410         case SS_ERR_OPEN_WRITE:
411                 return _("Cannot write the file");
412         case SS_ERR_NONE: /* Impossible. */
413         case SS_ERR_OTHER:
414         default:
415                 return _("Secure file saving error");
416         }
417 }
418 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */