aboutsummaryrefslogtreecommitdiffstats
path: root/rcfile_y.y
diff options
context:
space:
mode:
Diffstat (limited to 'rcfile_y.y')
-rw-r--r--rcfile_y.y5
1 files changed, 4 insertions, 1 deletions
diff --git a/rcfile_y.y b/rcfile_y.y
index 93d1c86e..3a42c4e5 100644
--- a/rcfile_y.y
+++ b/rcfile_y.y
@@ -68,7 +68,8 @@ extern char * yytext;
%token <proto> PROTO
%token <sval> STRING
%token <number> NUMBER
-%token NO KEEP FLUSH FETCHALL REWRITE FORCECR STRIPCR PASS8BITS DROPSTATUS
+%token NO KEEP FLUSH FETCHALL REWRITE FORCECR STRIPCR PASS8BITS
+%token DROPSTATUS DROPDELIVERED
%token DNS SERVICE PORT UIDL INTERVAL MIMEDECODE IDLE CHECKALIAS
%token SSL SSLKEY SSLCERT
@@ -322,6 +323,7 @@ user_option : TO localnames HERE
| STRIPCR {current.stripcr = FLAG_TRUE;}
| PASS8BITS {current.pass8bits = FLAG_TRUE;}
| DROPSTATUS {current.dropstatus = FLAG_TRUE;}
+ | DROPDELIVERED {current.dropdelivered = FLAG_TRUE;}
| MIMEDECODE {current.mimedecode = FLAG_TRUE;}
| IDLE {current.idle = FLAG_TRUE;}
@@ -337,6 +339,7 @@ user_option : TO localnames HERE
| NO STRIPCR {current.stripcr = FLAG_FALSE;}
| NO PASS8BITS {current.pass8bits = FLAG_FALSE;}
| NO DROPSTATUS {current.dropstatus = FLAG_FALSE;}
+ | NO DROPDELIVERED {current.dropdelivered = FLAG_FALSE;}
| NO MIMEDECODE {current.mimedecode = FLAG_FALSE;}
| NO IDLE {current.idle = FLAG_FALSE;}
#n136'>136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 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 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797
/**
 * \file transact.c -- transaction primitives for the fetchmail driver loop
 *
 * Copyright 2001 by Eric S. Raymond
 * For license terms, see the file COPYING in this directory.
 */

#include  "config.h"
#include "fetchmail.h"
#include  <stdio.h>
#include  <string.h>
#include  <ctype.h>
#ifdef HAVE_MEMORY_H
#include  <memory.h>
#endif /* HAVE_MEMORY_H */
#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
#include <limits.h>
#include <assert.h>

#ifdef HAVE_NET_SOCKET_H
#include <net/socket.h>
#endif
#include <sys/socket.h>
#include <netdb.h>
#include "fm_md5.h"

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

/** Macro to clamp the argument so it is >= INT_MIN. */
#define _FIX_INT_MIN(x) ((x) < INT_MIN ? INT_MIN : (x))
/** Macro to clamp the argument so it is <= INT_MAX. */
#define _FIX_INT_MAX(x) ((x) > INT_MAX ? INT_MAX : (x))
/** Macro to clamp the argument so it is representable as an int. */
#define CAST_TO_INT(x) ((int)(_FIX_INT_MIN(_FIX_INT_MAX(x))))
/** Macro to clamp the unsigned argument so it is representable as an int. */
#define UCAST_TO_INT(x) ((int)(_FIX_INT_MAX(x)))

/* global variables: please reinitialize them explicitly for proper
 * working in daemon mode */

/* session variables initialized in init_transact() */
int suppress_tags = FALSE;	/**< emit tags in the protocol? */
char tag[TAGLEN];		/**< buffer for the tag */
static unsigned int tagnum;	/**< local counter for the tag */
/** Macro to generate the tag and store it in #tag. */
#define GENSYM	(sprintf(tag, "A%04u", ++tagnum % TAGMOD), tag)
static const struct method *protocol; /**< description of the protocol used for the current poll */
char shroud[PASSWORDLEN*2+3];	/**< string to shroud in debug output */

/* session variables initialized in do_session() */
int mytimeout;		/**< value of nonreponse timeout */

/* mail variables initialized in readheaders() */
struct msgblk msgblk;   /**< stores attributes of the currently processed message */
static int accept_count /** count of accepted recipients */, reject_count /** count of rejected recipients */;

/** add given address to xmit_names if it exactly matches a full address
 * \returns nonzero if matched */
static int map_address(const char *addr,/**< address to match */
	struct query *ctl,		/**< contains list of aliases */
	struct idlist **xmit_names	/**< list of recipient names */)
{
    const char	*lname;

    lname = idpair_find(&ctl->localnames, addr);
    if (lname) {
	if (outlevel >= O_DEBUG)
	    report(stdout, GT_("mapped address %s to local %s\n"), addr, lname);
	save_str(xmit_names, lname, XMIT_ACCEPT);
	accept_count++;
    }
    return lname != NULL;
}

