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