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