/** add given name to xmit_names if it matches declared localnames */
static void map_name(const char *name, struct query *ctl, struct idlist **xmit_names)
/** \param name		name to map */
/** \param ctl		list of permissible aliases */
/** \param xmit_names	list of recipient names parsed out */
{
    const char	*lname;

    lname = idpair_find(&ctl->localnames, name);
    if (!lname && ctl->wildcard)
	lname = name;

    if (lname != (char *)NULL)
    {
	if (outlevel >= O_DEBUG)
	    report(stdout, GT_("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)
/** parse names out of a RFC822 header into an ID list */
/** \param hdr		RFC822 header in question */
/** \param ctl		list of permissible aliases */
/** \param xmit_names	list of recipient names parsed out */
{
    if (hdr == (char *)NULL)
	return;
    else
    {
	char	*cp;

	for (cp = nxtaddr(hdr); cp != NULL; cp = nxtaddr(NULL))
	{
	    char	*atsign;

	    /* 
	     * Handle empty address from a To: header containing only 
	     * a comment.
	     */
	    if (!*cp)
		continue;

	    /*
	     * If the name of the user begins with a qmail virtual
	     * domain prefix, ignore the prefix.  Doing this here
	     * means qvirtual will work either with ordinary name
	     * mapping or with a localdomains option.
	     */
	    if (ctl->server.qvirtual)
	    {
		int sl = strlen(ctl->server.qvirtual);
 
		if (!strncasecmp((char *)cp, ctl->server.qvirtual, sl))
		    cp += sl;
	    }

	    if ((atsign = strchr((char *)cp, '@'))) {
		struct idlist	*idp;

		/* try to match full address first, this takes
		 * precedence over localdomains and alias mappings */
		if (map_address(cp, ctl, xmit_names))
		    goto nomap;

		/*
		 * Does a trailing segment of the hostname match something
		 * on the localdomains list?  If so, save the whole name
		 * and keep going.
		 */
		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, GT_("passed through %s matching %s\n"), 
				  cp, idp->id);
			save_str(xmit_names, (const char *)cp, XMIT_ACCEPT);
			accept_count++;
			goto nomap;
		    }
		}

		/* if we matched a local domain, idp != NULL */
		if (!idp)
		{
		    /*
		     * Check to see if the right-hand part is an alias
		     * or MX equivalent of the mailserver.  If it's
		     * not, skip this name.  If it is, we'll keep
		     * going and try to find a mapping to a client name.
		     */
		    if (!is_host_alias(atsign+1, ctl, &ai0))
		    {
			save_str(xmit_names, cp, XMIT_REJECT);
			reject_count++;
			continue;
		    }
		}
		atsign[0] = '\0';
		map_name(cp, ctl, xmit_names);
	    nomap:;
	    }
	}
    }
}

/**
 * Return zero on a syntactically invalid address, nz on a valid one.
 *
 * This used to be strchr(a, '.'), but it turns out that lines like this
 *
 * Received: from punt-1.mail.demon.net by mailstore for markb@example.com
 *          id 938765929:10:27223:2; Fri, 01 Oct 99 08:18:49 GMT
 *
 * are not uncommon.  So now we just check that the following token is
 * not itself an email address.
 */
#define VALID_ADDRESS(a)	(!strchr((a), '@'))

/** write \a value into \a rbuf, indexed by \a tp, if there is
 * sufficient room left. */
#define RBUF_WRITE(value) do { if (tp < rbuf+sizeof(rbuf)-1) *tp++=(value); } while(0)

/** Try to extract real address from the Received line.
 * If a valid Received: line is found, we return the full address in
 * a buffer which can be parsed from nxtaddr().  This is to ensure that
 * the local domain part of the address can be passed along in 
 * find_server_names() if it contains one.
 * Note: We should return a dummy header containing the address 
 * which makes nxtaddr() behave correctly. 
 */
static char *parse_received(struct query *ctl, char *bufp)
{
    char *base, *ok = (char *)NULL;
    static char rbuf[HOSTLEN + USERNAMELEN + 4]; 
    struct addrinfo *ai0;

    /*
     * Try to extract the real envelope addressee.  We look here
     * specifically for the mailserver's Received line.
     * Note: this will only work for sendmail, or an MTA that
     * shares sendmail's convention for embedding the envelope
     * address in the Received line.  Sendmail itself only
     * does this when the mail has a single recipient.
     */
    if (outlevel >= O_DEBUG)
	report(stdout, GT_("analyzing Received line:\n%s"), bufp);

    /* search for whitepace-surrounded "by" followed by valid address */
    for (base = bufp;  ; base = ok + 2)
    {
	if (!(ok = strstr(base, "by")))
	    break;
	else if (!isspace((unsigned char)ok[-1]) || !isspace((unsigned char)ok[2]))
	    continue;
	else
	{
	    char	*sp, *tp;

	    /* extract space-delimited token after "by" */
	    for (sp = ok + 2; isspace((unsigned char)*sp); sp++)
		continue;
	    tp = rbuf;
	    for (; *sp && !isspace((unsigned char)*sp); sp++)
		RBUF_WRITE(*sp);
	    *tp = '\0';

	    /* look for valid address */
	    if (VALID_ADDRESS(rbuf))
		break;
	    else
		ok = sp - 1;	/* arrange to skip this token */
	}
    }
    if (ok)
    {
	/*
	 * If it's a DNS name of the mail server, look for the
	 * recipient name after a following "for".  Otherwise
	 * punt.
	 */
	if (is_host_alias(rbuf, ctl, &ai0))
	{
	    if (outlevel >= O_DEBUG)
		report(stdout, 
		      GT_("line accepted, %s is an alias of the mailserver\n"), rbuf);
	}
	else
	{
	    if (outlevel >= O_DEBUG)
		report(stdout, 
		      GT_("line rejected, %s is not an alias of the mailserver\n"), 
		      rbuf);
	    return(NULL);
	}

	/* search for whitepace-surrounded "for" followed by xxxx@yyyy */
	for (base = ok + 4 + strlen(rbuf);  ; base = ok + 2)
	{
	    if (!(ok = strstr(base, "for")))
		break;
	    else if (!isspace((unsigned char)ok[-1]) || !isspace((unsigned char)ok[3]))
		continue;
	    else
	    {
		char	*sp, *tp;

		/* extract space-delimited token after "for" */
		for (sp = ok + 3; isspace((unsigned char)*sp); sp++)
		    continue;
		tp = rbuf;
		for (; !isspace((unsigned char)*sp); sp++)
		    RBUF_WRITE(*sp);
		*tp = '\0';

		if (strchr(rbuf, '@'))
		    break;
		else
		    ok = sp - 1;	/* arrange to skip this token */
	    }
	}
	if (ok)
	{
	    flag	want_gt = FALSE;
	    char	*sp, *tp;

	    /* char after "for" could be space or a continuation newline */
	    for (sp = ok + 4; isspace((unsigned char)*sp); sp++)
		continue;
	    tp = rbuf;
	    RBUF_WRITE(':');	/* Here is the hack.  This is to be friends */
	    RBUF_WRITE(' ');	/* with nxtaddr()... */
	    if (*sp == '<')
	    {
		want_gt = TRUE;
		sp++;
	    }
	    while (*sp == '@')		/* skip routes */
		while (*sp && *sp++ != ':')
		    continue;
            while (*sp
                   && (want_gt ? (*sp != '>') : !isspace((unsigned char)*sp))
                   && *sp != ';')
		if (!isspace((unsigned char)*sp))
		{
		    RBUF_WRITE(*sp);
		    sp++;
		}    
		else
		{
		    /* uh oh -- whitespace here can't be right! */
		    ok = (char *)NULL;
		    break;
		}
	    RBUF_WRITE('\n');
	    *tp = '\0';
	    if (strlen(rbuf) <= 3)	/* apparently nothing has been found */
		ok = NULL;
	} else
	    ok = (char *)NULL;
    }

    if (!ok)
    {
	if (outlevel >= O_DEBUG)
	    report(stdout, GT_("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, GT_("found Received address `%s'\n"), rbuf+2);
	    *lf = '\n';
	}
	return(rbuf);
    }
}

/* shared by readheaders and readbody */
static int sizeticker; /**< internal state variable for print_ticker() */

/** Print ticker based on a amount of data transferred of \a bytes.
 * Increments \a *tickervar by \a bytes, and if it exceeds
 * \a SIZETICKER, print a dot and reduce *tickervar by \a SIZETICKER. */
static void print_ticker(int *tickervar, int bytes)
{
    *tickervar += bytes;
    while (*tickervar >= SIZETICKER)
    {
	if (want_progress())
	{
	    fputc('.', stdout);
	    fflush(stdout);
	}
	*tickervar -= SIZETICKER;
    }
}

/** Check if \a s is equal to a LF or CR LF sequence, followed by a NUL
 * byte. \todo FIXME merge this with end_of_header? */
#define EMPTYLINE(s)   (((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0') \
                       || ((s)[0] == '\n' && (s)[1] == '\0'))

/** Check if \a s is an empty line. Accept "\r*\n" as EOH in order to be bulletproof against broken survers */
static int end_of_header (const char *s)
{
    while (s[0] == '\r')
	s++;
    return (s[0] == '\n' && s[1] == '\0');
}

/** read message headers and ship to SMTP or MDA */
int readheaders(int sock,
		       long fetchlen,
		       long reallen,
		       struct query *ctl,
		       int num,
		       flag *suppress_readbody)
/** \param sock		to which the server is connected */
/** \param fetchlen	length of message according to fetch response */
/** \param reallen	length of message according to getsizes */
/** \param ctl		query control record */
/** \param num		index of message */
/** \param suppress_readbody	output: whether call to readbody() should be supressed */
{
    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;
    static char		*delivered_to = NULL;
    int 		n, oldlen, ch, remaining, skipcount;
    size_t		linelen;
    int			delivered_to_count;
    struct idlist 	*idp;
    flag		no_local_matches = FALSE;
    flag		has_nuls;
    int			olderrs, good_addresses, bad_addresses;
    int			retain_mail = 0, refuse_mail = 0;
    flag		already_has_return_path = FALSE;

    sizeticker = 0;
    has_nuls = FALSE;
    msgblk.return_path[0] = '\0';
    olderrs = ctl->errcount;

    /* read message headers */
    msgblk.reallen = reallen;

    /*
     * We used to free the header block unconditionally at the end of 
     * readheaders, but it turns out that if close_sink() hits an error
     * condition the code for sending bouncemail will actually look
     * at the freed storage and coredump...
     */
    xfree(msgblk.headers);
    free_str_list(&msgblk.recipients);
    xfree(delivered_to);

    /* initially, no message digest */
    memset(ctl->digest, '\0', sizeof(ctl->digest));

    received_for = NULL;
    from_offs = reply_to_offs = resent_from_offs = app_from_offs = 
	sender_offs = resent_sender_offs = env_offs = -1;
    oldlen = 0;
    msgblk.msglen = 0;
    skipcount = 0;
    delivered_to_count = 0;
    ctl->mimemsg = 0;

    for (remaining = fetchlen; remaining > 0 || protocol->delimited; )
    {
	char *line, *rline;

	line = (char *)xmalloc(sizeof(buf));
	linelen = 0;
	line[0] = '\0';
	do {
	    do {
		char	*sp, *tp;

		set_timeout(mytimeout);
		if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) {
		    set_timeout(0);
		    free(line);
		    return(PS_SOCKET);
		}
		set_timeout(0);

		/*
		 * Smash out any NULs, they could wreak havoc later on.
		 * Some network stacks seem to generate these at random,
		 * especially (according to reports) at the beginning of the
		 * first read.  NULs are illegal in RFC822 format.
		 */
		for (sp = tp = buf; sp < buf + n; sp++)
		    if (*sp)
			*tp++ = *sp;
		*tp = '\0';
		n = tp - buf;
	    } while
		  (n == 0);

	    remaining -= n;
	    linelen += n;
	    msgblk.msglen += n;

	    /*
	     * Try to gracefully handle the case where the length of a
	     * line exceeds MSGBUFSIZE.
	     */
	    if (n && buf[n-1] != '\n') 
	    {
		rline = (char *) realloc(line, linelen + 1);
		if (rline == NULL)
		{
		    free (line);
		    return(PS_IOERR);
		}
		line = rline;
		memcpy(line + linelen - n, buf, n);
		line[linelen] = '\0';
		ch = ' '; /* So the next iteration starts */
		continue;
	    }

	    /* lines may not be properly CRLF terminated; fix this for qmail */
	    /* we don't want to overflow the buffer here */
	    if (ctl->forcecr && buf[n-1]=='\n' && (n==1 || buf[n-2]!='\r'))
	    {
		char * tcp;
		rline = (char *) realloc(line, linelen + 2);
		if (rline == NULL)
		{
		    free (line);
		    return(PS_IOERR);
		}
		line = rline;
		memcpy(line + linelen - n, buf, n - 1);
		tcp = line + linelen - 1;
		*tcp++ = '\r';
		*tcp++ = '\n';
		*tcp = '\0';
		/* n++; - not used later on */
		linelen++;
	    }
	    else
	    {
		rline = (char *) realloc(line, linelen + 1);
		if (rline == NULL)
		{
		    free (line);
		    return(PS_IOERR);
		}
		line = rline;
		memcpy(line + linelen - n, buf, n + 1);
	    }

	    /* check for end of headers */
	    if (end_of_header(line))
	    {
eoh:
		if (linelen != strlen (line))
		    has_nuls = TRUE;
		free(line);
		goto process_headers;
	    }

	    /*
	     * Check for end of message immediately.  If one of your folders
	     * has been mangled, the delimiter may occur directly after the
	     * header.
	     */
	    if (protocol->delimited && line[0] == '.' && EMPTYLINE(line+1))
	    {
		if (suppress_readbody)
		    *suppress_readbody = TRUE;
		goto eoh; /* above */
	    }

	    /*
	     * At least one brain-dead website (netmind.com) is known to
	     * send out robotmail that's missing the RFC822 delimiter blank
	     * line before the body! Without this check fetchmail segfaults.
	     * With it, we treat such messages as spam and refuse them.
	     *
	     * Frederic Marchal reported in February 2006 that hotmail
	     * or something improperly wrapped a very long TO header
	     * (wrapped without inserting whitespace in the continuation
	     * line) and found that this code thus refused a message
	     * that should have been delivered.
	     *
	     * XXX FIXME: we should probably wrap the message up as
	     * message/rfc822 attachment and forward to postmaster (Rob
	     * MacGregor)
	     */
	    if (!refuse_mail
		&& !(ctl->server.badheader == BHACCEPT)
		&& !isspace((unsigned char)line[0])
		&& !strchr(line, ':'))
	    {
		if (linelen != strlen (line))
		    has_nuls = TRUE;
		if (outlevel > O_SILENT)
		    report(stdout,
			   GT_("incorrect header line found - see manpage for bad-header option\n"));
		if (outlevel >= O_VERBOSE)
		    report (stdout, GT_("line: %s"), line);
		refuse_mail = 1;
	    }

	    /* check for RFC822 continuations */
	    set_timeout(mytimeout);
	    ch = SockPeek(sock);
	    set_timeout(0);
	} while
	    (ch == ' ' || ch == '\t');	/* continuation to next line? */

	/* write the message size dots */
	if ((outlevel > O_SILENT && outlevel < O_VERBOSE) && linelen > 0)
	{
	    print_ticker(&sizeticker, linelen);
	}

	/*
	 * Decode MIME encoded headers. We MUST do this before
	 * looking at the Content-Type / Content-Transfer-Encoding
	 * headers (RFC 2046).
	 */
	if ( ctl->mimedecode )
	{
	    char *tcp;
	    UnMimeHeader(line);
	    /* the line is now shorter. So we retrace back till we find
	     * our terminating combination \n\0, we move backwards to
	     * make sure that we don't catch some \n\0 stored in the
	     * decoded part of the message */
	    for (tcp = line + linelen - 1; tcp > line && (*tcp != 0 || tcp[-1] != '\n'); tcp--) { }
	    if  (tcp > line) linelen = tcp - line;
	}


	/* skip processing if we are going to retain or refuse this mail */
	if (retain_mail || refuse_mail)
	{
	    free(line);
	    continue;
	}

	/* we see an ordinary (non-header, non-message-delimiter) line */
	if (linelen != strlen (line))
	    has_nuls = TRUE;

	/*
	 * The University of Washington IMAP server (the reference
	 * implementation of IMAP4 written by Mark Crispin) relies
	 * on being able to keep base-UID information in a special
	 * message at the head of the mailbox.  This message should
	 * neither be deleted nor forwarded.
	 *
	 * An example for such a message is (keep this in so people
	 * find it when looking where the special code is to handle the
	 * data):
	 *
	 *   From MAILER-DAEMON Wed Nov 23 11:38:42 2005
	 *   Date: 23 Nov 2005 11:38:42 +0100
	 *   From: Mail System Internal Data <MAILER-DAEMON@mail.example.org>
	 *   Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
	 *   Message-ID: <1132742322@mail.example.org>
	 *   X-IMAP: 1132742306 0000000001
	 *   Status: RO
	 *
	 *   This text is part of the internal format of your mail folder, and is not
	 *   a real message.  It is created automatically by the mail system software.
	 *   If deleted, important folder data will be lost, and it will be re-created
	 *   with the data reset to initial values.
	 *
	 * This message is only visible if a POP3 server that is unaware
	 * of these UWIMAP messages is used besides UWIMAP or PINE.
	 *
	 * We will just check if the first message in the mailbox has an
	 * X-IMAP: header.
	 */
#ifdef POP2_ENABLE
	/*
	 * We disable this check under POP2 because there's no way to
	 * prevent deletion of the message.  So at least we ought to
	 * forward it to the user so he or she will have some clue
	 * that things have gone awry.
	 */
	if (servport("pop2") != servport(protocol->service))
#endif /* POP2_ENABLE */
	    if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) {
		free(line);
		retain_mail = 1;
		continue;
	    }

	/*
	 * This code prevents fetchmail from becoming an accessory after
	 * the fact to upstream sendmails with the `E' option on.  It also
	 * copes with certain brain-dead POP servers (like NT's) that pass
	 * through Unix from_ lines.
	 *
	 * Either of these bugs can result in a non-RFC822 line at the
	 * beginning of the headers.  If fetchmail just passes it
	 * through, the client listener may think the message has *no*
	 * headers (since the first) line it sees doesn't look
	 * RFC822-conformant) and fake up a set.
	 *
	 * What the user would see in this case is bogus (synthesized)
	 * headers, followed by a blank line, followed by the >From, 
	 * followed by the real headers, followed by a blank line,
	 * followed by text.
	 *
	 * We forestall this lossage by tossing anything that looks
	 * like an escaped or passed-through From_ line in headers.
	 * These aren't RFC822 so our conscience is clear...
	 */
	if (!strncasecmp(line, ">From ", 6) || !strncasecmp(line, "From ", 5))
	{
	    free(line);
	    continue;
	}

	/*
	 * We remove all Delivered-To: headers if dropdelivered is set
	 * - special care must be taken if Delivered-To: is also used
	 * as envelope at the same time.
	 *
	 * This is to avoid false mail loops errors when delivering
	 * local messages to and from a Postfix or qmail mailserver.
	 */
	if (ctl->dropdelivered && !strncasecmp(line, "Delivered-To:", 13)) 
	{
	    if (delivered_to ||
	    	ctl->server.envelope == STRING_DISABLED ||
		!ctl->server.envelope ||
		strcasecmp(ctl->server.envelope, "Delivered-To") ||
		delivered_to_count != ctl->server.envskip)
		free(line);
	    else 
		delivered_to = line;
	    delivered_to_count++;
	    continue;
	}

	/*
	 * If we see a Status line, it may have been inserted by an MUA
	 * on the mail host, or it may have been inserted by the server
	 * program after the headers in the transaction stream.  This
	 * can actually hose some new-mail notifiers such as xbuffy,
	 * which assumes any Status line came from a *local* MDA and
	 * therefore indicates that the message has been seen.
	 *
	 * Some buggy POP servers (including at least the 3.3(20)
	 * version of the one distributed with IMAP) insert empty
	 * Status lines in the transaction stream; we'll chuck those
	 * unconditionally.  Nonempty ones get chucked if the user
	 * turns on the dropstatus flag.
	 */
	{
	    char	*tcp;

	    if (!strncasecmp(line, "Status:", 7))
		tcp = line + 7;
	    else if (!strncasecmp(line, "X-Mozilla-Status:", 17))
		tcp = line + 17;
	    else
		tcp = NULL;
	    if (tcp) {
		while (*tcp && isspace((unsigned char)*tcp)) tcp++;
		if (!*tcp || ctl->dropstatus)
		{
		    free(line);
		    continue;
		}
	    }
	}

	if (ctl->rewrite)
	    line = reply_hack(line, ctl->server.truename, &linelen);

	/*
	 * OK, this is messy.  If we're forwarding by SMTP, it's the
	 * SMTP-receiver's job (according to RFC821, page 22, section
	 * 4.1.1) to generate a Return-Path line on final delivery.
	 * The trouble is, we've already got one because the
	 * mailserver's SMTP thought *it* was responsible for final
	 * delivery.
	 *
	 * Stash away the contents of Return-Path (as modified by reply_hack)
	 * for use in generating MAIL FROM later on, then prevent the header
	 * from being saved with the others.  In effect, we strip it off here.
	 *
	 * If the SMTP server conforms to the standards, and fetchmail gets the
	 * envelope sender from the Return-Path, the new Return-Path should be
	 * exactly the same as the original one.
	 *
	 * We do *not* want to ignore empty Return-Path headers.  These should
	 * be passed through as a way of indicating that a message should
	 * not trigger bounces if delivery fails.  What we *do* need to do is
	 * make sure we never try to rewrite such a blank Return-Path.  We
	 * handle this with a check for <> in the rewrite logic above.
	 *
	 * Also, if an email has multiple Return-Path: headers, we only
	 * read the first occurance, as some spam email has more than one
	 * Return-Path.
	 *
	 */
	if ((already_has_return_path==FALSE) && !strncasecmp("Return-Path:", line, 12) && (cp = nxtaddr(line)))
	{
	    char nulladdr[] = "<>";
	    already_has_return_path = TRUE;
	    if (cp[0]=='\0')	/* nxtaddr() strips the brackets... */
		cp=nulladdr;
	    strlcpy(msgblk.return_path, cp, sizeof(msgblk.return_path));
	    if (!ctl->mda) {
		free(line);
		continue;
	    }
	}

	if (!msgblk.headers)
	{
	    oldlen = linelen;
	    msgblk.headers = (char *)xmalloc(oldlen + 1);
	    (void) memcpy(msgblk.headers, line, linelen);
	    msgblk.headers[oldlen] = '\0';
	    free(line);
	    line = msgblk.headers;
	}
	else
	{
	    char *newhdrs;
	    int	newlen;

	    newlen = oldlen + linelen;
	    newhdrs = (char *) realloc(msgblk.headers, newlen + 1);
	    if (newhdrs == NULL) {
		free(line);
		return(PS_IOERR);
	    }
	    msgblk.headers = newhdrs;
	    memcpy(msgblk.headers + oldlen, line, linelen);
	    msgblk.headers[newlen] = '\0';
	    free(line);
	    line = msgblk.headers + oldlen;
	    oldlen = newlen;
	}

	/* find offsets of various special headers */
	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);
	/*
	 * Netscape 4.7 puts "Sender: zap" in mail headers.  Perverse...
	 *
	 * But a literal reading of RFC822 sec. 4.4.2 supports the idea
	 * that Sender: *doesn't* have to be a working email address.
	 *
	 * The definition of the Sender header in RFC822 says, in
	 * part, "The Sender mailbox specification includes a word
	 * sequence which must correspond to a specific agent (i.e., a
	 * human user or a computer program) rather than a standard
	 * address."  That implies that the contents of the Sender
	 * field don't need to be a legal email address at all So
	 * ignore any Sender or Resent-Sender lines unless they
	 * contain @.
	 *
	 * (RFC2822 says the contents of Sender must be a valid mailbox
	 * address, which is also what RFC822 4.4.4 implies.)
	 */
	else if (!strncasecmp("Sender:", line, 7) && (strchr(line, '@') || strchr(line, '!')))
	    sender_offs = (line - msgblk.headers);
	else if (!strncasecmp("Resent-Sender:", line, 14) && (strchr(line, '@') || strchr(line, '!')))
	    resent_sender_offs = (line - msgblk.headers);

	/* if multidrop is on, gather addressee headers */
	if (MULTIDROP(ctl))
	{
	    if (!strncasecmp("To:", line, 3)
		|| !strncasecmp("Cc:", line, 3)
		|| !strncasecmp("Bcc:", line, 4)
		|| !strncasecmp("Apparently-To:", line, 14))
	    {
		*to_chainptr = (struct addrblk *)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 = (struct addrblk *)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 (retain_mail) {
	return(PS_RETAINED);
    }

    if (refuse_mail)
	return(PS_REFUSED);
    /*
     * This is the duplicate-message killer code.
     *
     * When mail delivered to a multidrop mailbox on the server is
     * addressed to multiple people on the client machine, there will
     * be one copy left in the box for each recipient.  This is not a
     * problem if we have the actual recipient address to dispatch on
     * (e.g. because we've mined it out of sendmail trace headers, or
     * a qmail Delivered-To line, or a declared sender envelope line).
     *
     * But if we're mining addressees out of the To/Cc/Bcc fields, and
     * if the mail is addressed to N people, each recipient will
     * get N copies.  This is bad when N > 1.
     *
     * Foil this by suppressing all but one copy of a message with a
     * given set of headers.
     *
     * Note: This implementation only catches runs of successive
     * messages with the same ID, but that should be good
     * enough. A more general implementation would have to store
     * ever-growing lists of seen message-IDs; in a long-running
     * daemon this would turn into a memory leak even if the 
     * implementation were perfect.
     * 
     * Don't mess with this code casually.  It would be way too easy
     * to break it in a way that blackholed mail.  Better to pass
     * the occasional duplicate than to do that...
     *
     * Matthias Andree:
     * The real fix however is to insist on Delivered-To: or similar
     * headers and require that one copy per recipient be dropped.
     * Everything else breaks sooner or later.
     */
    if (MULTIDROP(ctl) && msgblk.headers)
    {
	MD5_CTX context;

	MD5Init(&context);
	MD5Update(&context, (unsigned char *)msgblk.headers, strlen(msgblk.headers));
	MD5Final(ctl->digest, &context);

	if (!received_for && env_offs == -1 && !delivered_to)
	{
	    /*
	     * Hmmm...can MD5 ever yield all zeroes as a hash value?
	     * If so there is a one in 18-quadrillion chance this 
	     * code will incorrectly nuke the first message.
	     */
	    if (!memcmp(ctl->lastdigest, ctl->digest, DIGESTLEN))
		return(PS_REFUSED);
	}
	memcpy(ctl->lastdigest, ctl->digest, DIGESTLEN);
    }

    /*
     * Hack time.  If the first line of the message was blank, with no headers
     * (this happens occasionally due to bad gatewaying software) cons up
     * a set of fake headers.  
     *
     * If you modify the fake header template below, be sure you don't
     * make either From or To address @-less, otherwise the reply_hack
     * logic will do bad things.
     */
    if (msgblk.headers == (char *)NULL)
    {
	snprintf(buf, sizeof(buf),
		"From: FETCHMAIL-DAEMON\r\n"
		"To: %s@%s\r\n"
		"Subject: Headerless mail from %s's mailbox on %s\r\n",
		user, fetchmailhost, ctl->remotename, ctl->server.truename);
	msgblk.headers = xstrdup(buf);
    }

    /*
     * We can now process message headers before reading the text.
     * In fact we have to, as this will tell us where to forward to.
     */

    /* Check for MIME headers indicating possible 8-bit data */
    ctl->mimemsg = MimeBodyType(msgblk.headers, ctl->mimedecode);

#ifdef SDPS_ENABLE
    if (ctl->server.sdps && sdps_envfrom)
    {
	/* We have the real envelope return-path, stored out of band by
	 * SDPS - that's more accurate than any header is going to be.
	 */
	strlcpy(msgblk.return_path, sdps_envfrom, sizeof(msgblk.return_path));
	free(sdps_envfrom);
    } else
#endif /* SDPS_ENABLE */
    /*
     * If there is a Return-Path address on the message, this was
     * almost certainly the MAIL FROM address given the originating
     * sendmail.  This is the best thing to use for logging the
     * message origin (it sets up the right behavior for bounces and
     * mailing lists).  Otherwise, fall down to the next available 
     * envelope address (which is the most probable real sender).
     * *** The order is important! ***
     * This is especially useful when receiving mailing list
     * messages in multidrop mode.  if a local address doesn't
     * exist, the bounce message won't be returned blindly to the 
     * author or to the list itself but rather to the list manager
     * (ex: specified by "Sender:") which is much less annoying.  This 
     * is true for most mailing list packages.
     */
    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))) {}
	/* multi-line MAIL FROM addresses confuse SMTP terribly */
	if (ap && !strchr(ap, '\n')) {
	    strlcpy(msgblk.return_path, ap, sizeof(msgblk.return_path));
	}
    }

    /* cons up a list of local recipients */
    msgblk.recipients = (struct idlist *)NULL;
    accept_count = reject_count = 0;
    /* is this a multidrop box? */
    if (MULTIDROP(ctl))
    {
#ifdef SDPS_ENABLE
	if (ctl->server.sdps && sdps_envto)
	{
	    /* We have the real envelope recipient, stored out of band by
	     * SDPS - that's more accurate than any header is going to be.
	     */
	    find_server_names(sdps_envto, ctl, &msgblk.recipients);
	    free(sdps_envto);
	} else
#endif /* SDPS_ENABLE */ 
	    if (env_offs > -1) {	    /* We have the actual envelope addressee */
		if (outlevel >= O_DEBUG) {
		    const char *tmps = msgblk.headers + env_offs;
		    size_t l = strcspn(tmps, "\r\n");
		    report(stdout, GT_("Parsing envelope \"%s\" names \"%-.*s\"\n"), ctl->server.envelope, UCAST_TO_INT(l), tmps);
		}
		find_server_names(msgblk.headers + env_offs, ctl, &msgblk.recipients);
	    }
	else if (delivered_to && ctl->server.envelope != STRING_DISABLED &&
		ctl->server.envelope && !strcasecmp(ctl->server.envelope, "Delivered-To"))
	{
	    if (outlevel >= O_DEBUG) {
		const char *tmps = delivered_to + 2 + strlen(ctl->server.envelope);
		size_t l = strcspn(tmps, "\r\n");
		report(stdout, GT_("Parsing envelope \"%s\" names \"%-.*s\"\n"), ctl->server.envelope, UCAST_TO_INT(l), tmps);
	    }
	    find_server_names(delivered_to, ctl, &msgblk.recipients);
	    xfree(delivered_to);
	} else if (received_for) {
	    /*
	     * We have the Received for addressee.  
	     * It has to be a mailserver address, or we
	     * wouldn't have got here.
	     * We use find_server_names() to let local 
	     * hostnames go through.
	     */
	    if (outlevel >= O_DEBUG) {
		const char *tmps = received_for + 2;
		size_t l = strcspn(tmps, "\r\n");
		report(stdout, GT_("Parsing Received names \"%-.*s\"\n"), UCAST_TO_INT(l), tmps);
	    }
	    find_server_names(received_for, ctl, &msgblk.recipients);
	} else {
	    /*
	     * We haven't extracted the envelope address.
	     * So check all the "Resent-To" header addresses if 
	     * they exist.  If and only if they don't, consider
	     * the "To" addresses.
	     */
	    struct addrblk *nextptr;
	    if (outlevel >= O_DEBUG)
		   report(stdout, GT_("No envelope recipient found, resorting to header guessing.\n"));
	    if (resent_to_addrchain) {
		/* delete the "To" chain and substitute it 
		 * with the "Resent-To" list 
		 */
		while (to_addrchain) {
		    nextptr = to_addrchain->next;
		    free(to_addrchain);
		    to_addrchain = nextptr;
		}
		to_addrchain = resent_to_addrchain;
		resent_to_addrchain = NULL;
	    }
	    /* now look for remaining adresses */
	    while (to_addrchain) {
		if (outlevel >= O_DEBUG) {
		    const char *tmps = msgblk.headers+to_addrchain->offset;
		    size_t l = strcspn(tmps, "\r\n");
		    report(stdout, GT_("Guessing from header \"%-.*s\".\n"), UCAST_TO_INT(l), tmps);
		}

		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,
		      GT_("no local matches, forwarding to %s\n"),
		      run.postmaster);
	}
    }
    else	/* it's a single-drop box, use first localname */
	save_str(&msgblk.recipients, ctl->localnames->id, XMIT_ACCEPT);


    /*
     * Time to either address the message or decide we can't deliver it yet.
     */
    if (ctl->errcount > olderrs)	/* there were DNS errors above */
    {
	if (outlevel >= O_DEBUG)
	    report(stdout,
		   GT_("forwarding and deletion suppressed due to DNS errors\n"));
	return(PS_TRANSIENT);
    }
    else
    {
	/* set up stuffline() so we can deliver the message body through it */ 
	if ((n = open_sink(ctl, &msgblk,
			   &good_addresses, &bad_addresses)) != PS_SUCCESS)
	{
	    return(n);
	}
    }

    n = 0;
    /*
     * Some server/sendmail combinations cause problems when our
     * synthetic Received line is before the From header.  Cope
     * with this...
     */
    if ((rcv = strstr(msgblk.headers, "Received:")) == (char *)NULL)
	rcv = msgblk.headers;
    /* handle ">Received:" lines too */
    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)
    {
	/* utter any per-message Received information we need here */
        if (ctl->server.trueaddr) {
	    char saddr[50];
	    int e;

	    e = getnameinfo(ctl->server.trueaddr, ctl->server.trueaddr_len,
		    saddr, sizeof(saddr), NULL, 0,
		    NI_NUMERICHOST);
	    if (e)
		snprintf(saddr, sizeof(saddr), "(%-.*s)", (int)(sizeof(saddr) - 3), gai_strerror(e));
	    snprintf(buf, sizeof(buf),
		    "Received: from %s [%s]\r\n", 
		    ctl->server.truename, saddr);
	} else {
	    snprintf(buf, sizeof(buf),
		  "Received: from %s\r\n", ctl->server.truename);
	}
	n = stuffline(ctl, buf);
	if (n != -1)
	{
	    /*
	     * We SHOULD (RFC-2821 sec. 4.4/p. 53) make sure to only use
	     * IANA registered protocol names here.
	     */
	    snprintf(buf, sizeof(buf),
		    "\tby %s with %s (fetchmail-%s",
		    fetchmailhost,
		    protocol->name,
		    VERSION);
	    if (ctl->server.tracepolls)
	    {
		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
			" polling %s account %s",
			ctl->server.pollname,
			ctl->remotename);
		if (ctl->folder)
		    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
			    " folder %s",
			    ctl->folder);
	    }
	    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), ")\r\n");
	    n = stuffline(ctl, buf);
	    if (n != -1)
	    {
		buf[0] = '\t';
		if (good_addresses == 0)
		{
		    snprintf(buf+1, sizeof(buf)-1, "for <%s> (by default); ",
			    rcpt_address (ctl, run.postmaster, 0));
		}
		else if (good_addresses == 1)
		{
		    for (idp = msgblk.recipients; idp; idp = idp->next)
			if (idp->val.status.mark == XMIT_ACCEPT)
			    break;	/* only report first address */
		    if (idp)
			snprintf(buf+1, sizeof(buf)-1,
				"for <%s>", rcpt_address (ctl, idp->id, 1));
		    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf)-1,
			    " (%s); ",
			    MULTIDROP(ctl) ? "multi-drop" : "single-drop");
		}
		else
		    buf[1] = '\0';

		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s\r\n",
			rfc822timestamp());
		n = stuffline(ctl, buf);
	    }
	}
    }

    if (n != -1)
	n = stuffline(ctl, rcv);	/* ship out rest of msgblk.headers */

    if (n == -1)
    {
	report(stdout, GT_("writing RFC822 msgblk.headers\n"));
	release_sink(ctl);
	return(PS_IOERR);
    }
    
    if (want_progress())
	fputc('#', stdout);

    /* write error notifications */
    if (no_local_matches || has_nuls || bad_addresses)
    {
	int	errlen = 0;
	char	errhd[USERNAMELEN + POPBUFSIZE], *errmsg;

	errmsg = errhd;
	strlcpy(errhd, "X-Fetchmail-Warning: ", sizeof(errhd));
	if (no_local_matches)
	{
	    if (reject_count != 1)
		strlcat(errhd, GT_("no recipient addresses matched declared local names"), sizeof(errhd));
	    else
	    {
		for (idp = msgblk.recipients; idp; idp = idp->next)
		    if (idp->val.status.mark == XMIT_REJECT)
			break;
		snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
			GT_("recipient address %s didn't match any local name"), idp->id);
	    }
	}

	if (has_nuls)
	{
	    if (errhd[sizeof("X-Fetchmail-Warning: ")])
		snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd), "; ");
	    snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
			GT_("message has embedded NULs"));
	}

	if (bad_addresses)
	{
	    if (errhd[sizeof("X-Fetchmail-Warning: ")])
		snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd), "; ");
	    snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
			GT_("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;

	    errmsg = (char *)xmalloc(errlen + 3);
	    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");

	/* ship out the error line */
	stuffline(ctl, errmsg);

	if (errmsg != errhd)
	    free(errmsg);
    }

    /* issue the delimiter line */
    cp = buf;
    *cp++ = '\r';
    *cp++ = '\n';
    *cp = '\0';
    n = stuffline(ctl, buf);

    if ((size_t)n == strlen(buf))
	return PS_SUCCESS;
    else
	return PS_SOCKET;
}

