aboutsummaryrefslogtreecommitdiffstats
path: root/fetchmail-SA-2021-02.txt
blob: 8bbc934fc48cbc14e4841e1ee6be0edf292a5aef (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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

fetchmail-SA-2021-02: STARTTLS session encryption bypassing

Topics:		fetchmail fails to enforce an encrypted connection

Author:		Matthias Andree
Version:	1.0
Announced:	2021-08-26 (0.9), 2021-09-13 (1.0)
Type:		failure to enforce configured security policy
Impact:		fetchmail continues an unencrypted connection,
		thus reading unauthenticated input and sending
		information unencrypted over its transport
Danger:		medium
Acknowledgment:	Andrew C. Aitchison for reporting this against fetchmail
		Damian Poddebniak, Fabian Ising, Hanno Böck, and Sebastian 
		Schinzel for their Usenix Security 21 paper NO STARTTLS

CVE Name:	CVE-2021-39272
URL:		https://www.fetchmail.info/fetchmail-SA-2021-02.txt
Project URL:	https://www.fetchmail.info/

Affects:	- fetchmail releases up to and including 6.4.21

Not affected:	- fetchmail releases 6.4.22 and newer

Corrected in:	2021-09-13	fetchmail 6.4.22 release tarball

0. History of this announcement
===============================

2021-08-10	Andrew C. Aitchison contacts fetchmail maintainer with pointer 
		to Usenix Security 21 paper by Damian Poddebniak et al.
2021-08-16	a simplified recommendation to configure --ssl where possible
		(see section 3b. below) to mitigate impact was sent to the 
		 fetchmail mailing lists
2021-08-26 0.9	initial release along with fetchmail 6.4.22.rc1
2021-08-27 0.9.1 update references to 6.4.22.rc2.
2021-08-29 0.9.2 update references to 6.4.22.rc3 and correct 0.9.1 date.
2021-09-13 1.0	remove reference to rc3 from "corrected in" and bump v. to 1.0.


1. Background
=============

fetchmail is a software package to retrieve mail from remote POP3, IMAP,
ETRN or ODMR servers and forward it to local SMTP, LMTP servers or
message delivery agents. fetchmail supports SSL and TLS security layers
through the OpenSSL library, if enabled at compile time and if also
enabled at run time, in both SSL/TLS-wrapped mode on dedicated ports as
well as in-band-negotiated "STARTTLS" and "STLS" modes through the
regular protocol ports.


2. Problem description and Impact
=================================

fetchmail permits requiring that an IMAP or POP3 protocol exchange uses 
a TLS-encrypted transport, in 6.4 by way of an --sslproto auto or similar 
configuration.
  This TLS encryption can be established either as Implicit TLS connection,
which negotiates TLS first, or as a STARTTLS which starts as cleartext
protocol exchange that gets upgraded in the same TCP stream to TLS.

Without special configuration, fetchmail would opportunistically try to
upgrade cleartext connections to TLS by STARTTLS, but allow cleartext protocol 
exchange, which is documented.

IMAP also supports sessions that start in "authenticated state" (PREAUTH).
In this latter case, IMAP (RFC-3501) does not permit sending STARTTLS 
negotiations, which are only permissible in not-authenticated state.
  In such a combination of circumstances (1. IMAP protocol in use, 2. the 
server greets with PREAUTH, announcing authenticated state, 3. the user 
configured TLS mandatory, 4. the user did not configure "ssl" mode that uses 
separate ports for Implicit SSL/TLS), fetchmail 6.4.21 and older would
not encrypt the session.

There was a similar situation for POP3: if the remote name contained 
@compuserve.com, and if the server supported a non-standard "AUTH" command 
without mechanism argument and if it responded with a list that contained "RPA" 
(also in mixed or lower case), then fetchmail would not attempt STARTTLS.  
While the password itself is then protected by the RPA scheme (which employs 
MD5 however), fetchmail 6.4.21 and older would not encrypt the session.

Also, a configuration containing --auth ssh (meaning that fetchmail should not 
authenticate, on the assumption that the session will be pre-authenticated for 
instance through SSH running a mail server with --plugin, or TLS client 
certificates), would also defeat STARTTLS as result of an implementation defect.
This affected both POP3 and IMAP.


3. Solutions
============

PREFACE: distributors backporting fixes to old versions are asked to diff the 
manual page and review the changes, and the NEWS file, because the manual page 
has been updated with newer recommendations.  The same backport recommendations 
hold for the README.SSL file.


3a. Install fetchmail 6.4.22 or newer.

The fetchmail source code is available from
<https://sourceforge.net/projects/fetchmail/files/>.

The Git-based source code repository is currently published via
https://gitlab.com/fetchmail/fetchmail/-/tree/legacy_64 (primary)
https://sourceforge.net/p/fetchmail/git/ci/legacy_64/tree/ (copy)


3b. Where the IMAP or POP3 server supports this form of access,
fetchmail can be configured to use Implicit TLS, called "ssl" mode, meaning it 
will connect to a dedicated port (default: 993 for IMAP, 995 for POP3) and 
negotiate TLS without prior clear-text protocol exchange.
  Also, --ssl can be given on the command line, which switches all
configured server statements to this Implicit TLS mode.


A. Copyright, License and Non-Warranty
======================================

(C) Copyright 2021 by Matthias Andree, <matthias.andree@gmx.de>.
Some rights reserved.

© Copyright 2021 by Matthias Andree. This file is licensed under CC
BY-ND 4.0. To view a copy of this license, visit
http://creativecommons.org/licenses/by-nd/4.0/

THIS WORK IS PROVIDED FREE OF CHARGE AND WITHOUT ANY WARRANTIES.
Use the information herein at your own risk.

END of fetchmail-SA-2021-02
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEE3EplW9mTzUhx+oIQ5BKxVu/zhVoFAmE/ue8ACgkQ5BKxVu/z
hVqhNg//eqyQpbFMUt29merXUWlMYuZJOjbtbXOFiUWxKvul2Id+/7ldUWeZyDKB
eNDgNAY44cq9thY5hf+gf3C5D1jBPbxc3ChirNuykIAs+Z6Me8LbgSF/ax+aZ9wk
IecIWIkSlmdTNkEdA+vGRM8u5POBJGBNdMwTgrMd3Zj4LSZkYGYRy57g5tefrng3
3edHFaevZGyFo8qeNldKttycxEzSMF0n2yFtb4xNZThj8nw69/YlmV2ixTgrADq+
/hMjUiAzgI0dNXApPLO+mmvnnCVxvB4jYWCYDoL+IC36PDs8/0vn2m/RBhONqvoH
r6i7ToaEOtbIDKwhL3ZrCvDWLMUGgXqkufPl0rsY/ibSepzl9qb6mpHXGzvLiM5x
PCylIJrHZKk0zD2QWkAl6mWYmiVnJLxlMSgjBtZwlabetBqyLvkzeCMTkpETRPmF
cOL1x844s0q+z6MSNbAPQBkBZKRQ6A2SpVFMLWQ54shPOyTkyg33QWdeYuPwq2qx
rHGeOELyXpftIQ8uqzsYnHQNsF+ZCV0PjqI1hyexGGkk3Z+tUH0DHqWwBvk2LHU9
lAnLwPwQTtEY89jTkYj3aCGST0ip1P2Ad575gHJAAb4bQQozYx8mGViHQwFeg724
EBxUL8sFTXthstHIKeVi53xkqa/FcKlR+Y3062LY4lAkDUUhK2I=
=pqxb
-----END PGP SIGNATURE-----
n512' href='#n512'>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
/***********************************************************************
  module:       rpa.c
  program:      fetchmail
  programmer:   Michael J. Palmer <106177.1156@compuserve.com>
  date:         29 August 1997
  compiler:     GCC 2.7.2
  environment:  RedHat 4.0 Linux 2.0.18
  description:  RPA authorisation code for POP3 client

  The sole entry point is POP3_auth_rpa()

  For license terms, see the file COPYING in this directory.

 ***********************************************************************/

#include  "config.h"

#if defined(POP3_ENABLE) && defined(RPA_ENABLE)
#include  <stdio.h>
#include  <unistd.h>
#include  <ctype.h>
#include  <string.h> 

#include  "socket.h"
#include  "fetchmail.h"
#include  "md5.h"
#include  "i18n.h"

#ifdef TESTMODE
extern unsigned char line1[];
extern unsigned char line2[];
extern unsigned char line3[];

extern int linecount;
#endif

#ifndef NO_PROTO
  /* prototypes for internal functions */
  static int  POP3_rpa_resp(unsigned char* argbuf, int socket );
  static void LenAppend(unsigned char** pptr, int len);
  static int  LenSkip(unsigned char** pptr, int rxlen);
  static int  DecBase64(unsigned char* bufp);
  static void EncBase64(unsigned char* bufp, int len);
  static void ToUnicode(unsigned char** pptr, unsigned char delim,
			unsigned char* buf, int* plen, int conv);
  static int  SetRealmService(unsigned char* bufp);
  static void GenChallenge(unsigned char* buf, int len);
  static int  DigestPassphrase(unsigned char* passphrase,
			       unsigned char* rbuf, int unicodeit);
  static void CompUserResp();
  static int  CheckUserAuth();
  static void md5(unsigned char* in, int len, unsigned char* out);
#endif

/* RPA protocol definitions */

#define         EARLYVER        "\x01\x00"  /* Earliest supp version */
#define         LATEVER         "\x03\x00"  /* Latest supp version   */
#define         HDR             0x60        /* ASN.1 SEQUENCE        */
#define         MECH            "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
#define         FLAGS           "\x00\x01"  /* Mutual authentication */
#define         STRMAX          128         /* Bytes in Unicode      */
#define         Tsl             14          /* Timestamp bytelen     */
#define         Pul             16          /* Passphrase digest len */
#define         Cul             16          /* Usr challenge bytelen */
#define         Rul             16          /* Usr response bytelen  */
#define         Aul             16          /* User auth bytelen     */
#define         Kusl            16          /* Session key bytelen   */

#define         UNIPASS         1           /* 1=Unicode 0=iso8859   */
#define         PS_RPA          42          /* Return code           */

/* RPA authentication items */

unsigned char   Cs[256];                    /* Service challenge     */
int             Csl;                        /* Length of "    "      */
unsigned char   Ts[Tsl+1];                  /* Timestamp incl \0     */
unsigned char   Nu[STRMAX];                 /* Username in Unicode   */
int             Nul;                        /* Length of " in bytes  */
unsigned char   Ns[STRMAX];                 /* Service in Unicode    */
int             Nsl;                        /* Length of " in bytes  */
unsigned char   Nr[STRMAX];                 /* Realm in Unicode      */
int             Nrl;                        /* Length of " in bytes  */
unsigned char   Pu[Pul];                    /* Passphrase after MD5  */
unsigned char   Cu[Cul];                    /* User challenge        */
unsigned char   Ru[Rul];                    /* User response         */
unsigned char   Au[Aul];                    /* User auth from Deity  */
unsigned char   Kusu[Kusl];                 /* Obscured Session key  */
unsigned char   Kus[Kusl];                  /* Session key           */

/*********************************************************************
  function:      POP3_auth_rpa
  description:   send the AUTH RPA commands to the server, and
                 get the server's response. Then progress through the
                 RPA challenge/response protocol until we are
                 (hopefully) granted authorisation.
  arguments:
    userid       user's id@realm e.g. myuserid@csi.com
    passphrase   user's passphrase
                 (upper lower or mixed case as the realm has chosen.
                 spec allows various options :-(   )
    socket       socket to which the server is connected.

  return value:  zero if success, else non-zero.
  calls:         SockPrintf, POP3_rpa_resp, EncBase64, DecBase64,
                 LenAppend, GenChallenge
  globals:       read outlevel.
 *********************************************************************/

int POP3_auth_rpa (unsigned char *userid, unsigned char *passphrase, int socket)
{
    int      ok,rxlen,verh,verl,i,rll;
    unsigned char buf [POPBUFSIZE];
    unsigned char *bufp;
    int      status,aulin,kuslin;
    char* stdec[4] = { NGT_("Success") ,
		       NGT_("Restricted user (something wrong with account)") ,
		       NGT_("Invalid userid or passphrase") ,
		       NGT_("Deity error") };

    /* Initiate RPA authorisation */

    SockPrintf(socket,"AUTH RPA\r\n");

    if (outlevel >= O_MONITOR)
	report(stdout, "> AUTH RPA\n");

    /* Create unicode user name in Nu.              */
    /* Create MD5 digest of user's passphrase in Pu */

    bufp = userid;
    ToUnicode(&bufp, '@', Nu, &Nul, 1);  /* User (lowercase) */
    DigestPassphrase(passphrase, Pu, UNIPASS);

    /* Get + response from server (RPA ready) */

    if ((ok = POP3_rpa_resp(buf,socket)) != 0)
    {
	if (outlevel > O_SILENT && outlevel < O_MONITOR)
	    report(stdout, "%s\n",buf);

	return(ok);
    }

    /* Assemble Token 1 in buf */

    bufp    = buf;
    *bufp++ = HDR;
    LenAppend(&bufp,      17);
    memcpy(bufp, MECH,    11); bufp += 11;
    memcpy(bufp, EARLYVER, 2); bufp += 2;
    memcpy(bufp, LATEVER,  2); bufp += 2;
    memcpy(bufp, FLAGS,    2); bufp += 2;

    /* Send Token 1, receive Token 2 */

    EncBase64(buf, bufp-buf);
#ifndef TESTMODE
    SockPrintf(socket,"%s\r\n",buf);
#endif
    if (outlevel >= O_MONITOR)
	report(stdout, "> %s\n",buf);
    if ((ok = POP3_rpa_resp(buf,socket)) != 0)
    {
	if (outlevel > O_SILENT && outlevel < O_MONITOR)
	    report(stdout, "%s\n",buf);
	return(ok);
    }
    if ((rxlen = DecBase64(buf)) == 0)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA token 2: Base64 decode error\n"));
	return(PS_RPA);
    }
    bufp = buf;
    *(buf+rxlen) = 0;  /* Terminates realm list */
    if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);

    /* Interpret Token 2 */

    verh = *(bufp++); verl = *(bufp++);
    if (outlevel >= O_DEBUG)
	report(stdout, GT_("Service chose RPA version %d.%d\n"),verh,verl);
    Csl  = *(bufp++);
    memcpy(Cs, bufp, Csl);
    bufp += Csl;
    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("Service challenge (l=%d):\n"),Csl);
	for (i=0; i<Csl; i++)
	    report_build(stdout, "%02X ",Cs[i]);
	report_complete(stdout, "\n");
    }
    memcpy(Ts, bufp, Tsl);
    Ts[Tsl] = 0;
    bufp += Tsl;
    if (outlevel >= O_DEBUG)
	report(stdout, GT_("Service timestamp %s\n"),Ts);
    rll = *(bufp++) << 8; rll = rll | *(bufp++);
    if ((bufp-buf+rll) != rxlen)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA token 2 length error\n"));
	return(PS_RPA);
    }
    if (outlevel >= O_DEBUG)
	report(stdout, GT_("Realm list: %s\n"),bufp);
    if (SetRealmService(bufp) != 0)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA error in service@realm string\n"));
	return(PS_RPA);
    }

    /* Assemble Token 3 in buf */

    bufp      = buf;
    *(bufp++) = HDR;
    LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
    memcpy(bufp, MECH, 11); bufp += 11;
    *(bufp++) = 0;
    *(bufp++) = strlen(userid);
    memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
    GenChallenge(Cu,Cul);
    *(bufp++) = Cul;
    memcpy(bufp, Cu, Cul);  bufp += Cul;
    CompUserResp();
    *(bufp++) = Rul;
    memcpy(bufp, Ru, Rul);  bufp += Rul;

    /* Send Token 3, receive Token 4 */

    EncBase64(buf,bufp-buf);
