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