1 
2 /*
3  * Copyright (c) 2011 QUALCOMM Incorporated. All rights reserved.
4  * See License.txt file for terms and conditions for modification and
5  * redistribution.
6  *
7  * Revisions:
8  *
9  * 06/28/08  [rcg]
10  *         - Use cache even if new mail arrived (thanks to A'rpi).
11  *
12  * 02/23/01  [rcg]
13  *         - Non-existent cache file isn't an error.
14  *
15  * 01/02/01  [rcg]
16  *         - Don't set first_msg_hidden when no messages.
17  *
18  * 09/20/00  [rcg]
19  *         - File added.
20  *
21  */
22 
23 
24 
25 
26 #include <sys/types.h>
27 #include <errno.h>
28 #include <sys/errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <time.h>
34 #include <sys/stat.h>
35 #include <sys/file.h>
36 #include <fcntl.h>
37 
38 #include "config.h"
39 
40 #if HAVE_UNISTD_H
41 #  include <unistd.h>
42 #endif /* HAVE_UNISTD_H */
43 
44 #if HAVE_SYS_UNISTD_H
45 #  include <sys/unistd.h>
46 #endif /* HAVE_SYS_UNISTD_H */
47 
48 #if HAVE_STRINGS_H
49 #  include <strings.h>
50 #endif
51 
52 #include <sys/stat.h>
53 #include <sys/file.h>
54 
55 #include "genpath.h"
56 #include "popper.h"
57 #include "misc.h"
58 
59 
60 
61 typedef struct {
62     char    magic_num [ 6 ];        /* Magic number for file */
63 #define         CACHE_MAGIC_NUM "+Qpop+"
64     int     version;                /* Version of cache file */
65 #define         CACHE_VERSION   1
66     long    toc_str_sz;             /* bytes in MsgInfoList structure */
67     long    toc_size;               /* bytes in allocated MsgInfoList */
68     time_t  modtime;                /* modtime of spool file */
69     int     msg_count;              /* Number of msgs in list */
70     long    drop_size;              /* total bytes in spool */
71     off_t   spool_end;              /* offset of end of spool */
72     char    eol;                    /* set to '\n' */
73 } cache_hdr;
74 
75 
76 /*---------------------------------------------------------------------
77  * We save the MsgInfoList structure in a cache file for faster
78  * startup next time.
79  *
80  * The cache file starts with the contents of the cache_header structure,
81  * then has the MsgInfoList structure.  Everything is written in binary,
82  * in native endian format.
83  *
84  * The cache file is only written or read in server mode.  The assumption
85  * of server mode is that the spool is only modified by Qpopper and the
86  * local delivery agent, which only appends messages.
87  *---------------------------------------------------------------------
88  */
89 
90 
91 
92 /*
93  * Write the current MsgInfoList to the cache file.
94  *
95  * Parameters:
96  *     p:           points to POP structure.
97  *     spool_fd:    file descriptor for spool.
98  *     mp:          points to the MsgInfoList structure.
99  *
100  * Result:
101  *     POP_SUCCESS or POP_FAILURE.
102  */
103 int
cache_write(POP * p,int spool_fd,MsgInfoList * mp)104 cache_write ( POP *p, int spool_fd, MsgInfoList *mp )
105 {
106     long         toc_size        = p->msg_count * sizeof ( MsgInfoList );
107     int          msg_count       = p->msg_count;
108     char         cache_name [ MAXFILENAME ];
109     cache_hdr    header;
110     long         rslt            = 0;
111     int          cache_fd        = -1;
112     FILE        *cache_file      = NULL;
113     struct stat  stbuf;
114 
115 
116     if ( p->mmdf_separator ) {
117         pop_log ( p, POP_NOTICE, HERE,
118                   "Can't write cache file for %s; spool is in mmdf format",
119                   p->user );
120         return POP_FAILURE;
121     }
122 
123     if ( genpath ( p, cache_name, sizeof(cache_name), GNPH_CACHE ) < 0 ) {
124         pop_log ( p, POP_PRIORITY, HERE,
125                   "Unable to get .cache path for %.100s",
126                   p->user );
127         return POP_FAILURE;
128     }
129 
130     cache_fd = open ( cache_name, O_RDWR | O_CREAT | O_TRUNC, 0600 );
131     if ( cache_fd == -1 ) {
132         pop_log ( p, POP_PRIORITY, HERE,
133                   "Unable to open cache file '%s': %s (%d)",
134                   cache_name, STRERROR(errno), errno );
135         return POP_FAILURE;
136     }
137 
138     cache_file = fdopen ( cache_fd, "wb" );
139     if ( cache_file == NULL ) {
140         pop_log ( p, POP_PRIORITY, HERE,
141                   "Unable to open cache fd as stream '%s': %s (%d)",
142                   cache_name, STRERROR(errno), errno );
143         close ( cache_fd );
144         return POP_FAILURE;
145     }
146 
147     rslt = fstat ( spool_fd, &stbuf );
148     if ( rslt < 0 ) {
149         pop_log ( p, POP_PRIORITY, HERE,
150                   "fstat() failed for spool (%d): %s (%d)",
151                   spool_fd, STRERROR(errno), errno );
152         close  ( cache_fd );
153         fclose ( cache_file );
154         return POP_FAILURE;
155     }
156 
157     /*
158      * Get rid of any entries for deleted messages in MsgInfoList
159      */
160     if ( p->msgs_deleted > 0 ) {
161         MsgInfoList    *new_mp     = NULL;
162         int             msgx;
163         int             num  = 1;
164         int             vnum = 1;
165         int             keep_count = p->msg_count - p->msgs_deleted;
166         long            new_size   = 0;
167         MsgInfoList    *newp = NULL;
168 
169 
170         new_size = keep_count * sizeof ( MsgInfoList );
171         new_mp   = (MsgInfoList *) malloc ( new_size );
172         if ( new_mp == NULL ) {
173             pop_log ( p, POP_PRIORITY, HERE,
174                       "Unable to allocate %ld for new MsgInfoList",
175                       new_size );
176             close  ( cache_fd );
177             fclose ( cache_file );
178             return POP_FAILURE;
179         }
180 
181         DEBUG_LOG3 ( p, "Keeping %d of %d msgs; allocated %ld for new toc",
182                      keep_count, p->msg_count, new_size );
183 
184         keep_count = 0;
185         for ( msgx = 0; msgx < p->msg_count; ++msgx ) {
186             newp = &new_mp [ keep_count ];
187             if ( mp[ msgx ].del_flag == FALSE ) {
188                 *newp = mp [ msgx ];
189                 newp->number = num++;
190                 newp->visible_num =  mp[ msgx ].hide_flag ? 0 : vnum++;
191                 newp->orig_retr_state = newp->retr_flag;
192                 DEBUG_LOG5 ( p, "Copied msg %d as %d (%d) from toc %d to %d",
193                              mp[ msgx ].number, newp->number,
194                              newp->visible_num, msgx, keep_count );
195                 keep_count++;
196             }
197         }
198 
199     mp        = new_mp;
200     toc_size  = new_size;
201     msg_count = keep_count;
202     }
203     else {
204         int             msgx = 0;
205         MsgInfoList    *ptr  = NULL;
206 
207         while ( msgx < msg_count ) {
208             ptr = &mp [ msgx++ ];
209             ptr->orig_retr_state = ptr->retr_flag;
210         }
211     }
212 
213 
214     memcpy ( header.magic_num, CACHE_MAGIC_NUM, sizeof(header.magic_num) );
215     header.version      = CACHE_VERSION;
216     header.toc_str_sz   = sizeof ( MsgInfoList );
217     header.modtime      = stbuf.st_mtime;
218     header.msg_count    = msg_count;
219     header.toc_size     = toc_size;
220     header.drop_size    = p->drop_size;
221     header.spool_end    = p->spool_end;
222     header.eol          = '\n';
223 
224     rslt = write ( cache_fd, &header, sizeof (header) );
225     rslt = write ( cache_fd, mp, header.toc_size );
226     if ( rslt < header.toc_size ) {
227         pop_log ( p, POP_PRIORITY, HERE,
228                   "I/O error writing .cache file %s: %s (%d)",
229                   cache_name, STRERROR(errno), errno );
230         close  ( cache_fd );
231         fclose ( cache_file );
232         return POP_FAILURE;
233     }
234 
235     if ( p->msgs_deleted > 0 )
236         free ( mp ); /* be nice */
237 
238     rslt = close ( cache_fd );
239     if ( rslt < 0 ) {
240         pop_log ( p, POP_PRIORITY, HERE,
241                   "I/O error closing .cache file %s: %s (%d)",
242                   cache_name, STRERROR(errno), errno );
243         close  ( cache_fd );
244         fclose ( cache_file );
245         return POP_FAILURE;
246     }
247 
248     DEBUG_LOG5 ( p, "Wrote cache file \"%s\"; msg_count=%d; toc_size=%ld; "
249                     "drop_size=%ld; spool_end=%ld",
250                  cache_name, header.msg_count, header.toc_size,
251                  header.drop_size, (long) header.spool_end );
252 
253     return POP_SUCCESS;
254 }
255 
256 
257 /*
258  * Read the cache file into a newly-allocated MsgInfoList structure.
259  *
260  * Parameters:
261  *     p:           points to POP structure.
262  *     spool_fd:    file descriptor for spool.
263  *     mp:          points MsgInfoList structure pointer which we allocate.
264  *
265  * Result:
266  *     POP_SUCCESS or POP_FAILURE.
267  */
268 int
cache_read(POP * p,FILE * spool_fd,MsgInfoList ** mp)269 cache_read ( POP *p, FILE* spool_fd, MsgInfoList **mp )
270 {
271     char         cache_name [ MAXFILENAME ];
272     cache_hdr    header;
273     MsgInfoList *mp_buf          = NULL;
274     long         rslt            = 0;
275     int          cache_fd        = -1;
276     long         str_size        = 0;
277     struct stat  stbuf;
278 
279 
280     if ( genpath ( p, cache_name, sizeof(cache_name), GNPH_CACHE ) < 0 ) {
281         pop_log ( p, POP_PRIORITY, HERE,
282                   "Unable to get .cache path for %.100s",
283                   p->user );
284         return POP_FAILURE;
285     }
286 
287     cache_fd = open ( cache_name, O_RDONLY );
288     if ( cache_fd == -1 ) {
289         if ( errno == 2 ) {
290             DEBUG_LOG1 ( p, "(no cache file %s)", cache_name );
291         } else
292             pop_log ( p, POP_PRIORITY, HERE,
293                       "Unable to open cache file '%s': %s (%d)",
294                       cache_name, STRERROR(errno), errno );
295         return POP_FAILURE;
296     }
297 
298     rslt = read ( cache_fd, (char *) &header, sizeof(header) );
299     if ( rslt < (long) sizeof(header) ) {
300         pop_log ( p, POP_NOTICE, HERE,
301                   "I/O error reading .cache file %s: %s (%d)",
302                   cache_name, STRERROR(errno), errno );
303         close  ( cache_fd );
304         return POP_FAILURE;
305     }
306 
307     if ( memcmp ( header.magic_num,
308                   CACHE_MAGIC_NUM,
309                   sizeof(header.magic_num) ) != 0 ) {
310         pop_log ( p, POP_NOTICE, HERE,
311                   "Invalid .cache file %s",
312                   cache_name );
313         close  ( cache_fd );
314         return POP_FAILURE;
315     }
316 
317     if ( header.version != CACHE_VERSION ) {
318         pop_log ( p, POP_DEBUG, HERE,
319                   "Version mismatch on .cache file %s: %u vs %u",
320                   cache_name, header.version, CACHE_VERSION );
321         close  ( cache_fd );
322         return POP_FAILURE;
323     }
324 
325     if ( header.toc_str_sz != sizeof ( MsgInfoList ) ) {
326         pop_log ( p, POP_NOTICE, HERE,
327                   "toc size mismatch in .cache file %s: %ld vs %zu",
328                   cache_name, header.toc_str_sz, sizeof ( MsgInfoList ) );
329         close  ( cache_fd );
330         return POP_FAILURE;
331     }
332 
333     rslt = fstat ( fileno(spool_fd), &stbuf );
334     if ( rslt < 0 ) {
335         pop_log ( p, POP_PRIORITY, HERE,
336                   "fstat() failed for spool (%d) %s (%d)",
337                   fileno(spool_fd), STRERROR(errno), errno );
338         close  ( cache_fd );
339         return POP_FAILURE;
340     }
341 
342     if ( header.modtime > stbuf.st_mtime ) {
343         pop_log ( p, POP_DEBUG, HERE,
344                   "spool older than cache file %s",
345                   cache_name );
346         close ( cache_fd );
347         return POP_FAILURE;
348     }
349 
350     /*
351      * Exit if the spool is smaller than indicated in the cache, since
352      * otherwise we'll have corruption.
353      */
354     if ( header.spool_end > stbuf.st_size ) {
355         pop_log ( p, POP_NOTICE, HERE,
356                   "spool smaller than indicated in "
357                   "cache: %s (size %ld modtime %ld vs %ld %ld)",
358                   cache_name, (long) header.spool_end, header.modtime,
359                   (long) stbuf.st_size, stbuf.st_mtime );
360         close ( cache_fd );
361         return POP_FAILURE;
362     }
363 
364     if ( header.modtime != stbuf.st_mtime ) {
365         if ( p->bCons_cache ) {
366             pop_log ( p, POP_DEBUG, HERE,
367                   "'conservative-cache' set and spool modified since "
368                   "cache creation: %s (size %ld modtime %ld vs %ld %ld)",
369                   cache_name, (long) header.spool_end, header.modtime,
370                   (long) stbuf.st_size, stbuf.st_mtime );
371             close ( cache_fd );
372             return POP_FAILURE;
373         }
374         /* just a note in the debug log in case there are any problems s*/
375         DEBUG_LOG5 ( p, "Note: spool modified since cache creation: "
376                      "%s (size %ld modtime %ld vs %ld %ld)",
377                      cache_name, (long) header.spool_end, header.modtime,
378                      (long) stbuf.st_size, stbuf.st_mtime );
379     }
380 
381     /*
382      * Note that the local delivery agent may have
383      * added messages to the spool since the cache was written,
384      * in which case the new messages need to be processed normally.
385      */
386 
387     /*
388      * Allocate the MsgInfoList structure.  Add room for one more msg,
389      * to make sure no one walks off the end.
390      */
391     str_size = header.toc_size + sizeof ( MsgInfoList );
392     mp_buf = (MsgInfoList *) malloc ( str_size );
393     if ( mp_buf == NULL ) {
394         pop_log ( p, POP_PRIORITY, HERE,
395                   "Can't allocate memory (%ld) for message list",
396                   str_size );
397         close ( cache_fd );
398         return POP_FAILURE;
399     }
400 
401 
402     rslt = read ( cache_fd, mp_buf, header.toc_size );
403     if ( rslt < header.toc_size ) {
404         pop_log ( p, POP_PRIORITY, HERE,
405                   "I/O error reading .cache file %s: %s (%d)",
406                   cache_name, STRERROR(errno), errno );
407         close  ( cache_fd );
408         free   ( mp_buf );
409         return POP_FAILURE;
410     }
411 
412     rslt = close ( cache_fd );
413     if ( rslt < 0 ) {
414         pop_log ( p, POP_PRIORITY, HERE,
415                   "I/O error closing .cache file %s: %s (%d)",
416                   cache_name, STRERROR(errno), errno );
417         close  ( cache_fd );
418         free   ( mp_buf   );
419         return POP_FAILURE;
420     }
421 
422     if ( *mp != NULL )
423         free ( *mp );
424     *mp = mp_buf;
425 
426     p->drop_size         = header.drop_size;
427     p->msg_count         = header.msg_count;
428     if ( p->msg_count > 0 ) {
429         p->first_msg_hidden  = (*mp) [ 0 ].hide_flag;
430         p->visible_msg_count = (*mp) [ header.msg_count - 1 ].visible_num;
431     }
432 
433     /*
434      * Set temp drop to end of cached mail, so any new mail in temp drop will
435      * be processed normally by init_dropinfo()
436      */
437     fseek ( spool_fd, header.spool_end, SEEK_SET );
438 
439     DEBUG_LOG7 ( p, "Read cache file \"%s\"; msg_count=%d; toc_size=%ld; "
440                     "drop_size=%ld; spool_end=%ld; first_msg_hidden=%d; "
441                     "visible_msg_count=%d",
442                  cache_name, header.msg_count, header.toc_size,
443                  header.drop_size, (long) header.spool_end,
444                  p->first_msg_hidden, p->visible_msg_count );
445 
446     /*
447      * If the spool is larger than indicated in the cache, it's likely that
448      * new mail has arrived; we return an error so that pop_dropcopy() will
449      * call init_dropinfo() to process the new messages.  Note that if the
450      * spool was modified by a process other than Qpopper or a local delivery
451      * agent, specifically if the spool has anything deleted from it since
452      * the cache was created, the MsgInfoList won't match the spool, and
453      * mail will be corrupted.
454      */
455     if ( header.spool_end < stbuf.st_size ) {
456         DEBUG_LOG5 ( p, "spool larger than indicated in "
457                         "cache: %s (size %ld modtime %ld vs %ld %ld)",
458                      cache_name, (long) header.spool_end, header.modtime,
459                      (long) stbuf.st_size, stbuf.st_mtime );
460         return POP_FAILURE;
461     }
462 
463     /*
464      * If all mail in temp drop is in cache, no need for init_dropinfo()
465      * to be called.
466      */
467     return POP_SUCCESS;
468 }
469 
470 
471 /*
472  * Unlink the cache file if it exists.
473  */
474 void
cache_unlink(POP * p)475 cache_unlink ( POP *p )
476 {
477     char         cache_name [ MAXFILENAME ];
478 
479 
480     if ( genpath ( p, cache_name, sizeof(cache_name), GNPH_CACHE ) < 0 ) {
481         pop_log ( p, POP_PRIORITY, HERE,
482                   "Unable to get .cache path for %.100s",
483                   p->user );
484         return;
485     }
486 
487     unlink ( cache_name );
488 
489     DEBUG_LOG1 ( p, "Unlinked cache file %s", cache_name );
490 }
491 
492