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