aboutsummaryrefslogtreecommitdiffstats
path: root/website/testservers.html
blob: 5c212ccaba930f8af322515069f5b173c52387b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="description" content=""/>
<meta name="keywords" content=""/> 
<title>Fetchmail's Test List</title>
</head>
<body>
<table width="100%" cellpadding=0 summary="Canned page header"><tr>
<td width="30%">Back to <a href="/index.html">Home Page</a>
<td width="30%" align=right>Wed Oct 15 18:43:10 EDT 2003
</tr></table>
<hr />
<h1>Fetchmail's Test List</h1>

<p>Here are the server types on my regression-test list:</p>

<table border=1 width=80% align=center summary="Server list">
<tr>
<td><strong>Protocol &amp; Version:</strong></td>
<td><strong>Special Options:</strong></td>
</tr>
<tr><td>IMAP: CommuniGate IMAP server</td><td>IMAPrev1 STARTTLS AUTH=CRAM-MD5 AUTH=DIGEST-MD5</td>
<tr><td>POP3: CommuniGate POP3 server</td><td>CAPA LAST APOP CRAM-MD5</td>
<tr><td>POP3: IntraStore POP3 mail server</td><td>!CAPA LAST</td>
<tr><td>APOP: IntraStore POP3 mail server</td><td>!CAPA LAST APOP</td>
<tr><td>IMAP: IntraStore IMAP mail server</td><td>IMAPrev1 IDLE AUTH=CRAM-MD5 AUTH=SKEY AUTH=ANONYMOUS</td>
<tr><td>POP3: Eudora EIMS</td><td>CAPA LAST APOP SASL CRAM-MD5 NTLM</td>
<tr><td>POP3: gmx.de pop server</td><td>!CAPA UIDL</td>
<tr><td>IMAP: IMail IMAP server</td><td>IMAP4rev1 AUTH=CRAM-MD5</td>
<tr><td>IMAP: Microsoft Exchange</td><td>IDLE AUTH=NTLM</td>
<tr><td>POP3: qpopper 3.1.2 (Eudora) patched with mysql</td><td>CAPA UIDL</td>
<tr><td>IMAP: Courier IMAP</td><td>IMAP4rev1</td>
<tr><td>POP3: Courier POP3</td><td>CAPA UIDL</td>
<tr><td>APOP: Qpopper using APOP</td><td>!CAPA</td>
<tr><td>IMAP: UW IMAP</td><td>IMAPrev1</td>
<tr><td>IMAP: Courier IMAP</td><td>IMAP4rev1</td>
<tr><td>POP3: Qpopper 4.0.5</td><td>CAPA UIDL</td>
</tr></table>

<p>If you control a post-office server that is not one of the types listed
here, please consider lending me a test account.  Note that I do <em>not</em>
need shell access, just the permissions to send mail to a mailbox the server
looks at and to fetch mail off of it.</p>

<p>I'd like to have weird things like a POP2 server on here.  Also more
closed-source servers because they tend to be broken in odd
ways. These are the real robustness tests.</p>

<hr />
<table width="100%" cellpadding=0 summary="Canned page header"><tr>
<td width="30%">Back to <a href="/index.html">Home Page</a>
<td width="30%" align=right>Wed Oct 15 18:43:10 EDT 2003
</tr></table>

<br clear="left" />
</BODY>
</HTML>
43' href='#n643'>643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668
/*
 * driver.c -- generic driver for mail fetch method protocols
 *
 * Copyright 1997 by Eric S. Raymond
 * For license terms, see the file COPYING in this directory.
 */

#include  "config.h"
#include  <stdio.h>
#include  <setjmp.h>
#include  <errno.h>
#include  <string.h>
#ifdef HAVE_MEMORY_H
#include  <memory.h>
#endif /* HAVE_MEMORY_H */
#if defined(STDC_HEADERS)
#include  <stdlib.h>
#include  <limits.h>
#endif
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#if defined(HAVE_SYS_ITIMER_H)
#include <sys/itimer.h>
#endif
#include  <sys/time.h>
#include  <signal.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_NET_SOCKET_H
#include <net/socket.h>
#endif
#ifdef HAVE_PKG_hesiod
#include <hesiod.h>
#endif

#if defined(HAVE_RES_SEARCH) || defined(HAVE_GETHOSTBYNAME)
#include <netdb.h>
#include "mx.h"
#endif /* defined(HAVE_RES_SEARCH) || defined(HAVE_GETHOSTBYNAME) */

#include "kerberos.h"
#ifdef KERBEROS_V4
#include <netinet/in.h>
#endif /* KERBEROS_V4 */

#include "i18n.h"
#include "socket.h"

#include "fetchmail.h"
#include "tunable.h"

/* throw types for runtime errors */
#define THROW_TIMEOUT	1		/* server timed out */
#define THROW_SIGPIPE	2		/* SIGPIPE on stream socket */

/* magic values for the message length array */
#define MSGLEN_UNKNOWN	0		/* length unknown (0 is impossible) */
#define MSGLEN_INVALID	-1		/* length passed back is invalid */
#define MSGLEN_TOOLARGE	-2		/* message is too large */
#define MSGLEN_OLD	-3		/* message is old */

int pass;		/* how many times have we re-polled? */
int stage;		/* where are we? */
int phase;		/* where are we, for error-logging purposes? */
int batchcount;		/* count of messages sent in current batch */
flag peek_capable;	/* can we peek for better error recovery? */
int mailserver_socket_temp = -1;	/* socket to free if connect timeout */ 

static volatile int timeoutcount = 0;	/* count consecutive timeouts */
static volatile int idletimeout = 0;	/* timeout occured in idle stage? */

static jmp_buf	restart;

int isidletimeout(void)
/* last timeout occured in idle stage? */
{
    return idletimeout;
}

void resetidletimeout(void)
{
    idletimeout = 0;
}