/** Convenience function factored out from readbody(): 
 * send buffer \a buf via stuffline() and handle errors and progress.
 * Store return value in \a *n, and return PS_IOERR for failure or
 * PS_SUCCESS otherwise. */
static int rb_send(struct query *ctl, char *buf, int *n)
{
    *n = stuffline(ctl, buf);

    if (*n < 0)
    {
	report(stdout, GT_("error writing message text\n"));
	release_sink(ctl);
	return(PS_IOERR);
    }
    else if (want_progress())
    {
	fputc('*', stdout);
	fflush(stdout);
    }
    return PS_SUCCESS;
}

int readbody(int sock, struct query *ctl, flag forward, int len)
/** read and dispose of a message body presented on \a sock */
/** \param ctl		query control record */
/** \param sock		to which the server is connected */
/** \param forward	TRUE to forward */
/** \param len		length of message */
{
    int	linelen;
    char buf[MSGBUFSIZE+4];
    char *inbufp = buf;
    flag issoftline = FALSE;

    /*
     * Pass through the text lines in the body.
     *
     * Yes, this wants to be ||, not &&.  The problem is that in the most
     * important delimited protocol, POP3, the length is not reliable.
     * As usual, the problem is Microsoft brain damage; see FAQ item S2.
     * So, for delimited protocols we need to ignore the length here and
     * instead drop out of the loop with a break statement when we see
     * the message delimiter.
     */
    while (protocol->delimited || len > 0)
    {
	set_timeout(mytimeout);
	/* XXX FIXME: for undelimited protocols that ship the size, such
	 * as IMAP, we might want to use the count of remaining characters
	 * instead of the buffer size -- not for fetchmail 6.3.X though */
	if ((linelen = SockRead(sock, inbufp, sizeof(buf)-4-(inbufp-buf)))==-1)
	{
	    set_timeout(0);
	    release_sink(ctl);
	    return(PS_SOCKET);
	}
	set_timeout(0);

	/* write the message size dots */
	if (linelen > 0)
	{
	    print_ticker(&sizeticker, linelen);
	}

	/* Mike Jones, Manchester University, 2006:
	 * "To fix IMAP MIME Messages in which fetchmail adds the remainder of
	 * the IMAP packet including the ')' character (part of the IMAP)
	 * Protocol causing the addition of an extra MIME boundary locally."
	 *
	 * However, we shouldn't do this for delimited protocols:
	 * many POP3 servers (Microsoft, qmail) goof up message sizes
	 * so we might end truncating messages prematurely.
	 */
	if (!protocol->delimited && linelen > len) {
	    /* FIXME: HACK ALERT! This \r\n is only here to make sure the
	     * \n\0 hunt works later on. The \n generated here was not
	     * part of the original message!
	     * The real fix will be to use buffer + length strings,
	     * rather than 0-terminated C strings. */
	    inbufp[len++] = '\r';
	    inbufp[len++] = '\n';
	    inbufp[len] = '\0';
	    linelen = len;
	}

	len -= linelen;

	/* check for end of message */
	if (protocol->delimited && *inbufp == '.')
	{
	    if (EMPTYLINE(inbufp+1))
		break;
	    else
		msgblk.msglen--;	/* subtract the size of the dot escape */
	}

	msgblk.msglen += linelen;

	if (ctl->mimedecode && (ctl->mimemsg & MSG_NEEDS_DECODE)) {
	    issoftline = UnMimeBodyline(&inbufp, protocol->delimited, issoftline);
	    if (issoftline && (sizeof(buf)-1-(inbufp-buf) < 200))
	    {
		/*
		 * Soft linebreak, but less than 200 bytes left in
		 * input buffer. Rather than doing a buffer overrun,
		 * ignore the soft linebreak, NL-terminate data and
		 * deliver what we have now.
		 * (Who writes lines longer than 2K anyway?)
		 */
		*inbufp = '\n'; *(inbufp+1) = '\0';
		issoftline = 0;
	    }
	}

	/* ship out the text line */
	if (forward && (!issoftline))
	{
	    int	n, err;
	    inbufp = buf;

	    /* guard against very long lines */
	    buf[MSGBUFSIZE+1] = '\r';
	    buf[MSGBUFSIZE+2] = '\n';
	    buf[MSGBUFSIZE+3] = '\0';

	    err = rb_send(ctl, buf, &n);
	    if (err != PS_SUCCESS)
		return err;
	}
    }

    /* Flush buffer -- bug introduced by ESR on 1998-03-20 before
     * release 4.4.1 when ESR did not sufficiently audit Henrik
     * Storner's patch.
     * Trouble reported in June 2011 by Lars Hecking, with
     * text/html quoted-printable messages generated by
     * Outlook/Exchange that got mutilated by fetchmail.
     */
    if (forward && issoftline)
    {
	int n;

	/* force proper line termination */
	inbufp[0] = '\r';
	inbufp[1] = '\n';
	inbufp[2] = '\0';

	return rb_send(ctl, buf, &n);
    }

    return(PS_SUCCESS);
}