#ifndef TESTMODE
    SockPrintf(socket,"%s\r\n",buf);
#endif
    if (outlevel >= O_MONITOR)
	report(stdout, "> %s\n",buf);
    if ((ok = POP3_rpa_resp(buf,socket)) != 0)
    {
	if (outlevel > O_SILENT && outlevel < O_MONITOR)
	    report(stdout, "%s\n",buf);
	return(ok);
    }
    if ((rxlen = DecBase64(buf)) == 0)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA token 4: Base64 decode error\n"));
	return(PS_RPA);
    }
    bufp = buf;
    if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);

    /* Interpret Token 4 */

    aulin = *(bufp++);
    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("User authentication (l=%d):\n"),aulin);
	for (i=0; i<aulin; i++)
	    report_build(stdout, "%02X ",bufp[i]);
	report_complete(stdout, "\n");
    }
    if (aulin == Aul) memcpy(Au, bufp, Aul);
    bufp += aulin;
    kuslin = *(bufp++);
    if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
    bufp += kuslin;
    if (verh == 3)
    {
	status = *(bufp++);
	if (outlevel >= O_DEBUG)
	    report(stdout, GT_("RPA status: %02X\n"),status);
    }
    else status = 0;
    if ((bufp - buf) != rxlen)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA token 4 length error\n"));
	return(PS_RPA);
    }
    if (status != 0)
    {
	if (outlevel > O_SILENT)
	    if (status < 4)
		report(stderr, GT_("RPA rejects you: %s\n"),GT_(stdec[status]));
	    else
		report(stderr, GT_("RPA rejects you, reason unknown\n"));
	return(PS_AUTHFAIL);
    }
    if (Aul != aulin)
    {
	report(stderr, 
	       GT_("RPA User Authentication length error: %d\n"),aulin);
	return(PS_RPA);
    }
    if (Kusl != kuslin)
    {
	report(stderr, GT_("RPA Session key length error: %d\n"),kuslin);
	return(PS_RPA);
    }
    if (CheckUserAuth() != 0)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA _service_ auth fail. Spoof server?\n"));
	return(PS_AUTHFAIL);
    }
    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("Session key established:\n"));
	for (i=0; i<Kusl; i++)
	    report_build(stdout, "%02X ",Kus[i]);
	report_complete(stdout, "\n");
    }

    /* Assemble Token 5 in buf and send (not in ver 2 though)  */
    /* Version 3.0 definitely replies with +OK to this. I have */
    /* no idea what sort of response previous versions gave.   */

    if (verh != 2)
    {
	bufp      = buf;
	*(bufp++) = HDR;
	LenAppend(&bufp, 1 );
	*(bufp++) = 0x42;
	EncBase64(buf,bufp-buf);
#ifndef TESTMODE
	SockPrintf(socket,"%s\r\n",buf);
#endif
	if (outlevel >= O_MONITOR)
	    report(stdout, "> %s\n",buf);
	if ((ok = POP3_rpa_resp(buf,socket)) != 0)
	{
	    if (outlevel > O_SILENT && outlevel < O_MONITOR)
		report(stdout, "%s\n",buf);
	    return(ok);
	}
    }

    if (outlevel > O_SILENT)
	report(stdout, GT_("RPA authorisation complete\n"));

    return(PS_SUCCESS);
}


