clang=tidy: readability-uppercase-literal-suffix
[geeqie.git] / src / secure-save.cc
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 <memory>
22
23 #include "main.h"
24 #include <glib/gprintf.h>
25 #include <utime.h>
26
27 #include "secure-save.h"
28
29 /**
30  * @file secure-save.cc
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 /**
72  * @file
73  * @FIXME locking system on files about to be rewritten ?
74  * @FIXME Low risk race conditions about ssi->file_name.
75  */
76
77 SecureSaveErrno secsave_errno = SS_ERR_NONE;
78
79
80 /** Open a file for writing in a secure way. @returns a pointer to a
81  * structure secure_save_info on success, or NULL on failure. */
82 static SecureSaveInfo *
83 secure_open_umask(const gchar *file_name)
84 {
85         struct stat st;
86
87         secsave_errno = SS_ERR_NONE;
88
89         std::unique_ptr<SecureSaveInfo, decltype(&g_free)> ssi{g_new0(SecureSaveInfo, 1), g_free};
90         if (!ssi) {
91                 secsave_errno = SS_ERR_OUT_OF_MEM;
92                 return nullptr;
93         }
94
95         ssi->secure_save = TRUE;
96         ssi->preserve_perms = TRUE;
97         ssi->unlink_on_error = TRUE;
98
99         std::unique_ptr<gchar, decltype(&g_free)> file_name_watcher{g_strdup(file_name), g_free};
100         ssi->file_name = file_name_watcher.get();
101         if (!ssi->file_name) {
102                 secsave_errno = SS_ERR_OUT_OF_MEM;
103                 return nullptr;
104         }
105
106         /* Check properties of final file. */
107 #ifndef NO_UNIX_SOFTLINKS
108         if (lstat(ssi->file_name, &st)) {
109 #else
110         if (stat(ssi->file_name, &st)) {
111 #endif
112                 /* We ignore error caused by file inexistence. */
113                 if (errno != ENOENT) {
114                         /* lstat() error. */
115                         ssi->err = errno;
116                         secsave_errno = SS_ERR_STAT;
117                         return nullptr;
118                 }
119         } else {
120                 if (!S_ISREG(st.st_mode)) {
121                         /* Not a regular file, secure_save is disabled. */
122                         ssi->secure_save = FALSE;
123                 } else {
124 #ifdef HAVE_ACCESS
125                         /* XXX: access() do not work with setuid programs. */
126                         if (access(ssi->file_name, R_OK | W_OK) < 0) {
127                                 ssi->err = errno;
128                                 secsave_errno = SS_ERR_ACCESS;
129                                 return nullptr;
130                         }
131 #else
132                         FILE *f1;
133
134                         /* We still have a race condition here between
135                          * [l]stat() and fopen() */
136
137                         f1 = fopen(ssi->file_name, "rb+");
138                         if (f1) {
139                                 fclose(f1);
140                         } else {
141                                 ssi->err = errno;
142                                 secsave_errno = SS_ERR_OPEN_READ;
143                                 return nullptr;
144                         }
145 #endif
146                 }
147         }
148
149         if (ssi->secure_save) {
150                 /* We use a random name for temporary file, mkstemp() opens
151                  * the file and return a file descriptor named fd, which is
152                  * then converted to FILE * using fdopen().
153                  */
154                 gint fd;
155                 gchar *randname = g_strconcat(ssi->file_name, ".tmp_XXXXXX", NULL);
156
157                 if (!randname) {
158                         secsave_errno = SS_ERR_OUT_OF_MEM;
159                         return nullptr;
160                 }
161
162                 /* No need to use safe_mkstemp() here. --Zas */
163                 fd = g_mkstemp(randname);
164                 if (fd == -1) {
165                         secsave_errno = SS_ERR_MKSTEMP;
166                         g_free(randname);
167                         return nullptr;
168                 }
169
170                 ssi->fp = fdopen(fd, "wb");
171                 if (!ssi->fp) {
172                         secsave_errno = SS_ERR_OPEN_WRITE;
173                         ssi->err = errno;
174                         g_free(randname);
175                         return nullptr;
176                 }
177
178                 ssi->tmp_file_name = randname;
179         } else {
180                 /* No need to create a temporary file here. */
181                 ssi->fp = fopen(ssi->file_name, "wb");
182                 if (!ssi->fp) {
183                         secsave_errno = SS_ERR_OPEN_WRITE;
184                         ssi->err = errno;
185                         return nullptr;
186                 }
187         }
188
189         ssi->file_name = file_name_watcher.release();
190         return ssi.release();
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         if (!ssi) return -1;
219
220         auto ssi_free = [](SecureSaveInfo *ssi, gint ret)
221         {
222                 if (ssi->tmp_file_name)
223                         {
224                         if (ret && ssi->unlink_on_error) unlink(ssi->tmp_file_name);
225                         g_free(ssi->tmp_file_name);
226                         }
227                 if (ssi->file_name) g_free(ssi->file_name);
228                 g_free(ssi);
229                 return ret;
230         };
231
232         if (!ssi->fp) return ssi_free(ssi, -1);
233
234         if (ssi->err) { /* Keep previous errno. */
235                 fclose(ssi->fp); /* Close file */
236                 return ssi_free(ssi, ssi->err);
237         }
238
239         /* Ensure data is effectively written to disk, we first flush libc buffers
240          * using fflush(), then fsync() to flush kernel buffers, and finally call
241          * fclose() (which call fflush() again, but the first one is needed since
242          * it doesn't make much sense to flush kernel buffers and then libc buffers,
243          * while closing file releases file descriptor we need to call fsync(). */
244 #if defined(HAVE_FFLUSH) || defined(HAVE_FSYNC)
245         if (ssi->secure_save) {
246                 gboolean fail = FALSE;
247
248 #ifdef HAVE_FFLUSH
249                 fail = (fflush(ssi->fp) == EOF);
250 #endif
251
252 #ifdef HAVE_FSYNC
253                 if (!fail) fail = fsync(fileno(ssi->fp));
254 #endif
255
256                 if (fail) {
257                         gint ret = errno;
258                         secsave_errno = SS_ERR_OTHER;
259
260                         fclose(ssi->fp); /* Close file, ignore errors. */
261                         return ssi_free(ssi, ret);
262                 }
263         }
264 #endif
265
266         /* Close file. */
267         if (fclose(ssi->fp) == EOF) {
268                 secsave_errno = SS_ERR_OTHER;
269                 return ssi_free(ssi, errno);
270         }
271
272         if (ssi->secure_save && ssi->file_name && ssi->tmp_file_name) {
273                 struct stat st;
274
275                 /**
276                  * @FIXME Race condition on ssi->file_name. The file
277                  * named ssi->file_name may have changed since
278                  * secure_open() call (where we stat() file and
279                  * more..).  */
280 #ifndef NO_UNIX_SOFTLINKS
281                 if (lstat(ssi->file_name, &st) == 0)
282 #else
283                 if (stat(ssi->file_name, &st) == 0)
284 #endif
285                         {
286                         /* set the dest file attributes to that of source (ignoring errors) */
287                         if (ssi->preserve_perms)
288                                 {
289                                 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);
290                                 if (chmod(ssi->tmp_file_name, st.st_mode) != 0) log_printf("chmod('%s', %o) failed", ssi->tmp_file_name, st.st_mode);
291                                 }
292
293                         if (ssi->preserve_mtime)
294                                 {
295                                 struct utimbuf tb;
296
297                                 tb.actime = st.st_atime;
298                                 tb.modtime = st.st_mtime;
299                                 utime(ssi->tmp_file_name, &tb);
300                                 }
301                         }
302                 if (rename(ssi->tmp_file_name, ssi->file_name) == -1) {
303                         secsave_errno = SS_ERR_RENAME;
304                         return ssi_free(ssi, errno);
305                 }
306         }
307
308         return ssi_free(ssi, 0); /* Success. */
309 }
310
311
312 /** fputs() wrapper, set ssi->err to errno on error. If ssi->err is set when
313  * called, it immediately returns EOF.
314  */
315 gint
316 secure_fputs(SecureSaveInfo *ssi, const gchar *s)
317 {
318         gint ret;
319
320         if (!ssi || !ssi->fp || ssi->err) return EOF;
321
322         ret = fputs(s, ssi->fp);
323         if (ret == EOF) {
324                 secsave_errno = SS_ERR_OTHER;
325                 ssi->err = errno;
326         }
327
328         return ret;
329 }
330
331
332 /** fputc() wrapper, set ssi->err to errno on error. If ssi->err is set when
333  * called, it immediately returns EOF.
334  */
335 gint
336 secure_fputc(SecureSaveInfo *ssi, gint c)
337 {
338         gint ret;
339
340         if (!ssi || !ssi->fp || ssi->err) return EOF;
341
342         ret = fputc(c, ssi->fp);
343         if (ret == EOF) {
344                 ssi->err = errno;
345                 secsave_errno = SS_ERR_OTHER;
346         }
347
348         return ret;
349 }
350
351 /** fprintf() wrapper, set ssi->err to errno on error and return a negative
352  * value. If ssi->err is set when called, it immediately returns -1.
353  */
354 gint
355 secure_fprintf(SecureSaveInfo *ssi, const gchar *format, ...)
356 {
357         va_list ap;
358         gint ret;
359
360         if (!ssi || !ssi->fp || ssi->err) return -1;
361
362         va_start(ap, format);
363         ret = g_vfprintf(ssi->fp, format, ap);
364         va_end(ap);
365
366         return ret;
367 }
368
369 /** fwrite() wrapper, set ssi->err to errno on error and return a value less than
370  * the number of elements to write. If ssi->err is set when called, it immediately returns 0.
371  */
372 size_t
373 secure_fwrite(gconstpointer ptr, size_t size, size_t nmemb, SecureSaveInfo *ssi)
374 {
375         size_t ret;
376
377         if (!ssi || !ssi->fp || ssi->err) return 0;
378
379         ret = fwrite(ptr, size, nmemb, ssi->fp);
380         if (ret < nmemb)
381                 {
382                 ssi->err = errno;
383                 secsave_errno = SS_ERR_OTHER;
384                 }
385
386         return ret;
387 }
388
389 gchar *
390 secsave_strerror(SecureSaveErrno secsave_error)
391 {
392         switch (secsave_error) {
393         case SS_ERR_OPEN_READ:
394                 return _("Cannot read the file");
395         case SS_ERR_STAT:
396                 return _("Cannot get file status");
397         case SS_ERR_ACCESS:
398                 return _("Cannot access the file");
399         case SS_ERR_MKSTEMP:
400                 return _("Cannot create temp file");
401         case SS_ERR_RENAME:
402                 return _("Cannot rename the file");
403         case SS_ERR_DISABLED:
404                 return _("File saving disabled by option");
405         case SS_ERR_OUT_OF_MEM:
406                 return _("Out of memory");
407         case SS_ERR_OPEN_WRITE:
408                 return _("Cannot write the file");
409         case SS_ERR_NONE: /* Impossible. */
410         case SS_ERR_OTHER:
411         default:
412                 return _("Secure file saving error");
413         }
414 }
415 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */