/* ** Copyright 2003-2007 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "maildir/config.h" #include "maildir/maildircreate.h" #include "maildir/maildirmisc.h" #include "maildir/maildirwatch.h" #include "numlib/numlib.h" #if HAVE_UNISTD_H #include #endif #include "maildirkeywords.h" #if HAVE_DIRENT_H #include #define NAMLEN(dirent) strlen((dirent)->d_name) #else #define dirent direct #define NAMLEN(dirent) (dirent)->d_namlen #if HAVE_SYS_NDIR_H #include #endif #if HAVE_SYS_DIR_H #include #endif #if HAVE_NDIR_H #include #endif #endif int libmail_kwEnabled=1; struct keywordUpdateInfo { size_t highestN; size_t highestT; size_t totalCnt; int foundNewest; }; static void scan_updates(const char *dir, time_t t, time_t tn, struct maildir_kwReadInfo *info, struct keywordUpdateInfo **updateInfo); static void read_updates(const char *dir, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo); static void purge_old_updates(const char *dir, time_t tn, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo); static int save_updated_keywords(const char *maildir, const char *kwname, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo); static void doReadKeywords2(const char *maildir, const char *dir, struct maildir_kwReadInfo *rki); struct readLine { char *lineBuf; size_t lineBufSize; int errflag; }; static void rl_init(struct readLine *p) { p->lineBuf=NULL; p->lineBufSize=0; p->errflag=0; } static void rl_free(struct readLine *p) { if (p->lineBuf) free(p->lineBuf); } static char *rl_read(struct readLine *p, FILE *f) { size_t n=0; int ch; for (;;) { if (n >= p->lineBufSize) { size_t o= n + 1024; char *b= p->lineBuf ? realloc(p->lineBuf, o):malloc(o); if (!b) { p->errflag=1; return NULL; } p->lineBuf=b; p->lineBufSize=o; } if ((ch=getc(f)) == EOF || ch == '\n') break; p->lineBuf[n++]=(char)ch; } p->lineBuf[n]=0; return n == 0 && ch == EOF ? NULL:p->lineBuf; } int maildir_kwRead(const char *maildir, struct maildir_kwReadInfo *rki) { char *p=malloc(strlen(maildir)+sizeof("/" KEYWORDDIR)); if (!p) return -1; strcat(strcpy(p, maildir), "/" KEYWORDDIR); rki->errorOccured=0; doReadKeywords2(maildir, p, rki); free(p); return rki->errorOccured; } static void doReadKeywords2(const char *maildir, const char *dir, struct maildir_kwReadInfo *rki) { FILE *fp; char *kwname=malloc(strlen(dir)+sizeof("/:list")); struct keywordUpdateInfo *updateInfo; time_t t=time(NULL); time_t tn=t/300; rki->updateNeeded=0; rki->tryagain=0; if (!kwname) { rki->errorOccured= -1; return; } strcat(strcpy(kwname, dir), "/:list"); fp=fopen(kwname, "r"); if (!fp) /* Maybe the keyword directory needs creating? */ { struct stat stat_buf; mkdir(dir, 0700); /* Give it same mode as the maildir */ if (stat(maildir, &stat_buf) == 0) chmod(dir, stat_buf.st_mode & 0777); } /* ** If keywords are disabled, we still need to create the keyword ** directory, otherwise FAM-based IDLE will not work. */ if (!libmail_kwEnabled) { if (fp) fclose(fp); free(kwname); return; } if (fp && maildir_kwImport(fp, rki)) rki->updateNeeded=1; if (fp) fclose(fp); updateInfo=malloc(sizeof(struct keywordUpdateInfo) *( 1+(*rki->getMessageCount)(rki->voidarg))); if (!updateInfo) { free(kwname); return; } scan_updates(dir, t, tn, rki, &updateInfo); read_updates(dir, rki, updateInfo); if (!rki->tryagain && !rki->errorOccured) { if (save_updated_keywords(maildir, kwname, rki, updateInfo) == 0 && !rki->errorOccured) purge_old_updates(dir, tn, rki, updateInfo); } free(updateInfo); free(kwname); } int maildir_kwImport(FILE *fp, struct maildir_kwReadInfo *rki) { struct keywordIndex { struct keywordIndex *next; struct libmail_keywordEntry *kw; } *firstKw=NULL, *lastKw=NULL, **index; size_t numKw=0; struct libmail_kwMessage *tmpMsg; char *p; int rc=0; struct readLine rl; if ((tmpMsg=libmail_kwmCreate()) == NULL) { rki->errorOccured=-1; return 0; } rl_init(&rl); while ((p=rl_read(&rl, fp)) != NULL && *p) { struct keywordIndex *ki=malloc(sizeof(*firstKw)); if (!ki) { rki->errorOccured=-1; rl_free(&rl); libmail_kwmDestroy(tmpMsg); return 0; } if (lastKw) lastKw->next=ki; else firstKw=ki; lastKw=ki; ki->next=NULL; ++numKw; ki->kw=libmail_kweFind((*rki->getKeywordHashtable) (rki->voidarg), p, 1); if (libmail_kwmSet(tmpMsg, ki->kw) < 0) { rki->errorOccured= -1; break; } } if (rki->errorOccured || (index=malloc(sizeof(*firstKw)*(numKw+1))) == NULL) { while ((lastKw=firstKw) != NULL) { firstKw=firstKw->next; free(lastKw); } libmail_kwmDestroy(tmpMsg); rki->errorOccured= -1; rl_free(&rl); return 0; } numKw=0; while (firstKw) { index[numKw]=firstKw; ++numKw; firstKw=firstKw->next; } index[numKw]=0; if (p) while ((p=rl_read(&rl, fp)) != NULL) { char *q=strchr(p, ':'); struct libmail_kwMessage **i; size_t l; size_t n; if (!q) { rc=1; continue; /* Crap */ } *q++=0; i= (*rki->findMessageByFilename)(p, 0, &n, rki->voidarg); if (!i) { rc=1; continue; /* Stale data */ } if (*i) /* Already have it */ libmail_kwmDestroy(*i); (*i)=NULL; i=NULL; while ((q=strtok(q, " ")) != NULL) { l=atoi(q); q=NULL; if (l < numKw) { if (!i) i= (*rki-> findMessageByFilename) (p, 1, &n, rki->voidarg); /* Autocreate it */ if (*i == NULL) /* ENOMEM */ { rc= -1; break; } if (libmail_kwmSet(*i, index[l]->kw) < 0) rki->errorOccured= -1; } } } rl_free(&rl); for (numKw=0; index[numKw]; numKw++) free(index[numKw]); free(index); libmail_kwmDestroy(tmpMsg); return rc; } /* See the README */ static void scan_updates(const char *dir, time_t t, time_t tn, struct maildir_kwReadInfo *info, struct keywordUpdateInfo **updateInfo) { DIR *dirp; struct dirent *de; unsigned long i; size_t n= (*info->getMessageCount)(info->voidarg); for (i=0; id_name[0] == ':') continue; if (de->d_name[0] != '.') i=(*info->findMessageByFilename) (de->d_name, 0, &in, info->voidarg); else if ((x=libmail_atotime_t(de->d_name+1)) == 0) { if (strncmp(de->d_name, ".tmp.", 5) == 0) { if ((x=libmail_atotime_t(de->d_name+5)) != 0 && x >= t - 120) continue; /* New scratch file */ p=malloc(strlen(dir)+strlen(de->d_name)+2); if (!p) { closedir(dirp); info->errorOccured= -1; return; } strcat(strcat(strcpy(p, dir), "/"), de->d_name); unlink(p); free(p); } continue; } else { const char *p=strchr(de->d_name+1, '.'); if (!p) continue; i=(*info->findMessageByFilename) (p+1, 0, &in, info->voidarg); } p=malloc(strlen(dir)+strlen(de->d_name)+2); if (!p) { closedir(dirp); info->errorOccured= -1; return; } strcat(strcat(strcpy(p, dir), "/"), de->d_name); if (!i) { struct stat stat_buf; if (stat(p, &stat_buf) == 0 && stat_buf.st_mtime < t - 15 * 60) unlink(p); free(p); continue; } free(p); if (in >= n) { /* libmail_kwgReadMaildir autocrerate */ struct keywordUpdateInfo *u= realloc(*updateInfo, sizeof(**updateInfo) * (in+1)); if (!u) { closedir(dirp); info->errorOccured= -1; return; } *updateInfo=u; while (n <= in) { (*updateInfo)[n].highestN=0; (*updateInfo)[n].highestT=0; (*updateInfo)[n].totalCnt=0; (*updateInfo)[n].foundNewest=0; ++n; } } if (de->d_name[0] != '.') { x=tn+1; (*updateInfo)[in].foundNewest=1; } ++(*updateInfo)[in].totalCnt; if (x >= tn) { if (x > (*updateInfo)[in].highestT) (*updateInfo)[in].highestT=x; } else if ((*updateInfo)[in].highestN < x) { if ((*updateInfo)[in].highestN > 0) { char b[NUMBUFSIZE]; char *r; libmail_str_size_t((*updateInfo)[in].highestN, b); r=de->d_name; if (*r == '.') r=strchr(r+1, '.')+1; q=malloc(strlen(dir)+strlen(r)+ strlen(b)+4); if (!q) { closedir(dirp); info->errorOccured= -1; return; } sprintf(q, "%s/.%s.%s", dir, b, r); unlink(q); free(q); } (*updateInfo)[in].highestN=x; } } if (dirp) closedir(dirp); } static void read_updates(const char *dir, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo) { unsigned long i; size_t msgCnt= (*info->getMessageCount)(info->voidarg); struct readLine rl; struct libmail_kwHashtable *h= (*info->getKeywordHashtable)(info->voidarg); rl_init(&rl); for (i=0; igetMessageFilename)(i, info->voidarg); if (!fn) continue; /* Shouldn't happen */ p=malloc(strlen(dir)+strlen(b)+strlen(fn)+4); if (!p) { info->errorOccured= -1; rl_free(&rl); return; } if (updateInfo[i].foundNewest) sprintf(p, "%s/%s", dir, fn); else sprintf(p, "%s/.%s.%s", dir, b, fn); q=strrchr(strrchr(p, '/'), MDIRSEP[0]); if (q) *q=0; fp=fopen(p, "r"); free(p); if (!fp) { if (errno == ENOENT) { info->tryagain=1; rl_free(&rl); return; } continue; } if ((km=libmail_kwmCreate()) == NULL) { fclose(fp); info->errorOccured= -1; rl_free(&rl); return; } while ((p=rl_read(&rl, fp)) != NULL && *p) if (libmail_kwmSetName(h, km, p) < 0) { fclose(fp); info->errorOccured= -1; rl_free(&rl); return; } if (km->firstEntry == NULL) { oldKm= (*info->findMessageByIndex)(i, 0, info->voidarg); if (oldKm && *oldKm) { info->updateNeeded=1; libmail_kwmDestroy(*oldKm); *oldKm=NULL; } libmail_kwmDestroy(km); } else { oldKm= (*info->findMessageByIndex)(i, 1, info->voidarg); if (oldKm && *oldKm == NULL) { info->updateNeeded=1; (*info->updateKeywords)(i, km, info->voidarg); } else if (!oldKm /* Shouldn't happen */ || libmail_kwmCmp(*oldKm, km) == 0) { libmail_kwmDestroy(km); } else { info->updateNeeded=1; (*info->updateKeywords)(i, km, info->voidarg); } } fclose(fp); } rl_free(&rl); } struct saveUpdateInfo { FILE *fp; unsigned long counter; }; static int saveKeywordList(struct libmail_keywordEntry *ke, void *vp) { struct saveUpdateInfo *sui=(struct saveUpdateInfo *)vp; fprintf(sui->fp, "%s\n", keywordName(ke)); ke->u.userNum= sui->counter; ++sui->counter; return 0; } int maildir_kwExport(FILE *fp, struct maildir_kwReadInfo *info) { struct saveUpdateInfo sui; size_t i, n; sui.fp=fp; sui.counter=0; libmail_kwEnumerate((*info->getKeywordHashtable)(info->voidarg), saveKeywordList, &sui); if (sui.counter == 0) /* No keywords set for any message */ return 0; fprintf(fp, "\n"); n= (*info->getMessageCount)(info->voidarg); for (i=0; ifindMessageByIndex)(i, 0, info->voidarg); struct libmail_kwMessageEntry *kme; if (!km || !*km || (*km)->firstEntry == NULL) continue; for (p= (*info->getMessageFilename)(i, info->voidarg); *p && *p != MDIRSEP[0]; p++) putc(*p, fp); putc(':', fp); p=""; for (kme=(*km)->firstEntry; kme; kme=kme->next) { fprintf(fp, "%s%lu", p, kme->libmail_keywordEntryPtr ->u.userNum); p=" "; } fprintf(fp, "\n"); } return 1; } static int save_updated_keywords(const char *maildir, const char *kwname, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo) { struct maildir_tmpcreate_info createInfo; FILE *fp; if (!info->updateNeeded) return 0; maildir_tmpcreate_init(&createInfo); createInfo.maildir=maildir; createInfo.hostname=getenv("HOSTNAME"); createInfo.doordie=1; if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) { perror("maildir_tmpcreate_fp"); return -1; } if (maildir_kwExport(fp, info) == 0) { fclose(fp); unlink(createInfo.tmpname); maildir_tmpcreate_free(&createInfo); unlink(kwname); return 0; } if (fflush(fp) < 0 || ferror(fp)) { fclose(fp); unlink(createInfo.tmpname); perror(createInfo.tmpname); maildir_tmpcreate_free(&createInfo); return -1; } fclose(fp); if (rename(createInfo.tmpname, kwname)) { perror(createInfo.tmpname); unlink(createInfo.tmpname); perror(createInfo.tmpname); maildir_tmpcreate_free(&createInfo); return -1; } maildir_tmpcreate_free(&createInfo); return 0; } static void purge_old_updates(const char *dir, time_t tn, struct maildir_kwReadInfo *info, struct keywordUpdateInfo *updateInfo) { size_t i; time_t x; size_t n= (*info->getMessageCount)(info->voidarg); for (i=0; igetMessageFilename)(i, info->voidarg); if (!fn) continue; /* Shouldn't happen */ if (updateInfo[i].foundNewest) { size_t l; x=tn+1; libmail_str_size_t(x, b); l=strlen(dir)+strlen(b)+strlen(fn)+4; p=malloc(l); if (!p) { info->errorOccured= -1; return; } q=malloc(l); if (!q) { free(p); info->errorOccured= -1; return; } sprintf(p, "%s/%s", dir, fn); sprintf(q, "%s/.%s.%s", dir, b, fn); c=strrchr(strrchr(p, '/'), MDIRSEP[0]); if (c) *c=0; c=strrchr(strrchr(q, '/'), MDIRSEP[0]); if (c) *c=0; rename(p, q); free(q); free(p); continue; } if (! (updateInfo[i].totalCnt == 1 && updateInfo[i].highestN)) continue; libmail_str_size_t(updateInfo[i].highestN, b); p=malloc(strlen(dir)+strlen(b)+strlen(fn)+4); if (!p) { info->errorOccured= -1; return; } sprintf(p, "%s/.%s.%s", dir, b, fn); q=strrchr(strrchr(p, '/'), MDIRSEP[0]); if (q) *q=0; unlink(p); free(p); } } /***************/ static int maildir_kwSaveCommon(const char *maildir, const char *filename, struct libmail_kwMessage *newKeyword, const char **newKeywordArray, char **tmpname, char **newname, int tryAtomic); int maildir_kwSave(const char *maildir, const char *filename, struct libmail_kwMessage *newKeyword, char **tmpname, char **newname, int tryAtomic) { return maildir_kwSaveCommon(maildir, filename, newKeyword, NULL, tmpname, newname, tryAtomic); } int maildir_kwSaveArray(const char *maildir, const char *filename, const char **flags, char **tmpname, char **newname, int tryAtomic) { return maildir_kwSaveCommon(maildir, filename, NULL, flags, tmpname, newname, tryAtomic); } static int maildir_kwSaveCommon(const char *maildir, const char *filename, struct libmail_kwMessage *newKeyword, const char **newKeywordArray, char **tmpname, char **newname, int tryAtomic) { struct maildir_tmpcreate_info createInfo; FILE *fp; char *n=malloc(strlen(maildir)+strlen(filename)+10+ sizeof(KEYWORDDIR)), *p; struct libmail_kwMessageEntry *kme; if (!n) return -1; strcat(strcat(strcpy(n, maildir), "/" KEYWORDDIR "/"), filename); p=strrchr(strrchr(n, '/'), MDIRSEP[0]); if (p) *p=0; maildir_tmpcreate_init(&createInfo); createInfo.maildir=maildir; createInfo.msgsize=0; createInfo.hostname=getenv("HOSTNAME"); createInfo.doordie=1; if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) { free(n); return -1; } if (newKeywordArray) { size_t i; for (i=0; newKeywordArray[i]; i++) fprintf(fp, "%s\n", newKeywordArray[i]); } else for (kme=newKeyword ? newKeyword->firstEntry:NULL; kme; kme=kme->next) fprintf(fp, "%s\n", keywordName(kme->libmail_keywordEntryPtr)); errno=EIO; if (fflush(fp) < 0 || ferror(fp)) { fclose(fp); free(n); return -1; } fclose(fp); *tmpname=createInfo.tmpname; createInfo.tmpname=NULL; *newname=n; maildir_tmpcreate_free(&createInfo); if (tryAtomic) { char timeBuf[NUMBUFSIZE]; char *n=malloc(strlen(*tmpname) + sizeof(KEYWORDDIR) + NUMBUFSIZE+10); if (!n) { free(*tmpname); free(*newname); return -1; } strcat(strcat(strcat(strcpy(n, maildir), "/" KEYWORDDIR "/.tmp."), libmail_str_time_t(time(NULL), timeBuf)), strrchr( *tmpname, '/')+1); if (rename( *tmpname, n) < 0) { free(n); free(*tmpname); free(*newname); return -1; } free (*tmpname); *tmpname=n; } return 0; }