/*********************************************************************
  function:      POP3_rpa_resp
  description:   get the server's response to an RPA action.
                 Return received base64 string if successful
  arguments:
    argbuf       buffer to receive the string.
    socket       socket to which the server is connected.

  return value:  zero if okay, else return code.
  calls:         SockGets
  globals:       reads outlevel.
 *********************************************************************/

static int POP3_rpa_resp (argbuf,socket)
unsigned char *argbuf;
int socket;
{
    int ok;
    char buf [POPBUFSIZE];
    char *bufp;
    int sockrc;

    if (outlevel >= O_DEBUG)
	report(stdout,  GT_("Get response\n"));
#ifndef TESTMODE
    sockrc = gen_recv(socket, buf, sizeof(buf));
#else
    linecount++;
    if (linecount == 1) strcpy(buf,line1);
    if (linecount == 2) strcpy(buf,line2);
    if (linecount == 3) strcpy(buf,line3);
/*  report(stdout, "--> "); fflush(stderr);  */
/*  scanf("%s",&buf)                         */
    sockrc = PS_SUCCESS;
#endif
    if (sockrc == PS_SUCCESS) {
	bufp = buf;
	if ((*buf) == '+')
	{
	    bufp++;
/*      if (*bufp == ' ') bufp++; */
	    if (argbuf != NULL)
		strcpy(argbuf,bufp);
	    ok=0;
	}
	else if (strcmp(buf,"-ERR") == 0)
	    ok = PS_ERROR;
	else ok = PS_PROTOCOL;

    }
    else
	ok = PS_SOCKET;
    if (outlevel >= O_DEBUG)
	report(stdout,  GT_("Get response return %d [%s]\n"), ok, buf);
    buf[sockrc] = 0;
    return(ok);
}