void set_timeout(int timeleft)
/* reset the nonresponse-timeout */
{
#if !defined(__EMX__) && !defined(__BEOS__) 
    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 RETSIGTYPE timeout_handler (int signal)
/* handle SIGALRM signal indicating a server timeout */
{
    if(stage != STAGE_IDLE) {
	timeoutcount++;
	longjmp(restart, THROW_TIMEOUT);
    } else
	idletimeout = 1;
}

static RETSIGTYPE sigpipe_handler (int signal)
/* handle SIGPIPE signal indicating a broken stream socket */
{
    longjmp(restart, THROW_SIGPIPE);
}

#define CLEANUP_TIMEOUT 60 /* maximum timeout during cleanup */

static int cleanupSockClose (int fd)
/* close sockets in maximum CLEANUP_TIMEOUT seconds during cleanup */
{
    int scerror;
    SIGHANDLERTYPE alrmsave;
    alrmsave = set_signal_handler(SIGALRM, null_signal_handler);
    set_timeout(CLEANUP_TIMEOUT);
    scerror = SockClose(fd);
    set_timeout(0);
    set_signal_handler(SIGALRM, alrmsave);
    return (scerror);
}

#ifdef KERBEROS_V4
static int kerberos_auth(socket, canonical, principal) 
/* authenticate to the server host using Kerberos V4 */
int socket;		/* socket to server host */
char *canonical;	/* server name */
char *principal;
{
    char * host_primary;
    KTEXT ticket;
    MSG_DAT msg_data;
    CREDENTIALS cred;
    Key_schedule schedule;
    int rem;
    char * prin_copy = (char *) NULL;
    char * prin = (char *) NULL;
    char * inst = (char *) NULL;
    char * realm = (char *) NULL;

    if (principal != (char *)NULL && *principal)
    {
        char *cp;
        prin = prin_copy = xstrdup(principal);
	for (cp = prin_copy; *cp && *cp != '.'; ++cp)
	    ;
	if (*cp)
	{
	    *cp++ = '\0';
	    inst = cp;
	    while (*cp && *cp != '@')
	        ++cp;
	    if (*cp)
	    {
	        *cp++ = '\0';
	        realm = cp;
	    }
	}
    }
  
    xalloca(ticket, KTEXT, sizeof (KTEXT_ST));
    rem = (krb_sendauth (0L, socket, ticket,
			 prin ? prin : "pop",
			 inst ? inst : canonical,
			 realm ? realm : ((char *) (krb_realmofhost (canonical))),
			 ((unsigned long) 0),
			 (&msg_data),
			 (&cred),
			 (schedule),
			 ((struct sockaddr_in *) 0),
			 ((struct sockaddr_in *) 0),
			 "KPOPV0.1"));
    if (prin_copy)
    {
        free(prin_copy);
    }
    if (rem != KSUCCESS)
    {
	report(stderr, GT_("kerberos error %s\n"), (krb_get_err_text (rem)));
	return (PS_AUTHFAIL);
    }
    return (0);
}
#endif /* KERBEROS_V4 */

#ifdef KERBEROS_V5
static int kerberos5_auth(socket, canonical)
/* authenticate to the server host using Kerberos V5 */
int socket;             /* socket to server host */
const char *canonical;  /* server name */
{
    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,  /* no data to checksum */
         0,   /* no creds, use ccache instead */
         ccdef,
         &err_ret, 0,

         NULL); /* don't need reply */

    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, GT_("krb5_sendauth: %s [server says '%*s'] \n"),
                 error_message(retval),
                 err_ret->e_text);
