]> git.llucax.com Git - software/mutt-debian.git/blob - lib.c
fixes two problems with subdirs on dovecot (Closes: 530671, 530887)
[software/mutt-debian.git] / lib.c
1 /*
2  * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 1999-2004,2006-7 Thomas Roessler <roessler@does-not-exist.org>
4  * 
5  *     This program is free software; you can redistribute it
6  *     and/or modify it under the terms of the GNU General Public
7  *     License as published by the Free Software Foundation; either
8  *     version 2 of the License, or (at your option) any later
9  *     version.
10  * 
11  *     This program is distributed in the hope that it will be
12  *     useful, but WITHOUT ANY WARRANTY; without even the implied
13  *     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  *     PURPOSE.  See the GNU General Public License for more
15  *     details.
16  * 
17  *     You should have received a copy of the GNU General Public
18  *     License along with this program; if not, write to the Free
19  *     Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *     Boston, MA  02110-1301, USA.
21  */ 
22
23 /*
24  * This file used to contain some more functions, namely those
25  * which are now in muttlib.c.  They have been removed, so we have
26  * some of our "standard" functions in external programs, too.
27  */
28
29 #define _LIB_C 1
30
31 #if HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <string.h>
36 #include <ctype.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <sys/wait.h>
40 #include <errno.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43 #include <pwd.h>
44 #include <sys/types.h>
45 #include <dirent.h>
46
47 #ifdef HAVE_SYSEXITS_H
48 #include <sysexits.h>
49 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
50 #define EX_OK 0
51 #endif
52
53 #include "lib.h"
54
55
56 static struct sysexits
57 {
58   int v;
59   const char *str;
60
61 sysexits_h[] = 
62 {
63 #ifdef EX_USAGE
64   { 0xff & EX_USAGE, "Bad usage." },
65 #endif
66 #ifdef EX_DATAERR
67   { 0xff & EX_DATAERR, "Data format error." },
68 #endif
69 #ifdef EX_NOINPUT
70   { 0xff & EX_NOINPUT, "Cannot open input." },
71 #endif
72 #ifdef EX_NOUSER
73   { 0xff & EX_NOUSER, "User unknown." },
74 #endif
75 #ifdef EX_NOHOST
76   { 0xff & EX_NOHOST, "Host unknown." },
77 #endif
78 #ifdef EX_UNAVAILABLE
79   { 0xff & EX_UNAVAILABLE, "Service unavailable." },
80 #endif
81 #ifdef EX_SOFTWARE
82   { 0xff & EX_SOFTWARE, "Internal error." },
83 #endif
84 #ifdef EX_OSERR
85   { 0xff & EX_OSERR, "Operating system error." },
86 #endif
87 #ifdef EX_OSFILE
88   { 0xff & EX_OSFILE, "System file missing." },
89 #endif
90 #ifdef EX_CANTCREAT
91   { 0xff & EX_CANTCREAT, "Can't create output." },
92 #endif
93 #ifdef EX_IOERR
94   { 0xff & EX_IOERR, "I/O error." },
95 #endif
96 #ifdef EX_TEMPFAIL
97   { 0xff & EX_TEMPFAIL, "Deferred." },
98 #endif
99 #ifdef EX_PROTOCOL
100   { 0xff & EX_PROTOCOL, "Remote protocol error." },
101 #endif
102 #ifdef EX_NOPERM
103   { 0xff & EX_NOPERM, "Insufficient permission." },
104 #endif
105 #ifdef EX_CONFIG
106   { 0xff & EX_NOPERM, "Local configuration error." },
107 #endif
108   { S_ERR, "Exec error." },
109   { -1, NULL}
110 };
111
112 void mutt_nocurses_error (const char *fmt, ...)
113 {
114   va_list ap;
115
116   va_start (ap, fmt);
117   vfprintf (stderr, fmt, ap);
118   va_end (ap);
119   fputc ('\n', stderr);
120 }
121
122 void *safe_calloc (size_t nmemb, size_t size)
123 {
124   void *p;
125
126   if (!nmemb || !size)
127     return NULL;
128
129   if (((size_t) -1) / nmemb <= size)
130   {
131     mutt_error _("Integer overflow -- can't allocate memory!");
132     sleep (1);
133     mutt_exit (1);
134   }
135   
136   if (!(p = calloc (nmemb, size)))
137   {
138     mutt_error _("Out of memory!");
139     sleep (1);
140     mutt_exit (1);
141   }
142   return p;
143 }
144
145 void *safe_malloc (size_t siz)
146 {
147   void *p;
148
149   if (siz == 0)
150     return 0;
151   if ((p = (void *) malloc (siz)) == 0) /* __MEM_CHECKED__ */
152   {
153     mutt_error _("Out of memory!");
154     sleep (1);
155     mutt_exit (1);
156   }
157   return (p);
158 }
159
160 void safe_realloc (void *ptr, size_t siz)
161 {
162   void *r;
163   void **p = (void **)ptr;
164
165   if (siz == 0)
166   {
167     if (*p)
168     {
169       free (*p);                        /* __MEM_CHECKED__ */
170       *p = NULL;
171     }
172     return;
173   }
174
175   if (*p)
176     r = (void *) realloc (*p, siz);     /* __MEM_CHECKED__ */
177   else
178   {
179     /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x  --- __MEM_CHECKED__ */
180     r = (void *) malloc (siz);          /* __MEM_CHECKED__ */
181   }
182
183   if (!r)
184   {
185     mutt_error _("Out of memory!");
186     sleep (1);
187     mutt_exit (1);
188   }
189
190   *p = r;
191 }
192
193 void safe_free (void *ptr)      /* __SAFE_FREE_CHECKED__ */
194 {
195   void **p = (void **)ptr;
196   if (*p)
197   {
198     free (*p);                          /* __MEM_CHECKED__ */
199     *p = 0;
200   }
201 }
202
203 int safe_fclose (FILE **f)
204 {
205   int r = 0;
206   
207   if (*f)
208     r = fclose (*f);
209       
210   *f = NULL;
211   return r;
212 }
213
214 int safe_fsync_close (FILE **f)
215 {
216   int r = 0;
217
218   if (*f)
219   {
220     if (fflush (*f) || fsync (fileno (*f)))
221     {
222       r = -1;
223       fclose (*f);
224     }
225     else
226       r = fclose(*f);
227     *f = NULL;
228   }
229
230   return r;
231 }
232
233 char *safe_strdup (const char *s)
234 {
235   char *p;
236   size_t l;
237
238   if (!s || !*s)
239     return 0;
240   l = strlen (s) + 1;
241   p = (char *)safe_malloc (l);
242   memcpy (p, s, l);
243   return (p);
244 }
245
246 char *safe_strcat (char *d, size_t l, const char *s)
247 {
248   char *p = d;
249
250   if (!l) 
251     return d;
252
253   l--; /* Space for the trailing '\0'. */
254   
255   for (; *d && l; l--)
256     d++;
257   for (; *s && l; l--)
258     *d++ = *s++;
259
260   *d = '\0';
261   
262   return p;
263 }
264
265 char *safe_strncat (char *d, size_t l, const char *s, size_t sl)
266 {
267   char *p = d;
268
269   if (!l)
270     return d;
271   
272   l--; /* Space for the trailing '\0'. */
273   
274   for (; *d && l; l--)
275     d++;
276   for (; *s && l && sl; l--, sl--)
277     *d++ = *s++;
278
279   *d = '\0';
280   
281   return p;
282 }
283
284
285 void mutt_str_replace (char **p, const char *s)
286 {
287   FREE (p);             /* __FREE_CHECKED__ */
288   *p = safe_strdup (s);
289 }
290
291 void mutt_str_adjust (char **p)
292 {
293   if (!p || !*p) return;
294   safe_realloc (p, strlen (*p) + 1);
295 }
296
297 /* convert all characters in the string to lowercase */
298 char *mutt_strlower (char *s)
299 {
300   char *p = s;
301
302   while (*p)
303   {
304     *p = tolower ((unsigned char) *p);
305     p++;
306   }
307
308   return (s);
309 }
310
311 void mutt_unlink (const char *s)
312 {
313   int fd;
314   int flags;
315   FILE *f;
316   struct stat sb, sb2;
317   char buf[2048];
318
319   /* Defend against symlink attacks */
320   
321 #ifdef O_NOFOLLOW 
322   flags = O_RDWR | O_NOFOLLOW;
323 #else
324   flags = O_RDWR;
325 #endif
326   
327   if (lstat (s, &sb) == 0 && S_ISREG(sb.st_mode))
328   {
329     if ((fd = open (s, flags)) < 0)
330       return;
331     
332     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode) 
333         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
334     {
335       close (fd);
336       return;
337     }
338     
339     if ((f = fdopen (fd, "r+")))
340     {
341       unlink (s);
342       memset (buf, 0, sizeof (buf));
343       while (sb.st_size > 0)
344       {
345         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
346         sb.st_size -= MIN (sizeof (buf), sb.st_size);
347       }
348       fclose (f);
349     }
350   }
351 }
352
353 int mutt_copy_bytes (FILE *in, FILE *out, size_t size)
354 {
355   char buf[2048];
356   size_t chunk;
357
358   while (size > 0)
359   {
360     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
361     if ((chunk = fread (buf, 1, chunk, in)) < 1)
362       break;
363     if (fwrite (buf, 1, chunk, out) != chunk)
364     {
365       /* dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); */
366       return (-1);
367     }
368     size -= chunk;
369   }
370
371   return 0;
372 }
373
374 int mutt_copy_stream (FILE *fin, FILE *fout)
375 {
376   size_t l;
377   char buf[LONG_STRING];
378
379   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0)
380   {
381     if (fwrite (buf, 1, l, fout) != l)
382       return (-1);
383   }
384
385   return 0;
386 }
387
388 int 
389 compare_stat (struct stat *osb, struct stat *nsb)
390 {
391   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
392       osb->st_rdev != nsb->st_rdev)
393   {
394     return -1;
395   }
396
397   return 0;
398 }
399
400 int safe_symlink(const char *oldpath, const char *newpath)
401 {
402   struct stat osb, nsb;
403
404   if(!oldpath || !newpath)
405     return -1;
406   
407   if(unlink(newpath) == -1 && errno != ENOENT)
408     return -1;
409   
410   if (oldpath[0] == '/')
411   {
412     if (symlink (oldpath, newpath) == -1)
413       return -1;
414   }
415   else
416   {
417     char abs_oldpath[_POSIX_PATH_MAX];
418
419     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
420         (strlen (abs_oldpath) + 1 + strlen (oldpath) + 1 > sizeof abs_oldpath))
421     return -1;
422   
423     strcat (abs_oldpath, "/");          /* __STRCAT_CHECKED__ */
424     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
425     if (symlink (abs_oldpath, newpath) == -1)
426       return -1;
427   }
428
429   if(stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1
430      || compare_stat(&osb, &nsb) == -1)
431   {
432     unlink(newpath);
433     return -1;
434   }
435   
436   return 0;
437 }
438
439
440
441 /* 
442  * This function is supposed to do nfs-safe renaming of files.
443  * 
444  * Warning: We don't check whether src and target are equal.
445  */
446
447 int safe_rename (const char *src, const char *target)
448 {
449   struct stat ssb, tsb;
450
451   if (!src || !target)
452     return -1;
453
454   if (link (src, target) != 0)
455   {
456
457     /*
458      * Coda does not allow cross-directory links, but tells
459      * us it's a cross-filesystem linking attempt.
460      * 
461      * However, the Coda rename call is allegedly safe to use.
462      * 
463      * With other file systems, rename should just fail when 
464      * the files reside on different file systems, so it's safe
465      * to try it here.
466      *
467      */
468     
469     dprint (1, (debugfile, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
470
471     /*
472      * FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
473      * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear.
474      */
475     if (errno == EXDEV || errno == ENOSYS || errno == EPERM
476 #ifdef ENOTSUP
477         || errno == ENOTSUP
478 #endif
479 #ifdef EOPNOTSUPP
480         || errno == EOPNOTSUPP
481 #endif
482         )
483     {
484       dprint (1, (debugfile, "safe_rename: trying rename...\n"));
485       if (rename (src, target) == -1) 
486       {
487         dprint (1, (debugfile, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
488         return -1;
489       }
490       dprint (1, (debugfile, "safe_rename: rename succeeded.\n"));
491     
492       return 0;
493     }
494
495     return -1;
496   }
497
498   /*
499    * Stat both links and check if they are equal.
500    */
501   
502   if (lstat (src, &ssb) == -1)
503   {
504     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
505                 src, strerror (errno), errno));
506     return -1;
507   }
508   
509   if (lstat (target, &tsb) == -1)
510   {
511     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
512                 src, strerror (errno), errno));
513     return -1;
514   }
515
516   /* 
517    * pretend that the link failed because the target file
518    * did already exist.
519    */
520
521   if (compare_stat (&ssb, &tsb) == -1)
522   {
523     dprint (1, (debugfile, "safe_rename: stat blocks for %s and %s diverge; pretending EEXIST.\n", src, target));
524     errno = EEXIST;
525     return -1;
526   }
527
528   /*
529    * Unlink the original link.  Should we really ignore the return
530    * value here? XXX
531    */
532
533   if (unlink (src) == -1) 
534   {
535     dprint (1, (debugfile, "safe_rename: unlink (%s) failed: %s (%d)\n",
536                 src, strerror (errno), errno));
537   }
538   
539
540   return 0;
541 }
542
543
544 /* Create a temporary directory next to a file name */
545
546 static int mutt_mkwrapdir (const char *path, char *newfile, size_t nflen, 
547                     char *newdir, size_t ndlen)
548 {
549   const char *basename;
550   char parent[_POSIX_PATH_MAX];
551   char *p;
552   int rv;
553
554   strfcpy (parent, NONULL (path), sizeof (parent));
555   
556   if ((p = strrchr (parent, '/')))
557   {
558     *p = '\0';
559     basename = p + 1;
560   }
561   else
562   {
563     strfcpy (parent, ".", sizeof (parent));
564     basename = path;
565   }
566
567   do 
568   {
569     snprintf (newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
570     mktemp (newdir);
571   } 
572   while ((rv = mkdir (newdir, 0700)) == -1 && errno == EEXIST);
573   
574   if (rv == -1)
575     return -1;
576   
577   snprintf (newfile, nflen, "%s/%s", newdir, NONULL(basename));
578   return 0;  
579 }
580
581 /* remove a directory and everything under it */
582 int mutt_rmtree (const char* path)
583 {
584   DIR* dirp;
585   struct dirent* de;
586   char cur[_POSIX_PATH_MAX];
587   struct stat statbuf;
588   int rc = 0;
589
590   if (!(dirp = opendir (path)))
591   {
592     dprint (1, (debugfile, "mutt_rmtree: error opening directory %s\n", path));
593     return -1;
594   }
595   while ((de = readdir (dirp)))
596   {
597     if (!strcmp (".", de->d_name) || !strcmp ("..", de->d_name))
598       continue;
599
600     snprintf (cur, sizeof (cur), "%s/%s", path, de->d_name);
601     /* XXX make nonrecursive version */
602
603     if (stat(cur, &statbuf) == -1)
604     {
605       rc = 1;
606       continue;
607     }
608
609     if (S_ISDIR (statbuf.st_mode))
610       rc |= mutt_rmtree (cur);
611     else
612       rc |= unlink (cur);
613   }
614   closedir (dirp);
615
616   rc |= rmdir (path);
617
618   return rc;
619 }
620
621 static int mutt_put_file_in_place (const char *path, const char *safe_file, const char *safe_dir)
622 {
623   int rv;
624   
625   rv = safe_rename (safe_file, path);
626   unlink (safe_file);
627   rmdir (safe_dir);
628   return rv;
629 }
630
631 int safe_open (const char *path, int flags)
632 {
633   struct stat osb, nsb;
634   int fd;
635
636   if (flags & O_EXCL) 
637   {
638     char safe_file[_POSIX_PATH_MAX];
639     char safe_dir[_POSIX_PATH_MAX];
640
641     if (mutt_mkwrapdir (path, safe_file, sizeof (safe_file),
642                         safe_dir, sizeof (safe_dir)) == -1)
643       return -1;
644     
645     if ((fd = open (safe_file, flags, 0600)) < 0)
646     {
647       rmdir (safe_dir);
648       return fd;
649     }
650
651     /* NFS and I believe cygwin do not handle movement of open files well */
652     close (fd);
653     if (mutt_put_file_in_place (path, safe_file, safe_dir) == -1)
654       return -1;
655   }
656
657   if ((fd = open (path, flags & ~O_EXCL, 0600)) < 0)
658     return fd;
659     
660   /* make sure the file is not symlink */
661   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
662       compare_stat(&osb, &nsb) == -1)
663   {
664 /*    dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
665     close (fd);
666     return (-1);
667   }
668
669   return (fd);
670 }
671
672 /* when opening files for writing, make sure the file doesn't already exist
673  * to avoid race conditions.
674  */
675 FILE *safe_fopen (const char *path, const char *mode)
676 {
677   if (mode[0] == 'w')
678   {
679     int fd;
680     int flags = O_CREAT | O_EXCL;
681
682 #ifdef O_NOFOLLOW
683     flags |= O_NOFOLLOW;
684 #endif
685
686     if (mode[1] == '+')
687       flags |= O_RDWR;
688     else
689       flags |= O_WRONLY;
690
691     if ((fd = safe_open (path, flags)) < 0)
692       return (NULL);
693
694     return (fdopen (fd, mode));
695   }
696   else
697     return (fopen (path, mode));
698 }
699
700 static char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
701
702 void mutt_sanitize_filename (char *f, short slash)
703 {
704   if (!f) return;
705
706   for (; *f; f++)
707   {
708     if ((slash && *f == '/') || !strchr (safe_chars, *f))
709       *f = '_';
710   }
711 }
712
713 /* these characters must be escaped in regular expressions */
714
715 static char rx_special_chars[] = "^.[$()|*+?{\\";
716
717 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
718 {
719   while (*src && --destlen > 2)
720   {
721     if (strchr (rx_special_chars, *src))
722     {
723       *dest++ = '\\';
724       destlen--;
725     }
726     *dest++ = *src++;
727   }
728   
729   *dest = '\0';
730   
731   if (*src)
732     return -1;
733   else
734     return 0;
735 }
736
737 /* Read a line from ``fp'' into the dynamically allocated ``s'',
738  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
739  * If a line ends with "\", this char and the linefeed is removed,
740  * and the next line is read too.
741  */
742 char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line)
743 {
744   size_t offset = 0;
745   char *ch;
746
747   if (!s)
748   {
749     s = safe_malloc (STRING);
750     *size = STRING;
751   }
752
753   FOREVER
754   {
755     if (fgets (s + offset, *size - offset, fp) == NULL)
756     {
757       FREE (&s);
758       return NULL;
759     }
760     if ((ch = strchr (s + offset, '\n')) != NULL)
761     {
762       (*line)++;
763       *ch = 0;
764       if (ch > s && *(ch - 1) == '\r')
765         *--ch = 0;
766       if (ch == s || *(ch - 1) != '\\')
767         return s;
768       offset = ch - s - 1;
769     }
770     else
771     {
772       int c;
773       c = getc (fp); /* This is kind of a hack. We want to know if the
774                         char at the current point in the input stream is EOF.
775                         feof() will only tell us if we've already hit EOF, not
776                         if the next character is EOF. So, we need to read in
777                         the next character and manually check if it is EOF. */
778       if (c == EOF)
779       {
780         /* The last line of fp isn't \n terminated */
781         (*line)++;
782         return s;
783       }
784       else
785       {
786         ungetc (c, fp); /* undo our dammage */
787         /* There wasn't room for the line -- increase ``s'' */
788         offset = *size - 1; /* overwrite the terminating 0 */
789         *size += STRING;
790         safe_realloc (&s, *size);
791       }
792     }
793   }
794 }
795
796 char *
797 mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen)
798 {
799   size_t len;
800
801   len = end - beg;
802   if (len > destlen - 1)
803     len = destlen - 1;
804   memcpy (dest, beg, len);
805   dest[len] = 0;
806   return dest;
807 }
808
809 char *mutt_substrdup (const char *begin, const char *end)
810 {
811   size_t len;
812   char *p;
813
814   if (end)
815     len = end - begin;
816   else
817     len = strlen (begin);
818
819   p = safe_malloc (len + 1);
820   memcpy (p, begin, len);
821   p[len] = 0;
822   return p;
823 }
824
825 /* prepare a file name to survive the shell's quoting rules.
826  * From the Unix programming FAQ by way of Liviu.
827  */
828
829 size_t mutt_quote_filename (char *d, size_t l, const char *f)
830 {
831   size_t i, j = 0;
832
833   if(!f) 
834   {
835     *d = '\0';
836     return 0;
837   }
838
839   /* leave some space for the trailing characters. */
840   l -= 6;
841   
842   d[j++] = '\'';
843   
844   for(i = 0; j < l && f[i]; i++)
845   {
846     if(f[i] == '\'' || f[i] == '`')
847     {
848       d[j++] = '\'';
849       d[j++] = '\\';
850       d[j++] = f[i];
851       d[j++] = '\'';
852     }
853     else
854       d[j++] = f[i];
855   }
856   
857   d[j++] = '\'';
858   d[j]   = '\0';
859   
860   return j;
861 }
862
863 /* NULL-pointer aware string comparison functions */
864
865 int mutt_strcmp(const char *a, const char *b)
866 {
867   return strcmp(NONULL(a), NONULL(b));
868 }
869
870 int mutt_strcasecmp(const char *a, const char *b)
871 {
872   return strcasecmp(NONULL(a), NONULL(b));
873 }
874
875 int mutt_strncmp(const char *a, const char *b, size_t l)
876 {
877   return strncmp(NONULL(a), NONULL(b), l);
878 }
879
880 int mutt_strncasecmp(const char *a, const char *b, size_t l)
881 {
882   return strncasecmp(NONULL(a), NONULL(b), l);
883 }
884
885 size_t mutt_strlen(const char *a)
886 {
887   return a ? strlen (a) : 0;
888 }
889
890 int mutt_strcoll(const char *a, const char *b)
891 {
892   return strcoll(NONULL(a), NONULL(b));
893 }
894
895 const char *mutt_stristr (const char *haystack, const char *needle)
896 {
897   const char *p, *q;
898
899   if (!haystack)
900     return NULL;
901   if (!needle)
902     return (haystack);
903
904   while (*(p = haystack))
905   {
906     for (q = needle;
907          *p && *q &&
908            tolower ((unsigned char) *p) == tolower ((unsigned char) *q);
909          p++, q++)
910       ;
911     if (!*q)
912       return (haystack);
913     haystack++;
914   }
915   return NULL;
916 }
917
918 char *mutt_skip_whitespace (char *p)
919 {
920   SKIPWS (p);
921   return p;
922 }
923
924 void mutt_remove_trailing_ws (char *s)
925 {
926   char *p;
927   
928   for (p = s + mutt_strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--)
929     *p = 0;
930 }
931
932 /*
933  * Write the concatened pathname (dir + "/" + fname) into dst.
934  * The slash is ommitted when dir or fname is of 0 length.
935  * Returns NULL on error or a pointer to dst otherwise.
936  */
937 char *mutt_concatn_path (char *dst, size_t dstlen,
938     const char *dir, size_t dirlen, const char *fname, size_t fnamelen)
939 {
940   size_t req;
941   size_t offset = 0;
942
943   if (dstlen == 0)
944     return NULL; /* probably should not mask errors like this */
945
946   /* size check */
947   req = dirlen + fnamelen + 1; /* +1 for the trailing nul */
948   if (dirlen && fnamelen)
949     req++; /* when both components are non-nul, we add a "/" in between */
950   if (req > dstlen) { /* check for condition where the dst length is too short */
951     /* Two options here:
952      * 1) assert(0) or return NULL to signal error
953      * 2) copy as much of the path as will fit
954      * It doesn't appear that the return value is actually checked anywhere mutt_concat_path()
955      * is called, so we should just copy set dst to nul and let the calling function fail later.
956      */
957     dst[0] = 0; /* safe since we bail out early if dstlen == 0 */
958     return NULL;
959   }
960
961   if (dirlen) { /* when dir is not empty */
962     memcpy(dst, dir, dirlen);
963     offset = dirlen;
964     if (fnamelen)
965       dst[offset++] = '/';
966   }
967   if (fnamelen) { /* when fname is not empty */
968     memcpy(dst + offset, fname, fnamelen);
969     offset += fnamelen;
970   }
971   dst[offset] = 0;
972   return dst;
973 }
974
975 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
976 {
977   const char *fmt = "%s/%s";
978   
979   if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
980     fmt = "%s%s";
981   
982   snprintf (d, l, fmt, dir, fname);
983   return d;
984 }
985
986 const char *mutt_basename (const char *f)
987 {
988   const char *p = strrchr (f, '/');
989   if (p)
990     return p + 1;
991   else
992     return f;
993 }
994
995 const char *
996 mutt_strsysexit(int e)
997 {
998   int i;
999   
1000   for(i = 0; sysexits_h[i].str; i++)
1001   {
1002     if(e == sysexits_h[i].v)
1003       break;
1004   }
1005   
1006   return sysexits_h[i].str;
1007 }