/*********************************************************************
  function:      LenAppend
  description:   Store token length encoded as per ASN.1 DER rules
                 buffer pointer stepped on appropriately.
                 Copes with numbers up to 32767 at least.
  arguments:
    buf          pointer to buffer to receive result
    len          length value to encode

  return value:  none
  calls:         none
  globals:       none
 *********************************************************************/

static void LenAppend(pptr,len)
unsigned char **pptr;
int  len;
{
    if (len < 0x80)
    {
	**pptr = len; (*pptr)++;
    }
    else if (len < 0x100)
    {
	**pptr = 0x81; (*pptr)++;
	**pptr = len;  (*pptr)++;
    }
    else
    {
	**pptr = 0x82;       (*pptr)++;
	**pptr = len >> 8;   (*pptr)++;
	**pptr = len & 0xFF; (*pptr)++;
    }
}

/*********************************************************************
  function:      LenSkip
  description:   Check token header, length, and mechanism, and
                 skip past these.
  arguments:
    pptr         pointer to buffer pointer
    rxlen        number of bytes after base64 decode

  return value:  0 if error, else token length value
  calls:         none
  globals:       reads outlevel.
 *********************************************************************/

int LenSkip(pptr,rxlen)
unsigned char **pptr;
int rxlen;
{
    int len;
    unsigned char *save;
    save = *pptr;
    if (**pptr != HDR)
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("Hdr not 60\n"));
	return(0);
    }
    (*pptr)++;
    if (((**pptr) & 0x80) == 0 )
    {
	len = **pptr; (*pptr)++;
    }
    else if ((**pptr) == 0x81)
    {
	len = *(*pptr+1); (*pptr) += 2;
    }
    else if ((**pptr) == 0x82)
    {
	len = ((*(*pptr+1)) << 8) | *(*pptr+2);
	(*pptr) += 3;
    }
    else len = 0;
    if (len==0)
    {
	if (outlevel>O_SILENT)
	    report(stderr, GT_("Token length error\n"));
    }
    else if (((*pptr-save)+len) != rxlen)
    {
	if (outlevel>O_SILENT)
	    report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
	len = 0;
    }
    else if (memcmp(*pptr,MECH,11))
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("Mechanism field incorrect\n"));
	len = 0;
    }
    else (*pptr) += 11;  /* Skip mechanism field */
    return(len);
}

