#include "config.h"
#include <stdio.h>
#include <setjmp.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#if defined(STDC_HEADERS)
#include <stdlib.h>
#endif
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#if defined(HAVE_STDARG_H)
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#if defined(HAVE_SYS_ITIMER_H)
#include <sys/itimer.h>
#endif
#include <sys/time.h>
#include <signal.h>
#ifdef HAVE_RES_SEARCH
#include <netdb.h>
#include "mx.h"
#endif
#ifdef KERBEROS_V4
#ifdef KERBEROS_V5
#include <kerberosIV/des.h>
#include <kerberosIV/krb.h>
#else
#if defined (__bsdi__)
#include <des.h>
#include <krb.h>
#define krb_get_err_text(e) (krb_err_txt[e])
#else
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__linux__)
#define krb_get_err_text(e) (krb_err_txt[e])
#include <krb.h>
#include <des.h>
#else
#include <krb.h>
#include <des.h>
#endif
#endif
#endif
#include <netinet/in.h>
#include <netdb.h>
#endif
#ifdef KERBEROS_V5
#include <krb5.h>
#include <com_err.h>
#endif
#include "i18n.h"
#include "socket.h"
#include "fetchmail.h"
#include "tunable.h"
#define THROW_TIMEOUT 1
#define THROW_SIGPIPE 2
#ifndef strstr
extern char *strstr();
#endif
int batchcount;
flag peek_capable;
int pass;
int phase;
static const struct method *protocol;
static jmp_buf restart;
char tag[TAGLEN];
static int tagnum;
#define GENSYM (sprintf(tag, "A%04d", ++tagnum % TAGMOD), tag)
static char shroud[PASSWORDLEN];
static int mytimeout;
static int timeoutcount;
static int msglen;
void set_timeout(int timeleft)
{
#ifndef __EMX__
struct itimerval ntimeout;
if (timeleft == 0)
timeoutcount = 0;
ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
ntimeout.it_value.tv_sec = timeleft;
ntimeout.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &ntimeout, (struct itimerval *)NULL);
#endif
}
static void timeout_handler (int signal)
{
timeoutcount++;
longjmp(restart, THROW_TIMEOUT);
}
static void sigpipe_handler (int signal)
{
longjmp(restart, THROW_SIGPIPE);
}
static int accept_count, reject_count;
static void map_name(const char *name, struct query *ctl, struct idlist **xmit_names)
{
const char *lname;
int off = 0;
lname = idpair_find(&ctl->localnames, name+off);
if (!lname && ctl->wildcard)
lname = name+off;
if (lname != (char *)NULL)
{
if (outlevel >= O_DEBUG)
report(stdout, _("mapped %s to local %s\n"), name, lname);
save_str(xmit_names, lname, XMIT_ACCEPT);
accept_count++;
}
}
static void find_server_names(const char *hdr,
struct query *ctl,
struct idlist **xmit_names)
{
if (hdr == (char *)NULL)
return;
else
{
char *cp;
for (cp = nxtaddr(hdr);
cp != NULL;
cp = nxtaddr(NULL))
{
char *atsign;
if (ctl->server.qvirtual)
{
int sl = strlen(ctl->server.qvirtual);
if (!strncasecmp(cp, ctl->server.qvirtual, sl))
cp += sl;
}
if ((atsign = strchr(cp, '@'))) {
struct idlist *idp;
for (idp = ctl->server.localdomains; idp; idp = idp->next) {
char *rhs;
rhs = atsign + (strlen(atsign) - strlen(idp->id));
if (rhs > atsign &&
(rhs[-1] == '.' || rhs[-1] == '@') &&
strcasecmp(rhs, idp->id) == 0)
{
if (outlevel >= O_DEBUG)
report(stdout, _("passed through %s matching %s\n"),
cp, idp->id);
save_str(xmit_names, cp, XMIT_ACCEPT);
accept_count++;
goto nomap;
}
}
if (!idp)
{
if (!is_host_alias(atsign+1, ctl))
{
save_str(xmit_names, cp, XMIT_REJECT);
reject_count++;
continue;
}
}
atsign[0] = '\0';
map_name(cp, ctl, xmit_names);
nomap:;
}
}
}
}
static char *parse_received(struct query *ctl, char *bufp)
{
char *base, *ok = (char *)NULL;
static char rbuf[HOSTLEN + USERNAMELEN + 4];
if (outlevel >= O_DEBUG)
report(stdout, _("analyzing Received line:\n%s"), bufp);
for (base = bufp; ; base = ok + 2)
{
if (!(ok = strstr(base, "by")))
break;
else if (!isspace(ok[-1]) || !isspace(ok[2]))
continue;
else
{
char *sp, *tp;
for (sp = ok + 2; isspace(*sp); sp++)
continue;
tp = rbuf;
for (; !isspace(*sp); sp++)
*tp++ = *sp;
*tp = '\0';
if (strchr(rbuf, '.'))
break;
else
ok = sp - 1;
}
}
if (ok)
{
if (is_host_alias(rbuf, ctl))
{
if (outlevel >= O_DEBUG)
report(stdout,
_("line accepted, %s is an alias of the mailserver\n"), rbuf);
}
else
{
if (outlevel >= O_DEBUG)
report(stdout,
_("line rejected, %s is not an alias of the mailserver\n"),
rbuf);
return(NULL);
}
for (base = ok + 4 + strlen(rbuf); ; base = ok + 2)
{
if (!(ok = strstr(base, "for")))
break;
else if (!isspace(ok[-1]) || !isspace(ok[3]))
continue;
else
{
char *sp, *tp;
for (sp = ok + 3; isspace(*sp); sp++)
continue;
tp = rbuf;
for (; !isspace(*sp); sp++)
*tp++ = *sp;
*tp = '\0';
if (strchr(rbuf, '@'))
break;
else
ok = sp - 1;
}
}
if (ok)
{
flag want_gt = FALSE;
char *sp, *tp;
for (sp = ok + 4; isspace(*sp); sp++)
continue;
tp = rbuf;
*tp++ = ':';
*tp++ = ' ';
if (*sp == '<')
{
want_gt = TRUE;
sp++;
}
while (*sp == '@')
while (*sp && *sp++ != ':')
continue;
while (*sp
&& (want_gt ? (*sp != '>') : !isspace(*sp))
&& *sp != ';')
if (!isspace(*sp))
*tp++ = *sp++;
else
{
ok = (char *)NULL;
break;
}
*tp++ = '\n';
*tp = '\0';
if (strlen(rbuf) <= 3)
ok = NULL;
} else
ok = (char *)NULL;
}
if (!ok)
{
if (outlevel >= O_DEBUG)
report(stdout, _("no Received address found\n"));
return(NULL);
}
else
{
if (outlevel >= O_DEBUG) {
char *lf = rbuf + strlen(rbuf)-1;
*lf = '\0';
if (outlevel >= O_DEBUG)
report(stdout, _("found Received address `%s'\n"), rbuf+2);
*lf = '\n';
}
return(rbuf);
}
}
static int sizeticker;
static struct msgblk msgblk;
#define EMPTYLINE(s) ((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0')
static int readheaders(int sock,
long fetchlen,
long reallen,
struct query *ctl,
int num)
{
struct addrblk
{
int offset;
struct addrblk *next;
};
struct addrblk *to_addrchain = NULL;
struct addrblk **to_chainptr = &to_addrchain;
struct addrblk *resent_to_addrchain = NULL;
struct addrblk **resent_to_chainptr = &resent_to_addrchain;
char buf[MSGBUFSIZE+1];
int from_offs, reply_to_offs, resent_from_offs;
int app_from_offs, sender_offs, resent_sender_offs;
int env_offs;
char *received_for, *rcv, *cp;
int n, linelen, oldlen, ch, remaining, skipcount;
struct idlist *idp;
flag no_local_matches = FALSE;
flag headers_ok, has_nuls;
int olderrs, good_addresses, bad_addresses;
sizeticker = 0;
has_nuls = headers_ok = FALSE;
msgblk.return_path[0] = '\0';
olderrs = ctl->errcount;
msgblk.reallen = reallen;
msgblk.headers = received_for = NULL;
from_offs = reply_to_offs = resent_from_offs = app_from_offs =
sender_offs = resent_sender_offs = env_offs = -1;
oldlen = 0;
msglen = 0;
skipcount = 0;
ctl->mimemsg = 0;
for (remaining = fetchlen; remaining > 0 || protocol->delimited; remaining -= linelen)
{
char *line;
line = xmalloc(sizeof(buf));
linelen = 0;
line[0] = '\0';
do {
set_timeout(mytimeout);
if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) {
set_timeout(0);
free(line);
free(msgblk.headers);
return(PS_SOCKET);
}
set_timeout(0);
linelen += n;
msglen += n;
if (ctl->forcecr)
{
cp = buf + strlen(buf) - 1;
if (*cp == '\n' && (cp == buf || cp[-1] != '\r'))
{
*cp++ = '\r';
*cp++ = '\n';
*cp++ = '\0';
}
}
if (ctl->mimedecode)
UnMimeHeader(buf);
line = (char *) realloc(line, strlen(line) + strlen(buf) +1);
strcat(line, buf);
if (EMPTYLINE(line))
{
headers_ok = TRUE;
has_nuls = (linelen != strlen(line));
free(line);
goto process_headers;
}
if (protocol->delimited && line[0] == '.' && EMPTYLINE(line+1))
{
free(line);
has_nuls = (linelen != strlen(line));
goto process_headers;
}
set_timeout(mytimeout);
ch = SockPeek(sock);
set_timeout(0);
} while
(ch == ' ' || ch == '\t');
if ((outlevel > O_SILENT && outlevel < O_VERBOSE) && linelen > 0)
{
sizeticker += linelen;
while (sizeticker >= SIZETICKER)
{
if (!run.use_syslog)
{
fputc('.', stdout);
fflush(stdout);
}
sizeticker -= SIZETICKER;
}
}
has_nuls = (linelen != strlen(line));
if (MULTIDROP(ctl) && !strncasecmp(line, "Message-ID:", 11))
{
if (ctl->lastid && !strcasecmp(ctl->lastid, line))
return(PS_REFUSED);
else
{
if (ctl->lastid)
free(ctl->lastid);
ctl->lastid = strdup(line);
}
}
#ifdef POP2_ENABLE
if (protocol->port != 109)
#endif
if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) {
free(line);
free(msgblk.headers);
return(PS_RETAINED);
}
if (!strncasecmp(line, ">From ", 6) || !strncasecmp(line, "From ", 5))
{
free(line);
continue;
}
{
char *cp;
if (!strncasecmp(line, "Status:", 7))
cp = line + 7;
else if (!strncasecmp(line, "X-Mozilla-Status:", 17))
cp = line + 17;
else
cp = NULL;
if (cp) {
while (*cp && isspace(*cp)) cp++;
if (!*cp || ctl->dropstatus)
{
free(line);
continue;
}
}
}
if (ctl->rewrite)
line = reply_hack(line, ctl->server.truename);
if (!strncasecmp("Return-Path:", line, 12) && (cp = nxtaddr(line)))
{
strcpy(msgblk.return_path, cp);
if (!ctl->mda) {
free(line);
continue;
}
}
if (!msgblk.headers)
{
oldlen = strlen(line);
msgblk.headers = xmalloc(oldlen + 1);
(void) strcpy(msgblk.headers, line);
free(line);
line = msgblk.headers;
}
else
{
int newlen;
newlen = oldlen + strlen(line);
msgblk.headers = (char *) realloc(msgblk.headers, newlen + 1);
if (msgblk.headers == NULL) {
free(line);
return(PS_IOERR);
}
strcpy(msgblk.headers + oldlen, line);
free(line);
line = msgblk.headers + oldlen;
oldlen = newlen;
}
if (!strncasecmp("From:", line, 5))
from_offs = (line - msgblk.headers);
else if (!strncasecmp("Reply-To:", line, 9))
reply_to_offs = (line - msgblk.headers);
else if (!strncasecmp("Resent-From:", line, 12))
resent_from_offs = (line - msgblk.headers);
else if (!strncasecmp("Apparently-From:", line, 16))
app_from_offs = (line - msgblk.headers);
else if (!strncasecmp("Sender:", line, 7))
sender_offs = (line - msgblk.headers);
else if (!strncasecmp("Resent-Sender:", line, 14))
resent_sender_offs = (line - msgblk.headers);
else if (!strncasecmp("Message-Id:", buf, 11))
{
if (ctl->server.uidl)
{
char id[IDLEN+1];
buf[IDLEN+12] = 0;
sscanf(buf+12, "%s", id);
if (!str_find( &ctl->newsaved, num))
{
struct idlist *new = save_str(&ctl->newsaved,id,UID_SEEN);
new->val.status.num = num;
}
}
}
else if (!MULTIDROP(ctl))
continue;
else if (!strncasecmp("To:", line, 3)
|| !strncasecmp("Cc:", line, 3)
|| !strncasecmp("Bcc:", line, 4)
|| !strncasecmp("Apparently-To:", line, 14))
{
*to_chainptr = xmalloc(sizeof(struct addrblk));
(*to_chainptr)->offset = (line - msgblk.headers);
to_chainptr = &(*to_chainptr)->next;
*to_chainptr = NULL;
}
else if (!strncasecmp("Resent-To:", line, 10)
|| !strncasecmp("Resent-Cc:", line, 10)
|| !strncasecmp("Resent-Bcc:", line, 11))
{
*resent_to_chainptr = xmalloc(sizeof(struct addrblk));
(*resent_to_chainptr)->offset = (line - msgblk.headers);
resent_to_chainptr = &(*resent_to_chainptr)->next;
*resent_to_chainptr = NULL;
}
else if (ctl->server.envelope != STRING_DISABLED)
{
if (ctl->server.envelope
&& strcasecmp(ctl->server.envelope, "Received"))
{
if (env_offs == -1 && !strncasecmp(ctl->server.envelope,
line,
strlen(ctl->server.envelope)))
{
if (skipcount++ != ctl->server.envskip)
continue;
env_offs = (line - msgblk.headers);
}
}
else if (!received_for && !strncasecmp("Received:", line, 9))
{
if (skipcount++ != ctl->server.envskip)
continue;
received_for = parse_received(ctl, line);
}
}
}
process_headers:
if (!headers_ok)
{
if (outlevel > O_SILENT)
report(stdout,
_("message delimiter found while scanning headers\n"));
}
if (msgblk.headers == (char *)NULL)
{
#ifdef HAVE_SNPRINTF
snprintf(buf, sizeof(buf),
#else
sprintf(buf,
#endif
"From: FETCHMAIL-DAEMON\r\nTo: %s@%s\r\nSubject: Headerless mail from %s's mailbox on %s\r\n",
user, fetchmailhost, ctl->remotename, ctl->server.truename);
msgblk.headers = xstrdup(buf);
}
ctl->mimemsg = MimeBodyType(msgblk.headers, ctl->mimedecode);
#ifdef SDPS_ENABLE
if (ctl->server.sdps && sdps_envfrom)
{
strcpy(msgblk.return_path, sdps_envfrom);
free(sdps_envfrom);
} else
#endif
if( !msgblk.return_path[0] ){
char *ap = NULL;
if (resent_sender_offs >= 0 && (ap = nxtaddr(msgblk.headers + resent_sender_offs)));
else if (sender_offs >= 0 && (ap = nxtaddr(msgblk.headers + sender_offs)));
else if (resent_from_offs >= 0 && (ap = nxtaddr(msgblk.headers + resent_from_offs)));
else if (from_offs >= 0 && (ap = nxtaddr(msgblk.headers + from_offs)));
else if (reply_to_offs >= 0 && (ap = nxtaddr(msgblk.headers + reply_to_offs)));
else if (app_from_offs >= 0 && (ap = nxtaddr(msgblk.headers + app_from_offs)));
if (ap) strcpy( msgblk.return_path, ap );
}
msgblk.recipients = (struct idlist *)NULL;
accept_count = reject_count = 0;
if (MULTIDROP(ctl))
{
#ifdef SDPS_ENABLE
if (ctl->server.sdps && sdps_envto)
{
find_server_names(sdps_envto, ctl, &msgblk.recipients);
free(sdps_envto);
} else
#endif
if (env_offs > -1)
find_server_names(msgblk.headers + env_offs, ctl, &msgblk.recipients);
else if (received_for)
find_server_names(received_for, ctl, &msgblk.recipients);
else
{
register struct addrblk *nextptr;
if (resent_to_addrchain) {
while (to_addrchain) {
nextptr = to_addrchain->next;
free(to_addrchain);
to_addrchain = nextptr;
}
to_addrchain = resent_to_addrchain;
resent_to_addrchain = NULL;
}
while (to_addrchain) {
find_server_names(msgblk.headers+to_addrchain->offset, ctl, &msgblk.recipients);
nextptr = to_addrchain->next;
free(to_addrchain);
to_addrchain = nextptr;
}
}
if (!accept_count)
{
no_local_matches = TRUE;
save_str(&msgblk.recipients, run.postmaster, XMIT_ACCEPT);
if (outlevel >= O_DEBUG)
report(stdout,
_("no local matches, forwarding to %s\n"),
run.postmaster);
}
}
else
save_str(&msgblk.recipients, ctl->localnames->id, XMIT_ACCEPT);
if (ctl->errcount > olderrs)
{
if (outlevel >= O_DEBUG)
report(stdout,
_("forwarding and deletion suppressed due to DNS errors\n"));
free(msgblk.headers);
free_str_list(&msgblk.recipients);
return(PS_TRANSIENT);
}
else
{
if ((n = open_sink(ctl, &msgblk,
&good_addresses, &bad_addresses)) != PS_SUCCESS)
{
free(msgblk.headers);
free_str_list(&msgblk.recipients);
return(n);
}
}
n = 0;
if ((rcv = strstr(msgblk.headers, "Received:")) == (char *)NULL)
rcv = msgblk.headers;
while (rcv > msgblk.headers && rcv[-1] != '\n')
rcv--;
if (rcv > msgblk.headers)
{
char c = *rcv;
*rcv = '\0';
n = stuffline(ctl, msgblk.headers);
*rcv = c;
}
if (!run.invisible && n != -1)
{
sprintf(buf, "Received: from %s\r\n", ctl->server.truename);
n = stuffline(ctl, buf);
if (n != -1)
{
sprintf(buf, "\tby %s with %s (fetchmail-%s)\r\n",
fetchmailhost,
protocol->name,
VERSION);
n = stuffline(ctl, buf);
if (n != -1)
{
buf[0] = '\t';
if (good_addresses == 0)
{
sprintf(buf+1,
"for %s@%s (by default); ",
user, ctl->destaddr);
}
else if (good_addresses == 1)
{
for (idp = msgblk.recipients; idp; idp = idp->next)
if (idp->val.status.mark == XMIT_ACCEPT)
break;
if (strchr(idp->id, '@'))
sprintf(buf+1, "for %s", idp->id);
else
sprintf(buf+1, "for %s@%s", idp->id, ctl->destaddr);
sprintf(buf+strlen(buf), " (%s); ",
MULTIDROP(ctl) ? "multi-drop" : "single-drop");
}
else
buf[1] = '\0';
strcat(buf, rfc822timestamp());
strcat(buf, "\r\n");
n = stuffline(ctl, buf);
}
}
}
if (n != -1)
n = stuffline(ctl, rcv);
if (n == -1)
{
report(stdout, _("writing RFC822 msgblk.headers\n"));
release_sink(ctl);
free(msgblk.headers);
free_str_list(&msgblk.recipients);
return(PS_IOERR);
}
else if (!run.use_syslog && outlevel >= O_VERBOSE)
fputs("#", stderr);
if (no_local_matches || has_nuls || bad_addresses)
{
int errlen = 0;
char errhd[USERNAMELEN + POPBUFSIZE], *errmsg;
errmsg = errhd;
(void) strcpy(errhd, "X-Fetchmail-Warning: ");
if (no_local_matches)
{
if (reject_count != 1)
strcat(errhd, _("no recipient addresses matched declared local names"));
else
{
for (idp = msgblk.recipients; idp; idp = idp->next)
if (idp->val.status.mark == XMIT_REJECT)
break;
sprintf(errhd+strlen(errhd), _("recipient address %s didn't match any local name"), idp->id);
}
}
if (has_nuls)
{
if (errhd[sizeof("X-Fetchmail-Warning: ")])
strcat(errhd, "; ");
strcat(errhd, _("message has embedded NULs"));
}
if (bad_addresses)
{
if (errhd[sizeof("X-Fetchmail-Warning: ")])
strcat(errhd, "; ");
strcat(errhd, _("SMTP listener rejected local recipient addresses: "));
errlen = strlen(errhd);
for (idp = msgblk.recipients; idp; idp = idp->next)
if (idp->val.status.mark == XMIT_RCPTBAD)
errlen += strlen(idp->id) + 2;
xalloca(errmsg, char *, errlen+3);
(void) strcpy(errmsg, errhd);
for (idp = msgblk.recipients; idp; idp = idp->next)
if (idp->val.status.mark == XMIT_RCPTBAD)
{
strcat(errmsg, idp->id);
if (idp->next)
strcat(errmsg, ", ");
}
}
strcat(errmsg, "\r\n");
stuffline(ctl, errmsg);
}
cp = buf;
*cp++ = '\r';
*cp++ = '\n';
*cp++ = '\0';
stuffline(ctl, buf);
free(msgblk.headers);
free_str_list(&msgblk.recipients);
return(headers_ok ? PS_SUCCESS : PS_TRUNCATED);
}
static int readbody(int sock, struct query *ctl, flag forward, int len)
{
int linelen;
unsigned char buf[MSGBUFSIZE+4];
unsigned char *inbufp = buf;
flag issoftline = FALSE;
while (protocol->delimited || len > 0)
{
set_timeout(mytimeout);
if ((linelen = SockRead(sock, inbufp, sizeof(buf)-4-(inbufp-buf)))==-1)
{
set_timeout(0);
release_sink(ctl);
return(PS_SOCKET);
}
set_timeout(0);
if (linelen > 0)
{
sizeticker += linelen;
while (sizeticker >= SIZETICKER)
{
if (!run.use_syslog && outlevel > O_SILENT)
{
fputc('.', stdout);
fflush(stdout);
}
sizeticker -= SIZETICKER;
}
}
len -= linelen;
if (protocol->delimited && *inbufp == '.')
if (inbufp[1] == '\r' && inbufp[2] == '\n' && inbufp[3] == '\0')
break;
else if (inbufp[1] == '\n' && inbufp[2] == '\0')
break;
else
msglen--;
msglen += linelen;
if (ctl->mimedecode && (ctl->mimemsg & MSG_NEEDS_DECODE)) {
issoftline = UnMimeBodyline(&inbufp, protocol->delimited, issoftline);
if (issoftline && (sizeof(buf)-1-(inbufp-buf) < 200))
{
*inbufp = '\n'; *(inbufp+1) = '\0';
issoftline = 0;
}
}
if (forward && (!issoftline))
{
int n;
inbufp = buf;
buf[MSGBUFSIZE+1] = '\r';
buf[MSGBUFSIZE+2] = '\n';
buf[MSGBUFSIZE+3] = '\0';
n = stuffline(ctl, buf);
if (n < 0)
{
report(stdout, _("writing message text\n"));
release_sink(ctl);
return(PS_IOERR);
}
else if (outlevel >= O_VERBOSE)
fputc('*', stderr);
}
}
return(PS_SUCCESS);
}
#ifdef KERBEROS_V4
int
kerberos_auth (socket, canonical)
int socket;
#if defined(__FreeBSD__) || defined(__OpenBSD__)
char *canonical;
#else
const char *canonical;
#endif
{
char * host_primary;
KTEXT ticket;
MSG_DAT msg_data;
CREDENTIALS cred;
Key_schedule schedule;
int rem;
xalloca(ticket, KTEXT, sizeof (KTEXT_ST));
rem = (krb_sendauth (0L, socket, ticket, "pop",
canonical,
((char *) (krb_realmofhost (canonical))),
((unsigned long) 0),
(&msg_data),
(&cred),
(schedule),
((struct sockaddr_in *) 0),
((struct sockaddr_in *) 0),
"KPOPV0.1"));
if (rem != KSUCCESS)
{
report(stderr, _("kerberos error %s\n"), (krb_get_err_text (rem)));
return (PS_AUTHFAIL);
}
return (0);
}
#endif
#ifdef KERBEROS_V5
static int kerberos5_auth(socket, canonical)
int socket;
const char *canonical;
{
krb5_error_code retval;
krb5_context context;
krb5_ccache ccdef;
krb5_principal client = NULL, server = NULL;
krb5_error *err_ret = NULL;
krb5_auth_context auth_context = NULL;
krb5_init_context(&context);
krb5_init_ets(context);
krb5_auth_con_init(context, &auth_context);
if (retval = krb5_cc_default(context, &ccdef)) {
report(stderr, "krb5_cc_default: %s\n", error_message(retval));
return(PS_ERROR);
}
if (retval = krb5_cc_get_principal(context, ccdef, &client)) {
report(stderr, "krb5_cc_get_principal: %s\n", error_message(retval));
return(PS_ERROR);
}
if (retval = krb5_sname_to_principal(context, canonical, "pop",
KRB5_NT_UNKNOWN,
&server)) {
report(stderr, "krb5_sname_to_principal: %s\n", error_message(retval));
return(PS_ERROR);
}
retval = krb5_sendauth(context, &auth_context, (krb5_pointer) &socket,
"KPOPV1.0", client, server,
AP_OPTS_MUTUAL_REQUIRED,
NULL,
0,
ccdef,
&err_ret, 0,
NULL);
krb5_free_principal(context, server);
krb5_free_principal(context, client);
krb5_auth_con_free(context, auth_context);
if (retval) {
#ifdef HEIMDAL
if (err_ret && err_ret->e_text) {
report(stderr, _("krb5_sendauth: %s [server says '%*s'] \n"),
error_message(retval),
err_ret->e_text);
#else
if (err_ret && err_ret->text.length) {
report(stderr, _("krb5_sendauth: %s [server says '%*s'] \n"),
error_message(retval),
err_ret->text.length,
err_ret->text.data);
#endif
krb5_free_error(context, err_ret);
} else
report(stderr, "krb5_sendauth: %s\n", error_message(retval));
return(PS_ERROR);
}
return 0;
}
#endif
static void clean_skipped_list(struct idlist **skipped_list)
{
struct idlist *current=NULL, *prev=NULL, *tmp=NULL, *head=NULL;
prev = current = head = *skipped_list;
if (!head)
return;
do
{
if (current && current->val.status.mark == 0)
{
if (current == head)
{
head = current->next;
if (current->id) free(current->id);
free(current);
prev = current = head;
}
else
{
tmp = current->next;
prev->next = tmp;
if (current->id) free(current->id);
free(current);
current = tmp;
}
}
else
{
prev = current;
current = current->next;
}
} while(current);
*skipped_list = head;
}
static void send_size_warnings(struct query *ctl)
{
int size, nbr;
int msg_to_send = FALSE;
struct idlist *head=NULL, *current=NULL;
int max_warning_poll_count;
#define OVERHD "Subject: Fetchmail oversized-messages warning.\r\n\r\nThe following oversized messages remain on the mail server %s:"
head = ctl->skipped;
if (!head)
return;
for (current = head; current; current = current->next)
if (current->val.status.num == 0 && current->val.status.mark)
msg_to_send = TRUE;
if (!msg_to_send)
return;
if (open_warning_by_mail(ctl, (struct msgblk *)NULL))
return;
stuff_warning(ctl, OVERHD, ctl->server.pollname);
if (run.poll_interval == 0)
max_warning_poll_count = 0;
else
max_warning_poll_count = ctl->warnings/run.poll_interval;
for (current = head; current; current = current->next)
{
if (current->val.status.num == 0 && current->val.status.mark)
{
nbr = current->val.status.mark;
size = atoi(current->id);
stuff_warning(ctl,
_("\t%d msg %d octets long skipped by fetchmail.\n"),
nbr, size);
}
current->val.status.num++;
current->val.status.mark = 0;
if (current->val.status.num >= max_warning_poll_count)
current->val.status.num = 0;
}
close_warning_by_mail(ctl, (struct msgblk *)NULL);
#undef OVERHD
}
static int do_session(ctl, proto, maxfetch)
struct query *ctl;
const struct method *proto;
const int maxfetch;
{
int ok, js;
#ifdef HAVE_VOLATILE
volatile int mailserver_socket = -1;
#else
int mailserver_socket = -1;
#endif
const char *msg;
void (*pipesave)(int);
void (*alrmsave)(int);
struct idlist *current=NULL, *tmp=NULL;
protocol = proto;
ctl->server.base_protocol = protocol;
pass = 0;
tagnum = 0;
tag[0] = '\0';
ok = 0;
alrmsave = signal(SIGALRM, timeout_handler);
mytimeout = ctl->server.timeout;
pipesave = signal(SIGPIPE, sigpipe_handler);
if ((js = setjmp(restart)))
{
#ifdef HAVE_SIGPROCMASK
sigset_t allsigs;
sigfillset(&allsigs);
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
#endif
if (js == THROW_SIGPIPE)
{
report(stdout,
_("SIGPIPE thrown from an MDA or a stream socket error"));
ok = PS_SOCKET;
}
else if (js == THROW_TIMEOUT)
{
if (phase == OPEN_WAIT)
report(stdout,
_("timeout after %d seconds waiting to connect to server %s.\n"),
ctl->server.timeout, ctl->server.pollname);
else if (phase == SERVER_WAIT)
report(stdout,
_("timeout after %d seconds waiting for server %s.\n"),
ctl->server.timeout, ctl->server.pollname);
else if (phase == FORWARDING_WAIT)
report(stdout,
_("timeout after %d seconds waiting for %s.\n"),
ctl->server.timeout,
ctl->mda ? "MDA" : "SMTP");
else if (phase == LISTENER_WAIT)
report(stdout,
_("timeout after %d seconds waiting for listener to respond.\n"));
else
report(stdout,
_("timeout after %d seconds.\n"), ctl->server.timeout);
if (timeoutcount > MAX_TIMEOUTS
&& !open_warning_by_mail(ctl, (struct msgblk *)NULL))
{
stuff_warning(ctl,
_("Subject: fetchmail sees repeated timeouts\r\n"));
stuff_warning(ctl,
_("Fetchmail saw more than %d timouts while attempting to get mail from %s@%s.\n"),
MAX_TIMEOUTS,
ctl->remotename,
ctl->server.truename);
stuff_warning(ctl,
_("This could mean that your mailserver is stuck, or that your SMTP listener"));
stuff_warning(ctl,
_("is wedged, or that your mailbox file on the server has been corrupted by"));
stuff_warning(ctl,
_("a server error. You can run `fetchmail -v -v' to diagnose the problem."));
stuff_warning(ctl,
_("Fetchmail won't poll this mailbox again until you restart it."));
close_warning_by_mail(ctl, (struct msgblk *)NULL);
ctl->wedged = TRUE;
}
ok = PS_ERROR;
}
release_sink(ctl);
if (ctl->smtp_socket != -1)
close(ctl->smtp_socket);
if (mailserver_socket != -1)
SockClose(mailserver_socket);
}
else
{
char buf[POPBUFSIZE+1], *realhost;
int len, num, count, new, bytes, deletions = 0, *msgsizes = NULL;
#if INET6
int fetches, dispatches, oldphase;
#else
int port, fetches, dispatches, oldphase;
#endif
struct idlist *idp;
if (ctl->preconnect && (ok = system(ctl->preconnect)))
{
report(stderr,
_("pre-connection command failed with status %d\n"), ok);
ok = PS_SYNTAX;
goto closeUp;
}
oldphase = phase;
phase = OPEN_WAIT;
set_timeout(mytimeout);
#if !INET6
port = ctl->server.port ? ctl->server.port : protocol->port;
#endif
realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
#if INET6
if ((mailserver_socket = SockOpen(realhost,
ctl->server.service ? ctl->server.service : protocol->service,
ctl->server.netsec, ctl->server.plugin)) == -1)
#else
if ((mailserver_socket = SockOpen(realhost, port, NULL, ctl->server.plugin)) == -1)
#endif
{
#if !INET6
int err_no = errno;
#ifdef HAVE_RES_SEARCH
if (err_no != 0 && h_errno != 0)
report(stderr, _("fetchmail: internal inconsistency\n"));
#endif
if (err_no == EHOSTUNREACH && run.poll_interval)
goto ehostunreach;
report_build(stderr, _("fetchmail: %s connection to %s failed"),
protocol->name, ctl->server.pollname);
#ifdef HAVE_RES_SEARCH
if (h_errno != 0)
{
if (h_errno == HOST_NOT_FOUND)
report_complete(stderr, _(": host is unknown\n"));
else if (h_errno == NO_ADDRESS)
report_complete(stderr, _(": name is valid but has no IP address\n"));
else if (h_errno == NO_RECOVERY)
report_complete(stderr, _(": unrecoverable name server error\n"));
else if (h_errno == TRY_AGAIN)
report_complete(stderr, _(": temporary name server error\n"));
else
report_complete(stderr, _(": unknown DNS error %d\n"), h_errno);
}
else
#endif
report_complete(stderr, ": %s\n", strerror(err_no));
ehostunreach:
#endif
ok = PS_SOCKET;
set_timeout(0);
phase = oldphase;
goto closeUp;
}
set_timeout(0);
phase = oldphase;
#ifdef KERBEROS_V4
if (ctl->server.preauthenticate == A_KERBEROS_V4)
{
set_timeout(mytimeout);
ok = kerberos_auth(mailserver_socket, ctl->server.truename);
set_timeout(0);
if (ok != 0)
goto cleanUp;
}
#endif
#ifdef KERBEROS_V5
if (ctl->server.preauthenticate == A_KERBEROS_V5)
{
set_timeout(mytimeout);
ok = kerberos5_auth(mailserver_socket, ctl->server.truename);
set_timeout(0);
if (ok != 0)
goto cleanUp;
}
#endif
ok = (protocol->parse_response)(mailserver_socket, buf);
if (ok != 0)
goto cleanUp;
if (protocol->getauth)
{
if (protocol->password_canonify)
(protocol->password_canonify)(shroud, ctl->password);
else
strcpy(shroud, ctl->password);
ok = (protocol->getauth)(mailserver_socket, ctl, buf);
if (ok != 0)
{
if (ok == PS_LOCKBUSY)
report(stderr, _("Lock-busy error on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
else
{
if (ok == PS_ERROR)
ok = PS_AUTHFAIL;
report(stderr, _("Authorization failure on %s@%s\n"),
ctl->remotename,
ctl->server.truename);
if (run.poll_interval
&& !ctl->wedged
&& !open_warning_by_mail(ctl, (struct msgblk *)NULL))
{
stuff_warning(ctl,
_("Subject: fetchmail authentication failed\r\n"));
stuff_warning(ctl,
_("Fetchmail could not get mail from %s@%s."),
ctl->remotename,
ctl->server.truename);
stuff_warning(ctl,
_("The attempt to get authorization failed."));
stuff_warning(ctl,
_("This probably means your password is invalid."));
close_warning_by_mail(ctl, (struct msgblk *)NULL);
ctl->wedged = TRUE;
}
}
goto cleanUp;
}
}
ctl->errcount = fetches = 0;
for (idp = ctl->mailboxes; idp; idp = idp->next)
{
pass = 0;
do {
dispatches = 0;
++pass;
if (outlevel >= O_DEBUG)
if (idp->id)
report(stdout, _("selecting or re-polling folder %s\n"), idp->id);
else
report(stdout, _("selecting or re-polling default folder\n"));
ok = (protocol->getrange)(mailserver_socket, ctl, idp->id, &count, &new, &bytes);
if (ok != 0)
goto cleanUp;
if (idp->id)
(void) sprintf(buf, _("%s at %s (folder %s)"),
ctl->remotename, ctl->server.truename, idp->id);
else
(void) sprintf(buf, _("%s at %s"),
ctl->remotename, ctl->server.truename);
if (outlevel > O_SILENT)
if (count == -1)
report(stdout, _("Polling %s\n"), ctl->server.truename);
else if (count != 0)
{
if (new != -1 && (count - new) > 0)
report_build(stdout, _("%d %s (%d seen) for %s"),
count, count > 1 ? _("messages") :
_("message"),
count-new, buf);
else
report_build(stdout, _("%d %s for %s"),
count, count > 1 ? _("messages") :
_("message"), buf);
if (bytes == -1)
report_complete(stdout, ".\n");
else
report_complete(stdout, _(" (%d octets).\n"), bytes);
}
else
{
if (pass == 1 && (run.poll_interval == 0 || outlevel >= O_VERBOSE))
report(stdout, _("No mail for %s\n"), buf);
}
if (count == 0)
break;
if (check_only)
{
if (new == -1 || ctl->fetchall)
new = count;
fetches = new;
goto no_error;
}
else if (count > 0)
{
flag force_retrieval;
force_retrieval = !peek_capable && (ctl->errcount > 0);
if (proto->getsizes)
{
int i;
xalloca(msgsizes, int *, sizeof(int) * count);
for (i = 0; i < count; i++)
msgsizes[i] = -1;
ok = (proto->getsizes)(mailserver_socket, count, msgsizes);
if (ok != 0)
goto cleanUp;
if (bytes == -1)
{
bytes = 0;
for (i = 0; i < count; i++)
bytes += msgsizes[i];
}
}
for (num = 1; num <= count; num++)
{
flag toolarge = NUM_NONZERO(ctl->limit)
&& msgsizes && (msgsizes[num-1] > ctl->limit);
flag oldmsg = (!new) || (protocol->is_old && (protocol->is_old)(mailserver_socket,ctl,num));
flag fetch_it = !toolarge
&& (ctl->fetchall || force_retrieval || !oldmsg);
flag suppress_delete = FALSE;
flag suppress_forward = FALSE;
flag suppress_readbody = FALSE;
flag retained = FALSE;
if (msgsizes && msgsizes[num-1] == -1)
{
if (outlevel >= O_VERBOSE)
report(stdout,
_("Skipping message %d, length -1\n"),
num);
continue;
}
if (!fetch_it)
{
if (outlevel > O_SILENT)
{
report_build(stdout, _("skipping message %d"), num);
if (toolarge && !check_only)
{
char size[32];
int cnt;
sprintf(size, "%d", msgsizes[num-1]);
current = ctl->skipped;
cnt = current? current->val.status.num : 0;
if (current &&
str_in_list(¤t, size, FALSE))
{
for ( ; current;
current = current->next)
{
if (strcmp(current->id, size) == 0)
{
current->val.status.mark++;
break;
}
}
}
else
{
tmp = save_str(&ctl->skipped, size, 1);
tmp->val.status.num = cnt;
}
report_build(stdout, _(" (oversized, %d octets)"),
msgsizes[num-1]);
}
}
}
else
{
flag wholesize = !protocol->fetch_body;
ok = (protocol->fetch_headers)(mailserver_socket,ctl,num, &len);
if (ok != 0)
goto cleanUp;
if (len == -1 && msgsizes)
{
len = msgsizes[num - 1];
wholesize = TRUE;
}
if (outlevel > O_SILENT)
{
report_build(stdout, _("reading message %d of %d"),
num,count);
if (len > 0)
report_build(stdout, _(" (%d %soctets)"),
len, wholesize ? "" : _("header "));
if (outlevel >= O_VERBOSE)
report_complete(stdout, "\n");
else
report_complete(stdout, " ");
}
ok = readheaders(mailserver_socket, len, msgsizes[num-1],
ctl, num);
if (ok == PS_RETAINED)
suppress_forward = retained = TRUE;
else if (ok == PS_TRANSIENT)
suppress_delete = suppress_forward = TRUE;
else if (ok == PS_REFUSED)
suppress_forward = TRUE;
else if (ok == PS_TRUNCATED)
suppress_readbody = TRUE;
else if (ok)
goto cleanUp;
if (protocol->fetch_body && !suppress_readbody)
{
if (outlevel >= O_VERBOSE)
{
fputc('\n', stdout);
fflush(stdout);
}
if ((ok = (protocol->trail)(mailserver_socket, ctl, num)))
goto cleanUp;
len = 0;
if (!suppress_forward)
{
if ((ok=(protocol->fetch_body)(mailserver_socket,ctl,num,&len)))
goto cleanUp;
if (outlevel > O_SILENT && !wholesize)
report_complete(stdout,
_(" (%d body octets) "), len);
}
}
if (len > 0)
{
if (suppress_readbody)
{
ok = PS_SUCCESS;
}
else
{
ok = readbody(mailserver_socket,
ctl,
!suppress_forward,
len);
}
if (ok == PS_TRANSIENT)
suppress_delete = suppress_forward = TRUE;
else if (ok)
goto cleanUp;
if (protocol->trail)
{
if (outlevel >= O_VERBOSE)
{
fputc('\n', stdout);
fflush(stdout);
}
ok = (protocol->trail)(mailserver_socket, ctl, num);
if (ok != 0)
goto cleanUp;
}
}
if (!suppress_forward)
dispatches++;
if (msgsizes && msglen != msgsizes[num-1])
{
if (outlevel >= O_DEBUG)
report(stdout,
_("message %d was not the expected length (%d actual != %d expected)\n"),
num, msglen, msgsizes[num-1]);
}
if (!close_sink(ctl, &msgblk, !suppress_forward))
{
ctl->errcount++;
suppress_delete = TRUE;
}
fetches++;
}
if (ctl->newsaved)
{
struct idlist *sdp;
for (sdp = ctl->newsaved; sdp; sdp = sdp->next)
if (sdp->val.status.num == num)
sdp->val.status.mark = UID_SEEN;
}
if (retained)
{
if (outlevel > O_SILENT)
report(stdout, _(" retained\n"));
}
else if (protocol->delete
&& !suppress_delete
&& (fetch_it ? !ctl->keep : ctl->flush))
{
deletions++;
if (outlevel > O_SILENT)
report_complete(stdout, _(" flushed\n"));
ok = (protocol->delete)(mailserver_socket, ctl, num);
if (ok != 0)
goto cleanUp;
#ifdef POP3_ENABLE
delete_str(&ctl->newsaved, num);
#endif
}
else if (outlevel > O_SILENT)
report_complete(stdout, _(" not flushed\n"));
if (maxfetch && maxfetch <= fetches && fetches < count)
{
report(stdout, _("fetchlimit %d reached; %d messages left on server\n"),
maxfetch, count - fetches);
ok = PS_MAXFETCH;
goto cleanUp;
}
}
if (!check_only && ctl->skipped
&& run.poll_interval > 0 && !nodetach)
{
clean_skipped_list(&ctl->skipped);
send_size_warnings(ctl);
}
}
} while
(dispatches && protocol->retry && !ctl->keep && !ctl->errcount);
}
no_error:
ok = (protocol->logout_cmd)(mailserver_socket, ctl);
if (ok == 0)
ok = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL;
SockClose(mailserver_socket);
goto closeUp;
cleanUp:
if (ok != 0 && ok != PS_SOCKET)
(protocol->logout_cmd)(mailserver_socket, ctl);
SockClose(mailserver_socket);
}
msg = (const char *)NULL;
switch (ok)
{
case PS_SOCKET:
msg = _("socket");
break;
case PS_AUTHFAIL:
msg = _("authorization");
break;
case PS_SYNTAX:
msg = _("missing or bad RFC822 header");
break;
case PS_IOERR:
msg = _("MDA");
break;
case PS_ERROR:
msg = _("client/server synchronization");
break;
case PS_PROTOCOL:
msg = _("client/server protocol");
break;
case PS_LOCKBUSY:
msg = _("lock busy on server");
break;
case PS_SMTP:
msg = _("SMTP transaction");
break;
case PS_DNS:
msg = _("DNS lookup");
break;
case PS_UNDEFINED:
report(stderr, _("undefined error\n"));
break;
}
if (ok==PS_SOCKET || ok==PS_AUTHFAIL || ok==PS_SYNTAX
|| ok==PS_IOERR || ok==PS_ERROR || ok==PS_PROTOCOL
|| ok==PS_LOCKBUSY || ok==PS_SMTP || ok==PS_DNS)
report(stderr, _("%s error while fetching from %s\n"), msg, ctl->server.pollname);
closeUp:
if (ctl->postconnect && (ok = system(ctl->postconnect)))
{
report(stderr, _("post-connection command failed with status %d\n"), ok);
if (ok == PS_SUCCESS)
ok = PS_SYNTAX;
}
signal(SIGALRM, alrmsave);
signal(SIGPIPE, pipesave);
return(ok);
}
int do_protocol(ctl, proto)
struct query *ctl;
const struct method *proto;
{
int ok;
#ifndef KERBEROS_V4
if (ctl->server.preauthenticate == A_KERBEROS_V4)
{
report(stderr, _("Kerberos V4 support not linked.\n"));
return(PS_ERROR);
}
#endif
#ifndef KERBEROS_V5
if (ctl->server.preauthenticate == A_KERBEROS_V5)
{
report(stderr, _("Kerberos V5 support not linked.\n"));
return(PS_ERROR);
}
#endif
if (!proto->is_old)
{
if (ctl->flush) {
report(stderr,
_("Option --flush is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
else if (ctl->fetchall) {
report(stderr,
_("Option --all is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
}
if (!proto->getsizes && NUM_SPECIFIED(ctl->limit))
{
report(stderr,
_("Option --limit is not supported with %s\n"),
proto->name);
return(PS_SYNTAX);
}
if (proto->retry || !NUM_SPECIFIED(ctl->expunge))
return(do_session(ctl, proto, NUM_VALUE_OUT(ctl->fetchlimit)));
else
{
int totalcount = 0;
int lockouts = 0;
int expunge = NUM_VALUE_OUT(ctl->expunge);
int fetchlimit = NUM_VALUE_OUT(ctl->fetchlimit);
do {
ok = do_session(ctl, proto, expunge);
totalcount += expunge;
if (NUM_SPECIFIED(ctl->fetchlimit) && totalcount >= fetchlimit)
break;
if (ok != PS_LOCKBUSY)
lockouts = 0;
else if (lockouts >= MAX_LOCKOUTS)
break;
else
{
lockouts++;
sleep(3);
}
} while
(ok == PS_MAXFETCH || ok == PS_LOCKBUSY);
return(ok);
}
}
#if defined(HAVE_STDARG_H)
void gen_send(int sock, const char *fmt, ... )
#else
void gen_send(sock, fmt, va_alist)
int sock;
const char *fmt;
va_dcl
#endif
{
char buf [MSGBUFSIZE+1];
va_list ap;
if (protocol->tagged)
(void) sprintf(buf, "%s ", GENSYM);
else
buf[0] = '\0';
#if defined(HAVE_STDARG_H)
va_start(ap, fmt) ;
#else
va_start(ap);
#endif
#ifdef HAVE_VSNPRINTF
vsnprintf(buf + strlen(buf), sizeof(buf), fmt, ap);
#else
vsprintf(buf + strlen(buf), fmt, ap);
#endif
va_end(ap);
strcat(buf, "\r\n");
SockWrite(sock, buf, strlen(buf));
if (outlevel >= O_MONITOR)
{
char *cp;
if (shroud && shroud[0] && (cp = strstr(buf, shroud)))
{
char *sp;
sp = cp + strlen(shroud);
*cp++ = '*';
while (*sp)
*cp++ = *sp++;
*cp = '\0';
}
buf[strlen(buf)-2] = '\0';
report(stdout, "%s> %s\n", protocol->name, buf);
}
}
int gen_recv(sock, buf, size)
int sock;
char *buf;
int size;
{
int oldphase = phase;
phase = SERVER_WAIT;
set_timeout(mytimeout);
if (SockRead(sock, buf, size) == -1)
{
set_timeout(0);
phase = oldphase;
return(PS_SOCKET);
}
else
{
set_timeout(0);
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
if (buf[strlen(buf)-1] == '\r')
buf[strlen(buf)-1] = '\0';
if (outlevel >= O_MONITOR)
report(stdout, "%s< %s\n", protocol->name, buf);
phase = oldphase;
return(PS_SUCCESS);
}
}
#if defined(HAVE_STDARG_H)
int gen_transact(int sock, const char *fmt, ... )
#else
int gen_transact(int sock, fmt, va_alist)
int sock;
const char *fmt;
va_dcl
#endif
{
int ok;
char buf [MSGBUFSIZE+1];
va_list ap;
int oldphase = phase;
phase = SERVER_WAIT;
if (protocol->tagged)
(void) sprintf(buf, "%s ", GENSYM);
else
buf[0] = '\0';
#if defined(HAVE_STDARG_H)
va_start(ap, fmt) ;
#else
va_start(ap);
#endif
#ifdef HAVE_VSNPRINTF
vsnprintf(buf + strlen(buf), sizeof(buf), fmt, ap);
#else
vsprintf(buf + strlen(buf), fmt, ap);
#endif
va_end(ap);
strcat(buf, "\r\n");
SockWrite(sock, buf, strlen(buf));
if (outlevel >= O_MONITOR)
{
char *cp;
if (shroud && shroud[0] && (cp = strstr(buf, shroud)))
{
char *sp;
sp = cp + strlen(shroud);
*cp++ = '*';
while (*sp)
*cp++ = *sp++;
*cp = '\0';
}
buf[strlen(buf)-1] = '\0';
report(stdout, "%s> %s\n", protocol->name, buf);
}
ok = (protocol->parse_response)(sock, buf);
phase = oldphase;
return(ok);
}