Nico Golde:
[apps/madmutt.git] / lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 /*
12  * This file used to contain some more functions, namely those
13  * which are now in muttlib.c.  They have been removed, so we have
14  * some of our "standard" functions in external programs, too.
15  */
16
17 #if HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20
21 #include <string.h>
22 #include <ctype.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <pwd.h>
30
31 #include "lib.h"
32
33 #include "lib/mem.h"
34 #include "lib/str.h"
35 #include "lib/debug.h"
36
37 extern short Umask;
38
39 void mutt_nocurses_error (const char *fmt, ...)
40 {
41   va_list ap;
42
43   va_start (ap, fmt);
44   vfprintf (stderr, fmt, ap);
45   va_end (ap);
46   fputc ('\n', stderr);
47 }
48
49 int safe_fclose (FILE ** f)
50 {
51   int r = 0;
52
53   if (*f)
54     r = fclose (*f);
55
56   *f = NULL;
57   return r;
58 }
59
60 void mutt_unlink (const char *s)
61 {
62   int fd;
63   int flags;
64   FILE *f;
65   struct stat sb, sb2;
66   char buf[2048];
67
68   /* Defend against symlink attacks */
69
70 #ifdef O_NOFOLLOW
71   flags = O_RDWR | O_NOFOLLOW;
72 #else
73   flags = O_RDWR;
74 #endif
75
76   if (lstat (s, &sb) == 0 && S_ISREG (sb.st_mode)) {
77     if ((fd = open (s, flags)) < 0)
78       return;
79
80     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode)
81         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino)) {
82       close (fd);
83       return;
84     }
85
86     if ((f = fdopen (fd, "r+"))) {
87       unlink (s);
88       memset (buf, 0, sizeof (buf));
89       while (sb.st_size > 0) {
90         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
91         sb.st_size -= MIN (sizeof (buf), sb.st_size);
92       }
93       fclose (f);
94     }
95   }
96 }
97
98 int mutt_copy_bytes (FILE * in, FILE * out, size_t size)
99 {
100   char buf[2048];
101   size_t chunk;
102
103   while (size > 0) {
104     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
105     if ((chunk = fread (buf, 1, chunk, in)) < 1)
106       break;
107     if (fwrite (buf, 1, chunk, out) != chunk) {
108       debug_print (1, ("fwrite() returned short byte count\n"));
109       return (-1);
110     }
111     size -= chunk;
112   }
113
114   return 0;
115 }
116
117 int mutt_copy_stream (FILE * fin, FILE * fout)
118 {
119   size_t l;
120   char buf[LONG_STRING];
121
122   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0) {
123     if (fwrite (buf, 1, l, fout) != l)
124       return (-1);
125   }
126
127   return 0;
128 }
129
130 static int compare_stat (struct stat *osb, struct stat *nsb)
131 {
132   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
133       osb->st_rdev != nsb->st_rdev) {
134     return -1;
135   }
136
137   return 0;
138 }
139
140 int safe_symlink (const char *oldpath, const char *newpath)
141 {
142   struct stat osb, nsb;
143
144   if (!oldpath || !newpath)
145     return -1;
146
147   if (unlink (newpath) == -1 && errno != ENOENT)
148     return -1;
149
150   if (oldpath[0] == '/') {
151     if (symlink (oldpath, newpath) == -1)
152       return -1;
153   }
154   else {
155     char abs_oldpath[_POSIX_PATH_MAX];
156
157     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
158         (safe_strlen (abs_oldpath) + 1 + safe_strlen (oldpath) + 1 >
159          sizeof abs_oldpath))
160       return -1;
161
162     strcat (abs_oldpath, "/");  /* __STRCAT_CHECKED__ */
163     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
164     if (symlink (abs_oldpath, newpath) == -1)
165       return -1;
166   }
167
168   if (stat (oldpath, &osb) == -1 || stat (newpath, &nsb) == -1
169       || compare_stat (&osb, &nsb) == -1) {
170     unlink (newpath);
171     return -1;
172   }
173
174   return 0;
175 }
176
177 /* 
178  * This function is supposed to do nfs-safe renaming of files.
179  * 
180  * Warning: We don't check whether src and target are equal.
181  */
182
183 int safe_rename (const char *src, const char *target)
184 {
185   struct stat ssb, tsb;
186
187   if (!src || !target)
188     return -1;
189
190   if (link (src, target) != 0) {
191
192     /*
193      * Coda does not allow cross-directory links, but tells
194      * us it's a cross-filesystem linking attempt.
195      * 
196      * However, the Coda rename call is allegedly safe to use.
197      * 
198      * With other file systems, rename should just fail when 
199      * the files reside on different file systems, so it's safe
200      * to try it here.
201      *
202      */
203
204     if (errno == EXDEV)
205       return rename (src, target);
206
207     return -1;
208   }
209
210   /*
211    * Stat both links and check if they are equal.
212    */
213
214   if (stat (src, &ssb) == -1) {
215     return -1;
216   }
217
218   if (stat (target, &tsb) == -1) {
219     return -1;
220   }
221
222   /* 
223    * pretend that the link failed because the target file
224    * did already exist.
225    */
226
227   if (compare_stat (&ssb, &tsb) == -1) {
228     errno = EEXIST;
229     return -1;
230   }
231
232   /*
233    * Unlink the original link.  Should we really ignore the return
234    * value here? XXX
235    */
236
237   unlink (src);
238
239   return 0;
240 }
241
242 int safe_open (const char *path, int flags)
243 {
244   struct stat osb, nsb;
245   int fd;
246
247   umask (Umask);
248   if ((fd = open (path, flags, 0666)) < 0)
249     return fd;
250
251   /* make sure the file is not symlink */
252   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
253       compare_stat (&osb, &nsb) == -1) {
254     debug_print (1, ("%s is a symlink!\n", path));
255     close (fd);
256     return (-1);
257   }
258
259   return (fd);
260 }
261
262 /* when opening files for writing, make sure the file doesn't already exist
263  * to avoid race conditions.
264  */
265 FILE *safe_fopen (const char *path, const char *mode)
266 {
267   /* first set the current umask */
268   umask (Umask);
269   if (mode[0] == 'w') {
270     int fd;
271     int flags = O_CREAT | O_EXCL;
272
273 #ifdef O_NOFOLLOW
274     flags |= O_NOFOLLOW;
275 #endif
276
277     if (mode[1] == '+')
278       flags |= O_RDWR;
279     else
280       flags |= O_WRONLY;
281
282     if ((fd = safe_open (path, flags)) < 0)
283       return (NULL);
284
285     return (fdopen (fd, mode));
286   }
287   else
288     return (fopen (path, mode));
289 }
290
291 static char safe_chars[] =
292   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
293
294 void mutt_sanitize_filename (char *f, short slash)
295 {
296   if (!f)
297     return;
298
299   for (; *f; f++) {
300     if ((slash && *f == '/') || !strchr (safe_chars, *f))
301       *f = '_';
302   }
303 }
304
305 /* these characters must be escaped in regular expressions */
306
307 static char rx_special_chars[] = "^.[$()|*+?{\\";
308
309 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
310 {
311   while (*src && --destlen > 2) {
312     if (strchr (rx_special_chars, *src)) {
313       *dest++ = '\\';
314       destlen--;
315     }
316     *dest++ = *src++;
317   }
318
319   *dest = '\0';
320
321   if (*src)
322     return -1;
323   else
324     return 0;
325 }
326
327 /* Read a line from ``fp'' into the dynamically allocated ``s'',
328  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
329  * If a line ends with "\", this char and the linefeed is removed,
330  * and the next line is read too.
331  */
332 char *mutt_read_line (char *s, size_t * size, FILE * fp, int *line)
333 {
334   size_t offset = 0;
335   char *ch;
336
337   if (!s) {
338     s = safe_malloc (STRING);
339     *size = STRING;
340   }
341
342   FOREVER {
343     if (fgets (s + offset, *size - offset, fp) == NULL) {
344       FREE (&s);
345       return NULL;
346     }
347     if ((ch = strchr (s + offset, '\n')) != NULL) {
348       (*line)++;
349       *ch = 0;
350       if (ch > s && *(ch - 1) == '\r')
351         *--ch = 0;
352       if (ch == s || *(ch - 1) != '\\')
353         return s;
354       offset = ch - s - 1;
355     }
356     else {
357       int c;
358
359       c = getc (fp);            /* This is kind of a hack. We want to know if the
360                                    char at the current point in the input stream is EOF.
361                                    feof() will only tell us if we've already hit EOF, not
362                                    if the next character is EOF. So, we need to read in
363                                    the next character and manually check if it is EOF. */
364       if (c == EOF) {
365         /* The last line of fp isn't \n terminated */
366         (*line)++;
367         return s;
368       }
369       else {
370         ungetc (c, fp);         /* undo our dammage */
371         /* There wasn't room for the line -- increase ``s'' */
372         offset = *size - 1;     /* overwrite the terminating 0 */
373         *size += STRING;
374         safe_realloc (&s, *size);
375       }
376     }
377   }
378 }
379
380 /* prepare a file name to survive the shell's quoting rules.
381  * From the Unix programming FAQ by way of Liviu.
382  */
383
384 size_t mutt_quote_filename (char *d, size_t l, const char *f)
385 {
386   size_t i, j = 0;
387
388   if (!f) {
389     *d = '\0';
390     return 0;
391   }
392
393   /* leave some space for the trailing characters. */
394   l -= 6;
395
396   d[j++] = '\'';
397
398   for (i = 0; j < l && f[i]; i++) {
399     if (f[i] == '\'' || f[i] == '`') {
400       d[j++] = '\'';
401       d[j++] = '\\';
402       d[j++] = f[i];
403       d[j++] = '\'';
404     }
405     else
406       d[j++] = f[i];
407   }
408
409   d[j++] = '\'';
410   d[j] = '\0';
411
412   return j;
413 }
414
415 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
416 {
417   const char *fmt = "%s/%s";
418
419   if (!*fname || (*dir && dir[safe_strlen (dir) - 1] == '/'))
420     fmt = "%s%s";
421
422   snprintf (d, l, fmt, dir, fname);
423   return d;
424 }
425
426 const char *mutt_basename (const char *f)
427 {
428   const char *p = strrchr (f, '/');
429
430   if (p)
431     return p + 1;
432   else
433     return f;
434 }