/*********************************************************************
  function:      DecBase64
  description:   Decode a Base64 string, overwriting the original.
                 Note that result cannot be longer than input.

  arguments:
    bufp         buffer

  return value:  0 if error, else number of bytes in decoded result
  calls:         none
  globals:       reads outlevel.
 *********************************************************************/

static int DecBase64(bufp)
unsigned char *bufp;
{
    unsigned int   new, bits=0, cnt=0, i, part=0;
    unsigned char  ch;
    unsigned char* outp=bufp;
    unsigned char* inp=bufp;
    while((ch=*(inp++)) != 0)
    {
	if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
	{
	    if      ((ch>='A') && (ch <= 'Z'))   new = ch - 'A';
	    else if ((ch>='a') && (ch <= 'z'))   new = ch - 'a' + 26;
	    else if ((ch>='0') && (ch <= '9'))   new = ch - '0' + 52;
	    else if ( ch=='+'                )   new = 62;
	    else if ( ch=='/'                )   new = 63;
	    else {
		report(stderr,  GT_("dec64 error at char %d: %x\n"), inp - bufp, ch);
		return(0);
	    }
	    part=((part & 0x3F)*64) + new;
	    bits += 6;
	    if (bits >= 8)
	    {
		bits -= 8;
		*outp = (part >> bits);
		cnt++; outp++;
	    }
	}
    }
    if (outlevel >= O_MONITOR)
    {
	report(stdout, GT_("Inbound binary data:\n"));
	for (i=0; i<cnt; i++)
	{
	    report_build(stdout, "%02X ",bufp[i]);
	    if (((i % 16)==15) || (i==(cnt-1)))
		report_complete(stdout, "\n");
	}
    }
    return(cnt);
}