void init_transact(const struct method *proto)
/** initialize state for the send and receive functions */
{
    suppress_tags = FALSE;
    tagnum = 0;
    tag[0] = '\0';	/* nuke any tag hanging out from previous query */
    protocol = proto;
    shroud[0] = '\0';
}

/** shroud a password in the given buffer */
static void enshroud(char *buf)
{
    char *cp;

    if (shroud[0] && (cp = strstr(buf, shroud)))
    {
       char    *sp;

       sp = cp + strlen(shroud);
       *cp++ = '*';
       while (*sp)
           *cp++ = *sp++;
       *cp = '\0';
    }
}

#if defined(HAVE_STDARG_H)
/** assemble command in printf(3) style and send to the server */
void gen_send(int sock, const char *fmt, ... )
#else
void gen_send(sock, fmt, va_alist)
int sock;		/** socket to which server is connected */
const char *fmt;	/** printf-style format */
va_dcl
#endif
{
    char buf [MSGBUFSIZE+1];
    va_list ap;

    if (protocol->tagged && !suppress_tags)
        snprintf(buf, sizeof(buf) - 2, "%s ", GENSYM);
    else
	buf[0] = '\0';

#if defined(HAVE_STDARG_H)
    va_start(ap, fmt);
#else
    va_start(ap);
#endif
    vsnprintf(buf + strlen(buf), sizeof(buf)-2-strlen(buf), fmt, ap);
    va_end(ap);

    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\r\n");
    SockWrite(sock, buf, strlen(buf));

    if (outlevel >= O_MONITOR)
    {
	enshroud(buf);
	buf[strlen(buf)-2] = '\0';
	report(stdout, "%s> %s\n", protocol->name, buf);
    }
}