#else
      if (err_ret && err_ret->text.length) {
          report(stderr, GT_("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 /* KERBEROS_V5 */

static void clean_skipped_list(struct idlist **skipped_list)
/* struct "idlist" contains no "prev" ptr; we must remove unused items first */
{
    struct idlist *current=NULL, *prev=NULL, *tmp=NULL, *head=NULL;
    prev = current = head = *skipped_list;

    if (!head)
	return;
    do
    {
	/* if item has no reference, remove it */
	if (current && current->val.status.mark == 0)
	{
	    if (current == head) /* remove first item (head) */
	    {
		head = current->next;
		if (current->id) free(current->id);
		free(current);
		prev = current = head;
	    }
	    else /* remove middle/last item */
	    {
		tmp = current->next;
		prev->next = tmp;
		if (current->id) free(current->id);
		free(current);
		current = tmp;
	    }
	}
	else /* skip this item */
	{
	    prev = current;
	    current = current->next;
	}
    } while(current);

    *skipped_list = head;
}

static void send_size_warnings(struct query *ctl)
/* send warning mail with skipped msg; reset msg count when user notified */
{
    int size, nbr;
    int msg_to_send = FALSE;
    struct idlist *head=NULL, *current=NULL;
    int max_warning_poll_count;

    head = ctl->skipped;
    if (!head)
	return;

    /* don't start a notification message unless we need to */
    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;

    /*
     * There's no good way to recover if we can't send notification mail, 
     * but it's not a disaster, either, since the skipped mail will not
     * be deleted.
     */
    if (open_warning_by_mail(ctl, (struct msgblk *)NULL))
	return;
    stuff_warning(ctl,
	   GT_("Subject: Fetchmail oversized-messages warning.\n"
	     "\n"
	     "The following oversized messages remain on the mail server %s:"),
		  ctl->server.pollname);
 
    if (run.poll_interval == 0)
	max_warning_poll_count = 0;
    else
	max_warning_poll_count = ctl->warnings/run.poll_interval;

    /* parse list of skipped msg, adding items to the mail */
    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, 
		    GT_("\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);
}

static void mark_oversized(struct query *ctl, int num, int size)
/* mark a message oversized */
{
    struct idlist *current=NULL, *tmp=NULL;
    char sizestr[32];
    int cnt;

    /* convert size to string */
#ifdef HAVE_SNPRINTF
    snprintf(sizestr, sizeof(sizestr),
#else
    sprintf(sizestr,
#endif /* HAVE_SNPRINTF */
      "%d", size);

    /* build a list of skipped messages
     * val.id = size of msg (string cnvt)
     * val.status.num = warning_poll_count
     * val.status.mask = nbr of msg this size
     */

    current = ctl->skipped;

    /* initialise warning_poll_count to the
     * current value so that all new msg will
     * be included in the next mail
     */
    cnt = current ? current->val.status.num : 0;

    /* if entry exists, increment the count */
    if (current && str_in_list(&current, sizestr, FALSE))
    {
	for ( ; current; current = current->next)
	{
	    if (strcmp(current->id, sizestr) == 0)
	    {
		current->val.status.mark++;
		break;
	    }
	}
    }
    /* otherwise, create a new entry */
    /* initialise with current poll count */
    else
    {
	tmp = save_str(&ctl->skipped, sizestr, 1);
	tmp->val.status.num = cnt;
    }
}

static int fetch_messages(int mailserver_socket, struct query *ctl, 
			  int count, int *msgsizes, int maxfetch,
			  int *fetches, int *dispatches, int *deletions)
/* fetch messages in lockstep mode */
{
    flag force_retrieval;
    int num, firstnum = 1, lastnum = 0, err, len;
    int fetchsizelimit = ctl->fetchsizelimit;
    int msgsize;

    if (ctl->server.base_protocol->getpartialsizes && NUM_NONZERO(fetchsizelimit))
    {
	/* for POP3, we can get the size of one mail only! Unfortunately, this
	 * protocol specific test cannot be done elsewhere as the protocol
	 * could be "auto". */
	switch (ctl->server.protocol)
	{
	    case P_POP3: case P_APOP: case P_RPOP:
	    fetchsizelimit = 1;
	}

	/* Time to allocate memory to store the sizes */
	xalloca(msgsizes, int *, sizeof(int) * fetchsizelimit);
    }

    /*
     * What forces this code is that in POP2 and
     * IMAP2bis you can't fetch a message without
     * having it marked `seen'.  In POP3 and IMAP4, on the
     * other hand, you can (peek_capable is set by 
     * each driver module to convey this; it's not a
     * method constant because of the difference between
     * IMAP2bis and IMAP4, and because POP3 doesn't  peek
     * if fetchall is on).
     *
     * The result of being unable to peek is that if there's
     * any kind of transient error (DNS lookup failure, or
     * sendmail refusing delivery due to process-table limits)
     * the message will be marked "seen" on the server without
     * having been delivered.  This is not a big problem if
     * fetchmail is running in foreground, because the user
     * will see a "skipped" message when it next runs and get
     * clued in.
     *
     * But in daemon mode this leads to the message
     * being silently ignored forever.  This is not
     * acceptable.
     *
     * We compensate for this by checking the error
     * count from the previous pass and forcing all
     * messages to be considered new if it's nonzero.
     */
    force_retrieval = !peek_capable && (ctl->errcount > 0);

    for (num = 1; num <= count; num++)
    {
	flag suppress_delete = FALSE;
	flag suppress_forward = FALSE;
	flag suppress_readbody = FALSE;
	flag retained = FALSE;
	int msgcode = MSGLEN_UNKNOWN;

	/* check if the message is old
	 * Note: the size of the message may not be known here */
	if (ctl->fetchall || force_retrieval)
	    ;
	else if (ctl->server.base_protocol->is_old && (ctl->server.base_protocol->is_old)(mailserver_socket,ctl,num))
	    msgcode = MSGLEN_OLD;
	if (msgcode == MSGLEN_OLD)
	{
  		/* To avoid flooding the syslog when using --keep,
  		 * report "Skipped message" only when:
  		 *  1) --verbose is on, or
  		 *  2) fetchmail does not use syslog
  		 */
	    if (   (outlevel >= O_VERBOSE) ||
	           (outlevel > O_SILENT && !run.use_syslog)
	       )
	    {
		report_build(stdout, 
			     GT_("skipping message %s@%s:%d"),
			     ctl->remotename, ctl->server.truename, num);
	    }

	    goto flagthemail;
	}

	if (ctl->server.base_protocol->getpartialsizes && NUM_NONZERO(fetchsizelimit) &&
	    lastnum < num)
	{
	    /* Instead of getting the sizes of all mails at the start, we get
	     * the sizes in blocks of fetchsizelimit. This leads to better
	     * performance when there are too many mails (say, 10000) in
	     * the mailbox and either we are not getting all the mails at
	     * one go (--fetchlimit 100) or there is a frequent socket
	     * error while just getting the sizes of all mails! */

	    int i;
	    int oldstage = stage;
	    firstnum = num;
	    lastnum = num + fetchsizelimit - 1;
	    if (lastnum > count)
		lastnum = count;
	    for (i = 0; i < fetchsizelimit; i++)
		msgsizes[i] = 0;

	    stage = STAGE_GETSIZES;
	    err = (ctl->server.base_protocol->getpartialsizes)(mailserver_socket, num, lastnum, msgsizes);
	    if (err != 0)
		return err;
	    stage = oldstage;
	}

	msgsize = msgsizes ? msgsizes[num-firstnum] : 0;

	/* check if the message is oversized */
	if (NUM_NONZERO(ctl->limit) && (msgsize > ctl->limit))
	    msgcode = MSGLEN_TOOLARGE;
/*	else if (msgsize == 512)
	    msgcode = MSGLEN_OLD;  (hmh) sample code to skip message */

	if (msgcode < 0)
	{
	    if ((msgcode == MSGLEN_TOOLARGE) && !check_only)
	    {
		mark_oversized(ctl, num, msgsize);
		suppress_delete = TRUE;
	    }
	    if (outlevel > O_SILENT)
	    {
		/* old messages are already handled above */
		report_build(stdout, 
			     GT_("skipping message %s@%s:%d (%d octets)"),
			     ctl->remotename, ctl->server.truename, num,
			     msgsize);
		switch (msgcode)
		{
		case MSGLEN_INVALID:
		    /*
		     * Invalid lengths are produced by Post Office/NT's
		     * annoying habit of randomly prepending bogus
		     * LIST items of length -1.  Patrick Audley
		     * <paudley@pobox.com> tells us: LIST shows a
		     * size of -1, RETR and TOP return "-ERR
		     * System error - couldn't open message", and
		     * DELE succeeds but doesn't actually delete
		     * the message.
		     */
		    report_build(stdout, GT_(" (length -1)"));
		    break;
		case MSGLEN_TOOLARGE:
		    report_build(stdout, GT_(" (oversized)"));
		    break;
		}
	    }
	}
	else
	{
	    flag wholesize = !ctl->server.base_protocol->fetch_body;
	    flag separatefetchbody = (ctl->server.base_protocol->fetch_body) ? TRUE : FALSE;

	    /* request a message */
	    err = (ctl->server.base_protocol->fetch_headers)(mailserver_socket,ctl,num, &len);
	    if (err == PS_TRANSIENT)    /* server is probably Exchange */
	    {
		report(stdout,
			     GT_("couldn't fetch headers, message %s@%s:%d (%d octets)\n"),
			     ctl->remotename, ctl->server.truename, num,
			     msgsize);
		continue;
	    }
	    else if (err != 0)
		return(err);

	    /* -1 means we didn't see a size in the response */
	    if (len == -1)
	    {
		len = msgsize;
		wholesize = TRUE;
	    }

	    if (outlevel > O_SILENT)
	    {
		report_build(stdout, GT_("reading message %s@%s:%d of %d"),
			     ctl->remotename, ctl->server.truename,
			     num, count);

		if (len > 0)
		    report_build(stdout, GT_(" (%d %soctets)"),
				 len, wholesize ? "" : GT_("header "));
		if (outlevel >= O_VERBOSE)
		    report_complete(stdout, "\n");
		else
		    report_complete(stdout, " ");
	    }

	    /* 
	     * Read the message headers and ship them to the
	     * output sink.  
	     */
	    err = readheaders(mailserver_socket, len, msgsize,
			     ctl, num,
			     /* pass the suppress_readbody flag only if the underlying
			      * protocol does not fetch the body separately */
			     separatefetchbody ? 0 : &suppress_readbody);
	    if (err == PS_RETAINED)
		suppress_forward = suppress_delete = retained = TRUE;
	    else if (err == PS_TRANSIENT)
		suppress_delete = suppress_forward = TRUE;
	    else if (err == PS_REFUSED)
		suppress_forward = TRUE;
	    else if (err == PS_TRUNCATED)
		suppress_readbody = TRUE;
	    else if (err)
		return(err);

	    /* tell server we got it OK and resynchronize */
	    if (separatefetchbody && ctl->server.base_protocol->trail)
	    {
		if (outlevel >= O_VERBOSE && !isafile(1))
		{
		    fputc('\n', stdout);
		    fflush(stdout);
		}

		if ((err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, num)))
		    return(err);
	    }

	    /* do not read the body which is not being forwarded only if
	     * the underlying protocol allows the body to be fetched
	     * separately */
	    if (separatefetchbody && suppress_forward)
		suppress_readbody = TRUE;

	    /* 
	     * If we're using IMAP4 or something else that
	     * can fetch headers separately from bodies,
	     * it's time to request the body now.  This
	     * fetch may be skipped if we got an anti-spam
	     * or other PS_REFUSED error response during
	     * readheaders.
	     */
	    if (!suppress_readbody)
	    {
		if (separatefetchbody)
		{
		    len = -1;
		    if ((err=(ctl->server.base_protocol->fetch_body)(mailserver_socket,ctl,num,&len)))
			return(err);
		    /*
		     * Work around a bug in Novell's
		     * broken GroupWise IMAP server;
		     * its body FETCH response is missing
		     * the required length for the data
		     * string.  This violates RFC2060.
		     */
		    if (len == -1)
			len = msgsize - msgblk.msglen;
		    if (outlevel > O_SILENT && !wholesize)
			report_complete(stdout,
					GT_(" (%d body octets) "), len);
		}

		/* process the body now */
		err = readbody(mailserver_socket,
			      ctl,
			      !suppress_forward,
			      len);
		if (err == PS_TRANSIENT)
		    suppress_delete = suppress_forward = TRUE;
		else if (err)
		    return(err);

		/* tell server we got it OK and resynchronize */
		if (ctl->server.base_protocol->trail)
		{
		    if (outlevel >= O_VERBOSE && !isafile(1))
		    {
			fputc('\n', stdout);
			fflush(stdout);
		    }

		    err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, num);
		    if (err != 0)
			return(err);
		}
	    }

	    /* count # messages forwarded on this pass */
	    if (!suppress_forward)
		(*dispatches)++;

	    /*
	     * Check to see if the numbers matched?
	     *
	     * Yes, some servers foo this up horribly.
	     * All IMAP servers seem to get it right, and
	     * so does Eudora QPOP at least in 2.xx
	     * versions.
	     *
	     * Microsoft Exchange gets it completely
	     * wrong, reporting compressed rather than
	     * actual sizes (so the actual length of
	     * message is longer than the reported size).
	     * Another fine example of Microsoft brain death!
	     *
	     * Some older POP servers, like the old UCB
	     * POP server and the pre-QPOP QUALCOMM
	     * versions, report a longer size in the LIST
	     * response than actually gets shipped up.
	     * It's unclear what is going on here, as the
	     * QUALCOMM server (at least) seems to be
	     * reporting the on-disk size correctly.
	     */
	    if (msgblk.msglen != msgsize)
	    {
		if (outlevel >= O_DEBUG)
		    report(stdout,
			   GT_("message %s@%s:%d was not the expected length (%d actual != %d expected)\n"),
			   ctl->remotename, ctl->server.truename, num,
			   msgblk.msglen, msgsize);
	    }

	    /* end-of-message processing starts here */
	    if (!close_sink(ctl, &msgblk, !suppress_forward))
	    {
		ctl->errcount++;
		suppress_delete = TRUE;
	    }
	    if (!retained)
		(*fetches)++;
	}

flagthemail:
	/*
	 * At this point in flow of control, either
	 * we've bombed on a protocol error or had
	 * delivery refused by the SMTP server
	 * (unlikely -- I've never seen it) or we've
	 * seen `accepted for delivery' and the
	 * message is shipped.  It's safe to mark the
	 * message seen and delete it on the server
	 * now.
	 */

	/* maybe we delete this message now? */
	if (retained)
	{
	    if (outlevel > O_SILENT) 
		report(stdout, GT_(" retained\n"));
	}
	else if (ctl->server.base_protocol->delete
		 && !suppress_delete
		 && ((msgcode >= 0 && !ctl->keep)
		     || (msgcode == MSGLEN_OLD && ctl->flush)))
	{
	    (*deletions)++;
	    if (outlevel > O_SILENT) 
		report_complete(stdout, GT_(" flushed\n"));
	    err = (ctl->server.base_protocol->delete)(mailserver_socket, ctl, num);
	    if (err != 0)
		return(err);
	}
	else
	{
	    if (   (outlevel >= O_VERBOSE) ||
         		/* To avoid flooding the syslog when using --keep,
         		 * report "Skipped message" only when:
         		 *  1) --verbose is on, or
         		 *  2) fetchmail does not use syslog, or
         		 *  3) the message was skipped for some other
         		 *     reason than just being old.
         		 */
	           (outlevel > O_SILENT && (!run.use_syslog || msgcode != MSGLEN_OLD))
	       )
	    report_complete(stdout, GT_(" not flushed\n"));

	    /* maybe we mark this message as seen now? */
	    if (ctl->server.base_protocol->mark_seen
		&& !suppress_delete
		&& (msgcode >= 0 && ctl->keep))
	    {
		err = (ctl->server.base_protocol->mark_seen)(mailserver_socket, ctl, num);
		if (err != 0)
		    return(err);
	    }
	}

	/* perhaps this as many as we're ready to handle */
	if (maxfetch && maxfetch <= *fetches && num < count)
	{
	    report(stdout, GT_("fetchlimit %d reached; %d messages left on server %s account %s\n"),
		   maxfetch, count - *fetches, ctl->server.truename, ctl->remotename);
	    return(PS_MAXFETCH);
	}
    }

    return(PS_SUCCESS);
}

/* retrieve messages from server using given protocol method table */
static int do_session(
	/* parsed options with merged-in defaults */
	struct query *ctl,
	/* protocol method table */
	const struct method *proto,
	/* maximum number of messages to fetch */
	const int maxfetch)
{
    static int *msgsizes;
    volatile int err, mailserver_socket = -1;	/* pacifies -Wall */
    int deletions = 0, js;
    const char *msg;
    SIGHANDLERTYPE pipesave;
    SIGHANDLERTYPE alrmsave;

    ctl->server.base_protocol = proto;

    msgsizes = NULL;
    pass = 0;
    err = 0;
    init_transact(proto);

    /* set up the server-nonresponse timeout */
    alrmsave = set_signal_handler(SIGALRM, timeout_handler);
    mytimeout = ctl->server.timeout;

    /* set up the broken-pipe timeout */
    pipesave = set_signal_handler(SIGPIPE, sigpipe_handler);

    if ((js = setjmp(restart)))
    {
#ifdef HAVE_SIGPROCMASK
	/*
	 * Don't rely on setjmp() to restore the blocked-signal mask.
	 * It does this under BSD but is required not to under POSIX.
	 *
	 * If your Unix doesn't have sigprocmask, better hope it has
	 * BSD-like behavior.  Otherwise you may see fetchmail get
	 * permanently wedged after a second timeout on a bad read,
	 * because alarm signals were blocked after the first.
	 */
	sigset_t	allsigs;

	sigfillset(&allsigs);
	sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
#endif /* HAVE_SIGPROCMASK */
	
	if (js == THROW_SIGPIPE)
	{
	    set_signal_handler(SIGPIPE, SIG_IGN);
	    report(stdout,
		   GT_("SIGPIPE thrown from an MDA or a stream socket error\n"));
	    wait(0);
	}
	else if (js == THROW_TIMEOUT)
	{
	    if (phase == OPEN_WAIT)
		report(stdout,
		       GT_("timeout after %d seconds waiting to connect to server %s.\n"),
		       ctl->server.timeout, ctl->server.pollname);
	    else if (phase == SERVER_WAIT)
		report(stdout,
		       GT_("timeout after %d seconds waiting for server %s.\n"),
		       ctl->server.timeout, ctl->server.pollname);
	    else if (phase == FORWARDING_WAIT)
		report(stdout,
		       GT_("timeout after %d seconds waiting for %s.\n"),
		       ctl->server.timeout,
		       ctl->mda ? "MDA" : "SMTP");
	    else if (phase == LISTENER_WAIT)
		report(stdout,
		       GT_("timeout after %d seconds waiting for listener to respond.\n"), ctl->server.timeout);
	    else
		report(stdout, 
		       GT_("timeout after %d seconds.\n"), ctl->server.timeout);

	    /*
	     * If we've exceeded our threshold for consecutive timeouts, 
	     * try to notify the user, then mark the connection wedged.
	     * Don't do this if the connection can idle, though; idle
	     * timeouts just mean the frequency of mail is low.
	     */
	    if (timeoutcount > MAX_TIMEOUTS 
		&& !open_warning_by_mail(ctl, (struct msgblk *)NULL))
	    {
		stuff_warning(ctl,
			      GT_("Subject: fetchmail sees repeated timeouts\n"));
		stuff_warning(ctl,
			      GT_("Fetchmail saw more than %d timeouts while attempting to get mail from %s@%s.\n"), 
			      MAX_TIMEOUTS,
			      ctl->remotename,
			      ctl->server.truename);
		stuff_warning(ctl, 
    GT_("This could mean that your mailserver is stuck, or that your SMTP\n" \
    "server is wedged, or that your mailbox file on the server has been\n" \
    "corrupted by a server error.  You can run `fetchmail -v -v' to\n" \
    "diagnose the problem.\n\n" \
    "Fetchmail won't poll this mailbox again until you restart it.\n"));
		close_warning_by_mail(ctl, (struct msgblk *)NULL);
		ctl->wedged = TRUE;
	    }
	}

	err = PS_SOCKET;
	goto cleanUp;
    }
    else
    {
	char buf[MSGBUFSIZE+1], *realhost;
	int count, new, bytes;
#ifdef INET6_ENABLE
	int fetches, dispatches, oldphase;
#else /* INET6_ENABLE */
	int port, fetches, dispatches, oldphase;
#endif /* INET6_ENABLE */
	struct idlist *idp;

	/* execute pre-initialization command, if any */
	if (ctl->preconnect && (err = system(ctl->preconnect)))
	{
	    report(stderr, 
		   GT_("pre-connection command failed with status %d\n"), err);
	    err = PS_SYNTAX;
	    goto closeUp;
	}

	/* open a socket to the mail server */
	oldphase = phase;
	phase = OPEN_WAIT;
	set_timeout(mytimeout);
#ifndef INET6_ENABLE
#ifdef SSL_ENABLE
	port = ctl->server.port ? ctl->server.port : ( ctl->use_ssl ? ctl->server.base_protocol->sslport : ctl->server.base_protocol->port );
#else
	port = ctl->server.port ? ctl->server.port : ctl->server.base_protocol->port;
#endif
#endif /* !INET6_ENABLE */

#ifdef HAVE_PKG_hesiod
	/* If either the pollname or vianame are "hesiod" we want to
	   lookup the user's hesiod pobox host */
	if (!strcasecmp(ctl->server.queryname, "hesiod")) {
	    struct hes_postoffice *hes_p;
	    hes_p = hes_getmailhost(ctl->remotename);
	    if (hes_p != NULL && strcmp(hes_p->po_type, "POP") == 0) {
		 free(ctl->server.queryname);
		 ctl->server.queryname = xstrdup(hes_p->po_host);
		 if (ctl->server.via)
		     free(ctl->server.via);
		 ctl->server.via = xstrdup(hes_p->po_host);
	    } else {
		 report(stderr,
			GT_("couldn't find HESIOD pobox for %s\n"),
			ctl->remotename);
	    }
	}
#endif /* HESIOD */

#ifdef HAVE_GETHOSTBYNAME
	/*
	 * Canonicalize the server truename for later use.  This also
	 * functions as a probe for whether the mailserver is accessible.
	 * We try it on each poll cycle until we get a result.  This way,
	 * fetchmail won't fail if started up when the network is inaccessible.
	 */
	if (ctl->server.dns && !ctl->server.trueaddr)
	{
	    if (ctl->server.lead_server)
	    {
		char	*leadname = ctl->server.lead_server->truename;

		/* prevent core dump from ill-formed or duplicate entry */
		if (!leadname)
		{
		    report(stderr, GT_("Lead server has no name.\n"));
		    err = PS_DNS;
		    set_timeout(0);
		    phase = oldphase;
		    goto closeUp;
		}

		ctl->server.truename = xstrdup(leadname);
	    }
	    else
	    {
		struct hostent	*namerec;
		    
		/* 
		 * Get the host's IP, so we can report it like this:
		 *
		 * Received: from hostname [10.0.0.1]
		 */
		errno = 0;
		namerec = gethostbyname(ctl->server.queryname);
		if (namerec == (struct hostent *)NULL)
		{
		    report(stderr,
			   GT_("couldn't find canonical DNS name of %s (%s)\n"),
			   ctl->server.pollname, ctl->server.queryname);
		    err = PS_DNS;
		    set_timeout(0);
		    phase = oldphase;
		    goto closeUp;
		}
		else 
		{
		    ctl->server.truename=xstrdup((char *)namerec->h_name);
		    ctl->server.trueaddr=xmalloc(namerec->h_length);
		    memcpy(ctl->server.trueaddr, 
			   namerec->h_addr_list[0],
			   namerec->h_length);
		}
	    }
	}
#endif /* HAVE_GETHOSTBYNAME */

	realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;

	/* allow time for the port to be set up if we have a plugin */
	if (ctl->server.plugin)
	    (void)sleep(1);
#ifdef INET6_ENABLE
	if ((mailserver_socket = SockOpen(realhost, 
			     ctl->server.service ? ctl->server.service : ( ctl->use_ssl ? ctl->server.base_protocol->sslservice : ctl->server.base_protocol->service ),
			     ctl->server.netsec, ctl->server.plugin)) == -1)
#else /* INET6_ENABLE */
	if ((mailserver_socket = SockOpen(realhost, port, NULL, ctl->server.plugin)) == -1)
#endif /* INET6_ENABLE */
	{
	    char	errbuf[BUFSIZ];
#ifndef INET6_ENABLE
	    int err_no = errno;
#ifdef HAVE_RES_SEARCH
	    if (err_no != 0 && h_errno != 0)
		report(stderr, GT_("internal inconsistency\n"));
#endif
	    /*
	     * Avoid generating a bogus error every poll cycle when we're
	     * in daemon mode but the connection to the outside world
	     * is down.
	     */
	    if (!((err_no == EHOSTUNREACH || err_no == ENETUNREACH) 
		  && run.poll_interval))
	    {
		report_build(stderr, GT_("%s connection to %s failed"), 
			     ctl->server.base_protocol->name, ctl->server.pollname);
#ifdef HAVE_RES_SEARCH
		if (h_errno != 0)
		{
		    if (h_errno == HOST_NOT_FOUND)
			strcpy(errbuf, GT_("host is unknown."));
#ifndef __BEOS__
		    else if (h_errno == NO_ADDRESS)
			strcpy(errbuf, GT_("name is valid but has no IP address."));
#endif
		    else if (h_errno == NO_RECOVERY)
			strcpy(errbuf, GT_("unrecoverable name server error."));
		    else if (h_errno == TRY_AGAIN)
			strcpy(errbuf, GT_("temporary name server error."));
		    else
#ifdef HAVE_SNPRINTF
			snprintf(errbuf, sizeof(errbuf),
#else
			sprintf(errbuf,
#endif /* HAVE_SNPRINTF */
			  GT_("unknown DNS error %d."), h_errno);
		}
		else
#endif /* HAVE_RES_SEARCH */
		    strcpy(errbuf, strerror(err_no));
		report_complete(stderr, ": %s\n", errbuf);

#ifdef __UNUSED
		/* 
		 * Don't use this.  It was an attempt to address Debian bug
		 * #47143 (Notify user by mail when pop server nonexistent).
		 * Trouble is, that doesn't work; you trip over the case 
		 * where your SLIP or PPP link is down...
		 */
		/* warn the system administrator */
		if (open_warning_by_mail(ctl, (struct msgblk *)NULL) == 0)
		{
		    stuff_warning(ctl,
			 GT_("Subject: Fetchmail unreachable-server warning.\n"
			   "\n"
			   "Fetchmail could not reach the mail server %s:")
				  ctl->server.pollname);
		    stuff_warning(ctl, errbuf, ctl->server.pollname);
		    close_warning_by_mail(ctl, (struct msgblk *)NULL);
		}
#endif
	    }
#endif /* INET6_ENABLE */
	    err = PS_SOCKET;
	    set_timeout(0);
	    phase = oldphase;
	    goto closeUp;
	}

#ifdef SSL_ENABLE
	/* Save the socket opened. Useful if Fetchmail hangs on SSLOpen 
	 * because the socket can be closed.
	 */
	mailserver_socket_temp = mailserver_socket;
	set_timeout(mytimeout);

	/* perform initial SSL handshake on open connection */
	/* Note:  We pass the realhost name over for certificate
		verification.  We may want to make this configurable */
	if (ctl->use_ssl && SSLOpen(mailserver_socket,ctl->sslcert,ctl->sslkey,ctl->sslproto,ctl->sslcertck,
	    ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1) 
	{
	    report(stderr, GT_("SSL connection failed.\n"));
	    err = PS_AUTHFAIL;
	    goto closeUp;
	}
	
	/* Fetchmail didn't hang on SSLOpen, 
	 * then no need to set mailserver_socket_temp 
	 */
	mailserver_socket_temp = -1;
#endif
	
	/* A timeout is still defined before SSLOpen, 
	 * then Fetchmail hanging on SSLOpen is handled.
	 */
	set_timeout(0);
	phase = oldphase;
#ifdef KERBEROS_V4
	if (ctl->server.authenticate == A_KERBEROS_V4 && (strcasecmp(proto->name,"IMAP") != 0))
	{
	    set_timeout(mytimeout);
	    err = kerberos_auth(mailserver_socket, ctl->server.truename,
			       ctl->server.principal);
	    set_timeout(0);
 	    if (err != 0)
		goto cleanUp;
	}
#endif /* KERBEROS_V4 */

#ifdef KERBEROS_V5
	if (ctl->server.authenticate == A_KERBEROS_V5)
	{
	    set_timeout(mytimeout);
	    err = kerberos5_auth(mailserver_socket, ctl->server.truename);
	    set_timeout(0);
 	    if (err != 0)
		goto cleanUp;
	}
#endif /* KERBEROS_V5 */

	/* accept greeting message from mail server */
	err = (ctl->server.base_protocol->parse_response)(mailserver_socket, buf);
	if (err != 0)
	    goto cleanUp;

	/* try to get authorized to fetch mail */
	stage = STAGE_GETAUTH;
	if (ctl->server.base_protocol->getauth)
	{
	    err = (ctl->server.base_protocol->getauth)(mailserver_socket, ctl, buf);

	    if (err != 0)
	    {
		if (err == PS_LOCKBUSY)
		    report(stderr, GT_("Lock-busy error on %s@%s\n"),
			  ctl->remotename,
			  ctl->server.truename);
		else if (err == PS_SERVBUSY)
		    report(stderr, GT_("Server busy error on %s@%s\n"),
			  ctl->remotename,
			  ctl->server.truename);
		else if (err == PS_AUTHFAIL)
		{
		    report(stderr, GT_("Authorization failure on %s@%s%s\n"), 
			   ctl->remotename,
			   ctl->server.truename,
			   (ctl->wehaveauthed ? GT_(" (previously authorized)") : "")
			);

		    /*
		     * If we're running in background, try to mail the
		     * calling user a heads-up about the authentication 
		     * failure once it looks like this isn't a fluke 
		     * due to the server being temporarily inaccessible.
		     * When we get third succesive failure, we notify the user
		     * but only if we haven't already managed to get
		     * authorization.  After that, once we get authorization
		     * we let the user know service is restored.
		     */
		    if (run.poll_interval
			&& !ctl->wehavesentauthnote
			&& ((ctl->wehaveauthed && ++ctl->authfailcount >= 10)
			    || (!ctl->wehaveauthed && ++ctl->authfailcount >= 3))
			&& !open_warning_by_mail(ctl, (struct msgblk *)NULL))
		    {
			ctl->wehavesentauthnote = 1;
			stuff_warning(ctl,
				      GT_("Subject: fetchmail authentication failed on %s@%s\n"),
			    ctl->remotename, ctl->server.truename);
			stuff_warning(ctl,
				      GT_("Fetchmail could not get mail from %s@%s.\n"), 
				      ctl->remotename,
				      ctl->server.truename);
			if (ctl->wehaveauthed)
			    stuff_warning(ctl, GT_("\
The attempt to get authorization failed.\n\
Since we have already succeeded in getting authorization for this\n\
connection, this is probably another failure mode (such as busy server)\n\
that fetchmail cannot distinguish because the server didn't send a useful\n\
error message.\n\
\n\
However, if you HAVE changed your account details since starting the\n\
fetchmail daemon, you need to stop the daemon, change your configuration\n\
of fetchmail, and then restart the daemon.\n\
\n\
The fetchmail daemon will continue running and attempt to connect\n\
at each cycle.  No future notifications will be sent until service\n\
is restored."));
			else
			    stuff_warning(ctl, GT_("\
The attempt to get authorization failed.\n\
This probably means your password is invalid, but some servers have\n\
other failure modes that fetchmail cannot distinguish from this\n\
because they don't send useful error messages on login failure.\n\
\n\
The fetchmail daemon will continue running and attempt to connect\n\
at each cycle.  No future notifications will be sent until service\n\
is restored."));
			close_warning_by_mail(ctl, (struct msgblk *)NULL);
		    }
		}
		else if (err == PS_REPOLL)
		{
		  if (outlevel >= O_VERBOSE)
		    report(stderr, GT_("Repoll immediately on %s@%s\n"),
			   ctl->remotename,
			   ctl->server.truename);
		}
		else
		    report(stderr, GT_("Unknown login or authentication error on %s@%s\n"),
			   ctl->remotename,
			   ctl->server.truename);
		    
		goto cleanUp;
	    }
	    else
	    {
		/*
		 * This connection has given us authorization at least once.
		 *
		 * There are dodgy server (clubinternet.fr for example) that
		 * give spurious authorization failures on patently good
		 * account/password details, then 5 minutes later let you in!
		 *
		 * This is meant to build in some tolerance of such nasty bits
		 * of work.
		 */
		ctl->wehaveauthed = 1;
		/*if (ctl->authfailcount >= 3)*/
		if (ctl->wehavesentauthnote)
		{
		    ctl->wehavesentauthnote = 0;
		    report(stderr,
			   GT_("Authorization OK on %s@%s\n"),
			   ctl->remotename,
			   ctl->server.truename);
		    if (!open_warning_by_mail(ctl, (struct msgblk *)NULL))
		    {
			stuff_warning(ctl,
			      GT_("Subject: fetchmail authentication OK on %s@%s\n"),
				      ctl->remotename, ctl->server.truename);
			stuff_warning(ctl,
			      GT_("Fetchmail was able to log into %s@%s.\n"), 
				      ctl->remotename,
				      ctl->server.truename);
			stuff_warning(ctl, 
				      GT_("Service has been restored.\n"));
			close_warning_by_mail(ctl, (struct msgblk *)NULL);
		    
		    }
		}
		/*
		 * Reporting only after the first three
		 * consecutive failures, or ten consecutive
		 * failures after we have managed to get
		 * authorization.
		 */
		ctl->authfailcount = 0;
	    }
	}

	ctl->errcount = fetches = 0;

	/* now iterate over each folder selected */
	for (idp = ctl->mailboxes; idp; idp = idp->next)
	{
	    pass = 0;
	    do {
		dispatches = 0;
		++pass;

		/* reset timeout, in case we did an IDLE */
		mytimeout = ctl->server.timeout;

		if (outlevel >= O_DEBUG)
		{
		    if (idp->id)
			report(stdout, GT_("selecting or re-polling folder %s\n"), idp->id);
		    else
			report(stdout, GT_("selecting or re-polling default folder\n"));
		}

		/* compute # of messages and number of new messages waiting */
		stage = STAGE_GETRANGE;
		err = (ctl->server.base_protocol->getrange)(mailserver_socket, ctl, idp->id, &count, &new, &bytes);
		if (err != 0)
		    goto cleanUp;

		/* show user how many messages we downloaded */
		if (idp->id)
#ifdef HAVE_SNPRINTF
		    (void) snprintf(buf, sizeof(buf),
#else
		    (void) sprintf(buf,
#endif /* HAVE_SNPRINTF */
				   GT_("%s at %s (folder %s)"),
				   ctl->remotename, ctl->server.pollname, idp->id);
		else
#ifdef HAVE_SNPRINTF
		    (void) snprintf(buf, sizeof(buf),
#else
		    (void) sprintf(buf,
#endif /* HAVE_SNPRINTF */
			       GT_("%s at %s"),
				   ctl->remotename, ctl->server.pollname);
		if (outlevel > O_SILENT)
		{
		    if (count == -1)		/* only used for ETRN */
			report(stdout, GT_("Polling %s\n"), ctl->server.truename);
		    else if (count != 0)
		    {
			if (new != -1 && (count - new) > 0)
			    report_build(stdout, ngettext("%d message (%d %s) for %s", "%d messages (%d %s) for %s", (unsigned long)count),
				  count,
				  count-new, 
				  ngettext("seen", "seen", (unsigned long)count-new),
				  buf);
			else
			    report_build(stdout, ngettext("%d message for %s",
							  "%d messages for %s",
							  count), 
				  count, buf);
			if (bytes == -1)
			    report_complete(stdout, ".\n");
			else
			    report_complete(stdout, GT_(" (%d octets).\n"), bytes);
		    }
		    else
		    {
			/* these are pointless in normal daemon mode */
			if (pass == 1 && (run.poll_interval == 0 || outlevel >= O_VERBOSE))
			    report(stdout, GT_("No mail for %s\n"), buf); 
		    }
		}

		/* very important, this is where we leave the do loop */ 
		if (count == 0)
		    break;

		if (check_only)
		{
		    if (new == -1 || ctl->fetchall)
			new = count;
		    fetches = new;	/* set error status correctly */
		    /*
		     * There used to be a `goto noerror' here, but this
		     * prevented checking of multiple folders.  This
		     * comment is a reminder in case I introduced some
		     * subtle bug by removing it...
		     */
		}
		else if (count > 0)
		{    
		    int		i;

		    /*
		     * Don't trust the message count passed by the server.
		     * Without this check, it might be possible to do a
		     * DNS-spoofing attack that would pass back a ridiculous 
		     * count, and allocate a malloc area that would overlap
		     * a portion of the stack.
		     */
		    if (count > INT_MAX/sizeof(int))
		    {
			report(stderr, GT_("bogus message count!"));
			return(PS_PROTOCOL);
		    }

		    /* 
		     * We need the size of each message before it's
		     * loaded in order to pass it to the ESMTP SIZE
		     * option.  If the protocol has a getsizes method,
		     * we presume this means it doesn't get reliable
		     * sizes from message fetch responses.
		     *
		     * If the protocol supports getting sizes of subset of
		     * messages, we skip this step now.
		     */
		    if (proto->getsizes &&
			!(proto->getpartialsizes && NUM_NONZERO(ctl->fetchsizelimit)))
		    {
			xalloca(msgsizes, int *, sizeof(int) * count);
			for (i = 0; i < count; i++)
			    msgsizes[i] = 0;

			stage = STAGE_GETSIZES;
			err = (proto->getsizes)(mailserver_socket, count, msgsizes);
			if (err != 0)
			    goto cleanUp;

			if (bytes == -1)
			{
			    bytes = 0;
			    for (i = 0; i < count; i++)
				bytes += msgsizes[i];
			}
		    }

		    /* read, forward, and delete messages */
		    stage = STAGE_FETCH;

		    /* fetch in lockstep mode */
		    err = fetch_messages(mailserver_socket, ctl, 
					 count, msgsizes,
					 maxfetch,
					 &fetches, &dispatches, &deletions);
		    if (err)
			goto cleanUp;

		    if (!check_only && ctl->skipped
			&& run.poll_interval > 0 && !nodetach)
		    {
			clean_skipped_list(&ctl->skipped);
			send_size_warnings(ctl);
		    }
		}
	    } while
		  /*
		   * Only re-poll if we either had some actual forwards and 
		   * either allowed deletions and had no errors.
		   * Otherwise it is far too easy to get into infinite loops.
		   */
		  (dispatches && ctl->server.base_protocol->retry && !ctl->keep && !ctl->errcount);
	}

    /* no_error: */
	/* ordinary termination with no errors -- officially log out */
	err = (ctl->server.base_protocol->logout_cmd)(mailserver_socket, ctl);
	/*
	 * Hmmmm...arguably this would be incorrect if we had fetches but
	 * no dispatches (due to oversized messages, etc.)
	 */
	if (err == 0)
	    err = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL;
	cleanupSockClose(mailserver_socket);
	goto closeUp;

    cleanUp:
	/* we only get here on error */
	if (err != 0 && err != PS_SOCKET && err != PS_REPOLL)
	{
	    stage = STAGE_LOGOUT;
	    (ctl->server.base_protocol->logout_cmd)(mailserver_socket, ctl);
	}

	/* try to clean up all streams */
	release_sink(ctl);
	smtp_close(ctl, 0);
	if (mailserver_socket != -1) {
	    cleanupSockClose(mailserver_socket);
	    mailserver_socket = -1;
	}
	/* If there was a connect timeout, the socket should be closed.
	 * mailserver_socket_temp contains the socket to close.
	 */
	if (mailserver_socket_temp != -1) {
	    cleanupSockClose(mailserver_socket_temp);
	    mailserver_socket_temp = -1;
	}
    }

    msg = (const char *)NULL;	/* sacrifice to -Wall */
    switch (err)
    {
    case PS_SOCKET:
	msg = GT_("socket");
	break;
    case PS_SYNTAX:
	msg = GT_("missing or bad RFC822 header");
	break;
    case PS_IOERR:
	msg = GT_("MDA");
	break;
    case PS_ERROR:
	msg = GT_("client/server synchronization");
	break;
    case PS_PROTOCOL:
	msg = GT_("client/server protocol");
	break;
    case PS_LOCKBUSY:
	msg = GT_("lock busy on server");
	break;
    case PS_SMTP:
	msg = GT_("SMTP transaction");
	break;
    case PS_DNS:
	msg = GT_("DNS lookup");
	break;
    case PS_UNDEFINED:
	report(stderr, GT_("undefined error\n"));
	break;
    }
    /* no report on PS_MAXFETCH or PS_UNDEFINED or PS_AUTHFAIL */
    if (err==PS_SOCKET || err==PS_SYNTAX
		|| err==PS_IOERR || err==PS_ERROR || err==PS_PROTOCOL 
		|| err==PS_LOCKBUSY || err==PS_SMTP || err==PS_DNS)
    {
	char	*stem;

	if (phase == FORWARDING_WAIT || phase == LISTENER_WAIT)
	    stem = GT_("%s error while delivering to SMTP host %s\n");
	else
	    stem = GT_("%s error while fetching from %s\n");
	report(stderr, stem, msg, ctl->server.pollname);
    }

closeUp:
    /* execute wrapup command, if any */
    if (ctl->postconnect && (err = system(ctl->postconnect)))
    {
	report(stderr, GT_("post-connection command failed with status %d\n"), err);
	if (err == PS_SUCCESS)
	    err = PS_SYNTAX;
    }

    set_timeout(0); /* cancel any pending alarm */
    set_signal_handler(SIGALRM, alrmsave);
    set_signal_handler(SIGPIPE, pipesave);
    return(err);
}

int do_protocol(ctl, proto)
/* retrieve messages from server using given protocol method table */
struct query *ctl;		/* parsed options with merged-in defaults */
const struct method *proto;	/* protocol method table */
{
    int	err;

#ifndef KERBEROS_V4
    if (ctl->server.authenticate == A_KERBEROS_V4)
    {
	report(stderr, GT_("Kerberos V4 support not linked.\n"));
	return(PS_ERROR);
    }
#endif /* KERBEROS_V4 */

#ifndef KERBEROS_V5
    if (ctl->server.authenticate == A_KERBEROS_V5)
    {
	report(stderr, GT_("Kerberos V5 support not linked.\n"));
	return(PS_ERROR);
    }
#endif /* KERBEROS_V5 */

    /* lacking methods, there are some options that may fail */
    if (!proto->is_old)
    {
	/* check for unsupported options */
	if (ctl->flush) {
	    report(stderr,
		    GT_("Option --flush is not supported with %s\n"),
		    proto->name);
	    return(PS_SYNTAX);
	}
	else if (ctl->fetchall) {
	    report(stderr,
		    GT_("Option --all is not supported with %s\n"),
		    proto->name);
	    return(PS_SYNTAX);
	}
    }
    if (!proto->getsizes && NUM_SPECIFIED(ctl->limit))
    {
	report(stderr,
		GT_("Option --limit is not supported with %s\n"),
		proto->name);
	return(PS_SYNTAX);
    }

    /*
     * If no expunge limit or we do expunges within the driver,
     * then just do one session, passing in any fetchlimit.
     */
    if ((ctl->keep && !ctl->flush) ||
	proto->retry || !NUM_SPECIFIED(ctl->expunge))
	return(do_session(ctl, proto, NUM_VALUE_OUT(ctl->fetchlimit)));
    /*
     * There's an expunge limit, and it isn't handled in the driver itself.
     * OK; do multiple sessions, each fetching a limited # of messages.
     * Stop if the total count of retrieved messages exceeds ctl->fetchlimit
     * (if it was nonzero).
     */
    else
    {
	int totalcount = 0; 
	int lockouts   = 0;
	int expunge    = NUM_VALUE_OUT(ctl->expunge);
	int fetchlimit = NUM_VALUE_OUT(ctl->fetchlimit);

	do {
	    if (fetchlimit > 0 && (expunge == 0 || expunge > fetchlimit - totalcount))
		expunge = fetchlimit - totalcount;
	    err = do_session(ctl, proto, expunge);
	    totalcount += expunge;
	    if (NUM_SPECIFIED(ctl->fetchlimit) && totalcount >= fetchlimit)
		break;
	    if (err != PS_LOCKBUSY)
		lockouts = 0;
	    else if (lockouts >= MAX_LOCKOUTS)
		break;
	    else /* err == PS_LOCKBUSY */
	    {
		/*
		 * Allow time for the server lock to release.  if we
		 * don't do this, we'll often hit a locked-mailbox
		 * condition and fail.
		 */
		lockouts++;
		sleep(3);
	    }
	} while
	    (err == PS_MAXFETCH || err == PS_LOCKBUSY);

	return(err);
    }
}


/* driver.c ends here */