/*********************************************************************
  function:      EncBase64
  description:   Encode into Base64 string, overwriting the original.
                 Note that result CAN be longer than input, the buffer
                 is assumed to be big enough. Result string is
                 terminated with \0.

  arguments:
    bufp         buffer
    len          number of bytes in buffer (>0)

  return value:  none
  calls:         none
  globals:       reads outlevel;
 *********************************************************************/

static void EncBase64(bufp,len)
unsigned char *bufp;
int  len;
{
    unsigned char* outp;
    unsigned char  c1,c2,c3;
    char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    int  i;

    if (outlevel >= O_MONITOR)
    {
	report(stdout, GT_("Outbound data:\n"));
	for (i=0; i<len; i++)
	{
	    report_build(stdout, "%02X ",bufp[i]);
	    if (((i % 16)==15) || (i==(len-1)))
		report_complete(stdout, "\n");
	}
    }
    outp = bufp + (((len-1)/3)*4);
    *(outp+4) = 0;
    /* So we can do the update in place, start at the far end! */
    for (i=((len-1)/3)*3; i>=0; i-=3)
    {
	c1 = bufp[i];
	if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
	if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
	*(outp) = x[c1/4];
	*(outp+1) = x[((c1 & 3)*16) + (c2/16)];
	if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
	else *(outp+2) = '=';
	if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
	else *(outp+3) = '=';
	outp -= 4;
    }
}