/** get one line of input from the server */
int gen_recv(int sock  /** socket to which server is connected */,
	     char *buf /** buffer to receive input */,
	     int size  /** length of buffer */)
{
    size_t n;
    int oldphase = phase;	/* we don't have to be re-entrant */

    phase = SERVER_WAIT;
    set_timeout(mytimeout);
    if (SockRead(sock, buf, size) == -1)
    {
	set_timeout(0);
	phase = oldphase;
	if(is_idletimeout())
	{
	  resetidletimeout();
	  return(PS_IDLETIMEOUT);
	}
	else
	  return(PS_SOCKET);
    }
    else
    {
	set_timeout(0);
	n = strlen(buf);
	if (n > 0 && buf[n-1] == '\n')
	    buf[--n] = '\0';
	if (n > 0 && buf[n-1] == '\r')
	    buf[--n] = '\0';
	if (outlevel >= O_MONITOR)
	    report(stdout, "%s< %s\n", protocol->name, buf);
	phase = oldphase;
	return(PS_SUCCESS);
    }
}

/** \addtogroup gen_recv_split
 * @{
 * gen_recv_split() splits the response from a server which is too
 * long to fit into the buffer into multiple lines. If the prefix is
 * set as "MY FEATURES" and the response from the server is too long
 * to fit in the buffer, as in:
 *
 *   "MY FEATURES ABC DEF GHI JKLMNOPQRS TU VWX YZ"
 *
 * Repeated calls to gen_recv_split() may return:
 *
 *   "MY FEATURES ABC DEF GHI"
 *   "MY FEATURES JKLMNOPQRS"
 *   "MY FEATURES TU VWX YZ"
 *
 * A response not beginning with the prefix "MY FEATURES" will not be
 * split.
 *
 * To use:
 * - Declare a variable of type struct RecvSplit
 * - Call gen_recv_split_init() once
 * - Call gen_recv_split() in a loop, preferably with the same buffer
 *   size as the "buf" array in struct RecvSplit
 */

