/* ** Copyright 1998 - 2005 Double Precision, Inc. ** See COPYING for distribution information. */ #include "courier.h" #include "comctlfile.h" #include "comqueuename.h" #include "comstrtimestamp.h" #include "comtrack.h" #include "maxlongsize.h" #include #include #if HAVE_SYS_STAT_H #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_FCNTL_H #include #endif #include #include #include static const char rcsid[]="$Id: comctlfile.c,v 1.11 2005/02/04 02:44:59 mrsam Exp $"; int ctlfile_openi(ino_t i, struct ctlfile *p, int ro) { return (ctlfile_openfn(qmsgsctlname(i), p, ro, 1)); } int ctlfile_openit(ino_t i, time_t t, struct ctlfile *p, int ro) { return (ctlfile_openfn(qmsgqname(i, t), p, ro, 1)); } int ctlfile_openfn(const char *n, struct ctlfile *p, int ro, int chk) { struct stat stat_buf; unsigned nlines=2, nreceipients=1; char *cp; unsigned i, j, rcnt, orcnt, dsncnt, nrcnt; char *q; static char deferred_status[2]={COMCTLFILE_DELDEFERRED, 0}; p->cancelled=0; if ((p->fd=open(n, ro ? O_RDONLY:O_RDWR | O_APPEND)) < 0) return (-1); p->msgsize=0; #ifdef FD_CLOEXEC fcntl(p->fd, F_SETFD, FD_CLOEXEC); #endif if (fstat(p->fd, &stat_buf) < 0) { close (p->fd); return (-1); } if (chk && stat_buf.st_nlink != 2) { if (ro) { close(p->fd); errno=0; return (-1); } if (stat_buf.st_nlink == 1) /* ** Happens all the time - delivery complete, hanging ** link */ { unlink(n); close(p->fd); errno=0; return (-1); } clog_msg_start_err(); clog_msg_str("Invalid number of links for "); clog_msg_str(n); clog_msg_send(); close(p->fd); errno=EINVAL; return (-1); } p->contents=q=courier_malloc(stat_buf.st_size+1); i=stat_buf.st_size; while (i) { j=read(p->fd, q, i); if (j <= 0) { close(p->fd); free(p->contents); return (-1); } q += j; i -= j; } *q=0; p->n=stat_buf.st_ino; p->mtime=stat_buf.st_mtime; p->starttime=0; for (cp=p->contents; *cp; cp++) { if (*cp == '\r') *cp=' '; if (*cp == '\n') ++nlines; } p->lines=(char **)courier_malloc(nlines * sizeof(char *)); for (nlines=0, cp=p->contents; (cp=strtok(cp, "\n")) != 0; cp=0) { p->lines[nlines++]=cp; if (*cp == COMCTLFILE_RECEIPIENT) ++nreceipients; if (*cp == COMCTLFILE_CANCEL_MSG) p->cancelled=1; } p->lines[nlines]=0; p->sender=""; p->receipients=(char **)courier_malloc(nreceipients*sizeof(char *)); p->oreceipients=(char **)courier_malloc(nreceipients*sizeof(char *)); p->dsnreceipients=(char **)courier_malloc(nreceipients*sizeof(char *)); p->delstatus=(char **)courier_malloc(nreceipients*sizeof(char *)); for (i=0; ireceipients[i]=p->oreceipients[i]=p->dsnreceipients[i]=0; p->delstatus[i]=deferred_status; } for (i=0, nrcnt=0, rcnt=0, orcnt=0, dsncnt=0; p->lines[i]; i++) { switch (p->lines[i][0]) { case COMCTLFILE_SENDER: p->sender=p->lines[i]+1; break; case COMCTLFILE_RECEIPIENT: p->receipients[rcnt++]=p->lines[i]+1; break; case COMCTLFILE_ORECEIPIENT: if (orcnt < nreceipients) p->oreceipients[orcnt++]=p->lines[i]+1; break; case COMCTLFILE_DSN: if (dsncnt < nreceipients) p->dsnreceipients[dsncnt++]=p->lines[i]+1; break; case COMCTLFILE_DELSUCCESS: case COMCTLFILE_DELFAIL: j=atoi(p->lines[i]+1); if (j < nreceipients) p->delstatus[j]=p->lines[i]; break; } } p->nreceipients=rcnt; return (0); } void ctlfile_close(struct ctlfile *p) { free( p->oreceipients ); free( p->dsnreceipients ); free( p->receipients ); free( p->lines ); free( p->contents ); free( p->delstatus ); #if EXPLICITSYNC fsync(p->fd); #endif close(p->fd); } int ctlfile_searchfirst(struct ctlfile *p, char c) { int i; for (i=0; p->lines[i]; i++) if (p->lines[i][0] == c) return (i); return (-1); } static void notatomic() { clog_msg_start_err(); clog_msg_str("Write to control file FAILED - not atomic."); clog_msg_send(); exit(1); } void ctlfile_append(struct ctlfile *p, const char *q) { int l=strlen(q); if (write(p->fd, q, l) != l) notatomic(); } void ctlfile_appendv(struct ctlfile *p, const struct iovec *iov, int iovcnt) { ctlfile_appendvfd(p->fd, iov, iovcnt); } void ctlfile_appendvfd(int fd, const struct iovec *iov, int iovcnt) { int l=0; int i; for (i=0; isender); clog_msg_str(">,addr=<"); clog_msg_str(cf->receipients[rcptnum]); clog_msg_str(">: "); clog_msg_str(info); clog_msg_send(); } ctlfile_append_connectioninfofd(cf->fd, rcptnum, infotype, info); } void ctlfile_append_infofd(int fd, unsigned rcptnum, const char *info) { ctlfile_append_connectioninfofd(fd, rcptnum, COMCTLFILE_DELINFO_REPLY, info); } void ctlfile_append_connectioninfofd(int fd, unsigned rcptnum, char infotype, const char *info) { char fmtbuf[MAXLONGSIZE+10]; struct iovec iov[4]; char buf2[2]; sprintf(fmtbuf, "%c%u ", COMCTLFILE_DELINFO, rcptnum); iov[0].iov_base=(caddr_t)fmtbuf; iov[0].iov_len=strlen(fmtbuf); buf2[0]=infotype; buf2[1]=' '; iov[1].iov_base=(caddr_t)buf2; iov[1].iov_len=2; iov[3].iov_base=(caddr_t)"\n"; iov[3].iov_len=1; while (info && *info) { unsigned i; for (i=0; info[i] && info[i] != '\n'; i++) ; iov[2].iov_base=(caddr_t)info; iov[2].iov_len=i; if (i && info[i-1] == '\r') --iov[2].iov_len; ctlfile_appendvfd(fd, iov, 4); info += i; if (*info) ++info; } } /* Record delivery completion status into the log file */ static void ctlfile_append_msgsize(struct ctlfile *); void ctlfile_append_reply(struct ctlfile *cf, unsigned rcptnum, const char *errmsg, char type, const char *delextra) { char type2; if (errmsg && *errmsg) { clog_msg_start_info(); clog_msg_str("id="); clog_msg_msgid(cf); clog_msg_str(",from=<"); clog_msg_str(cf->sender); clog_msg_str(">,addr=<"); clog_msg_str(cf->receipients[rcptnum]); clog_msg_str(">"); if (type == COMCTLFILE_DELSUCCESS || type == COMCTLFILE_DELSUCCESS_NOLOG) { if (cf->msgsize != 0) ctlfile_append_msgsize(cf); clog_msg_str(",success"); } clog_msg_str(": "); clog_msg_str(errmsg); clog_msg_send(); if (type == COMCTLFILE_DELSUCCESS) goto ugly_hack; } clog_msg_start_info(); clog_msg_str("id="); clog_msg_msgid(cf); clog_msg_str(",from=<"); clog_msg_str(cf->sender); clog_msg_str(">,addr=<"); clog_msg_str(cf->receipients[rcptnum]); clog_msg_str(">"); if ((type == COMCTLFILE_DELSUCCESS || type == COMCTLFILE_DELSUCCESS_NOLOG) && cf->msgsize != 0) ctlfile_append_msgsize(cf); clog_msg_str(type == COMCTLFILE_DELSUCCESS || type == COMCTLFILE_DELSUCCESS_NOLOG ? ",status: success": type == COMCTLFILE_DELFAIL || type == COMCTLFILE_DELFAIL_NOTRACK ? ",status: failure": ",status: deferred"); clog_msg_send(); ugly_hack: if (type == COMCTLFILE_DELSUCCESS_NOLOG) { type=COMCTLFILE_DELSUCCESS; errmsg=0; } type2=type; if (type2 == COMCTLFILE_DELFAIL_NOTRACK) type2=COMCTLFILE_DELFAIL; ctlfile_append_replyfd(cf->fd, rcptnum, errmsg, type2, delextra); if (rcptnum < cf->nreceipients && /* Sanity check */ ctlfile_searchfirst(cf, COMCTLFILE_TRACK) >= 0) { time_t timestamp; int results; results=track_find(cf->receipients[rcptnum], ×tamp); switch (type) { case COMCTLFILE_DELFAIL_NOTRACK: break; case COMCTLFILE_DELSUCCESS: if (results == TRACK_ADDRDEFERRED || results == TRACK_ADDRFAILED) track_save(cf->receipients[rcptnum], TRACK_ADDRACCEPTED); break; case COMCTLFILE_DELFAIL: case COMCTLFILE_DELDEFERRED: if (results == TRACK_ADDRDEFERRED || results == TRACK_ADDRFAILED) { if (timestamp >= time(NULL) - (TRACK_NHOURS * 60 * 60)/2) break; /* If already had a failed attempt within ** half the tracking window, no need to ** add another record. */ } track_save(cf->receipients[rcptnum], type == COMCTLFILE_DELFAIL ? TRACK_ADDRFAILED: TRACK_ADDRDEFERRED); break; default: break; } } } static void ctlfile_append_msgsize(struct ctlfile *cf) { clog_msg_str(",size="); clog_msg_ulong(cf->msgsize); } void ctlfile_append_replyfd(int fd, unsigned rcptnum, const char *errmsg, char type, const char *delextra) { const char *cp; time_t timestamp; char numbuf[1+MAXLONGSIZE]; int numbuflen; struct iovec iov[12]; int n; static const char delinfo=COMCTLFILE_DELINFO; char replybuf[2]; time(×tamp); sprintf(numbuf, "%u ", rcptnum); numbuflen=strlen(numbuf); replybuf[0]=COMCTLFILE_DELINFO_REPLY; replybuf[1]=' '; /* ** errmsg can a multiline response. Each line is logged. The ** last line of the response is logged together with the final ** success/fail/deferred entry, so in most cases where there's ** a one-line response, this results in only one write call. */ n=0; while (errmsg && *errmsg) { for (cp=errmsg; *cp && *cp != '\n'; cp++) ; iov[0].iov_base=(caddr_t)&delinfo; iov[0].iov_len=1; iov[1].iov_base=(caddr_t)numbuf; iov[1].iov_len=numbuflen; iov[2].iov_base=(caddr_t)replybuf; iov[2].iov_len=2; iov[3].iov_base=(caddr_t)errmsg; /* Fix cruft like CRLF, or messages without trailing LFs */ if (cp > errmsg && cp[-1] == '\r') { iov[3].iov_len= cp - errmsg - 1; iov[4].iov_base=(caddr_t)"\n"; iov[4].iov_len=1; n=5; } else if (*cp == '\n') { iov[3].iov_len= cp - errmsg + 1; n=4; } else { iov[3].iov_len= cp - errmsg; iov[4].iov_base=(caddr_t)"\n"; iov[4].iov_len=1; n=5; } if (*cp == '\n') ++cp; errmsg=cp; if (*errmsg == '\0') break; /* Last line, attach it to status record */ ctlfile_appendvfd(fd, iov, n); n=0; } iov[n].iov_base= (caddr_t)&type; iov[n].iov_len=1; ++n; iov[n].iov_base= (caddr_t)numbuf; iov[n].iov_len= numbuflen; ++n; cp=strtimestamp(timestamp); iov[n].iov_base=(caddr_t)cp; iov[n].iov_len=strlen(cp); ++n; if (delextra) { iov[n].iov_base=(caddr_t)delextra; iov[n].iov_len=strlen(delextra); ++n; } iov[n].iov_base=(caddr_t)"\n"; iov[n].iov_len=1; ++n; ctlfile_appendvfd(fd, iov, n); } void ctlfile_nextattempt(struct ctlfile *ctf, time_t t) { ctlfile_nextattemptfd(ctf->fd, t); } void ctlfile_nextattemptfd(int fd, time_t t) { const char *cp=strtimestamp(t); struct iovec iov[3]; static const char a=COMCTLFILE_NEXTATTEMPT; iov[0].iov_base=(caddr_t)&a; iov[0].iov_len=1; iov[1].iov_base=(caddr_t)cp; iov[1].iov_len=strlen(cp); iov[2].iov_base="\n"; iov[2].iov_len=1; ctlfile_appendvfd(fd, iov, 3); #if EXPLICITSYNC fsync(fd); #endif } time_t ctlfile_getnextattempt(struct ctlfile *c) { unsigned i; time_t t; const char *cp; struct stat stat_buf; for (i=0; c->lines[i]; i++) ; while (i) { if (c->lines[--i][0] != COMCTLFILE_NEXTATTEMPT) continue; for (t=0, cp=c->lines[i]+1; *cp >= '0' && *cp <= '9'; ++cp) t=t*10 + (*cp-'0'); if (stat(qmsgqname(c->n, t), &stat_buf) == 0) return (t); } return (0); }