/*********************************************************************
  function:      ToUnicode
  description:   Convert ASCII (or iso-8859-1) byte string into
                 Unicode. Ensure length isn't too long (STRMAX).

  arguments:
    pptr         pointer to input buffer
    delim        delimiter character (in addition to \0)
    buf          buffer where Unicode will go
    plen         pointer to length variable (# bytes output)
    conv         1 to convert to lowercase, 0 leaves alone

  return value:  none
  calls:         none
  globals:       reads outlevel;
 *********************************************************************/

static void ToUnicode(pptr,delim,buf,plen,conv)
unsigned char **pptr; /* input string  */
unsigned char delim;
unsigned char *buf;   /* output buffer */
int  *plen;
int conv;
{
    unsigned char *p;
    int i;
    *plen = 0; p=buf;
    while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
    {
	*(p++) = 0;
	if (conv)
	    *(p++) = tolower(**pptr);
	else
	    *(p++) = (**pptr);
	(*plen) += 2;
	(*pptr)++;
    }
    if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
    {
	if (outlevel > O_SILENT)
	    report(stderr, GT_("RPA String too long\n"));
	*plen = 0;
    }
    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("Unicode:\n"));
	for (i=0; i<(*plen); i++)
	{
	    report_build(stdout, "%02X ",buf[i]);
	    if (((i % 16)==15) || (i==((*plen)-1)))
		report_complete(stdout, "\n");
	}
    }
}

/*********************************************************************
  function:      SetRealmService
  description:   Select a realm from list, and store it.

  arguments:
    bufp         pointer to buffer

  return value:  none
  calls:         none
  globals:       reads outlevel.
                 writes Ns Nsl Nr Nrl
 *********************************************************************/

static int SetRealmService(bufp)
unsigned char* bufp;
{
    /* For the moment we pick the first available realm. It would */
    /* make more sense to verify that the realm which the user    */
    /* has given (as part of id) is in the list, and select it's  */
    /* corresponding service name.                                */
    ToUnicode(&bufp, '@', Ns, &Nsl, 1);  /* Service    */
    bufp++;                              /* Skip the @ */
    ToUnicode(&bufp, ' ', Nr, &Nrl, 1);  /* Realm name */
    if ((Nrl == 0) || (Nsl == 0))
	return(PS_RPA);
    return(0);
}

/*********************************************************************
  function:      GenChallenge
  description:   Generate a random User challenge

  arguments:
    buf          pointer to buffer
    len          length in bytes

  return value:  none
  calls:         none
  globals:       reads outlevel.
                 reads /dev/random
 *********************************************************************/

static void GenChallenge(buf,len)
unsigned char *buf;
int  len;
{
    int  i;
    FILE *devrandom;

    devrandom = fopen("/dev/urandom","rb");
    if (devrandom == NULL && outlevel > O_SILENT)
    {
	report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n"));
	report(stdout, GT_("    prevent you logging in, but means you\n"));
	report(stdout, GT_("    cannot be sure you are talking to the\n"));
	report(stdout, GT_("    service that you think you are (replay\n"));
	report(stdout, GT_("    attacks by a dishonest service are possible.)\n"));
    }

    for(i=0; i<len; i++)
	buf[i] = devrandom ? fgetc(devrandom) : random();

    if (devrandom)
	fclose(devrandom);	/* should be safe, file mode was "r" */

    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("User challenge:\n"));
	for (i=0; i<len; i++)
	  {
	  report_build(stdout, "%02X ",buf[i]);
	  if (((i % 16)==15) || (i==(len-1)))
	    report_complete(stdout, "\n");
	  }
    }
}

/*********************************************************************
  function:      DigestPassphrase
  description:   Use MD5 to compute digest (Pu) of Passphrase
                 Don't map to lower case. We assume the user is
                 aware of the case requirement of the realm.
                 (Why oh why have options in the spec?!)
  arguments:
    passphrase   buffer containing string, \0 terminated
    rbuf         buffer into which digest goes

  return value:  0 if ok, else error code
  calls:         md5
  globals:       reads authentication items listed above.
                 writes Pu.
 *********************************************************************/

static int DigestPassphrase(passphrase,rbuf,unicodeit)
unsigned char *passphrase;
unsigned char *rbuf;
int unicodeit;
{
    int   len;
    unsigned char  workarea[STRMAX];
    unsigned char* ptr;

    if (unicodeit)  /* Option in spec. Yuck. */
    {
	ptr = passphrase;
	ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
	if (len == 0)
	    return(PS_SYNTAX);
	ptr = workarea;
    }
    else
    {
	ptr = rbuf;
	len = strlen(passphrase);
    }
    md5(ptr,len,rbuf);
    return(0);
}