static void overrun(const char *f, size_t l) __attribute__((noreturn));

/** Internal error report function. If this happens, the calling site
 * needs to be adjusted to set a shorter prefix, or the prefix capacity
 * needs to be raised in struct RecvSplit. */
static void overrun(const char *f, size_t l)
{
    report(stderr, GT_("Buffer too small. This is a bug in the caller of %s:%lu.\n"), f, (unsigned long)l);
    abort();
}

/** Initialize \a rs for later use by gen_recv_split. */
void gen_recv_split_init (const char *prefix /** prefix to match/repeat */,
	struct RecvSplit *rs /** structure to be initialized */)
{
    if (strlcpy(rs->prefix, prefix, sizeof(rs->prefix)) > sizeof(rs->prefix))
	overrun(__FILE__, __LINE__);
    rs->cached = 0;
    rs->buf[0] = '\0';
}

/** Function to split replies at blanks, and duplicate prefix.
 * gen_recv_split_init() must be called before this can be used. */
int gen_recv_split(int sock  /** socket to which server is connected */,
	     char *buf /** buffer to receive input */,
	     int size  /** length of buffer, must be the same for all calls */,
	     struct RecvSplit *rs /** cached information across calls */)
{
    size_t n = 0;
    int foundnewline = 0;
    char *p;
    int oldphase = phase;	/* we don't have to be re-entrant */

