X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=uid.c;h=d069d7d0484b5b2bdeb1c01170fd5187762d537b;hb=87bcf29364c4640edb87cc2186b965d1a564d70c;hp=fbb314e3cebcb443937bce27e6e9dbcf7cf95eea;hpb=20a1be81c938a2f89b95e5e4436124bd3fa6aa43;p=~andy%2Ffetchmail diff --git a/uid.c b/uid.c index fbb314e3..d069d7d0 100644 --- a/uid.c +++ b/uid.c @@ -1,31 +1,12 @@ -/* - * uid.c -- UIDL handling for POP3 servers without LAST +/** + * \file uid.c + * UID list handling (currently, only for POP3) * * For license terms, see the file COPYING in this directory. - */ - -#include "config.h" - -#include -#include -#include -#include -#if defined(STDC_HEADERS) -#include -#include -#endif -#if defined(HAVE_UNISTD_H) -#include -#endif - -#include "fetchmail.h" -#include "i18n.h" - -/* - * Machinery for handling UID lists live here. This is mainly to support - * RFC1725/RFC1939-conformant POP3 servers without a LAST command, but may also - * be useful for making the IMAP4 querying logic UID-oriented, if a future - * revision of IMAP forces me to. + * + * Machinery for handling UID lists live here. This is currently used + * by POP3, but may also be useful for making the IMAP4 querying logic + * UID-oriented. * * These functions are also used by the rest of the code to maintain * string lists. @@ -100,14 +81,41 @@ * Note: some comparisons (those used for DNS address lists) are caseblind! */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "fetchmail.h" +#include "gettext.h" +#include "sdump.h" + int dofastuidl = 0; -/* UIDs associated with un-queried hosts */ +#ifdef POP3_ENABLE +/** UIDs associated with un-queried hosts */ static struct idlist *scratchlist; -#ifdef POP3_ENABLE +static int dump_saved_uid(struct uid_db_record *rec, void *unused) +{ + char *t; + + (void)unused; + + t = sdump(rec->id, rec->id_len); + report_build(stdout, " %s", t); + free(t); + + return 0; +} + +/** Read saved IDs from \a idfile and attach to each host in \a hostlist. */ void initialize_saved_lists(struct query *hostlist, const char *idfile) -/* read file of saved IDs and attach to each host */ { struct stat statbuf; FILE *tmpfp; @@ -116,9 +124,9 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) /* make sure lists are initially empty */ for (ctl = hostlist; ctl; ctl = ctl->next) { ctl->skipped = (struct idlist *)NULL; - ctl->oldsaved = (struct idlist *)NULL; - ctl->newsaved = (struct idlist *)NULL; - ctl->oldsavedend = &ctl->oldsaved; + + init_uid_db(&ctl->oldsaved); + init_uid_db(&ctl->newsaved); } errno = 0; @@ -155,13 +163,13 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) while (fgets(buf, POPBUFSIZE, tmpfp) != (char *)NULL) { /* - * At this point, we assume the bug has two fields -- a user@host + * At this point, we assume the bug has two fields -- a user@host * part, and an ID part. Either field may contain spurious @ signs. - * The previous version of this code presumed one could split at - * the rightmost '@'. This is not correct, as InterMail puts an + * The previous version of this code presumed one could split at + * the rightmost '@'. This is not correct, as InterMail puts an * '@' in the UIDL. */ - + /* first, skip leading spaces */ user = buf + strspn(buf, " \t"); @@ -172,11 +180,14 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) * instead of a Message-ID, as GMX's (www.gmx.net) POP3 * StreamProxy V1.0 does. * - * this is one other trick. The userhost part + * this is one other trick. The userhost part * may contain ' ' in the user part, at least in * the lotus notes case. * So we start looking for the '@' after which the - * host will follow with the ' ' seperator finaly id. + * host will follow with the ' ' separator with the id. + * + * XXX FIXME: There is a case this code cannot handle: + * the user name cannot have blanks after a '@'. */ if ((delimp1 = strchr(user, '@')) != NULL && (id = strchr(delimp1,' ')) != NULL) @@ -185,7 +196,7 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) if ((*delimp1 != ' ') && (*delimp1 != '\t')) break; - /* + /* * It should be safe to assume that id starts after * the " " - after all, we're writing the " " * ourselves in write_saved_lists() :-) @@ -193,44 +204,43 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) id = id + strspn(id, " "); delimp1++; /* but what if there is only white space ?!? */ - saveddelim1 = *delimp1; /* save char after token */ + /* we have at least one @, else we are not in this branch */ + saveddelim1 = *delimp1; /* save char after token */ *delimp1 = '\0'; /* delimit token with \0 */ - if (id != NULL) - { - /* now remove trailing white space chars from id */ - if ((delimp2 = strpbrk(id, " \t\n")) != NULL ) { - saveddelim2 = *delimp2; - *delimp2 = '\0'; - } - atsign = strrchr(user, '@'); - if (atsign) { - *atsign = '\0'; - host = atsign + 1; - } - for (ctl = hostlist; ctl; ctl = ctl->next) { - if (strcasecmp(host, ctl->server.queryname) == 0 + /* now remove trailing white space chars from id */ + if ((delimp2 = strpbrk(id, " \t\n")) != NULL ) { + saveddelim2 = *delimp2; + *delimp2 = '\0'; + } + + atsign = strrchr(user, '@'); + /* we have at least one @, else we are not in this branch */ + *atsign = '\0'; + host = atsign + 1; + + /* find uidl db and save it */ + for (ctl = hostlist; ctl; ctl = ctl->next) { + if (strcasecmp(host, ctl->server.queryname) == 0 && strcasecmp(user, ctl->remotename) == 0) { - - save_str(&ctl->oldsaved, id, UID_SEEN); - break; - } + uid_db_insert(&ctl->oldsaved, id, UID_SEEN); + break; } - /* - * If it's not in a host we're querying, - * save it anyway. Otherwise we'd lose UIDL - * information any time we queried an explicit - * subset of hosts. - */ - if (ctl == (struct query *)NULL) { - /* restore string */ - *delimp1 = saveddelim1; - *atsign = '@'; - if (delimp2 != NULL) { - *delimp2 = saveddelim2; - } - save_str(&scratchlist, buf, UID_SEEN); + } + /* + * If it's not in a host we're querying, + * save it anyway. Otherwise we'd lose UIDL + * information any time we queried an explicit + * subset of hosts. + */ + if (ctl == (struct query *)NULL) { + /* restore string */ + *delimp1 = saveddelim1; + *atsign = '@'; + if (delimp2 != NULL) { + *delimp2 = saveddelim2; } + save_str(&scratchlist, buf, UID_SEEN); } } } @@ -240,283 +250,110 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) if (outlevel >= O_DEBUG) { struct idlist *idp; - int uidlcount = 0; for (ctl = hostlist; ctl; ctl = ctl->next) - if (ctl->server.uidl) { - report_build(stdout, GT_("Old UID list from %s:"), + report_build(stdout, GT_("Old UID list from %s:"), ctl->server.pollname); - for (idp = ctl->oldsaved; idp; idp = idp->next) - report_build(stdout, " %s", (char *)idp->id); - if (!idp) + + if (!uid_db_n_records(&ctl->oldsaved)) report_build(stdout, GT_(" ")); + else + traverse_uid_db(&ctl->oldsaved, dump_saved_uid, NULL); + report_complete(stdout, "\n"); - uidlcount++; } - if (uidlcount) - { - report_build(stdout, GT_("Scratch list of UIDs:")); - for (idp = scratchlist; idp; idp = idp->next) - report_build(stdout, " %s", (char *)idp->id); - if (!idp) + report_build(stdout, GT_("Scratch list of UIDs:")); + if (!scratchlist) report_build(stdout, GT_(" ")); - report_complete(stdout, "\n"); + else for (idp = scratchlist; idp; idp = idp->next) { + char *t = sdump(idp->id, strlen(idp->id)-1); + report_build(stdout, " %s\n", t); + free(t); } + report_complete(stdout, "\n"); } } -#endif /* POP3_ENABLE */ - -/* return a pointer to the last element of the list to help the quick, - * constant-time addition to the list, NOTE: this function does not dup - * the string, the caller must do that. */ -/*@shared@*/ static struct idlist **save_str_quick(/*@shared@*/ struct idlist **idl, - /*@only@*/ char *str, flag status) -/* save a number/UID pair on the given UID list */ -{ - struct idlist **end; - - /* do it nonrecursively so the list is in the right order */ - for (end = idl; *end; end = &(*end)->next) - continue; - - *end = (struct idlist *)xmalloc(sizeof(struct idlist)); - (*end)->val.status.mark = status; - (*end)->id = (unsigned char *)str; - (*end)->next = NULL; - - return end; -} - -/* return the end list element for direct modification */ -struct idlist *save_str(struct idlist **idl, const char *str, flag st) -{ - return *save_str_quick(idl, str ? xstrdup(str) : NULL, st); -} - -void free_str_list(struct idlist **idl) -/* free the given UID list */ -{ - struct idlist *i = *idl; - - while(i) { - struct idlist *t = i->next; - free(i->id); - free(i); - i = t; - } - *idl = 0; -} - -void save_str_pair(struct idlist **idl, const char *str1, const char *str2) -/* save an ID pair on the given list */ -{ - struct idlist **end; - - /* do it nonrecursively so the list is in the right order */ - for (end = idl; *end; end = &(*end)->next) - continue; - - *end = (struct idlist *)xmalloc(sizeof(struct idlist)); - (*end)->id = str1 ? xstrdup(str1) : (char *)NULL; - if (str2) - (*end)->val.id2 = xstrdup(str2); - else - (*end)->val.id2 = (char *)NULL; - (*end)->next = (struct idlist *)NULL; -} - -#ifdef __UNUSED__ -void free_str_pair_list(struct idlist **idl) -/* free the given ID pair list */ -{ - if (*idl == (struct idlist *)NULL) - return; - - free_idpair_list(&(*idl)->next); - free ((*idl)->id); - free ((*idl)->val.id2); - free(*idl); - *idl = (struct idlist *)NULL; -} -#endif - -struct idlist *str_in_list(struct idlist **idl, const char *str, const flag caseblind) -/* is a given ID in the given list? (comparison may be caseblind) */ -{ - struct idlist *walk; - if (caseblind) { - for( walk = *idl; walk; walk = walk->next ) - if( strcasecmp( str, (char *)walk->id) == 0 ) - return walk; - } else { - for( walk = *idl; walk; walk = walk->next ) - if( strcmp( str, (char *)walk->id) == 0 ) - return walk; - } - return NULL; -} -int str_nr_in_list( struct idlist **idl, const char *str ) - /* return the position of str in idl */ +static int mark_as_expunged_if(struct uid_db_record *rec, void *unused) { - int nr; - struct idlist *walk; - if ( !str ) - return -1; - for( walk = *idl, nr = 0; walk; nr ++, walk = walk->next ) - if( strcmp( str, walk->id) == 0 ) - return nr; - return -1; -} - -int str_nr_last_in_list( struct idlist **idl, const char *str) -/* return the last position of str in idl */ -{ - int nr, ret = -1; - struct idlist *walk; - if ( !str ) - return -1; - for( walk = *idl, nr = 0; walk; nr ++, walk = walk->next ) - if( strcmp( str, walk->id) == 0 ) - ret = nr; - return ret; -} + (void)unused; -void str_set_mark( struct idlist **idl, const char *str, const flag val) -/* update the mark on an of an id to given value */ -{ - int nr; - struct idlist *walk; - if (!str) - return; - for(walk = *idl, nr = 0; walk; nr ++, walk = walk->next) - if (strcmp(str, walk->id) == 0) - walk->val.status.mark = val; -} - -int count_list( struct idlist **idl) -/* count the number of elements in the list */ -{ - if( !*idl ) + if (rec->status == UID_DELETED) rec->status = UID_EXPUNGED; return 0; - return 1 + count_list( &(*idl)->next ); } -/*@null@*/ char *str_from_nr_list(struct idlist **idl, long number) -/* return the number'th string in idl */ +/** Assert that all UIDs marked deleted in query \a ctl have actually been +expunged. */ +void expunge_uids(struct query *ctl) { - if( !*idl || number < 0) - return 0; - if( number == 0 ) - return (*idl)->id; - return str_from_nr_list(&(*idl)->next, number-1); + traverse_uid_db(dofastuidl ? &ctl->oldsaved : &ctl->newsaved, + mark_as_expunged_if, NULL); } - -char *str_find(struct idlist **idl, long number) -/* return the id of the given number in the given list. */ +static const char *str_uidmark(int mark) { - if (*idl == (struct idlist *) 0) - return((char *) 0); - else if (number == (*idl)->val.status.num) - return((*idl)->id); - else - return(str_find(&(*idl)->next, number)); + static char buf[20]; + + switch(mark) { + case UID_UNSEEN: + return "UNSEEN"; + case UID_SEEN: + return "SEEN"; + case UID_EXPUNGED: + return "EXPUNGED"; + case UID_DELETED: + return "DELETED"; + default: + if (snprintf(buf, sizeof(buf), "MARK=%d", mark) < 0) + return "ERROR"; + else + return buf; + } } -struct idlist *id_find(struct idlist **idl, long number) -/* return the id of the given number in the given list. */ +static int dump_uid_db_record(struct uid_db_record *rec, void *arg) { - struct idlist *idp; - for (idp = *idl; idp; idp = idp->next) - if (idp->val.status.num == number) - return(idp); - return(0); -} + unsigned *n_recs; + char *t; -char *idpair_find(struct idlist **idl, const char *id) -/* return the id of the given id in the given list (caseblind comparison) */ -{ - if (*idl == (struct idlist *) 0) - return((char *) 0); - else if (strcasecmp(id, (*idl)->id) == 0) - return((*idl)->val.id2 ? (*idl)->val.id2 : (*idl)->id); - else - return(idpair_find(&(*idl)->next, id)); -} + n_recs = (unsigned int *)arg; + --*n_recs; -int delete_str(struct idlist **idl, long num) -/* delete given message from given list */ -{ - struct idlist *idp; + t = sdump(rec->id, rec->id_len); + report_build(stdout, " %s = %s%s", t, str_uidmark(rec->status), *n_recs ? "," : ""); + free(t); - for (idp = *idl; idp; idp = idp->next) - if (idp->val.status.num == num) - { - idp->val.status.mark = UID_DELETED; - return(1); - } - return(0); + return 0; } -struct idlist *copy_str_list(struct idlist *idl) -/* copy the given UID list */ +static void dump_uid_db(struct uid_db *db) { - struct idlist *newnode ; + unsigned n_recs; - if (idl == (struct idlist *)NULL) - return(NULL); - else - { - newnode = (struct idlist *)xmalloc(sizeof(struct idlist)); - memcpy(newnode, idl, sizeof(struct idlist)); - newnode->next = copy_str_list(idl->next); - return(newnode); - } -} - -void append_str_list(struct idlist **idl, struct idlist **nidl) -/* append nidl to idl (does not copy *) */ -{ - if ((*nidl) == (struct idlist *)NULL || *nidl == *idl) - return; - else if ((*idl) == (struct idlist *)NULL) - *idl = *nidl; - else if ((*idl)->next == (struct idlist *)NULL) - (*idl)->next = *nidl; - else if ((*idl)->next != *nidl) - append_str_list(&(*idl)->next, nidl); -} - -#ifdef POP3_ENABLE -void expunge_uids(struct query *ctl) -/* assert that all UIDs marked deleted have actually been expunged */ -{ - struct idlist *idl; + n_recs = uid_db_n_records(db); + if (!n_recs) { + report_build(stdout, GT_(" ")); + return; + } - for (idl = dofastuidl ? ctl->oldsaved : ctl->newsaved; idl; idl = idl->next) - if (idl->val.status.mark == UID_DELETED) - idl->val.status.mark = UID_EXPUNGED; + traverse_uid_db(db, dump_uid_db_record, &n_recs); } -void uid_swap_lists(struct query *ctl) -/* finish a query */ +/** Finish a successful query */ +void uid_swap_lists(struct query *ctl) { /* debugging code */ - if (ctl->server.uidl && outlevel >= O_DEBUG) + if (outlevel >= O_DEBUG) { - struct idlist *idp; - - if (dofastuidl) + if (dofastuidl) { report_build(stdout, GT_("Merged UID list from %s:"), ctl->server.pollname); - else + dump_uid_db(&ctl->oldsaved); + } else { report_build(stdout, GT_("New UID list from %s:"), ctl->server.pollname); - for (idp = dofastuidl ? ctl->oldsaved : ctl->newsaved; idp; idp = idp->next) - report_build(stdout, " %s = %d", (char *)idp->id, idp->val.status.mark); - if (!idp) - report_build(stdout, GT_(" ")); + dump_uid_db(&ctl->newsaved); + } report_complete(stdout, "\n"); } @@ -536,15 +373,10 @@ void uid_swap_lists(struct query *ctl) * with UIDLs from that account in .fetchids, there is no way for * them to ever get garbage-collected. */ - if (ctl->newsaved) + if (uid_db_n_records(&ctl->newsaved)) { - /* old state of mailbox may now be irrelevant */ - struct idlist *temp = ctl->oldsaved; - if (outlevel >= O_DEBUG) - report(stdout, GT_("swapping UID lists\n")); - ctl->oldsaved = ctl->newsaved; - ctl->newsaved = (struct idlist *) NULL; - free_str_list(&temp); + swap_uid_db_data(&ctl->newsaved, &ctl->oldsaved); + clear_uid_db(&ctl->newsaved); } /* in fast uidl, there is no need to swap lists: the old state of * mailbox cannot be discarded! */ @@ -552,44 +384,64 @@ void uid_swap_lists(struct query *ctl) report(stdout, GT_("not swapping UID lists, no UIDs seen this query\n")); } +/** Finish a query which had errors */ void uid_discard_new_list(struct query *ctl) -/* finish a query which had errors */ { /* debugging code */ - if (ctl->server.uidl && outlevel >= O_DEBUG) + if (outlevel >= O_DEBUG) { - struct idlist *idp; - /* this is now a merged list! the mails which were seen in this * poll are marked here. */ report_build(stdout, GT_("Merged UID list from %s:"), ctl->server.pollname); - for (idp = ctl->oldsaved; idp; idp = idp->next) - report_build(stdout, " %s = %d", (char *)idp->id, idp->val.status.mark); - if (!idp) - report_build(stdout, GT_(" ")); + dump_uid_db(&ctl->oldsaved); report_complete(stdout, "\n"); } - if (ctl->newsaved) + if (uid_db_n_records(&ctl->newsaved)) { /* new state of mailbox is not reliable */ if (outlevel >= O_DEBUG) report(stdout, GT_("discarding new UID list\n")); - free_str_list(&ctl->newsaved); - ctl->newsaved = (struct idlist *) NULL; + clear_uid_db(&ctl->newsaved); } } +/** Reset the number associated with each id */ void uid_reset_num(struct query *ctl) -/* reset the number associated with each id */ { - struct idlist *idp; - for (idp = ctl->oldsaved; idp; idp = idp->next) - idp->val.status.num = 0; + reset_uid_db_nums(&ctl->oldsaved); +} + +/** Write list of seen messages, at end of run. */ +static int count_seen_deleted(struct uid_db_record *rec, void *arg) +{ + if (rec->status == UID_SEEN || rec->status == UID_DELETED) + ++*(long *)arg; + return 0; +} + +struct write_saved_info { + struct query *ctl; + FILE *fp; +}; + +static int write_uid_db_record(struct uid_db_record *rec, void *arg) +{ + struct write_saved_info *info; + int rc; + + if (!(rec->status == UID_SEEN || rec->status == UID_DELETED)) + return 0; + + info = (struct write_saved_info *)arg; + rc = fprintf(info->fp, "%s@%s %s\n", + info->ctl->remotename, info->ctl->server.queryname, + rec->id); + return rc < 0 ? -1 : 0; } +/** Write new list of UIDs (state) to \a idfile. */ void write_saved_lists(struct query *hostlist, const char *idfile) -/* perform end-of-run write of seen-messages list */ { long idcount; FILE *tmpfp; @@ -598,41 +450,54 @@ void write_saved_lists(struct query *hostlist, const char *idfile) /* if all lists are empty, nuke the file */ idcount = 0; - for (ctl = hostlist; ctl; ctl = ctl->next) { - for (idp = ctl->oldsaved; idp; idp = idp->next) - if (idp->val.status.mark == UID_SEEN - || idp->val.status.mark == UID_DELETED) - idcount++; - } + for (ctl = hostlist; ctl; ctl = ctl->next) + traverse_uid_db(&ctl->oldsaved, count_seen_deleted, &idcount); /* either nuke the file or write updated last-seen IDs */ if (!idcount && !scratchlist) { - if (outlevel >= O_DEBUG) - report(stdout, GT_("Deleting fetchids file.\n")); - if (unlink(idfile)) - report(stderr, GT_("Error deleting %s: %s\n"), strerror(errno)); + if (outlevel >= O_DEBUG) { + if (access(idfile, F_OK) == 0) + report(stdout, GT_("Deleting fetchids file.\n")); + } + if (unlink(idfile) && errno != ENOENT) + report(stderr, GT_("Error deleting %s: %s\n"), idfile, strerror(errno)); } else { - char *newnam = xmalloc(strlen(idfile) + 2); + char *newnam = (char *)xmalloc(strlen(idfile) + 2); strcpy(newnam, idfile); strcat(newnam, "_"); if (outlevel >= O_DEBUG) report(stdout, GT_("Writing fetchids file.\n")); (void)unlink(newnam); /* remove file/link first */ if ((tmpfp = fopen(newnam, "w")) != (FILE *)NULL) { - int errflg; + struct write_saved_info info; + int errflg = 0; + + info.fp = tmpfp; + for (ctl = hostlist; ctl; ctl = ctl->next) { - for (idp = ctl->oldsaved; idp; idp = idp->next) - if (idp->val.status.mark == UID_SEEN - || idp->val.status.mark == UID_DELETED) - fprintf(tmpfp, "%s@%s %s\n", - ctl->remotename, ctl->server.queryname, (char *)idp->id); + info.ctl = ctl; + + if (traverse_uid_db(&ctl->oldsaved, write_uid_db_record, &info) < 0) { + int e = errno; + report(stderr, GT_("Write error on fetchids file %s: %s\n"), newnam, strerror(e)); + errflg = 1; + goto bailout; + } } + for (idp = scratchlist; idp; idp = idp->next) - fputs(idp->id, tmpfp); - fflush(tmpfp); - errflg = ferror(tmpfp); - fclose(tmpfp); + if (EOF == fputs(idp->id, tmpfp)) { + int e = errno; + report(stderr, GT_("Write error on fetchids file %s: %s\n"), newnam, strerror(e)); + errflg = 1; + goto bailout; + } + +bailout: + (void)fflush(tmpfp); /* return code ignored, we check ferror instead */ + errflg |= ferror(tmpfp); + errflg |= fclose(tmpfp); /* if we could write successfully, move into place; * otherwise, drop */ if (errflg) {