X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=uid.c;h=8163761b1dbc010907f563000e940c3955543c6c;hb=1c7ab650bf7acfdd94ee386c84dccc8d5cb157b6;hp=01b8999c495934c1de8dba958a7311ebe3a5cc48;hpb=473437ebe5a57fc2d4bfd782ecc004394f339033;p=~andy%2Ffetchmail diff --git a/uid.c b/uid.c index 01b8999c..8163761b 100644 --- a/uid.c +++ b/uid.c @@ -1,209 +1,508 @@ -/* Copyright 1993-95 by Carl Harris, Jr. Copyright 1996 by Eric S. Raymond - * All rights reserved. +/* + * uid.c -- UIDL handling for POP3 servers without LAST + * * For license terms, see the file COPYING in this directory. */ -/*********************************************************************** - module: uid.c - project: fetchmail - programmer: Eric S. Raymond - description: UID list handling - - ***********************************************************************/ - -#include +#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-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. (This would be bad. Server-side - * seen bits are better than UIDs, because they track messages seen by - * *all* clients.) + * revision of IMAP forces me to. + * + * These functions are also used by the rest of the code to maintain + * string lists. * * Here's the theory: * * At start of a query, we have a (possibly empty) list of UIDs to be - * considered `already seen'. These are messages that were left in + * considered seen in `oldsaved'. These are messages that were left in * the mailbox and *not deleted* on previous queries (we don't need to - * remember the UIDs of deleted messages because ... well, they're gone!). - * This list is set up by initialized_saved_list() from the .fetchids - * file and hangs off the host's `saved' member. + * remember the UIDs of deleted messages because ... well, they're gone!) + * This list is initially set up by initialize_saved_list() from the + * .fetchids file. * * Early in the query, during the execution of the protocol-specific - * getrange code, the driver expects that the host's `listed' member + * getrange code, the driver expects that the host's `newsaved' member * will be filled with a list of UIDs and message numbers representing - * the current mailbox state. If this list is empty, the server did + * the mailbox state. If this list is empty, the server did * not respond to the request for a UID listing. * * Each time a message is fetched, we can check its UID against the - * `saved' list to see if it is old. If not, it should be downloaded - * (and possibly deleted). It should be downloaded anyway if --all - * is on. It should not be deleted if --keep is on. + * `oldsaved' list to see if it is old. * - * Each time a message is deleted, we remove its id from the `listed' - * member. + * Each time a message-id is seen, we mark it with MARK_SEEN. * - * At the end of the query, whatever remains in the `listed' member - * (because it was not deleted) becomes the `saved' list. The old - * `saved' list is freed. + * Each time a message is deleted, we mark its id UID_DELETED in the + * `newsaved' member. When we want to assert that an expunge has been + * done on the server, we call expunge_uid() to register that all + * deleted messages are gone by marking them UID_EXPUNGED. + * + * At the end of the query, the `newsaved' member becomes the + * `oldsaved' list. The old `oldsaved' list is freed. + * + * At the end of the fetchmail run, seen and non-EXPUNGED members of all + * current `oldsaved' lists are flushed out to the .fetchids file to + * be picked up by the next run. If there are no un-expunged + * messages, the file is deleted. + * + * Note: some comparisons (those used for DNS address lists) are caseblind! */ /* UIDs associated with un-queried hosts */ static struct idlist *scratchlist; -void initialize_saved_lists(hostlist, idfile) +#ifdef POP3_ENABLE +void initialize_saved_lists(struct query *hostlist, const char *idfile) /* read file of saved IDs and attach to each host */ -struct hostrec *hostlist; -char *idfile; { - int st; + struct stat statbuf; FILE *tmpfp; - struct hostrec *hostp; + struct query *ctl; /* make sure lists are initially empty */ - for (hostp = hostlist; hostp; hostp = hostp->next) - hostp->saved = hostp->current = (struct idlist *)NULL; + for (ctl = hostlist; ctl; ctl = ctl->next) + ctl->skipped = ctl->oldsaved = ctl->newsaved = (struct idlist *)NULL; + + errno = 0; + + /* + * Croak if the uidl directory does not exist. + * This probably means an NFS mount failed and we can't + * see a uidl file that ought to be there. + * Question: is this a portable check? It's not clear + * that all implementations of lstat() will return ENOTDIR + * rather than plain ENOENT in this case... + */ + if (lstat(idfile, &statbuf) < 0) { + if (errno == ENOTDIR) + { + report(stderr, _("lstat: %s: %s\n"), idfile, strerror(errno)); + exit(PS_IOERR); + } + } /* let's get stored message UIDs from previous queries */ - if ((tmpfp = fopen(idfile, "r")) != (FILE *)NULL) { - char buf[POPBUFSIZE+1], host[HOSTLEN+1], id[IDLEN+1]; + if ((tmpfp = fopen(idfile, "r")) != (FILE *)NULL) + { + char buf[POPBUFSIZE+1]; + char *host = NULL; /* pacify -Wall */ + char *user; + char *id; + char *atsign; /* temp pointer used in parsing user and host */ + char *delimp1; + char saveddelim1; + char *delimp2; + char saveddelim2 = '\0'; /* pacify -Wall */ while (fgets(buf, POPBUFSIZE, tmpfp) != (char *)NULL) { - if ((st = sscanf(buf, "%s %s\n", host, id)) == 2) + /* + * 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 + * '@' in the UIDL. + */ + + /* first, skip leading spaces */ + user = buf + strspn(buf, " \t"); + + /* + * First, we split the buf into a userhost part and an id + * part ... but id doesn't necessarily start with a '<', + * espescially if the POP server returns an X-UIDL header + * instead of a Message-ID, as GMX's (www.gmx.net) POP3 + * StreamProxy V1.0 does. + */ + if ((id = strchr(user, ' ')) != NULL ) { - for (hostp = hostlist; hostp; hostp = hostp->next) - { - if (strcmp(host, hostp->servername) == 0) - { - save_uid(&hostp->saved, -1, id); + for (delimp1 = id; delimp1 >= user; delimp1--) + 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() :-) + */ + id = id + strspn(id, " "); + + delimp1++; /* but what if there is only white space ?!? */ + 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 (ctl->server.truename && + strcasecmp(host, ctl->server.truename) == 0 + && strcasecmp(user, ctl->remotename) == 0) { + + save_str(&ctl->oldsaved, id, UID_SEEN); + break; + } + } + /* if it's not in a host we're querying, + ** save it anyway */ + if (ctl == (struct query *)NULL) { + /* restore string */ + *delimp1 = saveddelim1; + *atsign = '@'; + if (delimp2 != NULL) { + *delimp2 = saveddelim2; + } + save_str(&scratchlist, buf, UID_SEEN); } } + } + } + fclose(tmpfp); /* not checking should be safe, mode was "r" */ + } + + if (outlevel >= O_DEBUG) + { + struct idlist *idp; + int uidlcount = 0; - /* if it's not in a host we're querying, save it anyway */ - if (hostp == (struct hostrec *)NULL) - save_uid(&scratchlist, -1, buf); + for (ctl = hostlist; ctl; ctl = ctl->next) + if (ctl->server.uidl) + { + report_build(stdout, _("Old UID list from %s:"), + ctl->server.pollname); + for (idp = ctl->oldsaved; idp; idp = idp->next) + report_build(stdout, " %s", idp->id); + if (!idp) + report_build(stdout, _(" ")); + report_complete(stdout, "\n"); + uidlcount++; } + + if (uidlcount) + { + report_build(stdout, _("Scratch list of UIDs:")); + for (idp = scratchlist; idp; idp = idp->next) + report_build(stdout, " %s", idp->id); + if (!idp) + report_build(stdout, _(" ")); + report_complete(stdout, "\n"); } - fclose(tmpfp); } } +#endif /* POP3_ENABLE */ -void save_uid(idl, num, str) +struct idlist *save_str(struct idlist **idl, const char *str, flag status) /* save a number/UID pair on the given UID list */ -struct idlist **idl; -int num; -char *str; { - struct idlist *new; + struct idlist **end; + + /* do it nonrecursively so the list is in the right order */ + for (end = idl; *end; end = &(*end)->next) + continue; - new = (struct idlist *)xmalloc(sizeof(struct idlist)); - new->num = num; - new->id = strdup(str); - new->next = *idl; - *idl = new; + *end = (struct idlist *)xmalloc(sizeof(struct idlist)); + (*end)->val.status.mark = status; + (*end)->id = str ? xstrdup(str) : (char *)NULL; + (*end)->next = NULL; + + return(*end); } -void free_uid_list(idl) +void free_str_list(struct idlist **idl) /* free the given UID list */ -struct idlist **idl; { if (*idl == (struct idlist *)NULL) return; - free_uid_list(&(*idl)->next); + free_str_list(&(*idl)->next); free ((*idl)->id); free(*idl); *idl = (struct idlist *)NULL; } -int uid_in_list(idl, str) -/* is a given ID in the given list? */ -struct idlist **idl; -char *str; +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 + +int str_in_list(struct idlist **idl, const char *str, const flag caseblind) +/* is a given ID in the given list? (comparison may be caseblind) */ +{ + if (*idl == (struct idlist *)NULL || str == (char *) NULL) return(0); - else if (strcmp(str, (*idl)->id) == 0) + else if (!caseblind && strcmp(str, (*idl)->id) == 0) + return(1); + else if (caseblind && strcasecmp(str, (*idl)->id) == 0) return(1); else - return(uid_in_list(&(*idl)->next, str)); + return(str_in_list(&(*idl)->next, str, caseblind)); } -int delete_uid(idl, str) -/* delete given UID from given list */ -struct idlist **idl; -char *str; +int str_nr_in_list( struct idlist **idl, const char *str ) + /* return the position of str in idl */ { - if (*idl == (struct idlist *)NULL) - return(0); - else if (strcmp((*idl)->id, str) == 0) - { - struct idlist *next = (*idl)->next; + 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; +} - free ((*idl)->id); - free(*idl); - *idl = next; - 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 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 ) + return 0; + return 1 + count_list( &(*idl)->next ); +} + +char *str_from_nr_list(struct idlist **idl, int number) +/* return the number'th string in idl */ +{ + if( !*idl || number < 0) + return 0; + if( number == 0 ) + return (*idl)->id; + return str_from_nr_list(&(*idl)->next, number-1); +} + + +char *str_find(struct idlist **idl, int number) +/* return the id of the given number in the given list. */ +{ + if (*idl == (struct idlist *) 0) + return((char *) 0); + else if (number == (*idl)->val.status.num) + return((*idl)->id); else - return(delete_uid(&(*idl)->next, str)); + return(str_find(&(*idl)->next, number)); +} + +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)); +} + +int delete_str(struct idlist **idl, int num) +/* delete given message from given list */ +{ + struct idlist *idp; + + for (idp = *idl; idp; idp = idp->next) + if (idp->val.status.num == num) + { + idp->val.status.mark = UID_DELETED; + return(1); + } return(0); } -void update_uid_lists(hostp) -/* perform end-of-query actions on UID lists */ -struct hostrec *hostp; +struct idlist *copy_str_list(struct idlist *idl) +/* copy the given UID list */ +{ + struct idlist *newnode ; + + 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; + + for (idl = ctl->newsaved; idl; idl = idl->next) + if (idl->val.status.mark == UID_DELETED) + idl->val.status.mark = UID_EXPUNGED; +} + +void uid_swap_lists(struct query *ctl) +/* finish a query */ +{ + /* debugging code */ + if (ctl->server.uidl && outlevel >= O_DEBUG) + { + struct idlist *idp; + + report_build(stdout, _("New UID list from %s:"), ctl->server.pollname); + for (idp = ctl->newsaved; idp; idp = idp->next) + report_build(stdout, " %s = %d", idp->id, idp->val.status.mark); + if (!idp) + report_build(stdout, _(" ")); + report_complete(stdout, "\n"); + } + /* - * Replace `saved' list with `current' list as modified by deletions. + * Don't swap UID lists unless we've actually seen UIDLs. + * This is necessary in order to keep UIDL information + * from being heedlessly deleted later on. */ - free_uid_list(&hostp->saved); - hostp->saved = hostp->current; + if (ctl->newsaved) + { + /* old state of mailbox may now be irrelevant */ + if (outlevel >= O_DEBUG) + report(stdout, _("swapping UID lists\n")); + free_str_list(&ctl->oldsaved); + free_str_list(&scratchlist); + ctl->oldsaved = ctl->newsaved; + ctl->newsaved = (struct idlist *) NULL; + } + else if (outlevel >= O_DEBUG) + report(stdout, _("not swapping UID lists, no UIDs seen this query\n")); } -void write_saved_lists(hostlist, idfile) -struct hostrec *hostlist; -char *idfile; +void write_saved_lists(struct query *hostlist, const char *idfile) +/* perform end-of-run write of seen-messages list */ { - int st, idcount; + int idcount; FILE *tmpfp; - struct hostrec *hostp; + struct query *ctl; struct idlist *idp; /* if all lists are empty, nuke the file */ idcount = 0; - for (hostp = hostlist; hostp; hostp = hostp->next) { - if (hostp->saved) - idcount++; + 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++; } /* either nuke the file or write updated last-seen IDs */ - if (!idcount) + if (!idcount && !scratchlist) + { + if (outlevel >= O_DEBUG) + report(stdout, _("Deleting fetchids file.\n")); unlink(idfile); + } else + { + if (outlevel >= O_DEBUG) + report(stdout, _("Writing fetchids file.\n")); if ((tmpfp = fopen(idfile, "w")) != (FILE *)NULL) { - for (hostp = hostlist; hostp; hostp = hostp->next) { - for (idp = hostp->saved; idp; idp = idp->next) - fprintf(tmpfp, "%s %s\n", hostp->servername, idp->id); + 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.truename, idp->id); } for (idp = scratchlist; idp; idp = idp->next) fputs(idp->id, tmpfp); fclose(tmpfp); } + } } +#endif /* POP3_ENABLE */ + +/* uid.c ends here */