    assert(size > 0);

    /* if this is not our first call, prepare the buffer */
    if (rs->cached)
    {
	/*
	 * if this condition is not met, we lose data
	 * because the cached data does not fit into the buffer.
	 * this cannot happen if size is the same throughout all calls.
	 */
	assert(strlen(rs->prefix) + strlen(rs->buf) + 1 <= (size_t)size);

	if ((strlcpy(buf, rs->prefix, size) >= (size_t)size)
		|| (strlcat(buf, rs->buf, size) >= (size_t)size)) {
	    overrun(__FILE__, __LINE__);
	}

	n = strlen(buf);
	/* clear the cache for the next call */
	rs->cached = 0;
	rs->buf[0] = '\0';
    }

    if ((size_t)size > n) {
	int rr;

	phase = SERVER_WAIT;
	set_timeout(mytimeout);
	rr = SockRead(sock, buf + n, size - n);
	set_timeout(0);
	phase = oldphase;
	if (rr == -1)
	    return PS_SOCKET;
    }

    n = strlen(buf);
    if (n > 0 && buf[n-1] == '\n')
    {
	buf[--n] = '\0';
	foundnewline = 1;
    }
    if (n > 0 && buf[n-1] == '\r')
	buf[--n] = '\0';

    if (foundnewline				/* we have found a complete line */
	|| strncasecmp(buf, rs->prefix, strlen(rs->prefix))	/* mismatch in prefix */
	|| !(p = strrchr(buf, ' '))		/* no space found in response */
	|| p < buf + strlen(rs->prefix))	/* space is at the wrong location */
    {
	if (outlevel >= O_MONITOR)
	    report(stdout, "%s< %s\n", protocol->name, buf);
	return(PS_SUCCESS);
    }