/*********************************************************************
  function:      CompUserResp
  description:   Use MD5 to compute User Response (Ru) from
                 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu

  arguments:     none

  return value:  none
  calls:         MD5
  globals:       reads authentication items listed above.
                 writes Ru.
 *********************************************************************/

static void CompUserResp()
{
    unsigned char  workarea[Pul+48+STRMAX*5+Tsl+Pul];
    unsigned char* p;
    p = workarea;
    memcpy(p , Pu,  Pul); p += Pul;
    memset(p , '\0', 48); p += 48;
    memcpy(p , Nu,  Nul); p += Nul;
    memcpy(p , Ns,  Nsl); p += Nsl;
    memcpy(p , Nr,  Nrl); p += Nrl;
    memcpy(p , Cu,  Cul); p += Cul;
    memcpy(p , Cs,  Csl); p += Csl;
    memcpy(p , Ts,  Tsl); p += Tsl;
    memcpy(p , Pu,  Pul); p += Pul;
    md5(workarea,p-workarea,Ru);
}

/*********************************************************************
  function:      CheckUserAuth
  description:   Use MD5 to verify Authentication Response to User (Au)
                 using  Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
                 Also creates unobscured session key Kus from obscured
                 one Kusu

  arguments:     none

  return value:  0 if ok, PS_RPA if mismatch
  calls:         MD5
  globals:       reads authentication items listed above.
                 writes Ru.
 *********************************************************************/

static int CheckUserAuth()
{
    unsigned char  workarea[Pul+48+STRMAX*7+Tsl+Pul];
    unsigned char* p;
    unsigned char  md5ans[16];
    int i;
    /* Create unobscured Kusu */
    p = workarea;
    memcpy(p , Pu,  Pul); p += Pul;
    memset(p , '\0', 48); p += 48;
    memcpy(p , Ns,  Nsl); p += Nsl;
    memcpy(p , Nu,  Nul); p += Nul;
    memcpy(p , Nr,  Nrl); p += Nrl;
    memcpy(p , Cs,  Csl); p += Csl;
    memcpy(p , Cu,  Cul); p += Cul;
    memcpy(p , Ts,  Tsl); p += Tsl;
    memcpy(p , Pu,  Pul); p += Pul;
    md5(workarea,p-workarea,md5ans);
    for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
    /* Compute Au from our information */
    p = workarea;
    memcpy(p , Pu,  Pul); p += Pul;
    memset(p , '\0', 48); p += 48;
    memcpy(p , Ns,  Nsl); p += Nsl;
    memcpy(p , Nu,  Nul); p += Nul;
    memcpy(p , Nr,  Nrl); p += Nrl;
    memcpy(p , Kusu,Kusl);p += Kusl;
    memcpy(p , Cs,  Csl); p += Csl;
    memcpy(p , Cu,  Cul); p += Cul;
    memcpy(p , Ts,  Tsl); p += Tsl;
    memcpy(p , Kus, Kusl);p += Kusl;
    memcpy(p , Pu,  Pul); p += Pul;
    md5(workarea,p-workarea,md5ans);
    /* Compare the two */
    for (i=0; i<16; i++)
	if (Au[i] != md5ans[i]) return(PS_RPA);
    return(0);
}

/*********************************************************************
  function:      md5
  description:   Apply MD5
  arguments:
    in           input byte stream
    len          length in bytes
    out          128 bit result buffer
  return value:  none
  calls:         MD5 primitives
  globals:       reads outlevel
 *********************************************************************/

static void md5(in,len,out)
unsigned char*    in;
int      len;
unsigned char*    out;
{
    int      i;
    MD5_CTX  md5context;

    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("MD5 being applied to data block:\n"));
	for (i=0; i<len; i++)
	{
	    report_build(stdout, "%02X ",in[i]);
	    if (((i % 16)==15) || (i==(len-1)))
		report_complete(stdout, "\n");
	}
    }
    MD5Init(   &md5context );
    MD5Update( &md5context, in, len );
    MD5Final(  out, &md5context );
    if (outlevel >= O_DEBUG)
    {
	report(stdout, GT_("MD5 result is: \n"));
	for (i=0; i<16; i++)
	{
	    report_build(stdout, "%02X ",out[i]);
	}
	report_complete(stdout, "\n");
    }
}
#endif /* POP3_ENABLE && RPA_ENABLE */

/* rpa.c ends here */