    /* we are ready to cache some information now. */
    rs->cached = 1;
    if (strlcpy(rs->buf, p, sizeof(rs->buf)) >= sizeof(rs->buf)) {
	overrun(__FILE__, __LINE__);
    }
    *p = '\0'; /* chop off what we've cached */
    if (outlevel >= O_MONITOR)
	report(stdout, "%s< %s\n", protocol->name, buf);
    if (outlevel >= O_DEBUG)
	report(stdout, "%s< %s%s...\n", protocol->name, rs->prefix, rs->buf);
    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;		/** socket to which server is connected */
const char *fmt;	/** printf-style format */
va_dcl
#endif
/** assemble command in printf(3) style, send to server, fetch a response */
{
    int ok;
    char buf [MSGBUFSIZE+1];
    va_list ap;
    int oldphase = phase;	/* we don't have to be re-entrant */

    phase = SERVER_WAIT;

    if (protocol->tagged && !suppress_tags)
	snprintf(buf, sizeof(buf) - 2, "%s ", GENSYM);
    else
	buf[0] = '\0';

#if defined(HAVE_STDARG_H)
    va_start(ap, fmt) ;
#else
    va_start(ap);
#endif
    vsnprintf(buf + strlen(buf), sizeof(buf)-2-strlen(buf), fmt, ap);
    va_end(ap);

    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\r\n");
    ok = SockWrite(sock, buf, strlen(buf));
    if (ok == -1 || (size_t)ok != strlen(buf)) {
	/* short write, bail out */
	return PS_SOCKET;
    }

    if (outlevel >= O_MONITOR)
    {
	enshroud(buf);
	buf[strlen(buf)-2] = '\0';
	report(stdout, "%s> %s\n", protocol->name, buf);
    }

    /* we presume this does its own response echoing */
    ok = (protocol->parse_response)(sock, buf);

    phase = oldphase;
    return(ok);
}

/* transact.c ends here */