ph10 2004/10/07 14:10:02 BST
Added files:
exim-src/src/auths Makefile README auth-spa.c auth-spa.h
b64decode.c b64encode.c call_pam.c
call_pwcheck.c call_radius.c cram_md5.c
cram_md5.h cyrus_sasl.c cyrus_sasl.h
get_data.c get_no64_data.c md5.c
plaintext.c plaintext.h pwcheck.c
pwcheck.h sha1.c spa.c spa.h
xtextdecode.c xtextencode.c
exim-src/src/lookups Makefile README cdb.c cdb.h dbmdb.c
dbmdb.h dnsdb.c dnsdb.h dsearch.c
dsearch.h ibase.c ibase.h ldap.c ldap.h
lf_check_file.c lf_functions.h lf_quote.c
lsearch.c lsearch.h mysql.c mysql.h nis.c
nis.h nisplus.c nisplus.h oracle.c
oracle.h passwd.c passwd.h pgsql.c
pgsql.h testdb.c testdb.h whoson.c
whoson.h
exim-src/src/routers Makefile README accept.c accept.h
dnslookup.c dnslookup.h ipliteral.c
ipliteral.h iplookup.c iplookup.h
manualroute.c manualroute.h
queryprogram.c queryprogram.h redirect.c
redirect.h rf_change_domain.c
rf_expand_data.c rf_functions.h
rf_get_errors_address.c
rf_get_munge_headers.c rf_get_transport.c
rf_get_ugid.c rf_lookup_hostlist.c
rf_queue_add.c rf_self_action.c
rf_set_ugid.c
exim-src/src/transports Makefile README appendfile.c
appendfile.h autoreply.c autoreply.h
lmtp.c lmtp.h pipe.c pipe.h smtp.c
smtp.h tf_maildir.c tf_maildir.h
Log:
Start
Revision Changes Path
1.1 +42 -0 exim/exim-src/src/auths/Makefile (new)
1.1 +97 -0 exim/exim-src/src/auths/README (new)
1.1 +1528 -0 exim/exim-src/src/auths/auth-spa.c (new)
1.1 +91 -0 exim/exim-src/src/auths/auth-spa.h (new)
1.1 +86 -0 exim/exim-src/src/auths/b64decode.c (new)
1.1 +76 -0 exim/exim-src/src/auths/b64encode.c (new)
1.1 +204 -0 exim/exim-src/src/auths/call_pam.c (new)
1.1 +122 -0 exim/exim-src/src/auths/call_pwcheck.c (new)
1.1 +190 -0 exim/exim-src/src/auths/call_radius.c (new)
1.1 +347 -0 exim/exim-src/src/auths/cram_md5.c (new)
1.1 +34 -0 exim/exim-src/src/auths/cram_md5.h (new)
1.1 +336 -0 exim/exim-src/src/auths/cyrus_sasl.c (new)
1.1 +37 -0 exim/exim-src/src/auths/cyrus_sasl.h (new)
1.1 +48 -0 exim/exim-src/src/auths/get_data.c (new)
1.1 +48 -0 exim/exim-src/src/auths/get_no64_data.c (new)
1.1 +359 -0 exim/exim-src/src/auths/md5.c (new)
1.1 +305 -0 exim/exim-src/src/auths/plaintext.c (new)
1.1 +34 -0 exim/exim-src/src/auths/plaintext.h (new)
1.1 +455 -0 exim/exim-src/src/auths/pwcheck.c (new)
1.1 +29 -0 exim/exim-src/src/auths/pwcheck.h (new)
1.1 +554 -0 exim/exim-src/src/auths/sha1.c (new)
1.1 +352 -0 exim/exim-src/src/auths/spa.c (new)
1.1 +41 -0 exim/exim-src/src/auths/spa.h (new)
1.1 +59 -0 exim/exim-src/src/auths/xtextdecode.c (new)
1.1 +63 -0 exim/exim-src/src/auths/xtextencode.c (new)
1.1 +40 -0 exim/exim-src/src/lookups/Makefile (new)
1.1 +156 -0 exim/exim-src/src/lookups/README (new)
1.1 +437 -0 exim/exim-src/src/lookups/cdb.c (new)
1.1 +59 -0 exim/exim-src/src/lookups/cdb.h (new)
1.1 +146 -0 exim/exim-src/src/lookups/dbmdb.c (new)
1.1 +21 -0 exim/exim-src/src/lookups/dbmdb.h (new)
1.1 +223 -0 exim/exim-src/src/lookups/dnsdb.c (new)
1.1 +16 -0 exim/exim-src/src/lookups/dnsdb.h (new)
1.1 +123 -0 exim/exim-src/src/lookups/dsearch.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/dsearch.h (new)
1.1 +558 -0 exim/exim-src/src/lookups/ibase.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/ibase.h (new)
1.1 +1371 -0 exim/exim-src/src/lookups/ldap.c (new)
1.1 +24 -0 exim/exim-src/src/lookups/ldap.h (new)
1.1 +115 -0 exim/exim-src/src/lookups/lf_check_file.c (new)
1.1 +16 -0 exim/exim-src/src/lookups/lf_functions.h (new)
1.1 +67 -0 exim/exim-src/src/lookups/lf_quote.c (new)
1.1 +401 -0 exim/exim-src/src/lookups/lsearch.c (new)
1.1 +25 -0 exim/exim-src/src/lookups/lsearch.h (new)
1.1 +436 -0 exim/exim-src/src/lookups/mysql.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/mysql.h (new)
1.1 +104 -0 exim/exim-src/src/lookups/nis.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/nis.h (new)
1.1 +279 -0 exim/exim-src/src/lookups/nisplus.c (new)
1.1 +17 -0 exim/exim-src/src/lookups/nisplus.h (new)
1.1 +619 -0 exim/exim-src/src/lookups/oracle.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/oracle.h (new)
1.1 +56 -0 exim/exim-src/src/lookups/passwd.c (new)
1.1 +16 -0 exim/exim-src/src/lookups/passwd.h (new)
1.1 +469 -0 exim/exim-src/src/lookups/pgsql.c (new)
1.1 +18 -0 exim/exim-src/src/lookups/pgsql.h (new)
1.1 +69 -0 exim/exim-src/src/lookups/testdb.c (new)
1.1 +16 -0 exim/exim-src/src/lookups/testdb.h (new)
1.1 +82 -0 exim/exim-src/src/lookups/whoson.c (new)
1.1 +16 -0 exim/exim-src/src/lookups/whoson.h (new)
1.1 +44 -0 exim/exim-src/src/routers/Makefile (new)
1.1 +59 -0 exim/exim-src/src/routers/README (new)
1.1 +130 -0 exim/exim-src/src/routers/accept.c (new)
1.1 +33 -0 exim/exim-src/src/routers/accept.h (new)
1.1 +408 -0 exim/exim-src/src/routers/dnslookup.c (new)
1.1 +41 -0 exim/exim-src/src/routers/dnslookup.h (new)
1.1 +195 -0 exim/exim-src/src/routers/ipliteral.c (new)
1.1 +36 -0 exim/exim-src/src/routers/ipliteral.h (new)
1.1 +397 -0 exim/exim-src/src/routers/iplookup.c (new)
1.1 +43 -0 exim/exim-src/src/routers/iplookup.h (new)
1.1 +426 -0 exim/exim-src/src/routers/manualroute.c (new)
1.1 +39 -0 exim/exim-src/src/routers/manualroute.h (new)
1.1 +510 -0 exim/exim-src/src/routers/queryprogram.c (new)
1.1 +42 -0 exim/exim-src/src/routers/queryprogram.h (new)
1.1 +758 -0 exim/exim-src/src/routers/redirect.c (new)
1.1 +67 -0 exim/exim-src/src/routers/redirect.h (new)
1.1 +86 -0 exim/exim-src/src/routers/rf_change_domain.c (new)
1.1 +50 -0 exim/exim-src/src/routers/rf_expand_data.c (new)
1.1 +33 -0 exim/exim-src/src/routers/rf_functions.h (new)
1.1 +120 -0 exim/exim-src/src/routers/rf_get_errors_address.c (new)
1.1 +119 -0 exim/exim-src/src/routers/rf_get_munge_headers.c (new)
1.1 +92 -0 exim/exim-src/src/routers/rf_get_transport.c (new)
1.1 +82 -0 exim/exim-src/src/routers/rf_get_ugid.c (new)
1.1 +193 -0 exim/exim-src/src/routers/rf_lookup_hostlist.c (new)
1.1 +108 -0 exim/exim-src/src/routers/rf_queue_add.c (new)
1.1 +142 -0 exim/exim-src/src/routers/rf_self_action.c (new)
1.1 +46 -0 exim/exim-src/src/routers/rf_set_ugid.c (new)
1.1 +26 -0 exim/exim-src/src/transports/Makefile (new)
1.1 +43 -0 exim/exim-src/src/transports/README (new)
1.1 +3147 -0 exim/exim-src/src/transports/appendfile.c (new)
1.1 +97 -0 exim/exim-src/src/transports/appendfile.h (new)
1.1 +817 -0 exim/exim-src/src/transports/autoreply.c (new)
1.1 +47 -0 exim/exim-src/src/transports/autoreply.h (new)
1.1 +750 -0 exim/exim-src/src/transports/lmtp.c (new)
1.1 +33 -0 exim/exim-src/src/transports/lmtp.h (new)
1.1 +1010 -0 exim/exim-src/src/transports/pipe.c (new)
1.1 +48 -0 exim/exim-src/src/transports/pipe.h (new)
1.1 +2746 -0 exim/exim-src/src/transports/smtp.c (new)
1.1 +69 -0 exim/exim-src/src/transports/smtp.h (new)
1.1 +539 -0 exim/exim-src/src/transports/tf_maildir.c (new)
1.1 +21 -0 exim/exim-src/src/transports/tf_maildir.h (new)
Index: Makefile
====================================================================
# $Cambridge: exim/exim-src/src/auths/Makefile,v 1.1 2004/10/07 13:10:00 ph10 Exp $
# Make file for building a library containing all the available authorization
# methods, and calling it auths.a. In addition, there are functions that are
# of general use in several methods; these are in separate modules so they are
# linked in only when needed. This Makefile is called from the main make file,
# after cd'ing to the auths subdirectory. When the relevant AUTH_ macros are
# defined, the equivalent modules herein is not included in the final binary.
OBJ = b64encode.o b64decode.o call_pam.o call_pwcheck.o call_radius.o \
xtextencode.o xtextdecode.o get_data.o get_no64_data.o md5.o \
cram_md5.o cyrus_sasl.o plaintext.o pwcheck.o sha1.o auth-spa.o spa.o
auths.a: $(OBJ)
/bin/rm -f auths.a
$(AR) auths.a $(OBJ)
$(RANLIB) $@
/bin/rm -rf ../drtables.o
.SUFFIXES: .o .c
.c.o:; $(CC) -c $(CFLAGS) $(INCLUDE) $*.c
auth-spa.o: $(HDRS) auth-spa.c
b64encode.o: $(HDRS) b64encode.c
b64decode.o: $(HDRS) b64decode.c
call_pam.o: $(HDRS) call_pam.c
call_pwcheck.o: $(HDRS) call_pwcheck.c pwcheck.h
call_radius.o: $(HDRS) call_radius.c
get_data.o: $(HDRS) get_data.c
get_no64_data.o: $(HDRS) get_no64_data.c
md5.o: $(HDRS) md5.c
pwcheck.o: $(HDRS) pwcheck.c pwcheck.h
sha1.o: $(HDRS) sha1.c
xtextencode.o: $(HDRS) xtextencode.c
xtextdecode.o: $(HDRS) xtextdecode.c
cram_md5.o: $(HDRS) cram_md5.c cram_md5.h
cyrus_sasl.o: $(HDRS) cyrus_sasl.c cyrus_sasl.h
plaintext.o: $(HDRS) plaintext.c plaintext.h
spa.o: $(HDRS) spa.c spa.h
# End
Index: README
====================================================================
$Cambridge: exim/exim-src/src/auths/README,v 1.1 2004/10/07 13:10:00 ph10 Exp $
AUTHS
The modules in this directory are in support of various authentication
functions. Some of them, such as the base64 encoding/decoding and MD5
computation, are just functions that might be used by several authentication
mechanisms. Others are the SMTP AUTH mechanisms themselves, included in the
final binary if the relevant AUTH_XXX value is set in Local/Makefile. The
general functions are in separate modules so that they get included in the
final binary only if they are actually called from somewhere.
GENERAL FUNCTIONS
The API for each of these functions is documented with the function's code.
auth_b64encode encode in base 64
auth_b64decode decode from base 64
auth_call_pam do PAM authentication (if build with SUPPORT_PAM)
auth_get_data issue SMTP AUTH challenge and read response
auth_xtextencode encode as xtext
auth_xtextdecode decode from xtext
INTERFACE TO SMTP AUTHENTICATION MECHANISMS
These are general SSL mechanisms, adapted for use with SMTP. Each
authentication mechanism has three functions, for initialization, server
authentication, and client authentication.
INITIALIZATION
The initialization function is called when the configuration is read, and can
check for incomplete or illegal settings. It has one argument, a pointer to the
instance block for this configured mechanism. It must set the flags called
"server" and "client" in the generic auth_instance block to indicate whether
the server and/or client functions are available for this authenticator.
Typically this depends on whether server or client configuration options have
been set, but it is also possible to have an authenticator that has only one of
the server or client functions.
SERVER AUTHENTICATION
The second function performs authentication as a server. It receives a pointer
to the instance block, and its second argument is the remainder of the data
from the AUTH command. The numeric variable maximum setting (expand_nmax) is
set to zero, with $0 initialized as unset. The authenticator may set up numeric
variables according to its specification; it should leave expand_nmax set at
the end so that they can be used for the expansion of the generic server_set_id
option, which happens centrally.
This function has access to the SMTP input and output so that it can write
intermediate responses and read more data if necessary. There is a packaged
function in auth_get_data() which outputs a challenge and reads a response.
The yield of a server authentication check must be one of:
OK success
DEFER couldn't complete the check
FAIL authentication failed
CANCELLED authentication forced to fail by "*" response to challenge
BAD64 bad base64 data received
UNEXPECTED unexpected data received
In the case of DEFER, auth_defer_msg should point to an error message.
CLIENT AUTHENTICATION
The third function performs authentication as a client. It receives a pointer
to the instance block, and four further arguments:
The smtp_inblock item for the connection to the remote host.
The normal command-reading timeout value.
A pointer to a buffer, to be used for receiving responses. It is done this
way so that the buffer is available for logging etc. in the calling
function in cases of error.
The size of the buffer.
The yield of a client authentication check must be one of:
OK success
FAIL_SEND error after writing a command; errno is set
FAIL failed after reading a response;
either errno is set (for timeouts, I/O failures) or
the buffer contains the SMTP response line
FORCEFAIL failed without reading a response (often "fail" in expansion)
ERROR local problem (typically expansion error); message in buffer
To communicate with the remote host the client should call
smtp_write_command(). If this yields FALSE, the authenticator should return
FAIL. After a successful write, the response is received by a call to
smtp_read_response(), which should use the buffer handed to the client function
as an argument.
****
Index: auth-spa.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/auth-spa.c,v 1.1 2004/10/07 13:10:00 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/*
* This file provides the necessary methods for authenticating with
* Microsoft's Secure Password Authentication.
* All the original code used here was torn by Marc Prud'hommeaux out of the
* Samba project (by Andrew Tridgell, Jeremy Allison, and others).
* Tom Kistner provided additional code, adding spa_build_auth_challenge() to
* support server authentication mode.
* Mark Lyda provided a patch to solve this problem:
- Exim is indicating in its Authentication Request message (Type 1) that it
can transmit text in either Unicode or OEM format.
- Microsoft's SMTP server (smtp.email.msn.com) is responding in its
Challenge message (Type 2) that it will be expecting the OEM format.
- Exim does not pay attention to the text format requested by Microsoft's
SMTP server and, instead, defaults to using the Unicode format.
* References:
*
http://www.innovation.ch/java/ntlm.html
*
http://www.kuro5hin.org/story/2002/4/28/1436/66154
* It seems that some systems have existing but different definitions of some
* of the following types. I received a complaint about "int16" causing
* compilation problems. So I (PH) have renamed them all, to be on the safe
* side, by adding 'x' on the end.
* typedef signed short int16;
* typedef unsigned short uint16;
* typedef unsigned uint32;
* typedef unsigned char uint8;
* The API is extremely simple:
* 1. Form a SPA authentication request based on the username
* and (optional) domain
* 2. Send the request to the server and get an SPA challenge
* 3. Build the challenge response and send it back.
*
* Example usage is as
* follows:
*
int main (int argc, char ** argv)
{
SPAAuthRequest request;
SPAAuthChallenge challenge;
SPAAuthResponse response;
char msgbuf[2048];
char buffer[512];
char *username, *password, *domain, *challenge_str;
if (argc < 3)
{
printf ("Usage: %s <username> <password> [SPA Challenge]\n",
argv [0]);
exit (1);
}
username = argv [1];
password = argv [2];
domain = 0;
spa_build_auth_request (&request, username, domain);
spa_bits_to_base64 (msgbuf, (unsigned char*)&request,
spa_request_length(&request));
printf ("SPA Login request for username=%s:\n %s\n",
argv [1], msgbuf);
if (argc < 4)
{
printf ("Run: %s <username> <password> [NTLM Challenge] " \
"to complete authenitcation\n", argv [0]);
exit (0);
}
challenge_str = argv [3];
spa_build_auth_response (&challenge, &response, username, password);
spa_bits_to_base64 (msgbuf, (unsigned char*)&response,
spa_request_length(&response));
printf ("SPA Response to challenge:\n %s\n for " \
"username=%s, password=%s:\n %s\n",
argv[3], argv [1], argv [2], msgbuf);
return 0;
}
*
*
* All the client code used here was torn by Marc Prud'hommeaux out of the
* Samba project (by Andrew Tridgell, Jeremy Allison, and others).
* Previous comments are below:
*/
/*
Unix SMB/Netbios implementation.
Version 1.9.
a partial implementation of DES designed for use in the
SMB authentication protocol
Copyright (C) Andrew Tridgell 1998
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* NOTES:
This code makes no attempt to be fast! In fact, it is a very
slow implementation
This code is NOT a complete DES implementation. It implements only
the minimum necessary for SMB authentication, as used by all SMB
products (including every copy of Microsoft Windows95 ever sold)
In particular, it can only do a unchained forward DES pass. This
means it is not possible to use this code for encryption/decryption
of data, instead it is only useful as a "hash" algorithm.
There is no entry point into this code that allows normal DES operation.
I believe this means that this code does not come under ITAR
regulations but this is NOT a legal opinion. If you are concerned
about the applicability of ITAR regulations to this code then you
should confirm it for yourself (and maybe let me know if you come
up with a different answer to the one above)
*/
#define DEBUG(a,b) ;
extern int DEBUGLEVEL;
#include <sys/types.h> /* For size_t */
#include "auth-spa.h"
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef _AIX
typedef unsigned char uchar;
#endif
typedef int BOOL;
#define False 0
#define True 1
#ifndef _BYTEORDER_H
#define _BYTEORDER_H
#define RW_PCVAL(read,inbuf,outbuf,len) \
{ if (read) { PCVAL (inbuf,0,outbuf,len); } \
else { PSCVAL(inbuf,0,outbuf,len); } }
#define RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
{ if (read) { if (big_endian) { RPIVAL(inbuf,0,outbuf,len); } else { PIVAL(inbuf,0,outbuf,len); } } \
else { if (big_endian) { RPSIVAL(inbuf,0,outbuf,len); } else { PSIVAL(inbuf,0,outbuf,len); } } }
#define RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
{ if (read) { if (big_endian) { RPSVAL(inbuf,0,outbuf,len); } else { PSVAL(inbuf,0,outbuf,len); } } \
else { if (big_endian) { RPSSVAL(inbuf,0,outbuf,len); } else { PSSVAL(inbuf,0,outbuf,len); } } }
#define RW_CVAL(read, inbuf, outbuf, offset) \
{ if (read) { (outbuf) = CVAL (inbuf,offset); } \
else { SCVAL(inbuf,offset,outbuf); } }
#define RW_IVAL(read, big_endian, inbuf, outbuf, offset) \
{ if (read) { (outbuf) = ((big_endian) ? RIVAL(inbuf,offset) : IVAL (inbuf,offset)); } \
else { if (big_endian) { RSIVAL(inbuf,offset,outbuf); } else { SIVAL(inbuf,offset,outbuf); } } }
#define RW_SVAL(read, big_endian, inbuf, outbuf, offset) \
{ if (read) { (outbuf) = ((big_endian) ? RSVAL(inbuf,offset) : SVAL (inbuf,offset)); } \
else { if (big_endian) { RSSVAL(inbuf,offset,outbuf); } else { SSVAL(inbuf,offset,outbuf); } } }
#undef CAREFUL_ALIGNMENT
/* we know that the 386 can handle misalignment and has the "right"
byteorder */
#ifdef __i386__
#define CAREFUL_ALIGNMENT 0
#endif
#ifndef CAREFUL_ALIGNMENT
#define CAREFUL_ALIGNMENT 1
#endif
#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
#define SCVAL(buf,pos,val) (CVAL(buf,pos) = (val))
#if CAREFUL_ALIGNMENT
#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
#define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8)
#define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16))
#define SVALS(buf,pos) ((int16x)SVAL(buf,pos))
#define IVALS(buf,pos) ((int32x)IVAL(buf,pos))
#define SSVAL(buf,pos,val) SSVALX((buf),(pos),((uint16x)(val)))
#define SIVAL(buf,pos,val) SIVALX((buf),(pos),((uint32x)(val)))
#define SSVALS(buf,pos,val) SSVALX((buf),(pos),((int16x)(val)))
#define SIVALS(buf,pos,val) SIVALX((buf),(pos),((int32x)(val)))
#else /* CAREFUL_ALIGNMENT */
/* this handles things for architectures like the 386 that can handle
alignment errors */
/*
WARNING: This section is dependent on the length of int16x and int32x
being correct
*/
/* get single value from an SMB buffer */
#define SVAL(buf,pos) (*(uint16x *)((char *)(buf) + (pos)))
#define IVAL(buf,pos) (*(uint32x *)((char *)(buf) + (pos)))
#define SVALS(buf,pos) (*(int16x *)((char *)(buf) + (pos)))
#define IVALS(buf,pos) (*(int32x *)((char *)(buf) + (pos)))
/* store single value in an SMB buffer */
#define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16x)(val))
#define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32x)(val))
#define SSVALS(buf,pos,val) SVALS(buf,pos)=((int16x)(val))
#define SIVALS(buf,pos,val) IVALS(buf,pos)=((int32x)(val))
#endif /* CAREFUL_ALIGNMENT */
/* macros for reading / writing arrays */
#define SMBMACRO(macro,buf,pos,val,len,size) \
{ int l; for (l = 0; l < (len); l++) (val)[l] = macro((buf), (pos) + (size)*l); }
#define SSMBMACRO(macro,buf,pos,val,len,size) \
{ int l; for (l = 0; l < (len); l++) macro((buf), (pos) + (size)*l, (val)[l]); }
/* reads multiple data from an SMB buffer */
#define PCVAL(buf,pos,val,len) SMBMACRO(CVAL,buf,pos,val,len,1)
#define PSVAL(buf,pos,val,len) SMBMACRO(SVAL,buf,pos,val,len,2)
#define PIVAL(buf,pos,val,len) SMBMACRO(IVAL,buf,pos,val,len,4)
#define PCVALS(buf,pos,val,len) SMBMACRO(CVALS,buf,pos,val,len,1)
#define PSVALS(buf,pos,val,len) SMBMACRO(SVALS,buf,pos,val,len,2)
#define PIVALS(buf,pos,val,len) SMBMACRO(IVALS,buf,pos,val,len,4)
/* stores multiple data in an SMB buffer */
#define PSCVAL(buf,pos,val,len) SSMBMACRO(SCVAL,buf,pos,val,len,1)
#define PSSVAL(buf,pos,val,len) SSMBMACRO(SSVAL,buf,pos,val,len,2)
#define PSIVAL(buf,pos,val,len) SSMBMACRO(SIVAL,buf,pos,val,len,4)
#define PSCVALS(buf,pos,val,len) SSMBMACRO(SCVALS,buf,pos,val,len,1)
#define PSSVALS(buf,pos,val,len) SSMBMACRO(SSVALS,buf,pos,val,len,2)
#define PSIVALS(buf,pos,val,len) SSMBMACRO(SIVALS,buf,pos,val,len,4)
/* now the reverse routines - these are used in nmb packets (mostly) */
#define SREV(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
#define IREV(x) ((SREV(x)<<16) | (SREV((x)>>16)))
#define RSVAL(buf,pos) SREV(SVAL(buf,pos))
#define RSVALS(buf,pos) SREV(SVALS(buf,pos))
#define RIVAL(buf,pos) IREV(IVAL(buf,pos))
#define RIVALS(buf,pos) IREV(IVALS(buf,pos))
#define RSSVAL(buf,pos,val) SSVAL(buf,pos,SREV(val))
#define RSSVALS(buf,pos,val) SSVALS(buf,pos,SREV(val))
#define RSIVAL(buf,pos,val) SIVAL(buf,pos,IREV(val))
#define RSIVALS(buf,pos,val) SIVALS(buf,pos,IREV(val))
/* reads multiple data from an SMB buffer (big-endian) */
#define RPSVAL(buf,pos,val,len) SMBMACRO(RSVAL,buf,pos,val,len,2)
#define RPIVAL(buf,pos,val,len) SMBMACRO(RIVAL,buf,pos,val,len,4)
#define RPSVALS(buf,pos,val,len) SMBMACRO(RSVALS,buf,pos,val,len,2)
#define RPIVALS(buf,pos,val,len) SMBMACRO(RIVALS,buf,pos,val,len,4)
/* stores multiple data in an SMB buffer (big-endian) */
#define RPSSVAL(buf,pos,val,len) SSMBMACRO(RSSVAL,buf,pos,val,len,2)
#define RPSIVAL(buf,pos,val,len) SSMBMACRO(RSIVAL,buf,pos,val,len,4)
#define RPSSVALS(buf,pos,val,len) SSMBMACRO(RSSVALS,buf,pos,val,len,2)
#define RPSIVALS(buf,pos,val,len) SSMBMACRO(RSIVALS,buf,pos,val,len,4)
#define DBG_RW_PCVAL(charmode,string,depth,base,read,inbuf,outbuf,len) \
{ RW_PCVAL(read,inbuf,outbuf,len) \
DEBUG(5,("%s%04x %s: ", \
tab_depth(depth), base,string)); \
if (charmode) print_asc(5, (unsigned char*)(outbuf), (len)); else \
{ int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%02x ", (outbuf)[idx])); } } \
DEBUG(5,("\n")); }
#define DBG_RW_PSVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
{ RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
DEBUG(5,("%s%04x %s: ", \
tab_depth(depth), base,string)); \
if (charmode) print_asc(5, (unsigned char*)(outbuf), 2*(len)); else \
{ int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%04x ", (outbuf)[idx])); } } \
DEBUG(5,("\n")); }
#define DBG_RW_PIVAL(charmode,string,depth,base,read,big_endian,inbuf,outbuf,len) \
{ RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
DEBUG(5,("%s%04x %s: ", \
tab_depth(depth), base,string)); \
if (charmode) print_asc(5, (unsigned char*)(outbuf), 4*(len)); else \
{ int idx; for (idx = 0; idx < len; idx++) { DEBUG(5,("%08x ", (outbuf)[idx])); } } \
DEBUG(5,("\n")); }
#define DBG_RW_CVAL(string,depth,base,read,inbuf,outbuf) \
{ RW_CVAL(read,inbuf,outbuf,0) \
DEBUG(5,("%s%04x %s: %02x\n", \
tab_depth(depth), base, string, outbuf)); }
#define DBG_RW_SVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
{ RW_SVAL(read,big_endian,inbuf,outbuf,0) \
DEBUG(5,("%s%04x %s: %04x\n", \
tab_depth(depth), base, string, outbuf)); }
#define DBG_RW_IVAL(string,depth,base,read,big_endian,inbuf,outbuf) \
{ RW_IVAL(read,big_endian,inbuf,outbuf,0) \
DEBUG(5,("%s%04x %s: %08x\n", \
tab_depth(depth), base, string, outbuf)); }
#endif /* _BYTEORDER_H */
void E_P16 (unsigned char *p14, unsigned char *p16);
void E_P24 (unsigned char *p21, unsigned char *c8, unsigned char *p24);
void D_P16 (unsigned char *p14, unsigned char *in, unsigned char *out);
void SMBOWFencrypt (uchar passwd[16], uchar * c8, uchar p24[24]);
void mdfour (unsigned char *out, unsigned char *in, int n);
/*
* base64.c -- base-64 conversion routines.
*
* For license terms, see the file COPYING in this directory.
*
* This base 64 encoding is defined in RFC2045 section 6.8,
* "Base64 Content-Transfer-Encoding", but lines must not be broken in the
* scheme used here.
*/
static const char base64digits[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#define BAD -1
static const char base64val[] = {
BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD,
BAD,
BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD,
BAD,
BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, BAD, 62, BAD, BAD, BAD,
63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, BAD, BAD, BAD, BAD, BAD, BAD,
BAD, 0, 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, BAD, BAD, BAD, BAD, BAD,
BAD, 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, BAD, BAD, BAD, BAD, BAD
};
#define DECODE64(c) (isascii(c) ? base64val[c] : BAD)
void
spa_bits_to_base64 (unsigned char *out, const unsigned char *in, int inlen)
/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
{
for (; inlen >= 3; inlen -= 3)
{
*out++ = base64digits[in[0] >> 2];
*out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
*out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
*out++ = base64digits[in[2] & 0x3f];
in += 3;
}
if (inlen > 0)
{
unsigned char fragment;
*out++ = base64digits[in[0] >> 2];
fragment = (in[0] << 4) & 0x30;
if (inlen > 1)
fragment |= in[1] >> 4;
*out++ = base64digits[fragment];
*out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c];
*out++ = '=';
}
*out = '\0';
}
int
spa_base64_to_bits (char *out, const char *in)
/* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */
{
int len = 0;
register unsigned char digit1, digit2, digit3, digit4;
if (in[0] == '+' && in[1] == ' ')
in += 2;
if (*in == '\r')
return (0);
do
{
digit1 = in[0];
if (DECODE64 (digit1) == BAD)
return (-1);
digit2 = in[1];
if (DECODE64 (digit2) == BAD)
return (-1);
digit3 = in[2];
if (digit3 != '=' && DECODE64 (digit3) == BAD)
return (-1);
digit4 = in[3];
if (digit4 != '=' && DECODE64 (digit4) == BAD)
return (-1);
in += 4;
*out++ = (DECODE64 (digit1) << 2) | (DECODE64 (digit2) >> 4);
++len;
if (digit3 != '=')
{
*out++ =
((DECODE64 (digit2) << 4) & 0xf0) | (DECODE64 (digit3) >> 2);
++len;
if (digit4 != '=')
{
*out++ = ((DECODE64 (digit3) << 6) & 0xc0) | DECODE64 (digit4);
++len;
}
}
}
while (*in && *in != '\r' && digit4 != '=');
return (len);
}
#define uchar unsigned char
static uchar perm1[56] = { 57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4
};
static uchar perm2[48] = { 14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
};
static uchar perm3[64] = { 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
};
static uchar perm4[48] = { 32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1
};
static uchar perm5[32] = { 16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25
};
static uchar perm6[64] = { 40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
};
static uchar sc[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
static uchar sbox[8][4][16] = {
{{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
{4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}},
{{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
{3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
{0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}},
{{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
{13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}},
{{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
{3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}},
{{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
{14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}},
{{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}},
{{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
{13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}},
{{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
{7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}}
};
static void
permute (char *out, char *in, uchar * p, int n)
{
int i;
for (i = 0; i < n; i++)
out[i] = in[p[i] - 1];
}
static void
lshift (char *d, int count, int n)
{
char out[64];
int i;
for (i = 0; i < n; i++)
out[i] = d[(i + count) % n];
for (i = 0; i < n; i++)
d[i] = out[i];
}
static void
concat (char *out, char *in1, char *in2, int l1, int l2)
{
while (l1--)
*out++ = *in1++;
while (l2--)
*out++ = *in2++;
}
static void
xor (char *out, char *in1, char *in2, int n)
{
int i;
for (i = 0; i < n; i++)
out[i] = in1[i] ^ in2[i];
}
static void
dohash (char *out, char *in, char *key, int forw)
{
int i, j, k;
char pk1[56];
char c[28];
char d[28];
char cd[56];
char ki[16][48];
char pd1[64];
char l[32], r[32];
char rl[64];
permute (pk1, key, perm1, 56);
for (i = 0; i < 28; i++)
c[i] = pk1[i];
for (i = 0; i < 28; i++)
d[i] = pk1[i + 28];
for (i = 0; i < 16; i++)
{
lshift (c, sc[i], 28);
lshift (d, sc[i], 28);
concat (cd, c, d, 28, 28);
permute (ki[i], cd, perm2, 48);
}
permute (pd1, in, perm3, 64);
for (j = 0; j < 32; j++)
{
l[j] = pd1[j];
r[j] = pd1[j + 32];
}
for (i = 0; i < 16; i++)
{
char er[48];
char erk[48];
char b[8][6];
char cb[32];
char pcb[32];
char r2[32];
permute (er, r, perm4, 48);
xor (erk, er, ki[forw ? i : 15 - i], 48);
for (j = 0; j < 8; j++)
for (k = 0; k < 6; k++)
b[j][k] = erk[j * 6 + k];
for (j = 0; j < 8; j++)
{
int m, n;
m = (b[j][0] << 1) | b[j][5];
n = (b[j][1] << 3) | (b[j][2] << 2) | (b[j][3] << 1) | b[j][4];
for (k = 0; k < 4; k++)
b[j][k] = (sbox[j][m][n] & (1 << (3 - k))) ? 1 : 0;
}
for (j = 0; j < 8; j++)
for (k = 0; k < 4; k++)
cb[j * 4 + k] = b[j][k];
permute (pcb, cb, perm5, 32);
xor (r2, l, pcb, 32);
for (j = 0; j < 32; j++)
l[j] = r[j];
for (j = 0; j < 32; j++)
r[j] = r2[j];
}
concat (rl, r, l, 32, 32);
permute (out, rl, perm6, 64);
}
static void
str_to_key (unsigned char *str, unsigned char *key)
{
int i;
key[0] = str[0] >> 1;
key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2);
key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3);
key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4);
key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5);
key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6);
key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7);
key[7] = str[6] & 0x7F;
for (i = 0; i < 8; i++)
{
key[i] = (key[i] << 1);
}
}
static void
smbhash (unsigned char *out, unsigned char *in, unsigned char *key, int forw)
{
int i;
char outb[64];
char inb[64];
char keyb[64];
unsigned char key2[8];
str_to_key (key, key2);
for (i = 0; i < 64; i++)
{
inb[i] = (in[i / 8] & (1 << (7 - (i % 8)))) ? 1 : 0;
keyb[i] = (key2[i / 8] & (1 << (7 - (i % 8)))) ? 1 : 0;
outb[i] = 0;
}
dohash (outb, inb, keyb, forw);
for (i = 0; i < 8; i++)
{
out[i] = 0;
}
for (i = 0; i < 64; i++)
{
if (outb[i])
out[i / 8] |= (1 << (7 - (i % 8)));
}
}
void
E_P16 (unsigned char *p14, unsigned char *p16)
{
unsigned char sp8[8] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
smbhash (p16, sp8, p14, 1);
smbhash (p16 + 8, sp8, p14 + 7, 1);
}
void
E_P24 (unsigned char *p21, unsigned char *c8, unsigned char *p24)
{
smbhash (p24, c8, p21, 1);
smbhash (p24 + 8, c8, p21 + 7, 1);
smbhash (p24 + 16, c8, p21 + 14, 1);
}
void
D_P16 (unsigned char *p14, unsigned char *in, unsigned char *out)
{
smbhash (out, in, p14, 0);
smbhash (out + 8, in + 8, p14 + 7, 0);
}
/****************************************************************************
Like strncpy but always null terminates. Make sure there is room!
The variable n should always be one less than the available size.
****************************************************************************/
char *
StrnCpy (char *dest, const char *src, size_t n)
{
char *d = dest;
if (!dest)
return (NULL);
if (!src)
{
*dest = 0;
return (dest);
}
while (n-- && (*d++ = *src++));
*d = 0;
return (dest);
}
size_t
skip_multibyte_char (char c)
{
/* bogus if to get rid of unused compiler warning */
if (c)
return 0;
else
return 0;
}
/*******************************************************************
safe string copy into a known length string. maxlength does not
include the terminating zero.
********************************************************************/
char *
safe_strcpy (char *dest, const char *src, size_t maxlength)
{
size_t len;
if (!dest)
{
DEBUG (0, ("ERROR: NULL dest in safe_strcpy\n"));
return NULL;
}
if (!src)
{
*dest = 0;
return dest;
}
len = strlen (src);
if (len > maxlength)
{
DEBUG (0, ("ERROR: string overflow by %d in safe_strcpy [%.50s]\n",
(int) (len - maxlength), src));
len = maxlength;
}
memcpy (dest, src, len);
dest[len] = 0;
return dest;
}
void
strupper (char *s)
{
while (*s)
{
{
size_t skip = skip_multibyte_char (*s);
if (skip != 0)
s += skip;
else
{
if (islower ((unsigned char)(*s)))
*s = toupper (*s);
s++;
}
}
}
}
/*
This implements the X/Open SMB password encryption
It takes a password, a 8 byte "crypt key" and puts 24 bytes of
encrypted password into p24
*/
void
spa_smb_encrypt (uchar * passwd, uchar * c8, uchar * p24)
{
uchar p14[15], p21[21];
memset (p21, '\0', 21);
memset (p14, '\0', 14);
StrnCpy ((char *) p14, (char *) passwd, 14);
strupper ((char *) p14);
E_P16 (p14, p21);
SMBOWFencrypt (p21, c8, p24);
#ifdef DEBUG_PASSWORD
DEBUG (100, ("spa_smb_encrypt: lm#, challenge, response\n"));
dump_data (100, (char *) p21, 16);
dump_data (100, (char *) c8, 8);
dump_data (100, (char *) p24, 24);
#endif
}
/* Routines for Windows NT MD4 Hash functions. */
static int
_my_wcslen (int16x * str)
{
int len = 0;
while (*str++ != 0)
len++;
return len;
}
/*
* Convert a string into an NT UNICODE string.
* Note that regardless of processor type
* this must be in intel (little-endian)
* format.
*/
static int
_my_mbstowcs (int16x * dst, uchar * src, int len)
{
int i;
int16x val;
for (i = 0; i < len; i++)
{
val = *src;
SSVAL (dst, 0, val);
dst++;
src++;
if (val == 0)
break;
}
return i;
}
/*
* Creates the MD4 Hash of the users password in NT UNICODE.
*/
void
E_md4hash (uchar * passwd, uchar * p16)
{
int len;
int16x wpwd[129];
/* Password cannot be longer than 128 characters */
len = strlen ((char *) passwd);
if (len > 128)
len = 128;
/* Password must be converted to NT unicode */
_my_mbstowcs (wpwd, passwd, len);
wpwd[len] = 0; /* Ensure string is null terminated */
/* Calculate length in bytes */
len = _my_wcslen (wpwd) * sizeof (int16x);
mdfour (p16, (unsigned char *) wpwd, len);
}
/* Does both the NT and LM owfs of a user's password */
void
nt_lm_owf_gen (char *pwd, uchar nt_p16[16], uchar p16[16])
{
char passwd[130];
memset (passwd, '\0', 130);
safe_strcpy (passwd, pwd, sizeof (passwd) - 1);
/* Calculate the MD4 hash (NT compatible) of the password */
memset (nt_p16, '\0', 16);
E_md4hash ((uchar *) passwd, nt_p16);
#ifdef DEBUG_PASSWORD
DEBUG (100, ("nt_lm_owf_gen: pwd, nt#\n"));
dump_data (120, passwd, strlen (passwd));
dump_data (100, (char *) nt_p16, 16);
#endif
/* Mangle the passwords into Lanman format */
passwd[14] = '\0';
strupper (passwd);
/* Calculate the SMB (lanman) hash functions of the password */
memset (p16, '\0', 16);
E_P16 ((uchar *) passwd, (uchar *) p16);
#ifdef DEBUG_PASSWORD
DEBUG (100, ("nt_lm_owf_gen: pwd, lm#\n"));
dump_data (120, passwd, strlen (passwd));
dump_data (100, (char *) p16, 16);
#endif
/* clear out local copy of user's password (just being paranoid). */
memset (passwd, '\0', sizeof (passwd));
}
/* Does the des encryption from the NT or LM MD4 hash. */
void
SMBOWFencrypt (uchar passwd[16], uchar * c8, uchar p24[24])
{
uchar p21[21];
memset (p21, '\0', 21);
memcpy (p21, passwd, 16);
E_P24 (p21, c8, p24);
}
/* Does the des encryption from the FIRST 8 BYTES of the NT or LM MD4 hash. */
void
NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
{
uchar p21[21];
memset (p21, '\0', 21);
memcpy (p21, passwd, 8);
memset (p21 + 8, 0xbd, 8);
E_P24 (p21, ntlmchalresp, p24);
#ifdef DEBUG_PASSWORD
DEBUG (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
dump_data (100, (char *) p21, 21);
dump_data (100, (char *) ntlmchalresp, 8);
dump_data (100, (char *) p24, 24);
#endif
}
/* Does the NT MD4 hash then des encryption. */
void
spa_smb_nt_encrypt (uchar * passwd, uchar * c8, uchar * p24)
{
uchar p21[21];
memset (p21, '\0', 21);
E_md4hash (passwd, p21);
SMBOWFencrypt (p21, c8, p24);
#ifdef DEBUG_PASSWORD
DEBUG (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n"));
dump_data (100, (char *) p21, 16);
dump_data (100, (char *) c8, 8);
dump_data (100, (char *) p24, 24);
#endif
}
static uint32x A, B, C, D;
static uint32x
F (uint32x X, uint32x Y, uint32x Z)
{
return (X & Y) | ((~X) & Z);
}
static uint32x
G (uint32x X, uint32x Y, uint32x Z)
{
return (X & Y) | (X & Z) | (Y & Z);
}
static uint32x
H (uint32x X, uint32x Y, uint32x Z)
{
return X ^ Y ^ Z;
}
static uint32x
lshift_a (uint32x x, int s)
{
x &= 0xFFFFFFFF;
return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s));
}
#define ROUND1(a,b,c,d,k,s) a = lshift_a(a + F(b,c,d) + X[k], s)
#define ROUND2(a,b,c,d,k,s) a = lshift_a(a + G(b,c,d) + X[k] + (uint32x)0x5A827999,s)
#define ROUND3(a,b,c,d,k,s) a = lshift_a(a + H(b,c,d) + X[k] + (uint32x)0x6ED9EBA1,s)
/* this applies md4 to 64 byte chunks */
static void
spa_mdfour64 (uint32x * M)
{
int j;
uint32x AA, BB, CC, DD;
uint32x X[16];
for (j = 0; j < 16; j++)
X[j] = M[j];
AA = A;
BB = B;
CC = C;
DD = D;
ROUND1 (A, B, C, D, 0, 3);
ROUND1 (D, A, B, C, 1, 7);
ROUND1 (C, D, A, B, 2, 11);
ROUND1 (B, C, D, A, 3, 19);
ROUND1 (A, B, C, D, 4, 3);
ROUND1 (D, A, B, C, 5, 7);
ROUND1 (C, D, A, B, 6, 11);
ROUND1 (B, C, D, A, 7, 19);
ROUND1 (A, B, C, D, 8, 3);
ROUND1 (D, A, B, C, 9, 7);
ROUND1 (C, D, A, B, 10, 11);
ROUND1 (B, C, D, A, 11, 19);
ROUND1 (A, B, C, D, 12, 3);
ROUND1 (D, A, B, C, 13, 7);
ROUND1 (C, D, A, B, 14, 11);
ROUND1 (B, C, D, A, 15, 19);
ROUND2 (A, B, C, D, 0, 3);
ROUND2 (D, A, B, C, 4, 5);
ROUND2 (C, D, A, B, 8, 9);
ROUND2 (B, C, D, A, 12, 13);
ROUND2 (A, B, C, D, 1, 3);
ROUND2 (D, A, B, C, 5, 5);
ROUND2 (C, D, A, B, 9, 9);
ROUND2 (B, C, D, A, 13, 13);
ROUND2 (A, B, C, D, 2, 3);
ROUND2 (D, A, B, C, 6, 5);
ROUND2 (C, D, A, B, 10, 9);
ROUND2 (B, C, D, A, 14, 13);
ROUND2 (A, B, C, D, 3, 3);
ROUND2 (D, A, B, C, 7, 5);
ROUND2 (C, D, A, B, 11, 9);
ROUND2 (B, C, D, A, 15, 13);
ROUND3 (A, B, C, D, 0, 3);
ROUND3 (D, A, B, C, 8, 9);
ROUND3 (C, D, A, B, 4, 11);
ROUND3 (B, C, D, A, 12, 15);
ROUND3 (A, B, C, D, 2, 3);
ROUND3 (D, A, B, C, 10, 9);
ROUND3 (C, D, A, B, 6, 11);
ROUND3 (B, C, D, A, 14, 15);
ROUND3 (A, B, C, D, 1, 3);
ROUND3 (D, A, B, C, 9, 9);
ROUND3 (C, D, A, B, 5, 11);
ROUND3 (B, C, D, A, 13, 15);
ROUND3 (A, B, C, D, 3, 3);
ROUND3 (D, A, B, C, 11, 9);
ROUND3 (C, D, A, B, 7, 11);
ROUND3 (B, C, D, A, 15, 15);
A += AA;
B += BB;
C += CC;
D += DD;
A &= 0xFFFFFFFF;
B &= 0xFFFFFFFF;
C &= 0xFFFFFFFF;
D &= 0xFFFFFFFF;
for (j = 0; j < 16; j++)
X[j] = 0;
}
static void
copy64 (uint32x * M, unsigned char *in)
{
int i;
for (i = 0; i < 16; i++)
M[i] = (in[i * 4 + 3] << 24) | (in[i * 4 + 2] << 16) |
(in[i * 4 + 1] << 8) | (in[i * 4 + 0] << 0);
}
static void
copy4 (unsigned char *out, uint32x x)
{
out[0] = x & 0xFF;
out[1] = (x >> 8) & 0xFF;
out[2] = (x >> 16) & 0xFF;
out[3] = (x >> 24) & 0xFF;
}
/* produce a md4 message digest from data of length n bytes */
void
mdfour (unsigned char *out, unsigned char *in, int n)
{
unsigned char buf[128];
uint32x M[16];
uint32x b = n * 8;
int i;
A = 0x67452301;
B = 0xefcdab89;
C = 0x98badcfe;
D = 0x10325476;
while (n > 64)
{
copy64 (M, in);
spa_mdfour64 (M);
in += 64;
n -= 64;
}
for (i = 0; i < 128; i++)
buf[i] = 0;
memcpy (buf, in, n);
buf[n] = 0x80;
if (n <= 55)
{
copy4 (buf + 56, b);
copy64 (M, buf);
spa_mdfour64 (M);
}
else
{
copy4 (buf + 120, b);
copy64 (M, buf);
spa_mdfour64 (M);
copy64 (M, buf + 64);
spa_mdfour64 (M);
}
for (i = 0; i < 128; i++)
buf[i] = 0;
copy64 (M, buf);
copy4 (out, A);
copy4 (out + 4, B);
copy4 (out + 8, C);
copy4 (out + 12, D);
A = B = C = D = 0;
}
char versionString[] = "libntlm version 0.21";
/* Utility routines that handle NTLM auth structures. */
/* The [IS]VAL macros are to take care of byte order for non-Intel
* Machines -- I think this file is OK, but it hasn't been tested.
* The other files (the ones stolen from Samba) should be OK.
*/
/* I am not crazy about these macros -- they seem to have gotten
* a bit complex. A new scheme for handling string/buffer fields
* in the structures probably needs to be designed
*/
#define spa_bytes_add(ptr, header, buf, count) \
{ \
if (buf && count) \
{ \
SSVAL(&ptr->header.len,0,count); \
SSVAL(&ptr->header.maxlen,0,count); \
SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \
memcpy(ptr->buffer+ptr->bufIndex, buf, count); \
ptr->bufIndex += count; \
} \
else \
{ \
ptr->header.len = \
ptr->header.maxlen = 0; \
SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \
} \
}
#define spa_string_add(ptr, header, string) \
{ \
char *p = string; \
int len = 0; \
if (p) len = strlen(p); \
spa_bytes_add(ptr, header, ((unsigned char*)p), len); \
}
#define spa_unicode_add_string(ptr, header, string) \
{ \
char *p = string; \
unsigned char *b = NULL; \
int len = 0; \
if (p) \
{ \
len = strlen(p); \
b = strToUnicode(p); \
} \
spa_bytes_add(ptr, header, b, len*2); \
}
#define GetUnicodeString(structPtr, header) \
unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
#define GetString(structPtr, header) \
toString((((char *)structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
#define DumpBuffer(fp, structPtr, header) \
dumpRaw(fp,((unsigned char*)structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
static void
dumpRaw (FILE * fp, unsigned char *buf, size_t len)
{
int i;
for (i = 0; i < len; ++i)
fprintf (fp, "%02x ", buf[i]);
fprintf (fp, "\n");
}
char *
unicodeToString (char *p, size_t len)
{
int i;
static char buf[1024];
assert (len + 1 < sizeof buf);
for (i = 0; i < len; ++i)
{
buf[i] = *p & 0x7f;
p += 2;
}
buf[i] = '\0';
return buf;
}
static unsigned char *
strToUnicode (char *p)
{
static unsigned char buf[1024];
size_t l = strlen (p);
int i = 0;
assert (l * 2 < sizeof buf);
while (l--)
{
buf[i++] = *p++;
buf[i++] = 0;
}
return buf;
}
static unsigned char *
toString (char *p, size_t len)
{
static unsigned char buf[1024];
assert (len + 1 < sizeof buf);
memcpy (buf, p, len);
buf[len] = 0;
return buf;
}
void
dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request)
{
fprintf (fp, "NTLM Request:\n");
fprintf (fp, " Ident = %s\n", request->ident);
fprintf (fp, " mType = %d\n", IVAL (&request->msgType, 0));
fprintf (fp, " Flags = %08x\n", IVAL (&request->flags, 0));
fprintf (fp, " User = %s\n", GetString (request, user));
fprintf (fp, " Domain = %s\n", GetString (request, domain));
}
void
dumpSmbNtlmAuthChallenge (FILE * fp, SPAAuthChallenge * challenge)
{
fprintf (fp, "NTLM Challenge:\n");
fprintf (fp, " Ident = %s\n", challenge->ident);
fprintf (fp, " mType = %d\n", IVAL (&challenge->msgType, 0));
fprintf (fp, " Domain = %s\n", GetUnicodeString (challenge, uDomain));
fprintf (fp, " Flags = %08x\n", IVAL (&challenge->flags, 0));
fprintf (fp, " Challenge = ");
dumpRaw (fp, challenge->challengeData, 8);
}
void
dumpSmbNtlmAuthResponse (FILE * fp, SPAAuthResponse * response)
{
fprintf (fp, "NTLM Response:\n");
fprintf (fp, " Ident = %s\n", response->ident);
fprintf (fp, " mType = %d\n", IVAL (&response->msgType, 0));
fprintf (fp, " LmResp = ");
DumpBuffer (fp, response, lmResponse);
fprintf (fp, " NTResp = ");
DumpBuffer (fp, response, ntResponse);
fprintf (fp, " Domain = %s\n", GetUnicodeString (response, uDomain));
fprintf (fp, " User = %s\n", GetUnicodeString (response, uUser));
fprintf (fp, " Wks = %s\n", GetUnicodeString (response, uWks));
fprintf (fp, " sKey = ");
DumpBuffer (fp, response, sessionKey);
fprintf (fp, " Flags = %08x\n", IVAL (&response->flags, 0));
}
void
spa_build_auth_request (SPAAuthRequest * request, char *user, char *domain)
{
char *u = strdup (user);
char *p = strchr (u, '@');
if (p)
{
if (!domain)
domain = p + 1;
*p = '\0';
}
request->bufIndex = 0;
memcpy (request->ident, "NTLMSSP\0\0\0", 8);
SIVAL (&request->msgType, 0, 1);
SIVAL (&request->flags, 0, 0x0000b207); /* have to figure out what these mean */
spa_string_add (request, user, u);
spa_string_add (request, domain, domain);
free (u);
}
void
spa_build_auth_challenge (SPAAuthRequest * request, SPAAuthChallenge * challenge)
{
char chalstr[8];
int i;
int p = (int)getpid();
int random_seed = (int)time(NULL) ^ ((p << 16) | p);
request = request; /* Added by PH to stop compilers whinging */
/* Ensure challenge data is cleared, in case it isn't all used. This
patch added by PH on suggestion of Russell King */
memset(challenge, 0, sizeof(SPAAuthChallenge));
challenge->bufIndex = 0;
memcpy (challenge->ident, "NTLMSSP\0", 8);
SIVAL (&challenge->msgType, 0, 2);
SIVAL (&challenge->flags, 0, 0x00008201);
SIVAL (&challenge->uDomain.len, 0, 0x0000);
SIVAL (&challenge->uDomain.maxlen, 0, 0x0000);
SIVAL (&challenge->uDomain.offset, 0, 0x00002800);
/* generate eight pseudo random bytes (method ripped from host.c) */
for(i=0;i<8;i++) {
chalstr[i] = (unsigned char)(random_seed >> 16) % 256;
random_seed = (1103515245 - (chalstr[i])) * random_seed + 12345;
};
memcpy(challenge->challengeData,chalstr,8);
}
/* This is the original source of this function, preserved here for reference.
The new version below was re-organized by PH following a patch and some further
suggestions from Mark Lyda to fix the problem that is described at the head of
this module. At the same time, I removed the untidiness in the code below that
involves the "d" and "domain" variables. */
#ifdef NEVER
void
spa_build_auth_response (SPAAuthChallenge * challenge,
SPAAuthResponse * response, char *user,
char *password)
{
uint8x lmRespData[24];
uint8x ntRespData[24];
char *d = strdup (GetUnicodeString (challenge, uDomain));
char *domain = d;
char *u = strdup (user);
char *p = strchr (u, '@');
if (p)
{
domain = p + 1;
*p = '\0';
}
spa_smb_encrypt ((uchar *)password, challenge->challengeData, lmRespData);
spa_smb_nt_encrypt ((uchar *)password, challenge->challengeData, ntRespData);
response->bufIndex = 0;
memcpy (response->ident, "NTLMSSP\0\0\0", 8);
SIVAL (&response->msgType, 0, 3);
spa_bytes_add (response, lmResponse, lmRespData, 24);
spa_bytes_add (response, ntResponse, ntRespData, 24);
spa_unicode_add_string (response, uDomain, domain);
spa_unicode_add_string (response, uUser, u);
spa_unicode_add_string (response, uWks, u);
spa_string_add (response, sessionKey, NULL);
response->flags = challenge->flags;
free (d);
free (u);
}
#endif
/* This is the re-organized version (see comments above) */
void
spa_build_auth_response (SPAAuthChallenge * challenge,
SPAAuthResponse * response, char *user,
char *password)
{
uint8x lmRespData[24];
uint8x ntRespData[24];
uint32x cf = IVAL(&challenge->flags, 0);
char *u = strdup (user);
char *p = strchr (u, '@');
char *d = NULL;
char *domain;
if (p)
{
domain = p + 1;
*p = '\0';
}
else domain = d = strdup((cf & 0x1)?
(const char *)GetUnicodeString(challenge, uDomain) :
(const char *)GetString(challenge, uDomain));
spa_smb_encrypt ((uchar *)password, challenge->challengeData, lmRespData);
spa_smb_nt_encrypt ((uchar *)password, challenge->challengeData, ntRespData);
response->bufIndex = 0;
memcpy (response->ident, "NTLMSSP\0\0\0", 8);
SIVAL (&response->msgType, 0, 3);
spa_bytes_add (response, lmResponse, lmRespData, (cf & 0x200) ? 24 : 0);
spa_bytes_add (response, ntResponse, ntRespData, (cf & 0x8000) ? 24 : 0);
if (cf & 0x1) { /* Unicode Text */
spa_unicode_add_string (response, uDomain, domain);
spa_unicode_add_string (response, uUser, u);
spa_unicode_add_string (response, uWks, u);
} else { /* OEM Text */
spa_string_add (response, uDomain, domain);
spa_string_add (response, uUser, u);
spa_string_add (response, uWks, u);
}
spa_string_add (response, sessionKey, NULL);
response->flags = challenge->flags;
if (d != NULL) free (d);
free (u);
}
Index: auth-spa.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/auth-spa.h,v 1.1 2004/10/07 13:10:00 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/*
* This file provides the necessary methods for authenticating with
* Microsoft's Secure Password Authentication.
* All the code used here was torn by Marc Prud'hommeaux out of the
* Samba project (by Andrew Tridgell, Jeremy Allison, and others).
*/
/* It seems that some systems have existing but different definitions of some
of the following types. I received a complaint about "int16" causing
compilation problems. So I (PH) have renamed them all, to be on the safe side.
typedef signed short int16;
typedef unsigned short uint16;
typedef unsigned uint32;
typedef unsigned char uint8;
*/
typedef signed short int16x;
typedef unsigned short uint16x;
typedef unsigned uint32x;
typedef unsigned char uint8x;
typedef struct
{
uint16x len;
uint16x maxlen;
uint32x offset;
} SPAStrHeader;
typedef struct
{
char ident[8];
uint32x msgType;
SPAStrHeader uDomain;
uint32x flags;
uint8x challengeData[8];
uint8x reserved[8];
SPAStrHeader emptyString;
uint8x buffer[1024];
uint32x bufIndex;
} SPAAuthChallenge;
typedef struct
{
char ident[8];
uint32x msgType;
uint32x flags;
SPAStrHeader user;
SPAStrHeader domain;
uint8x buffer[1024];
uint32x bufIndex;
} SPAAuthRequest;
typedef struct
{
char ident[8];
uint32x msgType;
SPAStrHeader lmResponse;
SPAStrHeader ntResponse;
SPAStrHeader uDomain;
SPAStrHeader uUser;
SPAStrHeader uWks;
SPAStrHeader sessionKey;
uint32x flags;
uint8x buffer[1024];
uint32x bufIndex;
} SPAAuthResponse;
#define spa_request_length(ptr) (((ptr)->buffer - (uint8x*)(ptr)) + (ptr)->bufIndex)
void spa_bits_to_base64 (unsigned char *, const unsigned char *, int);
int spa_base64_to_bits(char *, const char *);
void spa_build_auth_response (SPAAuthChallenge *challenge,
SPAAuthResponse *response, char *user, char *password);
void spa_build_auth_request (SPAAuthRequest *request, char *user,
char *domain);
extern void spa_smb_encrypt (unsigned char * passwd, unsigned char * c8,
unsigned char * p24);
extern void spa_smb_nt_encrypt (unsigned char * passwd, unsigned char * c8,
unsigned char * p24);
extern char *unicodeToString(char *p, size_t len);
extern void spa_build_auth_challenge(SPAAuthRequest *, SPAAuthChallenge *);
Index: b64decode.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/b64decode.c,v 1.1 2004/10/07 13:10:00 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Decode byte-string in base 64 *
*************************************************/
/* This function decodes a string in base 64 format as defined in RFC 2045
(MIME) and required by the SMTP AUTH extension (RFC 2554). The decoding
algorithm is written out in a straightforward way. Turning it into some kind of
compact loop is messy and would probably run more slowly.
Arguments:
code points to the coded string, zero-terminated
ptr where to put the pointer to the result, which is in
dynamic store
Returns: the number of bytes in the result,
or -1 if the input was malformed
A zero is added on to the end to make it easy in cases where the result is to
be interpreted as text. This is not included in the count. */
static uschar dec64table[] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /* 0-15 */
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, /* 16-31 */
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, /* 32-47 */
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255, /* 48-63 */
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 64-79 */
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, /* 80-95 */
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 */
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255 /* 112-127*/
};
int
auth_b64decode(uschar *code, uschar **ptr)
{
register int x, y;
uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
*ptr = result;
/* Each cycle of the loop handles a quantum of 4 input bytes. For the last
quantum this may decode to 1, 2, or 3 output bytes. */
while ((x = (*code++)) != 0)
{
if (x > 127 || (x = dec64table[x]) == 255) return -1;
if ((y = (*code++)) == 0 || (y = dec64table[y]) == 255)
return -1;
*result++ = (x << 2) | (y >> 4);
if ((x = (*code++)) == '=')
{
if (*code++ != '=' || *code != 0) return -1;
}
else
{
if (x > 127 || (x = dec64table[x]) == 255) return -1;
*result++ = (y << 4) | (x >> 2);
if ((y = (*code++)) == '=')
{
if (*code != 0) return -1;
}
else
{
if (y > 127 || (y = dec64table[y]) == 255) return -1;
*result++ = (x << 6) | y;
}
}
}
*result = 0;
return result - *ptr;
}
/* End of b64decode.c */
Index: b64encode.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/b64encode.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Encode byte-string in base 64 *
*************************************************/
/* This function encodes a string of bytes, containing any values whatsoever,
in base 64 as defined in RFC 2045 (MIME) and required by the SMTP AUTH
extension (RFC 2554). The encoding algorithm is written out in a
straightforward way. Turning it into some kind of compact loop is messy and
would probably run more slowly.
Arguments:
clear points to the clear text bytes
len the number of bytes to encode
Returns: a pointer to the zero-terminated base 64 string, which
is in working store
*/
static uschar *enc64table =
US"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
uschar *
auth_b64encode(uschar *clear, int len)
{
uschar *code = store_get(4*((len+2)/3) + 1);
uschar *p = code;
while (len-- >0)
{
register int x, y;
x = *clear++;
*p++ = enc64table[(x >> 2) & 63];
if (len-- <= 0)
{
*p++ = enc64table[(x << 4) & 63];
*p++ = '=';
*p++ = '=';
break;
}
y = *clear++;
*p++ = enc64table[((x << 4) | ((y >> 4) & 15)) & 63];
if (len-- <= 0)
{
*p++ = enc64table[(y << 2) & 63];
*p++ = '=';
break;
}
x = *clear++;
*p++ = enc64table[((y << 2) | ((x >> 6) & 3)) & 63];
*p++ = enc64table[x & 63];
}
*p = 0;
return code;
}
/* End of b64encode.c */
Index: call_pam.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/call_pam.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/* This module contains functions that call the PAM authentication mechanism
defined by Sun for Solaris and also available for Linux and other OS.
We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the PAM headers
available for compiling. Therefore, compile these functions only if SUPPORT_PAM
is defined. However, some compilers don't like compiling empty modules, so keep
them happy with a dummy when skipping the rest. Make it reference itself to
stop picky compilers complaining that it is unused, and put in a dummy argument
to stop even pickier compilers complaining about infinite loops. */
#ifndef SUPPORT_PAM
static void dummy(int x) { dummy(x-1); }
#else /* SUPPORT_PAM */
#ifdef PAM_H_IN_PAM
#include <pam/pam_appl.h>
#else
#include <security/pam_appl.h>
#endif
/* According to the specification, it should be possible to have an application
data pointer passed to the conversation function. However, I was unable to get
this to work on Solaris 2.6, so static variables are used instead. */
static int pam_conv_had_error;
static uschar *pam_args;
static BOOL pam_arg_ended;
/*************************************************
* PAM conversation function *
*************************************************/
/* This function is passed to the PAM authentication function, and it calls it
back when it wants data from the client. The string list is in pam_args. When
we reach the end, we pass back an empty string once. If this function is called
again, it will give an error response. This is protection against something
crazy happening.
Arguments:
num_msg number of messages associated with the call
msg points to an array of length num_msg of pam_message structures
resp set to point to the response block, which has to be got by
this function
appdata_ptr the application data pointer - not used because in Solaris
2.6 it always arrived in pam_converse() as NULL
Returns: a PAM return code
*/
static int
pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
struct pam_response **resp, void *appdata_ptr)
{
int i;
int sep = 0;
struct pam_response *reply;
if (pam_arg_ended) return PAM_CONV_ERR;
reply = malloc(sizeof(struct pam_response) * num_msg);
if (reply == NULL) return PAM_CONV_ERR;
for (i = 0; i < num_msg; i++)
{
uschar *arg;
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF:
arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
if (arg == NULL)
{
arg = US"";
pam_arg_ended = TRUE;
}
reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */
reply[i].resp_retcode = PAM_SUCCESS;
break;
case PAM_TEXT_INFO: /* Just acknowledge messages */
case PAM_ERROR_MSG:
reply[i].resp_retcode = PAM_SUCCESS;
reply[i].resp = NULL;
break;
default: /* Must be an error of some sort... */
free (reply);
pam_conv_had_error = TRUE;
return PAM_CONV_ERR;
}
}
*resp = reply;
return PAM_SUCCESS;
}
/*************************************************
* Perform PAM authentication *
*************************************************/
/* This function calls the PAM authentication mechanism, passing over one or
more data strings.
Arguments:
s a colon-separated list of strings
errptr where to point an error message
Returns: OK if authentication succeeded
FAIL if authentication failed
ERROR some other error condition
*/
int
auth_call_pam(uschar *s, uschar **errptr)
{
pam_handle_t *pamh = NULL;
struct pam_conv pamc;
int pam_error;
int sep = 0;
uschar *user;
/* Set up the input data structure: the address of the conversation function,
and a pointer to application data, which we don't use because I couldn't get it
to work under Solaris 2.6 - it always arrived in pam_converse() as NULL. */
pamc.conv = pam_converse;
pamc.appdata_ptr = NULL;
/* Initialize the static data - the current input data, the error flag, and the
flag for data end. */
pam_args = s;
pam_conv_had_error = FALSE;
pam_arg_ended = FALSE;
/* The first string in the list is the user. If this is an empty string, we
fail. PAM doesn't support authentication with an empty user (it prompts for it,
causing a potential mis-interpretation). */
user = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
if (user == NULL || user[0] == 0) return FAIL;
/* Start off PAM interaction */
DEBUG(D_auth)
debug_printf("Running PAM authentication for user \"%s\"\n", user);
pam_error = pam_start ("exim", CS user, &pamc, &pamh);
/* Do the authentication - the pam_authenticate() will call pam_converse() to
get the data it wants. After successful authentication we call pam_acct_mgmt()
to apply any other restrictions (e.g. only some times of day). */
if (pam_error == PAM_SUCCESS)
{
pam_error = pam_authenticate (pamh, PAM_SILENT);
if (pam_error == PAM_SUCCESS && !pam_conv_had_error)
pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
}
/* Finish the PAM interaction - this causes it to clean up store etc. Unclear
what should be passed as the second argument. */
pam_end(pamh, PAM_SUCCESS);
/* Sort out the return code. If not success, set the error message. */
if (pam_error == PAM_SUCCESS)
{
DEBUG(D_auth) debug_printf("PAM success\n");
return OK;
}
*errptr = (uschar *)pam_strerror(pamh, pam_error);
DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
if (pam_error == PAM_USER_UNKNOWN ||
pam_error == PAM_AUTH_ERR ||
pam_error == PAM_ACCT_EXPIRED)
return FAIL;
return ERROR;
}
#endif /* SUPPORT_PAM */
/* End of call_pam.c */
Index: call_pwcheck.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/call_pwcheck.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module contains interface functions to the two Cyrus authentication
daemons. The original one was "pwcheck", which gives its name to the source
file. This is now deprecated in favour of "saslauthd". */
#include "../exim.h"
#include "pwcheck.h"
/*************************************************
* External entry point for pwcheck *
*************************************************/
/* This function calls the now-deprecated "pwcheck" Cyrus-SASL authentication
daemon, passing over a colon-separated user name and password. As this is
called from the string expander, the string will always be in dynamic store and
can be overwritten.
Arguments:
s a colon-separated username:password string
errptr where to point an error message
Returns: OK if authentication succeeded
FAIL if authentication failed
ERROR some other error condition
*/
int
auth_call_pwcheck(uschar *s, uschar **errptr)
{
uschar *reply = NULL;
uschar *pw = Ustrrchr(s, ':');
if (pw == NULL)
{
*errptr = US"pwcheck: malformed input - missing colon";
return ERROR;
}
*pw++ = 0; /* Separate user and password */
DEBUG(D_auth)
debug_printf("Running pwcheck authentication for user \"%s\"\n", s);
switch (pwcheck_verify_password(CS s, CS pw, (const char **)(&reply)))
{
case PWCHECK_OK:
DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply);
return OK;
case PWCHECK_NO:
DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply);
return FAIL;
default:
DEBUG(D_auth) debug_printf("pwcheck: query failed (%s)\n", reply);
*errptr = reply;
return ERROR;
}
}
/*************************************************
* External entry point for pwauthd *
*************************************************/
/* This function calls the "saslauthd" Cyrus-SASL authentication daemon,
saslauthd, As this is called from the string expander, all the strings will
always be in dynamic store and can be overwritten.
Arguments:
username username
password password
service optional service
realm optional realm
errptr where to point an error message
Returns: OK if authentication succeeded
FAIL if authentication failed
ERROR some other error condition
*/
int
auth_call_saslauthd(uschar *username, uschar *password, uschar *service,
uschar *realm, uschar **errptr)
{
uschar *reply = NULL;
if (service == NULL) service = US"";
if (realm == NULL) realm = US"";
DEBUG(D_auth)
debug_printf("Running saslauthd authentication for user \"%s\" \n", username);
switch (saslauthd_verify_password(username, password, service,
realm, (const uschar **)(&reply)))
{
case PWCHECK_OK:
DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply);
return OK;
case PWCHECK_NO:
DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply);
return FAIL;
default:
DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply);
*errptr = reply;
return ERROR;
}
}
/* End of call_pwcheck.c */
Index: call_radius.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/call_radius.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file was originally supplied by Ian Kirk. The libradius support came
from Alex Kiernan. */
#include "../exim.h"
/* This module contains functions that call the Radius authentication
mechanism.
We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the Radius headers
available for compiling. Therefore, compile these functions only if
RADIUS_CONFIG_FILE is defined. However, some compilers don't like compiling
empty modules, so keep them happy with a dummy when skipping the rest. Make it
reference itself to stop picky compilers complaining that it is unused, and put
in a dummy argument to stop even pickier compilers complaining about infinite
loops. */
#ifndef RADIUS_CONFIG_FILE
static void dummy(int x) { dummy(x-1); }
#else /* RADIUS_CONFIG_FILE */
/* Two different Radius libraries are supported. The default is radiusclient. */
#ifdef RADIUS_LIB_RADLIB
#include <radlib.h>
#else
#ifndef RADIUS_LIB_RADIUSCLIENT
#define RADIUS_LIB_RADIUSCLIENT
#endif
#include <radiusclient.h>
#endif
/*************************************************
* Perform RADIUS authentication *
*************************************************/
/* This function calls the Radius authentication mechanism, passing over one or
more data strings.
Arguments:
s a colon-separated list of strings
errptr where to point an error message
Returns: OK if authentication succeeded
FAIL if authentication failed
ERROR some other error condition
*/
int
auth_call_radius(uschar *s, uschar **errptr)
{
uschar *user;
uschar *radius_args = s;
int result;
int sep = 0;
#ifdef RADIUS_LIB_RADLIB
struct rad_handle *h;
#else
VALUE_PAIR *send = NULL;
VALUE_PAIR *received;
unsigned int service = PW_AUTHENTICATE_ONLY;
char msg[4096];
#endif
user = string_nextinlist(&radius_args, &sep, big_buffer, big_buffer_size);
if (user == NULL) user = US"";
DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" "
"and \"%s\"\n", user, radius_args);
*errptr = NULL;
/* Authenticate using the radiusclient library */
#ifdef RADIUS_LIB_RADIUSCLIENT
rc_openlog("exim");
if (rc_read_config(RADIUS_CONFIG_FILE) != 0)
*errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE);
else if (rc_read_dictionary(rc_conf_str("dictionary")) != 0)
*errptr = string_sprintf("RADIUS: can't read dictionary");
else if (rc_avpair_add(&send, PW_USER_NAME, user, 0) == NULL)
*errptr = string_sprintf("RADIUS: add user name failed\n");
else if (rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0) == NULL)
*errptr = string_sprintf("RADIUS: add password failed\n");
else if (rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0) == NULL)
*errptr = string_sprintf("RADIUS: add service type failed\n");
if (*errptr != NULL)
{
DEBUG(D_auth) debug_printf("%s\n", *errptr);
return ERROR;
}
result = rc_auth(0, send, &received, msg);
DEBUG(D_auth) debug_printf("RADIUS code returned %d\n", result);
switch (result)
{
case OK_RC:
return OK;
case ERROR_RC:
return FAIL;
case TIMEOUT_RC:
*errptr = US"RADIUS: timed out";
return ERROR;
default:
case BADRESP_RC:
*errptr = string_sprintf("RADIUS: unexpected response (%d)", result);
return ERROR;
}
#else /* RADIUS_LIB_RADIUSCLIENT not set => RADIUS_LIB_RADLIB is set */
/* Authenticate using the libradius library */
h = rad_auth_open();
if (h == NULL)
{
*errptr = string_sprintf("RADIUS: can't initialise libradius");
return ERROR;
}
if (rad_config(h, RADIUS_CONFIG_FILE) != 0 ||
rad_create_request(h, RAD_ACCESS_REQUEST) != 0 ||
rad_put_string(h, RAD_USER_NAME, CS user) != 0 ||
rad_put_string(h, RAD_USER_PASSWORD, CS radius_args) != 0 ||
rad_put_int(h, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) != 0)
{
*errptr = string_sprintf("RADIUS: %s", rad_strerror(h));
result = ERROR;
}
else
{
result = rad_send_request(h);
switch(result)
{
case RAD_ACCESS_ACCEPT:
result = OK;
break;
case RAD_ACCESS_REJECT:
result = FAIL;
break;
case -1:
*errptr = string_sprintf("RADIUS: %s", rad_strerror(h));
result = ERROR;
break;
default:
*errptr = string_sprintf("RADIUS: unexpected response (%d)", result);
result= ERROR;
break;
}
}
if (*errptr != NULL) DEBUG(D_auth) debug_printf("%s\n", *errptr);
rad_close(h);
return result;
#endif /* RADIUS_LIB_RADLIB */
}
#endif /* RADIUS_CONFIG_FILE */
/* End of call_radius.c */
Index: cram_md5.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/cram_md5.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* The stand-alone version just tests the algorithm. We have to drag
in the MD5 computation functions, without their own stand-alone main
program. */
#ifdef STAND_ALONE
#define CRAM_STAND_ALONE
#include "md5.c"
/* This is the normal, non-stand-alone case */
#else
#include "../exim.h"
#include "cram_md5.h"
/* Options specific to the cram_md5 authentication mechanism. */
optionlist auth_cram_md5_options[] = {
{ "client_name", opt_stringptr,
(void *)(offsetof(auth_cram_md5_options_block, client_name)) },
{ "client_secret", opt_stringptr,
(void *)(offsetof(auth_cram_md5_options_block, client_secret)) },
{ "server_secret", opt_stringptr,
(void *)(offsetof(auth_cram_md5_options_block, server_secret)) }
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
int auth_cram_md5_options_count =
sizeof(auth_cram_md5_options)/sizeof(optionlist);
/* Default private options block for the contidion authentication method. */
auth_cram_md5_options_block auth_cram_md5_option_defaults = {
NULL, /* server_secret */
NULL, /* client_secret */
NULL /* client_name */
};
/*************************************************
* Initialization entry point *
*************************************************/
/* Called for each instance, after its options have been read, to
enable consistency checks to be done, or anything else that needs
to be set up. */
void
auth_cram_md5_init(auth_instance *ablock)
{
auth_cram_md5_options_block *ob =
(auth_cram_md5_options_block *)(ablock->options_block);
if (ob->server_secret != NULL) ablock->server = TRUE;
if (ob->client_secret != NULL)
{
ablock->client = TRUE;
if (ob->client_name == NULL) ob->client_name = primary_hostname;
}
}
#endif /* STAND_ALONE */
/*************************************************
* Peform the CRAM-MD5 algorithm *
*************************************************/
/* The CRAM-MD5 algorithm is described in RFC 2195. It computes
MD5((secret XOR opad), MD5((secret XOR ipad), challenge))
where secret is padded out to 64 characters (after being reduced to an MD5
digest if longer than 64) and ipad and opad are 64-byte strings of 0x36 and
0x5c respectively, and comma means concatenation.
Arguments:
secret the shared secret
challenge the challenge text
digest 16-byte slot to put the answer in
Returns: nothing
*/
static void
compute_cram_md5(uschar *secret, uschar *challenge, uschar *digestptr)
{
md5 base;
int i;
int len = Ustrlen(secret);
uschar isecret[64];
uschar osecret[64];
uschar md5secret[16];
/* If the secret is longer than 64 characters, we compute its MD5 digest
and use that. */
if (len > 64)
{
md5_start(&base);
md5_end(&base, (uschar *)secret, len, md5secret);
secret = (uschar *)md5secret;
len = 16;
}
/* The key length is now known to be <= 64. Set up the padded and xor'ed
versions. */
memcpy(isecret, secret, len);
memset(isecret+len, 0, 64-len);
memcpy(osecret, isecret, 64);
for (i = 0; i < 64; i++)
{
isecret[i] ^= 0x36;
osecret[i] ^= 0x5c;
}
/* Compute the inner MD5 digest */
md5_start(&base);
md5_mid(&base, isecret);
md5_end(&base, (uschar *)challenge, Ustrlen(challenge), md5secret);
/* Compute the outer MD5 digest */
md5_start(&base);
md5_mid(&base, osecret);
md5_end(&base, md5secret, 16, digestptr);
}
#ifndef STAND_ALONE
/*************************************************
* Server entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_cram_md5_server(auth_instance *ablock, uschar *data)
{
auth_cram_md5_options_block *ob =
(auth_cram_md5_options_block *)(ablock->options_block);
uschar *challenge = string_sprintf("<%d.%d@%s>", getpid(), time(NULL),
primary_hostname);
uschar *clear, *secret;
uschar digest[16];
int i, rc, len;
/* If we are running in the test harness, always send the same challenge,
an example string taken from the RFC. */
if (running_in_test_harness)
challenge = US"<1896.697170952@???>";
/* No data should have been sent with the AUTH command */
if (*data != 0) return UNEXPECTED;
/* Send the challenge, read the return */
if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
/* The return consists of a user name, space-separated from the CRAM-MD5
digest, expressed in hex. Extract the user name and put it in $1. Then check
that the remaining length is 32. */
expand_nstring[1] = clear;
while (*clear != 0 && !isspace(*clear)) clear++;
if (!isspace(*clear)) return FAIL;
*clear++ = 0;
expand_nlength[1] = clear - expand_nstring[1] - 1;
if (len - expand_nlength[1] - 1 != 32) return FAIL;
expand_nmax = 1;
/* Expand the server_secret string so that it can compute a value dependent on
the user name if necessary. */
debug_print_string(ablock->server_debug_string); /* customized debugging */
secret = expand_string(ob->server_secret);
/* A forced fail implies failure of authentication - i.e. we have no secret for
the given name. */
if (secret == NULL)
{
if (expand_string_forcedfail) return FAIL;
auth_defer_msg = expand_string_message;
return DEFER;
}
/* Compute the CRAM-MD5 digest that we should have received from the client. */
compute_cram_md5(secret, challenge, digest);
HDEBUG(D_auth)
{
uschar buff[64];
debug_printf("CRAM-MD5: user name = %s\n", expand_nstring[1]);
debug_printf(" challenge = %s\n", challenge);
debug_printf(" received = %s\n", clear);
Ustrcpy(buff," digest = ");
for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
debug_printf("%.54s\n", buff);
}
/* We now have to compare the digest, which is 16 bytes in binary, with the
data received, which is expressed in lower case hex. We checked above that
there were 32 characters of data left. */
for (i = 0; i < 16; i++)
{
int a = *clear++;
int b = *clear++;
if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) +
((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL;
}
return OK;
}
/*************************************************
* Client entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_cram_md5_client(
auth_instance *ablock, /* authenticator block */
smtp_inblock *inblock, /* input connection */
smtp_outblock *outblock, /* output connection */
int timeout, /* command timeout */
uschar *buffer, /* for reading response */
int buffsize) /* size of buffer */
{
auth_cram_md5_options_block *ob =
(auth_cram_md5_options_block *)(ablock->options_block);
uschar *secret = expand_string(ob->client_secret);
uschar *name = expand_string(ob->client_name);
uschar *challenge, *p;
int i;
uschar digest[16];
/* If expansion of either the secret or the user name failed, return CANCELLED
or ERROR, as approriate. */
if (secret == NULL || name == NULL)
{
if (expand_string_forcedfail) return CANCELLED;
string_format(buffer, buffsize, "expansion of \"%s\" failed in "
"%s authenticator: %s",
(secret == NULL)? ob->client_secret : ob->client_name,
ablock->name, expand_string_message);
return ERROR;
}
/* Initiate the authentication exchange and read the challenge, which arrives
in base 64. */
if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
return FAIL_SEND;
if (smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout) < 0)
return FAIL;
if (auth_b64decode(buffer + 4, &challenge) < 0)
{
string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
big_buffer + 4);
return ERROR;
}
/* Run the CRAM-MD5 algorithm on the secret and the challenge */
compute_cram_md5(secret, challenge, digest);
/* Create the response from the user name plus the CRAM-MD5 digest */
string_format(big_buffer, big_buffer_size - 36, "%s", name);
p = big_buffer;
while (*p != 0) p++;
*p++ = ' ';
for (i = 0; i < 16; i++)
{
sprintf(CS p, "%02x", digest[i]);
p += 2;
}
/* Send the response, in base 64, and check the result. The response is
in big_buffer, but auth_b64encode() returns its result in working store,
so calling smtp_write_command(), which uses big_buffer, is OK. */
buffer[0] = 0;
if (smtp_write_command(outblock, FALSE, "%s\r\n", auth_b64encode(big_buffer,
p - big_buffer)) < 0) return FAIL_SEND;
return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)?
OK : FAIL;
}
#endif /* STAND_ALONE */
/*************************************************
**************************************************
* Stand-alone test program *
**************************************************
*************************************************/
#ifdef STAND_ALONE
int main(int argc, char **argv)
{
int i;
uschar *secret = US argv[1];
uschar *challenge = US argv[2];
uschar digest[16];
compute_cram_md5(secret, challenge, digest);
for (i = 0; i < 16; i++) printf("%02x", digest[i]);
printf("\n");
return 0;
}
#endif
/* End of cram_md5.c */
Index: cram_md5.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/cram_md5.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Private structure for the private options. */
typedef struct {
uschar *server_secret;
uschar *client_secret;
uschar *client_name;
} auth_cram_md5_options_block;
/* Data for reading the private options. */
extern optionlist auth_cram_md5_options[];
extern int auth_cram_md5_options_count;
/* Block containing default values. */
extern auth_cram_md5_options_block auth_cram_md5_option_defaults;
/* The entry points for the mechanism */
extern void auth_cram_md5_init(auth_instance *);
extern int auth_cram_md5_server(auth_instance *, uschar *);
extern int auth_cram_md5_client(auth_instance *, smtp_inblock *,
smtp_outblock *, int, uschar *, int);
/* End of cram_md5.h */
Index: cyrus_sasl.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/cyrus_sasl.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2003 */
/* See the file NOTICE for conditions of use and distribution. */
/* This code was contributed by Matthew Byng-Maddick */
/* Copyright (c) A L Digital 2004 */
/* A generic (mechanism independent) Cyrus SASL authenticator. */
#include "../exim.h"
/* We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the Cyrus SASL header
available for compiling. Therefore, compile these functions only if
AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
modules, so keep them happy with a dummy when skipping the rest. Make it
reference itself to stop picky compilers complaining that it is unused, and put
in a dummy argument to stop even pickier compilers complaining about infinite
loops. */
#ifndef AUTH_CYRUS_SASL
static void dummy(int x) { dummy(x-1); }
#else
#include <sasl/sasl.h>
#include "cyrus_sasl.h"
/* Options specific to the cyrus_sasl authentication mechanism. */
optionlist auth_cyrus_sasl_options[] = {
{ "server_hostname", opt_stringptr,
(void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
{ "server_mech", opt_stringptr,
(void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) },
{ "server_realm", opt_stringptr,
(void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) },
{ "server_service", opt_stringptr,
(void *)(offsetof(auth_cyrus_sasl_options_block, server_service)) }
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
int auth_cyrus_sasl_options_count =
sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
/* Default private options block for the contidion authentication method. */
auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
US"smtp", /* server_service */
US"$primary_hostname", /* server_hostname */
NULL, /* server_realm */
NULL /* server_mech */
};
/*************************************************
* Initialization entry point *
*************************************************/
/* Called for each instance, after its options have been read, to
enable consistency checks to be done, or anything else that needs
to be set up. */
void
auth_cyrus_sasl_init(auth_instance *ablock)
{
auth_cyrus_sasl_options_block *ob =
(auth_cyrus_sasl_options_block *)(ablock->options_block);
sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
sasl_conn_t *conn;
uschar *list, *listptr, *buffer;
int rc, i;
unsigned int len;
uschar *rs_point;
/* default the mechanism to our "public name" */
if(ob->server_mech == NULL)
ob->server_mech=string_copy(ablock->public_name);
/* we're going to initialise the library to check that there is an
* authenticator of type whatever mechanism we're using
*/
rc=sasl_server_init(cbs, "exim");
if( rc != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't initialise Cyrus SASL library.", ablock->name);
rc=sasl_server_new(CS ob->server_service, CS primary_hostname,
CS ob->server_realm, NULL, NULL, NULL, 0, &conn);
if( rc != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't initialise Cyrus SASL server connection.", ablock->name);
rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
if( rc != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't get Cyrus SASL mechanism list.", ablock->name);
i=':';
listptr=list;
HDEBUG(D_auth) debug_printf("Cyrus SASL knows about: %s\n", list);
/* the store_get / store_reset mechanism is hierarchical
* the hierarchy is stored for us behind our back. This point
* creates a hierarchy point for this function.
*/
rs_point=store_get(0);
/* loop until either we get to the end of the list, or we match the
* public name of this authenticator
*/
while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
strcmpic(buffer,ob->server_mech) );
if(!buffer)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
store_reset(rs_point);
HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
/* make sure that if we get here then we're allowed to advertise. */
ablock->server = TRUE;
sasl_dispose(&conn);
sasl_done();
}
/*************************************************
* Server entry point *
*************************************************/
/* For interface, see auths/README */
/* note, we don't care too much about memory allocation in this, because this is entirely
* within a shortlived child
*/
int
auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
{
auth_cyrus_sasl_options_block *ob =
(auth_cyrus_sasl_options_block *)(ablock->options_block);
uschar *output, *out2, *input, *clear, *hname;
uschar *debug = NULL; /* Stops compiler complaining */
sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
sasl_conn_t *conn;
int rc, firsttime=1, clen;
unsigned int inlen, outlen;
input=data;
inlen=Ustrlen(data);
HDEBUG(D_auth) debug=string_copy(data);
hname=expand_string(ob->server_hostname);
if(hname == NULL)
{
auth_defer_msg = expand_string_message;
return DEFER;
}
if(inlen)
{
clen=auth_b64decode(input, &clear);
if(clen < 0)
{
return BAD64;
}
input=clear;
inlen=clen;
}
rc=sasl_server_init(cbs, "exim");
if (rc != SASL_OK)
{
auth_defer_msg = US"couldn't initialise Cyrus SASL library";
return DEFER;
}
rc=sasl_server_new(CS ob->server_service, CS ob->server_hostname,
CS ob->server_realm, NULL, NULL, NULL, 0, &conn);
if( rc != SASL_OK )
{
auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
sasl_done();
return DEFER;
}
rc=SASL_CONTINUE;
while(rc==SASL_CONTINUE)
{
if(firsttime)
{
firsttime=0;
HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
(const char **)(&output), &outlen);
}
else
{
/* make sure that we have a null-terminated string */
out2=store_get(outlen+1);
memcpy(out2,output,outlen);
out2[outlen]='\0';
if((rc=auth_get_data(&input, out2, outlen))!=OK)
{
/* we couldn't get the data, so free up the library before
* returning whatever error we get */
sasl_dispose(&conn);
sasl_done();
return rc;
}
inlen=Ustrlen(input);
HDEBUG(D_auth) debug=string_copy(input);
if(inlen)
{
clen=auth_b64decode(input, &clear);
if(clen < 0)
{
sasl_dispose(&conn);
sasl_done();
return BAD64;
}
input=clear;
inlen=clen;
}
HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
}
if(rc==SASL_BADPROT)
{
sasl_dispose(&conn);
sasl_done();
return UNEXPECTED;
}
else if( rc==SASL_FAIL || rc==SASL_BUFOVER
|| rc==SASL_BADMAC || rc==SASL_BADAUTH
|| rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
|| rc==SASL_EXPIRED || rc==SASL_DISABLED
|| rc==SASL_NOUSER )
{
/* these are considered permanent failure codes */
HDEBUG(D_auth)
debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
"Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
sasl_errstring(rc, NULL, NULL));
sasl_dispose(&conn);
sasl_done();
return FAIL;
}
else if(rc==SASL_NOMECH)
{
/* this is a temporary failure, because the mechanism is not
* available for this user. If it wasn't available at all, we
* shouldn't have got here in the first place...
*/
HDEBUG(D_auth)
debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
auth_defer_msg =
string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
sasl_dispose(&conn);
sasl_done();
return DEFER;
}
else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
{
/* Anything else is a temporary failure, and we'll let SASL print out
* the error string for us
*/
HDEBUG(D_auth)
debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
auth_defer_msg =
string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
sasl_dispose(&conn);
sasl_done();
return DEFER;
}
else if(rc==SASL_OK)
{
/* get the username and copy it into $1 */
rc=sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
expand_nstring[1]=string_copy(out2);
expand_nlength[1]=Ustrlen(expand_nstring[1]);
expand_nmax=1;
HDEBUG(D_auth)
debug_printf("Cyrus SASL %s authentiction succeeded for %s\n", ob->server_mech, out2);
/* close down the connection, freeing up library's memory */
sasl_dispose(&conn);
sasl_done();
return OK;
}
}
/* NOTREACHED */
return 0; /* Stop compiler complaints */
}
/*************************************************
* Client entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_cyrus_sasl_client(
auth_instance *ablock, /* authenticator block */
smtp_inblock *inblock, /* input connection */
smtp_outblock *outblock, /* output connection */
int timeout, /* command timeout */
uschar *buffer, /* for reading response */
int buffsize) /* size of buffer */
{
/* We don't support clients (yet) in this implementation of cyrus_sasl */
return FAIL;
}
#endif /* AUTH_CYRUS_SASL */
/* End of cyrus_sasl.c */
Index: cyrus_sasl.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/cyrus_sasl.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2003 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) A L Digital Ltd 2004 */
/* Private structure for the private options. */
typedef struct {
uschar *server_service;
uschar *server_hostname;
uschar *server_realm;
uschar *server_mech;
} auth_cyrus_sasl_options_block;
/* Data for reading the private options. */
extern optionlist auth_cyrus_sasl_options[];
extern int auth_cyrus_sasl_options_count;
/* Block containing default values. */
extern auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults;
/* The entry points for the mechanism */
extern void auth_cyrus_sasl_init(auth_instance *);
extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
smtp_outblock *, int, uschar *, int);
/* End of cyrus_sasl.h */
Index: get_data.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/get_data.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Issue a challenge and get a response *
*************************************************/
/* This function is used by authentication drivers to output a challenge
to the SMTP client and read the response line.
Arguments:
aptr set to point to the response (which is in big_buffer)
challenge the challenge text (unencoded, may be binary)
challen the length of the challenge text
Returns: OK on success
BAD64 if response too large for buffer
CANCELLED if response is "*"
*/
int
auth_get_data(uschar **aptr, uschar *challenge, int challen)
{
int c;
int p = 0;
smtp_printf("334 %s\r\n", auth_b64encode(challenge, challen));
while ((c = receive_getc()) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
big_buffer[p++] = c;
}
if (p > 0 && big_buffer[p-1] == '\r') p--;
big_buffer[p] = 0;
if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
*aptr = big_buffer;
return OK;
}
/* End of get_data.c */
Index: get_no64_data.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/get_no64_data.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Issue a non-b64 challenge and get a response *
*************************************************/
/* This function is used by authentication drivers to output a challenge
to the SMTP client and read the response line. This version does not use base
64 encoding for the text on the 334 line. It is used by the SPA authenticator.
Arguments:
aptr set to point to the response (which is in big_buffer)
challenge the challenge text (unencoded)
Returns: OK on success
BAD64 if response too large for buffer
CANCELLED if response is "*"
*/
int
auth_get_no64_data(uschar **aptr, uschar *challenge)
{
int c;
int p = 0;
smtp_printf("334 %s\r\n", challenge);
while ((c = receive_getc()) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
big_buffer[p++] = c;
}
if (p > 0 && big_buffer[p-1] == '\r') p--;
big_buffer[p] = 0;
if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
*aptr = big_buffer;
return OK;
}
/* End of get_no64_data.c */
Index: md5.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/md5.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#ifndef STAND_ALONE
#include "../exim.h"
/* For stand-alone testing, we need to have the structure defined, and
to be able to do I/O */
#else
#include <stdio.h>
#include "../mytypes.h"
typedef struct md5 {
unsigned int length;
unsigned int abcd[4];
}
md5;
#endif
/*************************************************
* Start off a new MD5 computation. *
*************************************************/
/*
Argument: pointer to md5 storage structure
Returns: nothing
*/
void
md5_start(md5 *base)
{
base->abcd[0] = 0x67452301;
base->abcd[1] = 0xefcdab89;
base->abcd[2] = 0x98badcfe;
base->abcd[3] = 0x10325476;
base->length = 0;
}
/*************************************************
* Process another 64-byte block *
*************************************************/
/* This function implements central part of the algorithm which is described
in RFC 1321.
Arguments:
base pointer to md5 storage structure
text pointer to next 64 bytes of subject text
Returns: nothing
*/
void
md5_mid(md5 *base, const uschar *text)
{
register unsigned int a = base->abcd[0];
register unsigned int b = base->abcd[1];
register unsigned int c = base->abcd[2];
register unsigned int d = base->abcd[3];
int i;
unsigned int X[16];
base->length += 64;
/* Load the 64 bytes into a set of working integers, treating them as 32-bit
numbers in little-endian order. */
for (i = 0; i < 16; i++)
{
X[i] = (unsigned int)(text[0]) |
((unsigned int)(text[1]) << 8) |
((unsigned int)(text[2]) << 16) |
((unsigned int)(text[3]) << 24);
text += 4;
}
/* For each round of processing there is a function to be applied. We define it
as a macro each time round. */
/***********************************************
* Round 1 *
* F(X,Y,Z) = XY v not(X) Z *
* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) *
***********************************************/
#define OP(a, b, c, d, k, s, ti) \
a += ((b & c) | (~b & d)) + X[k] + (unsigned int)ti; \
a = b + ((a << s) | (a >> (32 - s)))
OP(a, b, c, d, 0, 7, 0xd76aa478);
OP(d, a, b, c, 1, 12, 0xe8c7b756);
OP(c, d, a, b, 2, 17, 0x242070db);
OP(b, c, d, a, 3, 22, 0xc1bdceee);
OP(a, b, c, d, 4, 7, 0xf57c0faf);
OP(d, a, b, c, 5, 12, 0x4787c62a);
OP(c, d, a, b, 6, 17, 0xa8304613);
OP(b, c, d, a, 7, 22, 0xfd469501);
OP(a, b, c, d, 8, 7, 0x698098d8);
OP(d, a, b, c, 9, 12, 0x8b44f7af);
OP(c, d, a, b, 10, 17, 0xffff5bb1);
OP(b, c, d, a, 11, 22, 0x895cd7be);
OP(a, b, c, d, 12, 7, 0x6b901122);
OP(d, a, b, c, 13, 12, 0xfd987193);
OP(c, d, a, b, 14, 17, 0xa679438e);
OP(b, c, d, a, 15, 22, 0x49b40821);
#undef OP
/***********************************************
* Round 2 *
* F(X,Y,Z) = XZ v Y not(Z) *
* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) *
***********************************************/
#define OP(a, b, c, d, k, s, ti) \
a += ((b & d) | (c & ~d)) + X[k] + (unsigned int)ti; \
a = b + ((a << s) | (a >> (32 - s)))
OP(a, b, c, d, 1, 5, 0xf61e2562);
OP(d, a, b, c, 6, 9, 0xc040b340);
OP(c, d, a, b, 11, 14, 0x265e5a51);
OP(b, c, d, a, 0, 20, 0xe9b6c7aa);
OP(a, b, c, d, 5, 5, 0xd62f105d);
OP(d, a, b, c, 10, 9, 0x02441453);
OP(c, d, a, b, 15, 14, 0xd8a1e681);
OP(b, c, d, a, 4, 20, 0xe7d3fbc8);
OP(a, b, c, d, 9, 5, 0x21e1cde6);
OP(d, a, b, c, 14, 9, 0xc33707d6);
OP(c, d, a, b, 3, 14, 0xf4d50d87);
OP(b, c, d, a, 8, 20, 0x455a14ed);
OP(a, b, c, d, 13, 5, 0xa9e3e905);
OP(d, a, b, c, 2, 9, 0xfcefa3f8);
OP(c, d, a, b, 7, 14, 0x676f02d9);
OP(b, c, d, a, 12, 20, 0x8d2a4c8a);
#undef OP
/***********************************************
* Round 3 *
* F(X,Y,Z) = X xor Y xor Z *
* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) *
***********************************************/
#define OP(a, b, c, d, k, s, ti) \
a += (b ^ c ^ d) + X[k] + (unsigned int)ti; \
a = b + ((a << s) | (a >> (32 - s)))
OP(a, b, c, d, 5, 4, 0xfffa3942);
OP(d, a, b, c, 8, 11, 0x8771f681);
OP(c, d, a, b, 11, 16, 0x6d9d6122);
OP(b, c, d, a, 14, 23, 0xfde5380c);
OP(a, b, c, d, 1, 4, 0xa4beea44);
OP(d, a, b, c, 4, 11, 0x4bdecfa9);
OP(c, d, a, b, 7, 16, 0xf6bb4b60);
OP(b, c, d, a, 10, 23, 0xbebfbc70);
OP(a, b, c, d, 13, 4, 0x289b7ec6);
OP(d, a, b, c, 0, 11, 0xeaa127fa);
OP(c, d, a, b, 3, 16, 0xd4ef3085);
OP(b, c, d, a, 6, 23, 0x04881d05);
OP(a, b, c, d, 9, 4, 0xd9d4d039);
OP(d, a, b, c, 12, 11, 0xe6db99e5);
OP(c, d, a, b, 15, 16, 0x1fa27cf8);
OP(b, c, d, a, 2, 23, 0xc4ac5665);
#undef OP
/***********************************************
* Round 4 *
* F(X,Y,Z) = Y xor (X v not(Z)) *
* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) *
***********************************************/
#define OP(a, b, c, d, k, s, ti) \
a += (c ^ (b | ~d)) + X[k] + (unsigned int)ti; \
a = b + ((a << s) | (a >> (32 - s)))
OP(a, b, c, d, 0, 6, 0xf4292244);
OP(d, a, b, c, 7, 10, 0x432aff97);
OP(c, d, a, b, 14, 15, 0xab9423a7);
OP(b, c, d, a, 5, 21, 0xfc93a039);
OP(a, b, c, d, 12, 6, 0x655b59c3);
OP(d, a, b, c, 3, 10, 0x8f0ccc92);
OP(c, d, a, b, 10, 15, 0xffeff47d);
OP(b, c, d, a, 1, 21, 0x85845dd1);
OP(a, b, c, d, 8, 6, 0x6fa87e4f);
OP(d, a, b, c, 15, 10, 0xfe2ce6e0);
OP(c, d, a, b, 6, 15, 0xa3014314);
OP(b, c, d, a, 13, 21, 0x4e0811a1);
OP(a, b, c, d, 4, 6, 0xf7537e82);
OP(d, a, b, c, 11, 10, 0xbd3af235);
OP(c, d, a, b, 2, 15, 0x2ad7d2bb);
OP(b, c, d, a, 9, 21, 0xeb86d391);
#undef OP
/* Add the new values back into the accumulators. */
base->abcd[0] += a;
base->abcd[1] += b;
base->abcd[2] += c;
base->abcd[3] += d;
}
/*************************************************
* Process the final text string *
*************************************************/
/* The string may be of any length. It is padded out according to the rules
for computing MD5 digests. The final result is then converted to text form
and returned.
Arguments:
base pointer to the md5 storage structure
text pointer to the final text vector
length length of the final text vector
digest points to 16 bytes in which to place the result
Returns: nothing
*/
void
md5_end(md5 *base, const uschar *text, int length, uschar *digest)
{
int i;
uschar work[64];
/* Process in chunks of 64 until we have less than 64 bytes left. */
while (length >= 64)
{
md5_mid(base, text);
text += 64;
length -= 64;
}
/* If the remaining string contains more than 55 bytes, we must pad it
out to 64, process it, and then set up the final chunk as 56 bytes of
padding. If it has less than 56 bytes, we pad it out to 56 bytes as the
final chunk. */
memcpy(work, text, length);
work[length] = 0x80;
if (length > 55)
{
memset(work+length+1, 0, 63-length);
md5_mid(base, work);
base->length -= 64;
memset(work, 0, 56);
}
else
{
memset(work+length+1, 0, 55-length);
}
/* The final 8 bytes of the final chunk are a 64-bit representation of the
length of the input string *bits*, before padding, low order word first, and
low order bytes first in each word. This implementation is designed for short
strings, and so operates with a single int counter only. */
length += base->length; /* Total length in bytes */
length <<= 3; /* Total length in bits */
work[56] = length & 0xff;
work[57] = (length >> 8) & 0xff;
work[58] = (length >> 16) & 0xff;
work[59] = (length >> 24) & 0xff;
memset(work+60, 0, 4);
/* Process the final 64-byte chunk */
md5_mid(base, work);
/* Pass back the result, low-order byte first in each word. */
for (i = 0; i < 4; i++)
{
register int x = base->abcd[i];
*digest++ = x & 0xff;
*digest++ = (x >> 8) & 0xff;
*digest++ = (x >> 16) & 0xff;
*digest++ = (x >> 24) & 0xff;
}
}
/*************************************************
**************************************************
* Stand-alone test program *
**************************************************
*************************************************/
#if defined STAND_ALONE & !defined CRAM_STAND_ALONE
/* Test values */
static uschar *tests[] = {
"", "d41d8cd98f00b204e9800998ecf8427e",
"a", "0cc175b9c0f1b6a831c399e269772661",
"abc", "900150983cd24fb0d6963f7d28e17f72",
"message digest", "f96b697d7cb7938d525a2f31aaf161d0",
"abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"d174ab98d277d9f5a5611c2c9f419d9f",
"1234567890123456789012345678901234567890123456789012345678901234567890"
"1234567890",
"57edf4a22be3c955ac49da2e2107b67a",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"a0842fcc02167127b0bb9a7c38e71ba8"
};
int main(void)
{
md5 base;
int i = 0x01020304;
uschar *ctest = (uschar *)(&i);
uschar buffer[256];
uschar digest[16];
printf("Checking md5: %s-endian\n", (ctest[0] == 0x04)? "little" : "big");
for (i = 0; i < sizeof(tests)/sizeof(uschar *); i += 2)
{
int j;
uschar s[33];
printf("%s\nShould be: %s\n", tests[i], tests[i+1]);
md5_start(&base);
md5_end(&base, tests[i], strlen(tests[i]), digest);
for (j = 0; j < 16; j++) sprintf(s+2*j, "%02x", digest[j]);
printf("Computed: %s\n", s);
if (strcmp(s, tests[i+1]) != 0) printf("*** No match ***\n");
printf("\n");
}
}
#endif
/* End of md5.c */
Index: plaintext.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/plaintext.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "plaintext.h"
/* Options specific to the plaintext authentication mechanism. */
optionlist auth_plaintext_options[] = {
{ "client_send", opt_stringptr,
(void *)(offsetof(auth_plaintext_options_block, client_send)) },
{ "server_condition", opt_stringptr,
(void *)(offsetof(auth_plaintext_options_block, server_condition)) },
{ "server_prompts", opt_stringptr,
(void *)(offsetof(auth_plaintext_options_block, server_prompts)) }
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
int auth_plaintext_options_count =
sizeof(auth_plaintext_options)/sizeof(optionlist);
/* Default private options block for the plaintext authentication method. */
auth_plaintext_options_block auth_plaintext_option_defaults = {
NULL, /* server_condition */
NULL, /* server_prompts */
NULL /* client_send */
};
/*************************************************
* Initialization entry point *
*************************************************/
/* Called for each instance, after its options have been read, to
enable consistency checks to be done, or anything else that needs
to be set up. */
void
auth_plaintext_init(auth_instance *ablock)
{
auth_plaintext_options_block *ob =
(auth_plaintext_options_block *)(ablock->options_block);
if (ablock->public_name == NULL) ablock->public_name = ablock->name;
if (ob->server_condition != NULL) ablock->server = TRUE;
if (ob->client_send != NULL) ablock->client = TRUE;
}
/*************************************************
* Server entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_plaintext_server(auth_instance *ablock, uschar *data)
{
auth_plaintext_options_block *ob =
(auth_plaintext_options_block *)(ablock->options_block);
uschar *prompts = ob->server_prompts;
uschar *clear, *cond, *end, *s;
int number = 1;
int len, rc;
int sep = 0;
/* Expand a non-empty list of prompt strings */
if (prompts != NULL)
{
prompts = expand_string(prompts);
if (prompts == NULL)
{
auth_defer_msg = expand_string_message;
return DEFER;
}
}
/* If data was supplied on the AUTH command, decode it, and split it up into
multiple items at binary zeros. If the data consists of the string "=" it
indicates a single, empty string. */
if (*data != 0)
{
if (Ustrcmp(data, "=") == 0)
{
expand_nstring[++expand_nmax] = US"";
expand_nlength[expand_nmax] = 0;
}
else
{
if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
end = clear + len;
while (clear < end && expand_nmax < EXPAND_MAXN)
{
expand_nstring[++expand_nmax] = clear;
while (*clear != 0) clear++;
expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
}
}
}
/* Now go through the list of prompt strings. Skip over any whose data has
already been provided as part of the AUTH command. For the rest, send them
out as prompts, and get a data item back. If the data item is "*", abandon the
authentication attempt. Otherwise, split it into items as above. */
while ((s = string_nextinlist(&prompts, &sep, big_buffer, big_buffer_size))
!= NULL && expand_nmax < EXPAND_MAXN)
{
if (number++ <= expand_nmax) continue;
if ((rc = auth_get_data(&data, s, Ustrlen(s))) != OK) return rc;
if ((len = auth_b64decode(data, &clear)) < 0) return BAD64;
end = clear + len;
/* This loop must run at least once, in case the length is zero */
do
{
expand_nstring[++expand_nmax] = clear;
while (*clear != 0) clear++;
expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
}
while (clear < end && expand_nmax < EXPAND_MAXN);
}
/* We now have a number of items of data in $1, $2, etc. Match against the
decoded data by expanding the condition. Also expand the id to set if
authentication succeeds. */
cond = expand_string(ob->server_condition);
HDEBUG(D_auth)
{
int i;
debug_printf("%s authenticator:\n", ablock->name);
for (i = 1; i <= expand_nmax; i++)
debug_printf(" $%d = %.*s\n", i, expand_nlength[i], expand_nstring[i]);
debug_print_string(ablock->server_debug_string); /* customized debug */
if (cond == NULL)
debug_printf("expansion failed: %s\n", expand_string_message);
else
debug_printf("expanded string: %s\n", cond);
}
/* A forced expansion failure causes authentication to fail. Other expansion
failures yield DEFER, which will cause a temporary error code to be returned to
the AUTH command. The problem is at the server end, so the client should try
again later. */
if (cond == NULL)
{
if (expand_string_forcedfail) return FAIL;
auth_defer_msg = expand_string_message;
return DEFER;
}
/* Return FAIL for empty string, "0", "no", and "false"; return OK for
"1", "yes", and "true"; return DEFER for anything else, with the string
available as an error text for the user. */
if (*cond == 0 ||
Ustrcmp(cond, "0") == 0 ||
strcmpic(cond, US"no") == 0 ||
strcmpic(cond, US"false") == 0)
return FAIL;
if (Ustrcmp(cond, "1") == 0 ||
strcmpic(cond, US"yes") == 0 ||
strcmpic(cond, US"true") == 0)
return OK;
auth_defer_msg = cond;
auth_defer_user_msg = string_sprintf(": %s", cond);
return DEFER;
}
/*************************************************
* Client entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_plaintext_client(
auth_instance *ablock, /* authenticator block */
smtp_inblock *inblock, /* connection inblock */
smtp_outblock *outblock, /* connection outblock */
int timeout, /* command timeout */
uschar *buffer, /* buffer for reading response */
int buffsize) /* size of buffer */
{
auth_plaintext_options_block *ob =
(auth_plaintext_options_block *)(ablock->options_block);
uschar *text = ob->client_send;
uschar *s;
BOOL first = TRUE;
int sep = 0;
/* The text is broken up into a number of different data items, which are
sent one by one. The first one is sent with the AUTH command; the remainder are
sent in response to subsequent prompts. Each is expanded before being sent. */
while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL)
{
int i, len;
uschar *ss = expand_string(s);
/* Forced expansion failure is not an error; authentication is abandoned. On
all but the first string, we have to abandon the authentication attempt by
sending a line containing "*". Save the failed expansion string, because it
is in big_buffer, and that gets used by the sending function. */
if (ss == NULL)
{
uschar *ssave = string_copy(s);
if (!first)
{
if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
(void) smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
}
if (expand_string_forcedfail) return CANCELLED;
string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
"authenticator: %s", ssave, ablock->name, expand_string_message);
return ERROR;
}
len = Ustrlen(ss);
/* The character ^ is used as an escape for a binary zero character, which is
needed for the PLAIN mechanism. It must be doubled if really needed. */
for (i = 0; i < len; i++)
{
if (ss[i] == '^')
{
if (ss[i+1] != '^') ss[i] = 0; else
{
i++;
len--;
memmove(ss + i, ss + i + 1, len - i);
}
}
}
/* The first string is attached to the AUTH command; others are sent
unembelished. */
if (first)
{
first = FALSE;
if (smtp_write_command(outblock, FALSE, "AUTH %s%s%s\r\n",
ablock->public_name, (len == 0)? "" : " ",
auth_b64encode(ss, len)) < 0)
return FAIL_SEND;
}
else
{
if (smtp_write_command(outblock, FALSE, "%s\r\n",
auth_b64encode(ss, len)) < 0)
return FAIL_SEND;
}
/* If we receive a success response from the server, authentication
has succeeded. There may be more data to send, but is there any point
in provoking an error here? */
if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout)) return OK;
/* Not a success response. If errno != 0 there is some kind of transmission
error. Otherwise, check the response code in the buffer. If it starts with
'3', more data is expected. */
if (errno != 0 || buffer[0] != '3') return FAIL;
/* If there is no more data to send, we have to cancel the authentication
exchange and return ERROR. */
if (text == NULL)
{
if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
(void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
string_format(buffer, buffsize, "Too few items in client_send in %s "
"authenticator", ablock->name);
return ERROR;
}
}
/* Control should never actually get here. */
return FAIL;
}
/* End of plaintext.c */
Index: plaintext.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/plaintext.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Private structure for the private options. */
typedef struct {
uschar *server_condition;
uschar *server_prompts;
uschar *client_send;
} auth_plaintext_options_block;
/* Data for reading the private options. */
extern optionlist auth_plaintext_options[];
extern int auth_plaintext_options_count;
/* Block containing default values. */
extern auth_plaintext_options_block auth_plaintext_option_defaults;
/* The entry points for the mechanism */
extern void auth_plaintext_init(auth_instance *);
extern int auth_plaintext_server(auth_instance *, uschar *);
extern int auth_plaintext_client(auth_instance *, smtp_inblock *,
smtp_outblock *, int, uschar *, int);
/* End of plaintext.h */
Index: pwcheck.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/pwcheck.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/* SASL server API implementation
* Rob Siemborski
* Tim Martin
* $Id: checkpw.c,v 1.49 2002/03/07 19:14:04 ken3 Exp $
*/
/*
* Copyright (c) 2001 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@???
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Taken from Cyrus-SASL library and adapted by Alexander S. Sabourenkov
* Oct 2001 - Apr 2002: Slightly modified by Philip Hazel.
* Aug 2003: new code for saslauthd from Alexander S. Sabourenkov incorporated
* by Philip Hazel (minor mods to avoid compiler warnings)
*
* screwdriver@???
*
*/
/* Originally this module supported only the pwcheck daemon, which is where its
name comes from. Nowadays it supports saslauthd as well; pwcheck is in fact
deprecated. The definitions of CYRUS_PWCHECK_SOCKET and CYRUS_SASLAUTHD_SOCKET
determine whether the facilities are actually supported or not. */
#include "../exim.h"
#include "pwcheck.h"
#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
#include <sys/uio.h>
static int retry_read(int, void *, unsigned );
static int retry_writev(int, struct iovec *, int );
static int read_string(int, uschar **);
static int write_string(int, const uschar *, int);
#endif
/* A dummy function that always fails if pwcheck support is not
wanted. */
#ifndef CYRUS_PWCHECK_SOCKET
int pwcheck_verify_password(const char *userid,
const char *passwd,
const char **reply)
{
userid = userid; /* Keep picky compilers happy */
passwd = passwd;
*reply = "pwcheck support is not included in this Exim binary";
return PWCHECK_FAIL;
}
/* This is the real function */
#else
/* taken from cyrus-sasl file checkpw.c */
/* pwcheck daemon-authenticated login */
int pwcheck_verify_password(const char *userid,
const char *passwd,
const char **reply)
{
int s, start, r, n;
struct sockaddr_un srvaddr;
struct iovec iov[2];
static char response[1024];
if (reply) { *reply = NULL; }
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) { return PWCHECK_FAIL; }
memset((char *)&srvaddr, 0, sizeof(srvaddr));
srvaddr.sun_family = AF_UNIX;
strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path));
r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
if (r == -1) {
DEBUG(D_auth)
debug_printf("Cannot connect to pwcheck daemon (at '%s')\n",CYRUS_PWCHECK_SOCKET);
if (reply) { *reply = "cannot connect to pwcheck daemon"; }
return PWCHECK_FAIL;
}
iov[0].iov_base = (char *)userid;
iov[0].iov_len = strlen(userid)+1;
iov[1].iov_base = (char *)passwd;
iov[1].iov_len = strlen(passwd)+1;
retry_writev(s, iov, 2);
start = 0;
while (start < sizeof(response) - 1) {
n = read(s, response+start, sizeof(response) - 1 - start);
if (n < 1) break;
start += n;
}
close(s);
if (start > 1 && !strncmp(response, "OK", 2)) {
return PWCHECK_OK;
}
response[start] = '\0';
if (reply) { *reply = response; }
return PWCHECK_NO;
}
#endif
/* A dummy function that always fails if saslauthd support is not
wanted. */
#ifndef CYRUS_SASLAUTHD_SOCKET
int saslauthd_verify_password(const uschar *userid,
const uschar *passwd,
const uschar *service,
const uschar *realm,
const uschar **reply)
{
userid = userid; /* Keep picky compilers happy */
passwd = passwd;
service = service;
realm = realm;
*reply = US"saslauthd support is not included in this Exim binary";
return PWCHECK_FAIL;
}
/* This is the real function */
#else
/* written from scratch */
/* saslauthd daemon-authenticated login */
int saslauthd_verify_password(const uschar *userid,
const uschar *password,
const uschar *service,
const uschar *realm,
const uschar **reply)
{
uschar *daemon_reply;
int s, r;
struct sockaddr_un srvaddr;
DEBUG(D_auth)
debug_printf("saslauthd userid='%s' servicename='%s'"
" realm='%s'\n", userid, service, realm );
if (reply)
*reply = NULL;
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) {
if (reply)
*reply = CUstrerror(errno);
return PWCHECK_FAIL;
}
memset((char *)&srvaddr, 0, sizeof(srvaddr));
srvaddr.sun_family = AF_UNIX;
strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
sizeof(srvaddr.sun_path));
r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
if (r == -1) {
DEBUG(D_auth)
debug_printf("Cannot connect to saslauthd daemon (at '%s'): %s\n",
CYRUS_SASLAUTHD_SOCKET, strerror(errno));
if (reply)
*reply = string_sprintf("cannot connect to saslauthd daemon at "
"%s: %s", CYRUS_SASLAUTHD_SOCKET,
strerror(errno));
return PWCHECK_FAIL;
}
if ( write_string(s, userid, Ustrlen(userid)) < 0) {
DEBUG(D_auth)
debug_printf("Failed to send userid to saslauthd daemon \n");
close(s);
return PWCHECK_FAIL;
}
if ( write_string(s, password, Ustrlen(password)) < 0) {
DEBUG(D_auth)
debug_printf("Failed to send password to saslauthd daemon \n");
close(s);
return PWCHECK_FAIL;
}
memset((void *)password, 0, Ustrlen(password));
if ( write_string(s, service, Ustrlen(service)) < 0) {
DEBUG(D_auth)
debug_printf("Failed to send service name to saslauthd daemon \n");
close(s);
return PWCHECK_FAIL;
}
if ( write_string(s, realm, Ustrlen(realm)) < 0) {
DEBUG(D_auth)
debug_printf("Failed to send realm to saslauthd daemon \n");
close(s);
return PWCHECK_FAIL;
}
if ( read_string(s, &daemon_reply ) < 2) {
DEBUG(D_auth)
debug_printf("Corrupted answer '%s' received. \n", daemon_reply);
close(s);
return PWCHECK_FAIL;
}
close(s);
DEBUG(D_auth)
debug_printf("Answer '%s' received. \n", daemon_reply);
*reply = daemon_reply;
if ( (daemon_reply[0] == 'O') && (daemon_reply[1] == 'K') )
return PWCHECK_OK;
if ( (daemon_reply[0] == 'N') && (daemon_reply[1] == 'O') )
return PWCHECK_NO;
return PWCHECK_FAIL;
}
#endif
/* helper functions */
#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
#define MAX_REQ_LEN 1024
/* written from scratch */
/* FUNCTION: read_string */
/* SYNOPSIS
* read a sasld-style counted string into
* store-allocated buffer, set pointer to the buffer,
* return number of bytes read or -1 on failure.
* END SYNOPSIS */
static int read_string(int fd, uschar **retval) {
unsigned short count;
int rc;
rc = (retry_read(fd, &count, sizeof(count)) < (int) sizeof(count));
if (!rc) {
count = ntohs(count);
if (count > MAX_REQ_LEN) {
return -1;
} else {
*retval = store_get(count + 1);
rc = (retry_read(fd, *retval, count) < (int) count);
(*retval)[count] = '\0';
return count;
}
}
return -1;
}
/* FUNCTION: write_string */
/* SYNOPSIS
* write a sasld-style counted string into given fd
* written bytes on success, -1 on failure.
* END SYNOPSIS */
static int write_string(int fd, const uschar *string, int len) {
unsigned short count;
int rc;
struct iovec iov[2];
count = htons(len);
iov[0].iov_base = (void *) &count;
iov[0].iov_len = sizeof(count);
iov[1].iov_base = (void *) string;
iov[1].iov_len = len;
rc = retry_writev(fd, iov, 2);
return rc;
}
/* taken from cyrus-sasl file saslauthd/saslauthd-unix.c */
/* FUNCTION: retry_read */
/* SYNOPSIS
* Keep calling the read() system call with 'fd', 'buf', and 'nbyte'
* until all the data is read in or an error occurs.
* END SYNOPSIS */
static int retry_read(int fd, void *inbuf, unsigned nbyte)
{
int n;
int nread = 0;
char *buf = (char *)inbuf;
if (nbyte == 0) return 0;
for (;;) {
n = read(fd, buf, nbyte);
if (n == 0) {
/* end of file */
return -1;
}
if (n == -1) {
if (errno == EINTR) continue;
return -1;
}
nread += n;
if (n >= (int) nbyte) return nread;
buf += n;
nbyte -= n;
}
}
/* END FUNCTION: retry_read */
/* FUNCTION: retry_writev */
/* SYNOPSIS
* Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
* until all the data is written out or an error occurs.
* END SYNOPSIS */
static int /* R: bytes written, or -1 on error */
retry_writev (
/* PARAMETERS */
int fd, /* I: fd to write on */
struct iovec *iov, /* U: iovec array base
* modified as data written */
int iovcnt /* I: number of iovec entries */
/* END PARAMETERS */
)
{
/* VARIABLES */
int n; /* return value from writev() */
int i; /* loop counter */
int written; /* bytes written so far */
static int iov_max; /* max number of iovec entries */
/* END VARIABLES */
/* initialization */
#ifdef MAXIOV
iov_max = MAXIOV;
#else /* ! MAXIOV */
# ifdef IOV_MAX
iov_max = IOV_MAX;
# else /* ! IOV_MAX */
iov_max = 8192;
# endif /* ! IOV_MAX */
#endif /* ! MAXIOV */
written = 0;
for (;;) {
while (iovcnt && iov[0].iov_len == 0) {
iov++;
iovcnt--;
}
if (!iovcnt) {
return written;
}
n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
if (n == -1) {
if (errno == EINVAL && iov_max > 10) {
iov_max /= 2;
continue;
}
if (errno == EINTR) {
continue;
}
return -1;
} else {
written += n;
}
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len > (unsigned) n) {
iov[i].iov_base = (char *)iov[i].iov_base + n;
iov[i].iov_len -= n;
break;
}
n -= iov[i].iov_len;
iov[i].iov_len = 0;
}
if (i == iovcnt) {
return written;
}
}
/* NOTREACHED */
}
/* END FUNCTION: retry_writev */
#endif
/* End of auths/pwcheck.c */
Index: pwcheck.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/pwcheck.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file provides support for authentication via the Cyrus SASL pwcheck
daemon (whence its name) and the newer saslauthd daemon. */
/* Error codes used internally within the authentication functions */
/* PWCHECK_OK - auth successful
PWCHECK_NO - access denied
PWCHECK_FAIL - [temporary] failure */
#define PWCHECK_OK 0
#define PWCHECK_NO 1
#define PWCHECK_FAIL 2
/* Cyrus functions for doing the business. */
extern int pwcheck_verify_password(const char *, const char *, const char **);
extern int saslauthd_verify_password(const uschar *, const uschar *,
const uschar *, const uschar *, const uschar **);
/* End of pwcheck.h */
Index: sha1.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/sha1.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#ifndef STAND_ALONE
#include "../exim.h"
/* For stand-alone testing, we need to have the structure defined, and
to be able to do I/O */
#else
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char uschar;
typedef struct sha1 {
unsigned int H[5];
unsigned int length;
}
sha1;
#endif
/*************************************************
* Start off a new SHA-1 computation. *
*************************************************/
/*
Argument: pointer to sha1 storage structure
Returns: nothing
*/
void
sha1_start(sha1 *base)
{
base->H[0] = 0x67452301;
base->H[1] = 0xefcdab89;
base->H[2] = 0x98badcfe;
base->H[3] = 0x10325476;
base->H[4] = 0xc3d2e1f0;
base->length = 0;
}
/*************************************************
* Process another 64-byte block *
*************************************************/
/* This function implements central part of the algorithm
Arguments:
base pointer to sha1 storage structure
text pointer to next 64 bytes of subject text
Returns: nothing
*/
void
sha1_mid(sha1 *base, const uschar *text)
{
register int i;
unsigned int A, B, C, D, E;
unsigned int W[80];
base->length += 64;
for (i = 0; i < 16; i++)
{
W[i] = (text[0] << 24) | (text[1] << 16) | (text[2] << 8) | text[3];
text += 4;
}
for (i = 16; i < 80; i++)
{
register unsigned int x = W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16];
W[i] = (x << 1) | (x >> 31);
}
A = base->H[0];
B = base->H[1];
C = base->H[2];
D = base->H[3];
E = base->H[4];
for (i = 0; i < 20; i++)
{
unsigned int T;
T = ((A << 5) | (A >> 27)) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5a827999;
E = D;
D = C;
C = (B << 30) | (B >> 2);
B = A;
A = T;
}
for (i = 20; i < 40; i++)
{
unsigned int T;
T = ((A << 5) | (A >> 27)) + (B ^ C ^ D) + E + W[i] + 0x6ed9eba1;
E = D;
D = C;
C = (B << 30) | (B >> 2);
B = A;
A = T;
}
for (i = 40; i < 60; i++)
{
unsigned int T;
T = ((A << 5) | (A >> 27)) + ((B & C) | (B & D) | (C & D)) + E + W[i] +
0x8f1bbcdc;
E = D;
D = C;
C = (B << 30) | (B >> 2);
B = A;
A = T;
}
for (i = 60; i < 80; i++)
{
unsigned int T;
T = ((A << 5) | (A >> 27)) + (B ^ C ^ D) + E + W[i] + 0xca62c1d6;
E = D;
D = C;
C = (B << 30) | (B >> 2);
B = A;
A = T;
}
base->H[0] += A;
base->H[1] += B;
base->H[2] += C;
base->H[3] += D;
base->H[4] += E;
}
/*************************************************
* Process the final text string *
*************************************************/
/* The string may be of any length. It is padded out according to the rules
for computing SHA-1 digests. The final result is then converted to text form
and returned.
Arguments:
base pointer to the sha1 storage structure
text pointer to the final text vector
length length of the final text vector
digest points to 16 bytes in which to place the result
Returns: nothing
*/
void
sha1_end(sha1 *base, const uschar *text, int length, uschar *digest)
{
int i;
uschar work[64];
/* Process in chunks of 64 until we have less than 64 bytes left. */
while (length >= 64)
{
sha1_mid(base, text);
text += 64;
length -= 64;
}
/* If the remaining string contains more than 55 bytes, we must pad it
out to 64, process it, and then set up the final chunk as 56 bytes of
padding. If it has less than 56 bytes, we pad it out to 56 bytes as the
final chunk. */
memcpy(work, text, length);
work[length] = 0x80;
if (length > 55)
{
memset(work+length+1, 0, 63-length);
sha1_mid(base, work);
base->length -= 64;
memset(work, 0, 56);
}
else
{
memset(work+length+1, 0, 55-length);
}
/* The final 8 bytes of the final chunk are a 64-bit representation of the
length of the input string *bits*, before padding, high order word first, and
high order bytes first in each word. This implementation is designed for short
strings, and so operates with a single int counter only. */
length += base->length; /* Total length in bytes */
length <<= 3; /* Total length in bits */
work[63] = length & 0xff;
work[62] = (length >> 8) & 0xff;
work[61] = (length >> 16) & 0xff;
work[60] = (length >> 24) & 0xff;
memset(work+56, 0, 4);
/* Process the final 64-byte chunk */
sha1_mid(base, work);
/* Pass back the result, high-order byte first in each word. */
for (i = 0; i < 5; i++)
{
register int x = base->H[i];
*digest++ = (x >> 24) & 0xff;
*digest++ = (x >> 16) & 0xff;
*digest++ = (x >> 8) & 0xff;
*digest++ = x & 0xff;
}
}
/*************************************************
**************************************************
* Stand-alone test program *
**************************************************
*************************************************/
#ifdef STAND_ALONE
/* Test values. The first 128 may contain binary zeros and have increasing
length. */
static uschar *tests[] = {
"",
"\x24",
"\x70\xf0",
"\x0e\x1e\xf0",
"\x08\x38\x78\x8f",
"\x10\x3e\x08\xfc\x0f",
"\xe7\xc7\x1e\x07\xef\x03",
"\xe0\xfb\x71\xf8\xf9\xc1\xfc",
"\xff\x7c\x60\x3c\x1f\x80\xe2\x07",
"\xf0\x3f\xc8\x60\x81\xfe\x01\xf8\x7f",
"\x9f\xc7\xf8\x1f\xc1\xe3\xc7\xc7\x3f\x00",
"\x00\x7f\xbf\xdf\xc0\xfe\x02\x7e\x00\xf8\x7f",
"\x01\x01\xc0\x1e\x03\xf8\x30\x08\x0f\xf3\xf9\xff",
"\xc4\x03\xfc\x0f\xf8\x01\xc0\x0f\xf0\x06\x10\x41\xff",
"\xff\x07\x80\x47\xfc\x1f\xc0\x60\x30\x1f\xe0\x3c\x03\xff",
"\x80\x3f\x84\x3e\xff\xc0\x3f\x0f\x00\x7f\xc0\x1f\xe7\xfc\x00",
"\xff\xe0\x7f\x01\x81\x81\xff\x81\xff\x00\x3e\x00\x20\x7f\x80\x0f",
"\x00\x3e\x00\x70\x1f\xe0\x0f\xfa\xff\xc8\x3f\xf3\xfe\x00\xff\x80\xff",
"\x7f\xef\xc0\x1e\x7c\xff\xe0\x1f\xfe\x00\x1f\xf0\x08\xff\xc0\x7f\xf0\x00",
"\xe0\x0f\x80\x07\x0c\x01\xff\xe0\x03\xf0\x2f\xf8\x3f\xef\x00\x78\x01\xfe\x00",
"\xe7\x00\x10\x00\xf8\x18\x0f\xf0\xff\x00\xff\x80\x3f\xc3\xfe\xf0\x0f\xfc\x01\xff",
"\x00\x1f\xf8\x0f\xfc\x00\xfc\x00\xff\x87\xc0\x0f\x80\x7b\xff\x00\x0f\x02\x01\xff\xc0",
"\x00\x0f\xf0\x03\xc7\xf8\x3e\x03\xff\x80\x03\xff\x80\x07\xff\x0f\xff\x1f\x83\xff\x80\x1f",
"\xff\xc0\x1f\x80\x3f\x9f\xf8\x78\x3e\x7f\xf8\x00\x3e\x20\x04\x3f\x80\x7f\xfc\x00\x1f\xfe\x00",
"\x3f\x07\x80\xe0\x07\xe0\x00\xfc\x7f\xc0\xc0\x0f\x8f\xf0\x80\x0e\x0e\x03\xff\xbf\xfc\x01\xff\xe0",
"\xff\xfc\x11\xfc\xe0\x0e\x1f\xff\x87\x80\x1f\xe0\xff\xfd\xff\xc0\x03\xff\xc0\x0f\x00\x07\xf0\x01\xff",
"\xf0\x07\xc1\xfe\x00\xf8\x01\xe7\x80\xff\x80\x3f\x1f\x7f\x8c\x00\x1c\x00\x0f\xf8\x07\xfc\x00\xff\xfc\x00",
"\x00\x0f\xf8\x3f\xc0\x60\x00\x7f\xf8\xff\x00\x03\xf0\x3c\x07\xc0\x7f\xe0\x3f\xf8\x01\x00\x7e\x03\xff\xc0\x00",
"\x00\x0f\xf8\x03\x00\x1f\xff\x00\x0f\xfe\x00\x3f\x00\x03\xff\xe0\x07\xc0\xff\x00\x3c\x7f\xf0\x01\xff\xf8\x3f\xff",
"\x00\x01\xe0\xe0\x1f\xfe\x00\x03\xfc\x00\x0f\xff\xe0\x0f\xff\x00\x0e\x00\x7f\xfc\x0f\xfe\x00\x78\x00\x3f\xff\x00\xff",
"\x80\x41\xff\xc3\xfe\x00\x1e\x00\x0f\xff\xe0\xff\x80\x0f\xe0\x00\x7f\xf7\xff\x01\xfe\x01\xff\xdf\xff\x00\x01\xff\xe0\x00",
"\xf8\x07\x00\xff\xc0\x7f\xbe\x00\x0f\xff\x00\x03\xe3\xf0\xff\xf0\x00\x1f\x81\xff\x80\x0f\xff\x80\x20\x03\xf0\x03\x80\xff\xfc",
"\x00\x38\x20\x00\x7f\xf0\x01\xff\xfe\xcf\xfe\x07\xff\xc0\x00\x7f\xf8\x1f\x00\x00\xc0\x00\xc0\x0f\xff\x3e\x0f\xc0\x0f\xff\x80\x00",
"\x1f\xf8\x07\xff\xf8\x03\xe0\x01\xff\xfc\x3f\xf8\x00\x38\x1f\x00\x3f\xdc\x01\xc0\x04\xff\xff\x00\x0f\xfc\x08\x02\x00\x01\xf0\x3f\xff",
"\x80\x07\x86\x00\x03\xff\xe0\x00\x3f\xf8\x00\x0f\x80\x0f\xf8\x0f\xff\xe0\x00\x1f\x80\x00\x7f\xf8\xc0\x0f\xff\xf0\x7c\x04\x07\xff\x00\x00",
"\x01\xff\x00\x18\x3e\x0f\x00\x07\xff\xc0\x00\xf0\x1f\xfe\x07\x80\x60\x0f\xf8\x00\x3f\xfe\x38\x1f\xc0\x00\x3f\x81\xff\xfc\x1f\xe0\x00\x3f\xff",
"\xf0\x3f\xff\xc0\x00\x7f\xf0\x00\x3f\xff\x0f\xe0\x07\x0f\xfc\x7e\x03\xff\xf0\xfc\x0f\x9f\xc0\x3f\xff\xcf\xff\x00\x00\xff\xc0\x00\xe7\x01\xff\xf8",
"\x00\x01\xff\x80\x20\x00\x7f\xe0\x00\x7e\x07\xff\xf8\xc7\xf8\xff\xf0\x0f\xfe\x00\x00\xe0\x0f\xe0\x00\x1f\xff\x87\xff\x00\x01\xf0\x00\x7f\xc1\xff\xff",
"\x00\x00\x7f\xff\xc0\x01\xfe\x7e\x01\xff\xfe\xff\xf0\x7f\xff\xcf\xf8\x07\xfe\x00\x0f\xff\xc0\x07\xff\xfc\x00\x3e\x00\x07\xfc\x00\x7f\xc0\x07\x80\x0f\xff",
"\xff\xff\x03\xff\x07\xf8\xff\xff\x80\x00\x7f\xfe\xff\xfe\x00\x03\xff\xf8\x1f\xff\x3f\xf8\x1f\xff\x00\x1f\xff\x0f\xc0\x7f\xf0\x01\xff\xe0\x00\x1f\xff\x00\x00",
"\xff\xff\x00\x00\xff\xfc\x00\x03\x0f\xff\xf0\x01\xf8\x00\x0f\xe1\xff\xff\x03\xe0\x3f\x1f\xff\x80\x00\x7c\x00\x01\xff\xc0\x01\x7f\xfe\x00\x0e\x07\xff\xe0\xff\xff",
"\xc0\x00\x3f\xfe\x03\xfc\x0c\x00\x04\x01\xff\xe1\xe0\x03\xff\xe0\x30\x01\xff\x00\x00\x3c\x1e\x01\x80\x01\xff\x00\x40\x3f\xfe\x00\x3f\xff\x80\x7c\x01\xff\x80\x00\x7f",
"\x3f\xa0\x00\x0f\xff\x81\xff\xc0\x0f\xf0\x7f\xf8\x00\x0f\xc0\x00\x7f\xe0\x01\xe0\x00\x04\xff\x00\x1f\xfe\x00\x01\xff\x80\x07\xff\xfe\x00\x3f\xff\xc0\x03\xff\x80\x00\x3f",
"\xf0\x1f\xff\x01\xff\x80\xff\xc0\x80\x07\xf0\x00\x03\xff\x80\x00\x18\x01\xff\xfc\x00\xff\xfc\x03\xff\xff\x00\x7f\xc0\x03\xff\xc7\xff\xc0\x03\xf0\xff\x80\x00\x3f\xfe\x00\x00",
"\x07\xf1\xbf\xff\xe0\x00\x78\x00\x07\xe0\x00\x80\x03\xf0\x3f\xf7\x00\x00\x38\x00\xfe\x00\xf8\x0f\xfe\x00\x00\x80\x3f\xff\xc1\xff\xfc\x00\xff\xff\x8f\xf0\x00\x1f\xff\xf0\x0f\xff",
"\x00\x1c\x00\x07\xff\xfc\x00\x5e\x3f\xff\x00\x00\x3c\xff\xff\xc0\x3f\xff\x81\xe0\x70\x00\x1f\xfc\x00\x03\xff\x00\x00\x7f\xff\xc0\x1f\x8c\x0f\xff\xf0\xff\x80\x07\xe0\x10\x01\xff\xff",
"\xc0\x00\x07\xff\x80\x7f\xff\x80\x01\x80\x3f\xff\xcf\xc0\xfe\x00\xff\xc0\x1f\xfc\x01\xff\xf8\x00\xff\xfe\x0f\xff\xf0\x06\x00\x00\xc0\x3f\xff\x80\x78\xff\xfc\x00\x0f\xff\xf0\x00\x0f\xff",
"\xff\xe0\x07\xff\xf8\x00\x7f\xf0\x1f\xff\x80\x01\xff\xf8\x1f\xf8\x01\x03\xff\xe0\x00\x03\xe0\x78\x0f\xff\x00\x0f\xfc\x1f\xf8\x00\x0f\xff\xe0\x1f\x00\x07\xff\xfc\x00\x1f\x03\xff\xf7\xff\xff",
"\xc0\xf8\x00\x03\xfe\x00\x3f\xff\xf0\x00\x03\xfc\x0f\xff\x80\x00\xe3\xff\xf8\x3f\xfe\x00\x00\x73\xe0\xff\xfc\x07\xff\xc3\xff\xfe\x03\x00\x00\x70\x00\x03\xff\xf8\x0f\xff\xe0\x00\x1f\xff\xf8\x00",
"\xff\xf0\x0f\xc7\xff\xfc\x00\x3f\xfe\x00\x00\x3f\xff\x80\x3f\x80\x00\x3f\xff\xc0\x00\x70\x01\xff\xc1\x80\x03\xff\xff\x80\x00\x61\xff\xfe\x03\xfd\x80\x3f\xff\xe0\x01\xc1\xff\xff\x80\x00\x0f\xfe\x00",
"\xff\xfc\x00\x03\xff\xf0\x0f\xf8\x00\x07\xdf\x8f\xff\xf8\x00\x01\xff\xfe\x00\x80\x00\xff\x80\x1f\xf0\x00\x01\x1c\x00\x00\x3f\xf8\x00\x3f\xff\xef\xff\xfe\x01\xc3\x80\x80\x01\xff\xff\xc0\x00\x07\xff\xff",
"\xff\xff\xc0\x01\xff\xc1\xff\xff\x87\xff\xff\x00\x3f\x00\x00\x1f\xfc\x00\x01\xff\x80\x1f\xc0\x1f\xff\x00\x00\xff\x80\x1f\xff\xf8\x7f\xf8\x3f\xff\xc1\xff\xff\xe0\x01\xc0\x3f\xf7\xff\xfe\xfc\x00\x00\x3f\xff",
"\x00\xff\x81\xff\xe0\x03\xf8\x0e\x00\x00\xff\xf8\x1f\xff\xfe\x00\x00\xff\x80\x00\x07\xff\xf8\x01\xff\xe0\x00\x0f\xf0\x01\xfe\x00\x3f\xf0\x7f\xe0\x00\x7f\xff\xe0\x1f\xff\xfc\x01\xff\xe0\x01\x80\x00\x07\xff\xff",
"\x00\x0f\xff\xf0\x00\x00\xe0\x0f\xf8\x00\x00\xff\xff\x80\x03\xff\xe1\xff\xff\x3f\xf8\x0f\xff\xc7\xe0\x00\x1f\xff\x00\x3f\xfe\x0f\xff\xf0\x03\x00\xc0\x00\x1f\xff\xfc\x3f\xff\xe0\x3f\xff\xf8\x1f\xf0\x00\x1f\xff\xc0",
"\x01\x80\x00\x1f\x01\xff\xff\x83\x00\x01\xfc\x00\x7f\xe0\x0e\x7f\xfe\x00\x00\x38\x00\xff\x00\x00\x3f\xff\x83\x83\xff\xc0\x00\x7f\xff\x80\x1f\xff\xf0\x1f\xff\xfc\x00\x03\x7f\xff\x81\xc0\x00\x07\xff\x83\xff\xff\x00\x00",
"\xff\x80\x0d\xff\xe0\x03\xff\xf0\x00\xff\xfc\x00\xf0\x01\xf8\x07\xff\xf8\x0f\x80\x0f\xff\xff\x00\xff\xff\x87\xff\xe1\xff\xfc\x67\x8c\x7f\xfe\x00\x03\xff\x3f\xfc\x07\x01\xff\xff\xe0\x00\x01\xff\xff\xc0\x0c\x40\x0f\xff\xff",
"\x00\x00\x1f\xff\xfe\x00\x1f\x00\x00\x1f\xff\xff\x07\xff\xff\xc0\x07\xff\xe0\x00\x02\x00\x00\xff\x00\x78\x00\x00\xe0\x00\x08\x00\x1f\xff\xff\x00\x03\xf8\x1f\x00\x00\x0f\xff\xc0\x00\x01\xff\xff\xe1\xf8\x00\x00\x3f\x80\x0f\xff",
"\x00\x0f\xf8\x00\xfc\x00\x03\xff\xff\x00\x00\x3f\xf0\x01\xff\xff\xe0\x7f\xf8\x00\xf8\x0f\xff\xff\x80\x00\x0f\xff\xfc\x0f\xff\xe0\x00\x00\xff\xc3\xff\xf0\x07\xff\xff\x00\x38\xf8\x00\x20\x1f\xfe\x3f\xfe\x00\xfe\x00\x7f\xff\xc0\x00",
"\x00\x3f\x00\xe0\x00\x0f\xff\xfc\x7f\xff\xfc\x00\x00\x7e\x00\x00\xff\xfe\x1f\xf0\x00\x1f\xf0\x00\x1f\xff\x87\xf0\x00\x3f\xc0\x0f\xff\x87\xff\x00\x3f\x81\xff\xff\xf7\xff\xe0\xff\xe0\x3f\x9f\xff\x00\x07\x00\x7f\xfc\x03\xff\xf0\x00\x00",
"\xe0\x3f\xff\xf0\xff\x80\x3e\x00\x03\xff\xe0\x00\x0f\xfc\x00\x07\xff\xf8\x00\x00\x7f\x80\x00\x0f\xf8\x01\xff\x7f\xff\xf0\x00\x3f\xff\xfe\x7f\xff\xe0\x00\xff\xc3\xff\xff\x00\x00\xf0\x00\x00\x7f\xff\x00\x3f\xff\xf0\x00\x01\xc0\x03\xff\xff",
"\x00\x03\xc0\x01\xff\xdf\xfd\xff\x9f\xfe\x1f\xff\xff\x00\x3f\xff\xfe\x00\x00\x7f\xcf\xff\xf0\x1f\xff\xfe\x07\xf0\x00\xff\xff\xe0\x00\x01\x00\x07\xff\x80\x1f\xe0\x00\x00\xff\xfe\x03\xff\xff\x80\x03\xf0\x0f\xff\xfe\x00\x00\x1f\xff\xf8\x00\x00",
"\x00\x1f\xff\xfb\xff\xfe\x00\x07\xff\xf0\x00\x00\xff\xff\x00\x00\x0f\xf3\xff\xfe\x00\x78\x00\x00\x3e\x00\x00\x3f\xff\xf8\x00\x1f\xff\xff\x80\x00\x03\xff\xff\x00\x07\xff\xee\x00\x1f\xfc\x00\x78\x00\x00\x1f\xff\x07\xff\xfe\x03\xff\xff\xe0\x00\x00",
"\x00\x7f\xff\xfe\x00\x00\x3f\xfc\x03\xff\xfc\x1f\xff\xf0\x7f\xd8\x03\xf0\x00\xfd\xfc\x38\x00\x08\x00\x10\x00\xe0\x06\x00\x7f\xfe\x00\x00\x0f\xff\x80\x00\x3f\x03\xff\xfe\xff\xff\xf9\xff\xf8\x00\x07\xff\xfc\x01\xff\xc0\x00\x03\xff\xff\xe0\x03\xff\xff",
"\xff\xf0\x0f\xff\xff\x00\x06\x00\xff\xff\xf0\x07\xff\xe0\x04\x00\x03\x00\x00\x03\xf0\xff\xff\x00\x03\xff\xfb\xff\xc3\xff\xf0\x07\xff\xff\xc7\x00\x7f\x80\x00\x03\xff\xf8\x00\x1f\xe1\xff\xf8\x63\xfc\x00\x3f\xc0\x9f\xff\xf8\x00\x00\x7f\xff\x1f\xff\xfc\x00",
"\x00\x3f\xff\xfc\x00\x0f\xc7\x80\x00\x02\x00\x1e\x00\x00\x60\x7f\x03\xfe\x00\x00\x1f\xff\x80\x1f\xf8\x00\x00\xff\xff\x80\x00\x03\xff\xc0\x00\x7f\xff\xc0\x7f\xe0\x03\xfc\x00\xff\xf7\xff\xff\x00\x00\x1f\xf0\x00\x03\xff\xff\xe1\xff\xff\x80\x0f\xf8\x00\x00\x1f",
"\x00\x01\xfe\x00\x03\x83\xf3\xff\xff\x80\x07\xff\xfc\x3f\xff\xfc\x03\xff\x80\x00\x06\x00\x00\x78\x00\x07\xff\xff\x80\x07\xfc\x01\xf8\x00\x07\xff\xff\xc0\x00\x38\x00\x07\xff\xfe\x3f\xff\xf8\x3f\xff\xcf\x3f\xfc\x00\x7f\xff\x00\x1f\xff\x80\x00\x30\x03\xff\xff\x00",
"\xf8\x00\x38\x00\x00\x3e\x3f\x00\x00\x3f\xff\xf0\x02\x00\x00\x0f\xff\xff\x80\x80\x03\xff\xc0\x00\x04\x00\x0f\xc0\x3f\xff\xfe\x00\x00\x3f\xff\xfe\x00\x3f\xff\xf8\x00\x30\x00\x7b\xff\x00\x00\x03\xff\xfc\x3f\xe1\xff\x80\x00\x70\x1f\xff\xc0\x07\xfc\x00\x1f\xff\xf0\x00",
"\x00\x03\xf8\x18\x00\x00\x70\x3f\xff\xf8\x00\x00\xff\xcf\xff\xff\xc0\x03\xff\xfe\x00\x10\x00\x00\xfe\x03\xff\xf8\x00\x00\x7e\x00\x00\x7f\x8f\xff\xc0\x00\x00\x7f\xff\xe0\x00\x3c\x07\xc0\x00\x00\x7f\xff\x01\xff\xf8\x01\xff\x80\x00\x0f\xff\xf9\xe0\x00\x3f\xff\xe0\x00\x00",
"\xff\xfe\x00\x3f\xc0\x1f\xff\xf0\x7f\xf8\x00\x01\xff\xf8\x1f\xff\xfe\x00\x00\xff\xff\xf8\x00\x7f\xff\x80\x3f\xff\xff\x00\x7f\xff\xf8\x00\x0c\x00\x00\x0f\xfe\x7e\x00\x3f\xe0\x18\x7f\xfe\x00\x00\x38\x00\x00\x3f\xff\xfe\x00\x00\x03\xfc\xff\xe1\xfe\x1f\xff\xfe\x00\x00\x07\xff",
"\x00\x00\x07\xff\xfe\x00\x00\x07\xfe\x00\x00\x3f\xe0\x00\x7f\xff\xc0\x00\x00\x7f\xff\xfc\x00\xfe\x00\x03\xff\xe0\x00\x1f\x0f\xfc\x00\x1f\xff\x80\x00\x07\xff\xff\xf0\x00\xff\xff\xf0\x00\x00\x1f\xff\xf8\x01\xff\xe0\x1f\xff\xff\x00\x1f\x80\x07\xf0\x00\x01\xff\xf8\x00\x01\xff\xff",
"\x00\x00\x3f\xff\xff\x03\xfe\x00\x00\x07\xc0\x00\x00\x7f\xfc\x0f\xf0\x00\x00\x1f\xff\xfe\x00\x00\x07\xc0\x00\x00\xff\xfe\x00\x00\x3f\xff\xfc\x01\xff\x7f\xfc\x00\x1f\xf8\x00\x1f\xff\x07\xff\xff\xe0\x00\x7f\xff\xfc\x01\xff\xff\xf0\x00\x01\xff\xf8\x00\x1e\x00\x00\x7f\xfc\x00\x3f\xff",
"\xfe\x3f\xff\x83\xff\xfe\x00\x07\xff\xff\xf0\x00\x3e\x00\x00\xff\xff\xfc\x00\x40\x3f\xfe\x00\x00\x03\xf0\x00\x00\x70\x3f\xf8\x0f\xff\xff\xe0\x1f\x80\x00\x03\xc3\xff\xff\xf0\x00\x01\xff\xf0\x0f\x80\x00\x0f\xe0\xff\xff\xfe\xf0\x00\x01\xff\xc0\x00\x00\x7f\xf0\x00\x00\x7f\xfe\xe0\x00\x00",
"\x00\x00\x03\xff\xf0\x01\xfc\x00\x00\xff\xff\x00\x00\x7f\xff\xff\x80\x07\xff\x8f\xff\x80\x00\x0f\xff\xf0\x00\x00\x3c\x00\x03\xc0\xff\xff\xfe\x01\xff\xff\x80\x0c\x7f\xff\xf8\x00\x00\x1f\xf0\x00\x00\x7f\x80\x00\x00\x80\x00\x00\xff\xff\xf0\x1f\xff\xe0\x00\xff\xff\xfe\x1f\xff\x1f\xc0\x00\x00",
"\xff\xff\xfe\x07\xff\xc0\x00\x06\x3f\x9f\xf0\x07\xff\xf0\x3f\xfe\x1f\xff\xff\x81\xff\xff\xc0\x00\x02\x00\xfe\x00\x04\x00\x07\x00\x00\x01\xff\xff\xfe\x00\x00\x07\xff\xfe\x00\x1f\xfe\x00\x00\xff\xff\xe0\x07\xf8\x00\xff\xff\xfc\x00\x3f\xf3\xff\xff\xc0\x00\x7f\xff\xe0\x00\x0f\xff\xfc\x07\xff\xff",
"\xff\xf0\x00\x00\x7e\x00\x1e\x03\xff\xff\x00\x00\x73\xff\xf0\x00\x00\x0f\xff\xdf\xff\xff\xdf\xfc\x00\x07\xfe\x07\xff\xfe\x00\x00\x1f\xdf\xef\xff\xf0\x3f\xff\xfc\x00\x00\x07\xff\xff\xf0\x00\x00\x7f\xe0\x07\xff\x80\x00\x00\x7f\xe0\x03\xff\xff\xf9\xff\xe0\x00\x00\x3f\xe3\xff\xff\xfc\x00\x00\x03\xff",
"\x00\x03\xff\x00\x00\x3f\xff\x80\x01\xf0\x00\x0f\xfe\x00\x00\x06\x00\x03\xff\xff\xfc\x03\xff\xff\xf7\x80\x00\x00\x7f\xc0\x0f\xff\xe3\xfe\x0f\x00\x00\x7f\xff\x00\x7f\xf8\x00\x00\xff\xff\xee\x00\x7e\x01\xc0\x00\x1f\xe0\x00\x07\xff\xff\xf8\x00\x00\xe1\xff\xfc\x3f\xe7\xff\xff\xf8\x3f\xff\xfc\x00\x1f\xff",
"\x00\x00\x0f\xff\xf8\x00\x00\xff\xff\xfc\x00\x1f\xe0\x07\xff\xff\x00\x01\xff\xdf\xff\x80\x00\x3f\xff\xfc\x00\x00\x0f\xfc\x07\xff\x00\x00\xff\x80\x00\x03\xff\xff\xf0\x00\x07\xff\xff\xf0\x00\xff\xfe\x1f\xff\xff\xe0\x3f\xff\xfe\x00\x00\x60\x00\x00\xff\xff\x7f\xff\xf0\x00\x03\xff\xff\xc0\x07\x00\x01\xff\xff",
"\x00\x00\x20\x7f\xfe\x0f\x83\xff\xff\x80\x03\xff\x00\x00\x00\xff\xff\xe0\x00\x1f\xff\xff\xe0\x00\x3f\xfe\x7f\xff\xf0\x00\x1f\xff\xff\xe0\x00\x00\xff\xff\x87\xff\xc0\x00\x17\xfd\xff\x9f\xff\xfb\xff\xff\xe0\x00\x03\xe0\x00\x07\xff\x9f\xff\xff\x80\x00\x7f\xff\xff\x00\x01\xff\xff\xc0\xff\xff\xc0\x10\x00\x00\x1f",
"\x00\x00\x07\xff\xc0\x00\xff\xe0\x00\x07\xff\x80\x03\x80\x00\x0f\xf8\x00\x00\x7f\xff\xfe\x00\x00\x18\x00\xff\xf0\x20\x01\xff\xfe\x00\x00\x60\x0f\xf0\xe0\x03\xff\xfe\x00\x3e\x1f\xff\xfc\x00\x03\xff\x80\x00\x00\xff\xf8\x00\x01\x00\x00\x0f\xf3\xff\xfc\x00\x03\xff\xff\xe1\xff\xff\xc1\xf0\x00\x00\xff\xff\xff\x00\x00",
"\xff\xff\xf0\x00\x00\x07\xff\xfc\x00\x7f\x87\xff\xff\x00\x00\x00\x7f\xff\xc0\x7f\xff\x80\x00\x03\xf0\xff\x3f\xff\x80\x30\x07\xff\xff\x1f\x8e\x00\x7f\xff\xff\xc0\x01\xff\xfc\x07\xf8\x00\x00\x7f\xff\xfc\x00\x3f\xf0\x00\xf8\x00\x00\x07\xff\x00\x00\x0e\x00\x0f\xff\x80\x00\x7f\xc0\x01\xff\x8f\xf8\x00\x07\x01\xff\xff\xff",
"\xff\x80\x3f\xff\x3f\xfe\x00\x00\xff\xff\xff\x9f\xff\xf8\x3f\xff\xf8\x00\x00\x0f\xf8\x00\x00\x03\xfe\x00\x7f\xff\xff\x00\x0f\xff\x01\xff\xf0\x0f\xff\xe0\x20\x7f\xff\xfc\xff\x01\xf8\x00\x07\xff\xe0\x00\x7f\xf8\x00\x0f\xff\x80\x00\x00\x7f\xe0\x00\x3f\xf8\x01\xfe\x00\x07\xff\xf0\x00\x00\x7f\xff\xff\xc0\x00\x01\xff\xff\xff",
"\x00\x7f\xff\xe0\x00\x01\xff\xff\xf8\x00\x00\x3f\xff\xfc\x00\x7f\xfe\x00\x00\x03\xff\xff\xf0\x03\xff\xe0\x00\x7f\x80\x00\x0f\xff\x3f\xf8\x00\x00\x7f\xff\xff\x00\x07\x80\x1f\x83\xf8\x00\x00\x0f\xfe\x3f\xff\xc0\x3f\xff\xfe\x1f\xe0\x00\x07\xc0\x03\xff\xf0\x0f\xc0\x00\x03\xff\xff\x80\x00\x00\x7f\x80\x00\x00\xff\xff\x80\x00\x00",
"\xfe\x00\x00\x20\x00\x04\x00\x0f\xff\xff\xc0\x01\xff\xf8\x3f\xc0\x00\x00\xff\xff\xc0\x00\xff\xff\xff\x80\x00\x3f\xf8\x00\x7f\xff\xfe\x7f\xf8\x00\x7f\xff\x80\x07\xff\xc0\x00\x0f\xff\xf8\x00\x7f\xff\xc0\x00\xff\xff\xc0\x3f\xff\xff\xe0\x0f\xff\xff\xe0\xe0\x1f\xff\x80\x00\x00\x7f\xff\xc0\x71\xff\xff\xfc\x00\x01\xff\xff\xf8\x00\x00",
"\xff\xff\xe0\x00\x0f\xff\xf0\x00\x00\x3f\xff\xff\xc0\x00\xff\xff\x00\x00\x0f\xff\xff\xe0\x00\x01\xff\x00\x00\x1f\xff\xe0\x3f\xfc\x00\x03\xe0\x1f\xf0\x1f\xf8\x00\x00\x3f\xff\xff\xc0\x0f\xfe\x00\x00\x20\x00\x00\xff\xfc\x00\x0f\xff\xfe\x3f\xff\xff\x00\xff\xf0\x00\x00\x80\x00\x1f\x03\xe0\x01\xff\xfa\x00\x3f\xe0\x00\x00\x70\x00\x00\x0f",
"\xfd\xff\xc0\x00\x20\x01\xfe\x00\x3f\xf8\x00\x03\xff\x00\x00\x03\xf8\xff\xcf\xc3\xff\xff\xfc\x00\x03\xff\xff\xfc\x00\x00\x78\x3f\xff\xf0\x01\xff\xe0\x0f\xff\xff\x00\x00\x07\xff\xff\xfc\xff\xff\xf8\x00\x01\xff\x80\x00\x07\xff\xff\xfc\x00\x00\x1c\x00\x01\xff\xff\x07\xf8\x00\x00\x1f\xff\xff\xf0\x00\x01\xfe\x00\x7f\xff\xf0\x1f\xfc\x00\x00",
"\x00\x00\x00\x3f\xff\x00\x00\x07\xff\xff\xfc\x00\x00\x01\xe0\x0f\xff\x83\xfc\x01\xff\xff\xf0\x0f\xff\x00\x00\x00\x3f\xff\xff\xc0\x1f\xff\xff\xe0\x00\x20\x00\x00\x3f\xff\xff\x00\x00\xff\xfc\x03\xff\xff\xf0\x00\x1f\xff\xfc\x00\x00\x03\xff\xff\xc0\x7f\x80\x00\xff\xff\xff\x00\x0f\x0f\xff\xff\xe0\x00\x00\xff\xf8\x00\x00\xff\xfe\x00\x00\x0f\xff",
"\xff\xfe\x03\x80\x00\x03\xff\xff\xc0\x3f\xff\xff\x00\x03\xff\xff\xf8\x00\x00\x01\xff\xff\xcf\xfc\x00\x00\xe0\xef\xf8\x00\x0f\xff\xe3\xf8\x00\x3f\xff\xff\x80\x3f\xbf\xfe\x00\x00\xff\xfc\x00\x00\x01\xff\x00\x00\xcf\xc0\x01\xfc\x00\x00\x7f\xff\xff\xc0\x00\x10\x7f\xff\xfc\xfe\x00\x00\x07\xc0\xff\xff\xff\x8f\xff\x00\x00\x1f\x9e\x00\x00\x01\xff\xff",
"\x00\x7f\xff\xe0\x00\x01\xff\xff\xc3\xff\x80\x01\x03\xfc\x00\x00\x00\xfc\x01\xff\xff\xf8\x7f\xe7\xf0\x00\x00\x7f\xc0\x3f\xff\xff\xc0\x00\x00\x1f\xff\x80\x00\x01\xff\xe0\x00\x00\x70\x00\x00\x1c\x7f\xff\xf8\x1f\xfc\x00\x00\x07\xef\xe0\xff\xff\xc1\xff\xfc\x00\x00\x01\xff\xff\xff\xa0\x07\xff\x00\x1e\x00\x1f\xfc\x00\x00\x38\x00\x18\xc0\x00\x00\x7f\xff",
"\x00\x0f\xff\xf8\x00\x00\x07\xff\x00\xfc\x00\x00\x03\xff\xfc\x00\x07\xff\xff\xe0\x00\x00\xff\xfc\x0f\xff\x00\x00\x0f\xff\xfe\x0f\x80\x07\xff\x03\xff\xff\xf9\xff\xfe\x00\x00\x03\xf8\x00\x00\x07\xe0\x00\x00\xc0\x00\x1f\xff\xf0\x7f\xff\xff\xc0\x07\xff\xff\xfc\x00\x00\x3f\xff\xff\xe0\x00\x00\x1f\xff\xf8\x1f\xfe\x00\x00\x3f\xff\xff\xe0\x3f\x06\x00\x00\x00",
"\xff\xf0\x00\x08\x00\x0f\xef\xff\xff\xfc\x00\x00\x7f\xff\xf0\x00\x7f\xff\xf8\x00\xff\xff\x81\xff\xff\xe0\xff\xff\xff\x00\x00\x00\x80\x00\x03\xff\x80\x3f\xff\xfc\x00\x00\x1f\xff\xc0\x0f\xff\xfe\x00\x00\x00\x73\xf0\x1f\xfe\x00\xff\xc0\x3f\xff\x00\x3f\xff\x83\xff\xfe\x01\xff\xff\xf7\xff\xff\x80\x00\x00\x3f\x00\x00\x1f\xe3\xff\xff\xf0\x00\x0f\xff\xf0\x00\x00",
"\x00\x00\x7f\xfc\x00\x7f\xe0\x00\x0f\xff\xe0\x01\xf8\x00\x3f\xff\x00\x00\x78\x00\x7f\xe0\x00\x00\x1f\x00\x07\xff\xff\xf8\xf9\xf0\x01\xff\xf8\x07\xc0\x0f\xff\xf8\x00\x07\xf8\x7f\xfe\x00\x00\x0f\xff\xe3\xf0\x00\x07\xff\xff\xfc\x03\x1c\x00\x00\x7f\xe0\x00\xff\xff\xfc\x00\x00\x0f\xf3\xff\xe0\x00\x00\x0f\xff\xf9\x00\x00\x10\x00\x3f\xff\xfc\xf8\x7f\xff\x00\x00\x00",
"\x00\x03\xff\xff\xc0\x7f\xff\xff\xc0\x00\x03\xff\xff\xff\x00\x00\x0f\xff\xf0\x1f\xff\xf0\x00\x07\xff\xff\xef\xff\x81\xf7\xff\xfe\x00\x07\xff\xf0\x00\x00\x1f\xff\xc0\x0f\x80\x00\x0f\xff\xfc\x00\x00\xff\xff\xff\xc0\x03\xff\xe3\xff\xff\xfe\x00\x1f\xff\xff\x00\x00\xff\xff\xff\x0f\xff\xf1\xf8\x00\x00\x01\xff\xff\xff\x80\x1f\xff\xfe\x00\x08\x00\x00\x7f\xff\xff\x80\x00",
"\x1f\xe0\x00\x7c\x1f\xc0\x07\xff\xc0\x07\xff\xff\xfe\x00\x3c\x00\x00\x00\xff\xff\x80\x00\x07\xff\xff\x00\x1f\xf8\xff\xc0\x00\xff\x81\xff\x01\xff\xfe\x00\x78\x7f\xff\xf0\x00\x01\x80\x00\x00\x1f\xff\x00\x00\x1f\xff\xf0\x00\x1f\xff\xff\xe0\x00\x3c\x00\x00\x1f\xff\xff\x80\x03\xff\xe0\x01\xff\xff\xf9\xff\xf8\x00\x00\x7c\x00\x00\xfe\x00\x00\xff\xff\xff\x00\x00\x0f\xff\xff",
"\xfc\x00\x01\xff\x00\x00\x0c\x00\xff\xff\xe3\xff\xff\xf0\x80\x0e\x0e\x00\x00\x0f\xfe\x00\x03\xff\xc0\x00\x00\x7f\xff\xff\xe0\xc0\x00\x00\x07\xe0\xff\xff\x03\x9f\xff\xff\xc1\xc0\x00\x03\xff\xff\xc3\xff\xff\xfc\xff\xff\xc0\x00\x01\xfc\x00\x0f\xfc\x00\x00\x00\x7f\xff\xff\x03\xff\xff\xfc\x0f\xff\xfe\x00\x00\x03\x80\x3f\xff\xff\x00\x00\xff\xff\xf8\x00\x03\xff\xff\x80\x00\x00",
"\xff\xff\x80\xff\xff\xf8\x00\x00\xfc\x00\x03\xff\xf8\x00\x0f\xff\xff\x00\x03\x00\x00\x00\x7f\xff\xf0\x00\x3f\xff\xf0\x00\x01\xfc\x01\x00\x03\xff\x80\x1f\xff\xe3\xff\xff\xf8\x00\x1f\xff\xff\xf8\x01\xff\xdf\xff\xfb\xff\xc0\x00\x00\x3f\xff\xf8\x00\x00\x80\xc7\xff\xff\xf8\x0f\xff\x00\x60\x1f\xff\xe0\x00\x01\xff\xff\xfe\x0f\xff\xff\xfc\x00\x00\x00\xf0\x06\x03\xff\xff\xfe\x00\x00",
"\xff\x00\x0f\xff\xfc\x00\x0f\xff\xff\xfc\x07\xff\xfc\x00\x00\xf0\x00\x00\x80\x00\x7f\xfe\x00\x00\x0f\xff\xff\xfc\x3f\x00\xff\xff\xff\x00\x1f\xff\xff\xf0\x00\x1f\xff\xff\xe0\x00\x1f\xff\xff\xc3\xff\x00\x00\x01\xff\xff\xf0\x00\x00\x0f\xff\xe0\x07\xfc\x00\x00\x00\xfe\x00\x07\xff\xff\xf8\x00\x00\x3f\x00\x00\x0f\x80\x00\x3f\xff\xc0\x00\x11\xff\xef\x00\x07\x00\x7f\xff\xfc\x00\x00\x00",
"\xfe\x00\x00\x7f\xf7\xff\xff\x00\x00\x0f\xff\xff\xe0\x01\xff\xe0\x00\x00\x03\xff\xe0\x00\xff\xfe\x00\x01\xff\xf7\xff\xf8\x00\x0f\xff\x00\x00\x00\x38\x00\x07\xff\xf8\x07\xff\xfc\x00\x1f\xff\xff\x0f\xc1\xff\xff\xc0\x00\xff\xff\x0f\xff\xf0\x01\xff\xf8\x00\x01\xff\xff\x80\x00\x00\x0f\xf8\x00\x3f\xff\xfe\x3f\xff\xff\xf0\x00\x00\x38\x0f\xc3\xff\xff\xff\x1f\xff\xc0\x3f\xff\xff\xe0\x00\x00",
"\xff\xff\xf0\x00\x7f\xc0\x07\xff\xff\x81\xff\xc0\x00\x01\xff\xff\xff\xc0\x00\x00\x1f\xff\xfe\x03\xc0\x00\x0f\xff\xff\xfc\x00\x03\xff\xff\xff\x83\xff\xc0\x00\x07\xf0\x00\x00\x1f\x80\x00\x00\x3f\xff\xff\xe7\xff\x00\x07\xff\xff\xfc\x00\x00\x1f\xff\xf8\x00\x03\xf0\x00\xff\xfc\x00\x1f\xff\xff\xe0\x03\xff\xf0\x00\x01\xff\xff\xff\x80\x00\x00\x0f\xff\xff\xf8\x10\x00\x1e\x03\xff\xff\xff\x80\x00",
"\x00\x01\xff\xfe\x00\x00\x00\x7f\xff\xff\xf0\x00\x00\x1f\xff\x80\x07\xff\xff\xff\x80\x00\x01\xff\xfc\x03\xff\xff\x9f\xff\xfe\x00\x08\x00\x00\x06\x00\x00\x00\x81\xff\xff\xc0\x00\x07\xf8\x00\xff\xff\xff\x00\x00\x00\xff\xff\xf8\x00\x3f\xff\xff\xf8\x3f\xf8\x00\x00\x00\x7f\xff\xc0\x7f\xff\xff\xc0\xff\xff\xfe\x00\x00\x01\xff\xf8\x00\x00\x03\xff\xc1\xff\xf0\x00\x00\x03\x07\xf8\x01\xff\xfe\x00\x00",
"\xf8\x00\x00\x01\xff\xff\x80\x00\x0f\xff\xff\x8f\xff\x00\x1f\xff\x0f\xff\xc0\x00\x00\xff\xff\xfc\x00\x0f\xff\xf0\x00\x70\x00\x00\x3f\xff\xc0\x00\x00\x7c\x00\x00\x7e\x00\x0f\xfc\x00\x00\x00\xff\xff\x80\x00\x00\x0f\xff\xff\xfc\x38\x00\x00\x03\xf0\x00\x31\xf0\x1f\xff\xff\xc0\x07\xff\xff\xe0\x1f\xff\xff\xf3\xfe\x00\x00\x00\xff\xff\xc0\x00\x1f\xe7\xff\xe1\xff\xff\xdf\x00\x00\x00\x1f\xff\x00\x00\x00",
"\x3f\xff\xff\xf8\x00\x00\x00\x60\x00\x0f\xff\xff\xe0\x07\xff\xff\xff\x00\x00\x3f\xff\xff\xf0\x1f\xff\xff\x80\x00\x70\x00\x00\x01\x00\x00\x00\x3f\xff\xfe\x00\x00\x00\x1f\xf8\xfc\xc0\x0f\xff\xf8\x00\x3f\xff\xc0\xff\xff\x80\x00\x03\xff\xff\xf8\x00\x3f\xff\xfc\x00\x00\x0f\x81\xff\xc0\x03\xff\xc0\x3f\xff\xff\x80\x03\xff\xfe\x00\xff\xff\xfe\x00\x00\x1c\x00\x00\x00\x3f\xff\xff\xf8\x00\x7f\xff\xc0\x00\x00",
"\x00\x00\x00\x0f\xff\xfe\x0f\xff\xff\x87\xff\xff\xff\x00\x80\x00\x0f\xff\xc0\x00\x03\xf0\x1f\xf7\xe0\x00\x00\x70\x00\x01\xff\xff\xff\x80\x01\xfe\x07\xf0\x00\x01\xff\xfc\x00\x00\x04\x00\x01\xff\xfe\x07\xff\xff\xfe\x00\x07\xc0\x00\x00\xff\xff\xff\x87\xf0\x03\xff\xfc\x00\x00\x1f\xf8\x00\x01\xff\xff\xc0\x00\x00\x3f\xff\xc0\x00\x00\x7f\x8f\xff\xf8\x00\x00\x00\x7f\xff\xe0\x06\x0e\x00\x00\x0f\xff\xff\x80\x00",
"\x03\xff\xff\xfd\xe0\x00\x00\x1f\xff\xf8\x01\xff\xff\xfb\xff\xff\xe0\x01\xf0\xf0\x00\x00\xff\xff\xff\x80\x00\x00\x7f\xff\xff\xe0\x1f\xff\xff\xf0\x01\x80\xff\xff\xff\xe0\x00\x00\x7f\xff\xf0\xff\xff\xc0\x00\x00\x07\xfe\x00\x00\x00\x1e\x00\x1f\xff\xff\xe0\x1f\xff\xe0\x01\xff\xc0\x00\x3f\xff\xe0\x00\x00\x0f\xf0\x00\xff\xff\x7f\xc0\x1f\xf8\x3f\xff\xff\xc0\x00\x00\x7f\xff\xe0\xff\xfc\x00\x00\xff\x0f\xff\xff\xff",
"\x07\xff\xfc\x03\xff\xff\xff\xdf\xff\xff\x87\xff\x18\x00\x03\x80\x01\xff\x00\x00\x00\x1f\xff\x00\x00\x3f\xff\xff\xc0\x1f\xe0\x3f\xff\xff\xfc\x00\x00\x01\xff\xf8\x00\x00\x3f\xff\xff\xf8\x00\x00\x07\xe0\x00\x07\xff\xf9\xff\xe0\x00\x3f\xe0\x00\x7f\xef\xf0\x00\x07\x81\xff\xfc\x00\x00\x00\xff\xe0\x00\x30\x00\x00\x00\xff\xff\xf0\x00\x00\x03\xff\xfc\x7f\x07\xf8\x03\xff\xff\xff\x00\x3f\xfc\x00\x00\x01\xff\xc0\x00\x00",
"\x00\x7f\xfc\x00\x00\x03\xff\xf8\x00\x00\x61\xfe\x7f\xff\xfe\x00\x00\x1f\xff\xfc\x3f\xff\x80\x01\xff\xff\xff\xe0\x00\xff\xff\xff\x80\x1f\xf8\x00\x7f\xff\xff\xf8\x00\x00\x07\xff\xff\xe0\x00\x00\x07\xff\xff\xff\x80\x00\xff\x80\x0f\xff\xff\xfc\x00\x00\x7f\xff\xfe\x00\x00\x00\x30\x00\x00\x7f\x80\x00\x07\xff\xff\xf0\x00\x00\x03\xff\xc0\x0f\xff\xff\x80\x3f\xff\x80\x03\xff\xff\xfe\x03\xff\xff\xff\x7f\xfc\x1f\xf0\x00\x00",
"\x1f\xf0\x00\x00\x7f\xff\xfe\x02\x00\x00\x03\xff\xff\xff\xd8\x07\xff\xff\xe0\x01\xff\xff\x80\x00\x00\x07\xc0\x00\x0f\xff\xc0\x7f\xf0\x00\x07\xff\xff\x80\x00\x07\xf0\x00\x00\x7f\xfc\x03\xff\xff\xff\xc0\x00\x01\xff\xff\xf9\xff\xfe\x00\x00\x1f\xff\xc0\x00\x00\x03\xfe\x3f\xff\xff\x00\x07\xfe\x00\x00\x03\xc0\x00\x3f\xf8\x00\x10\x03\xfc\x00\x0f\xff\xc0\x00\x7f\xff\xe0\x00\xff\xf0\x00\x00\x7f\xff\xe0\x00\x00\x0f\xff\xff\xff",
"\xff\xff\xff\xf8\x00\x00\x60\x00\x00\x00\xff\xff\xfc\x03\xff\xfc\x00\x00\x3c\x00\x3f\xe0\x7f\xf8\x00\x07\xff\xf8\x0f\xf8\x00\x00\x7f\xff\xff\xfc\x00\x7f\xc2\x00\x03\xff\xff\xfe\x00\x01\xff\xff\xff\xf0\x03\xff\xff\xf0\x18\x07\xc0\x00\x0f\xff\xc0\x00\x00\x7f\xff\xff\x87\xe0\x00\x00\x07\x00\x1f\x80\x04\x07\xff\xe0\x00\x00\x1f\xff\x81\xff\x80\x00\x03\xff\xfc\x00\x00\x07\xff\xff\xff\xc0\x00\x00\x1f\x80\x01\xff\xff\x00\x00\x00",
"\x00\x0f\xfc\x1f\xf8\x00\x0f\xff\xff\xf8\x07\xff\xf1\xfc\x00\x1f\xff\xff\xf0\x00\x0f\xff\xdf\xff\xff\xff\x00\x00\x03\xff\xff\xff\x00\x01\xff\xff\xff\xc0\x10\x0f\xf0\x00\x00\x00\xfc\x00\x1f\x00\x07\x00\x01\xf0\x00\x00\x1f\xe0\x00\x00\x30\x7c\x3f\xff\xe0\x00\xff\xfc\x07\xff\xfc\x00\x3f\xff\xff\xf8\xff\xff\xc1\xfc\x1f\xff\xff\xf8\x00\x01\xff\xfc\x00\x00\x0f\xff\xff\xff\x00\x00\xff\xf8\x0c\x00\x00\x07\xff\xff\x00\x00\x00\x7f\xff",
"\xff\xff\x80\xff\xff\x00\x00\x00\x7f\xff\xff\x00\x1f\xfc\x00\x06\x00\x0f\xf8\x00\x00\x01\x80\x00\x00\x7f\xff\xff\xe0\x3f\xff\xff\xfc\x00\x60\x00\x00\x00\xfe\x00\x00\x07\xff\xff\xf0\x7f\xff\xff\xf8\x00\x00\x80\x00\x00\x0f\xff\xff\xff\xbf\xff\xff\xc0\x07\xff\xfe\x00\x00\x1c\x00\x1f\xfc\x07\x00\x01\xff\xff\x00\x00\x00\x80\x00\x1f\xff\x03\x80\x00\x00\x3f\xff\xff\xf8\x00\x07\xff\xff\xff\x80\x00\x1f\xff\xff\xe0\x1f\xff\xff\xc0\x00\x00",
"\xff\xff\xff\xf8\x00\x00\x00\x7e\x00\x00\x00\x1f\xff\x80\x07\xff\xff\xff\x80\x00\x00\x0f\xff\xff\xc3\xff\xf0\x00\x00\x04\x7f\xc0\x7f\xf0\x00\x3f\xff\x80\x00\x7f\xe0\x00\x03\xff\xc0\x00\x07\xff\x00\x00\x0f\xff\x80\x00\x00\x07\x80\x00\x00\x0f\xff\xff\xff\x01\xff\xff\xff\xc0\x03\xc0\x00\x00\x03\xff\xff\xe0\x00\x0f\xff\xff\xc0\x00\x03\xff\xfe\x00\x03\xff\xf8\x00\x00\x0f\xff\xff\xc0\x01\xff\xe0\x00\x00\xff\xff\xfc\x00\x00\x1f\xff\xff\xff",
"\xff\xf0\x00\x3f\xfc\x00\x00\xff\xff\xff\xe0\x1f\xc3\xfe\x00\x07\xff\xf8\x00\x0f\xf0\x01\xff\xff\xf0\x00\x00\xff\xc0\x0f\xff\xff\x80\x00\x00\xff\xff\xf3\xff\x80\x00\x00\x80\x08\x38\x00\x00\x0f\xff\xf0\x00\x1f\xff\xff\xfc\x00\x0f\x80\x00\x70\x00\x00\x31\xff\xff\xfe\x3f\xff\xf8\x00\x00\x00\x3c\x3f\xf0\x0f\xff\xff\x00\x03\xff\xfb\xff\xff\xff\x00\x0f\xff\xff\xfe\x00\x00\x00\xf0\x00\x00\x00\xff\xff\xfc\x00\x7f\xff\xf0\x00\x01\xff\xff\xfe\x00",
"\xff\xff\xf0\x0f\xf8\x3f\xff\xff\xe0\x00\x03\xff\xfe\x00\x00\x3f\xff\x80\xff\xff\xff\x00\x00\x00\xff\xff\xf8\xff\xff\xf0\x00\x0f\xf1\xff\xff\x00\x00\x00\x0f\xff\xfc\x00\x00\x00\x1f\xf0\x00\x00\x1f\xf0\x03\xff\xff\xff\xe1\xff\xe0\x00\x00\x1f\xff\xc1\xfe\x00\x00\x07\xff\xfc\x00\x00\x00\x1f\xfe\x3f\xc0\x00\x00\x01\xff\xfc\x00\x00\x1f\xff\xff\x0f\xe0\x00\x01\xfc\x00\xfe\x00\x00\x00\x7f\xe0\x00\x1f\xff\xff\xe0\x7f\x00\x0f\xf0\x00\xff\xfe\x00\x00",
"\x00\x7f\xff\xc0\x07\xff\xff\xff\x80\x07\xff\xff\xfe\x00\x00\x00\x7e\x00\x00\x00\x0f\xff\x80\x1f\xff\xfe\x07\xff\xff\xf0\x03\xc7\xff\xff\xfe\x00\x00\x00\x7f\xfe\x00\x00\x1f\xff\xfe\x00\x00\x00\xff\xfc\x00\x1f\xff\xc0\x00\x00\x3f\xff\x00\x1e\x00\x00\x03\xff\xff\xff\x80\x00\x00\x7f\xff\xf8\x03\xff\xfc\x00\x01\xff\xff\xfe\x00\x0f\xff\x02\x00\x07\xff\xff\xfe\x00\x00\x00\xfe\x01\xff\xff\xf7\xff\xff\x19\xff\xff\x00\x00\x07\xff\xc1\xff\x00\x00\x07\xff",
"\x00\x00\x00\x40\x00\x00\x07\xff\xff\xf8\x1f\xff\xff\xc0\x00\x3e\xff\xff\xf0\x00\x00\x00\x7f\xff\xfe\x00\x00\x00\xff\x80\x00\x00\x3f\xf1\xff\xff\xff\xe0\x00\x00\x01\xff\xff\xff\x00\x00\x1f\xff\xf8\x00\x07\xff\xff\xf8\x00\x1f\xff\xc1\xff\xff\xff\xe0\x01\xff\xff\xff\xe0\x00\x00\x07\xff\xff\xff\xcf\xff\xe0\x00\x3f\xe0\xff\xff\xc0\x00\x07\xff\xff\xe0\x01\xff\xfc\x3f\x00\x01\xff\xff\xfe\x00\x01\xff\x0f\xff\xff\xfc\x00\x00\x01\xff\xff\x80\x00\x00\x0f\xff",
"\xff\xff\xf0\xff\xff\xff\xc0\x03\x80\x00\x01\x00\x00\x03\xff\xff\xff\xf1\xff\xff\xff\xe0\x07\xfc\x00\x00\x03\xff\xf0\x00\x00\x00\x7e\x00\x00\x00\x07\x00\x3f\xff\xfc\x00\x0f\xc7\xff\xff\x00\x00\x07\xff\xff\xc0\x00\x00\x03\xff\xfc\x00\x00\xff\xe0\x00\x00\x00\x7f\xf0\x00\x00\xff\xff\xff\xfd\xff\x00\xff\xe0\xff\xff\xe0\x07\xff\xff\xf8\x7f\xff\xfe\x18\x00\x00\x01\xf0\x00\x1f\xff\xfe\x01\xc0\x01\xff\xff\xff\xf8\x00\x01\xfe\xff\xff\xff\x80\x00\x00\x07\xff\xff",
"\xff\xff\xf0\x00\x1f\xff\xff\x00\x00\x3f\xff\xf0\x00\xff\xff\x00\x07\xff\xff\xf8\x03\xff\xff\xe7\xff\xff\xff\x81\x00\x00\x01\xff\x00\x00\x3f\xff\xff\xf8\x00\x00\x00\xf0\x07\xff\xc0\x0f\xf0\x00\x3f\xff\xc0\x00\x7f\xf8\x00\xff\xfc\x00\x00\x1f\xff\x80\x1f\xfc\x00\x00\x01\xff\xff\x00\x00\x7f\xff\xff\xfe\x00\x00\x0f\xff\x80\x00\x00\x0f\xff\xff\xfc\x00\x00\x00\xff\xff\xfc\x00\x0f\xff\x80\x00\x3f\xff\xfe\x00\x00\x3f\xe0\x0f\xff\xff\x00\x00\x01\xff\xf0\x00\x00\x00",
"\xff\xc0\x00\x00\x03\xff\xff\xfc\x00\xff\xff\xfc\x00\x00\x00\x7f\xff\xff\x00\x00\x00\x1f\xe0\x00\x0f\xff\xc0\xf0\x00\x00\x7f\xff\xff\xe0\x00\x20\x1f\xff\xff\xff\x00\x00\x00\x1f\x80\x00\x00\x07\xff\xf1\xff\xff\xff\xc0\x00\x00\x1f\xff\xff\xf0\x00\x3f\xff\xf8\x00\x3f\xff\xff\xfe\x01\xff\xff\xfe\x7f\x9e\x00\x1f\xff\xfc\x00\x7f\xe0\x7f\xff\xff\xe0\x00\x7f\xff\xfe\x00\x00\x01\xff\xff\xff\xf8\x01\xff\xc0\x03\x00\x0f\xff\xf8\x00\x00\x0f\xf0\x0f\xff\x00\x00\x00\x0f\xff",
"\x00\x03\xff\xff\xff\xcf\xff\xf8\x7f\x8f\xff\xff\xfc\x01\xff\xff\xfc\x00\x00\x1f\xff\xff\xff\x80\x00\x00\x01\xff\xff\xe1\xff\xf0\x00\x00\x00\xff\xff\xff\xf8\x03\x80\x00\x3f\x80\x00\x0f\xff\xff\xff\xc0\x00\x00\x02\x7f\xff\xf8\x03\xff\xc0\x00\x00\x3f\xff\x80\x00\x00\x01\xff\xff\xe0\x00\x00\x03\x80\x00\x00\xff\xe0\x7f\xff\xff\xfc\x00\x00\x01\xff\xff\xfc\x00\x00\x00\xff\xff\x80\x00\x07\xfe\x00\x00\x07\xff\xf0\x00\x00\x1f\x80\x00\x00\x3e\x1f\xff\xff\xff\x9f\xff\xff\xff",
"\xff\xff\xfe\x00\x03\xff\xff\xff\x80\x01\xff\xff\xff\xa0\x3f\xff\xf8\x00\x7f\x03\xff\xff\xc0\x00\x0f\xff\xc3\xff\xf8\x00\x03\xff\xff\xff\xc0\x7f\xf0\x1f\xe0\x0f\xff\xff\xc0\x00\x1f\xfe\x0f\xff\xff\xe0\x00\x07\xff\xff\xfc\x00\x00\xfc\x00\x3f\xff\xff\xf0\x00\x00\x00\x3f\xfc\x00\x00\x00\x0f\xff\xc4\x00\x00\xff\xc0\x00\x03\xff\xff\xff\x80\x00\x03\xfc\x0f\xff\xff\xf0\x00\x00\x03\xff\xff\xc0\x07\xff\xff\xf8\x0c\x3f\xff\xf0\x00\x1f\xff\x80\x00\x00\x01\xff\xfe\x00\x00\x3f\xff",
"\x00\x00\x00\x1f\xff\xc3\xff\xff\x80\x00\x00\x3f\xff\xff\xfe\x00\x00\x00\x0f\xe0\x00\x7f\xff\xe0\x0f\xfe\x00\xff\xff\xff\xe0\x03\xff\xf8\x00\x00\x00\x3f\xe0\x00\x00\x01\xff\xff\xe7\xff\xff\xff\xe0\x00\x00\x07\xff\xff\xc0\x00\x1f\xe0\x00\x00\x01\xff\xff\x00\x00\x03\xff\xff\xff\xe0\x00\x00\x1f\xe0\x03\xff\xff\x00\x00\x00\x07\xff\xf0\x3f\x80\x00\x00\x0f\xff\xff\xfe\x00\x00\xff\xff\xff\x00\x00\x3f\xff\xff\x80\x0f\xff\xfe\x01\xff\xff\xff\xf8\x3f\xff\xff\xc0\x00\x00\xff\xff\xff",
"\xff\xff\xff\x80\x00\xff\xff\xff\xf0\x00\x00\x1f\x00\x3f\xff\xfe\x0f\xf0\x1c\x00\xff\xff\xe0\x00\x0f\xc0\x1f\xff\xc0\x00\x00\x01\xff\x80\xf7\xff\xf8\x00\x00\x3f\xff\xc0\x00\x00\x01\xff\xf0\x00\x03\xe3\xfc\x00\x07\xf8\x00\x00\x3f\xff\xff\xfe\x00\x00\x1f\x00\x00\x00\x18\x00\x3f\xff\xff\xf8\x0f\xe0\x00\x00\x00\x60\x00\x07\xff\xff\xfe\x00\x60\xff\xff\xff\xf8\x00\x00\x3f\xff\x80\x00\x00\x3f\xff\xff\xf0\x00\x00\x0f\xf0\x00\xff\xff\xf1\xff\x00\x3f\xff\xff\xff\x01\xff\xe0\x00\x00\x00",
"\xff\xff\xff\xfc\x00\x00\x00\xff\xff\xf0\x01\xff\xff\xf8\x00\x00\xff\xff\xff\xe7\xff\xf8\x01\xf8\x7f\xff\xff\x80\x00\x3f\xff\xfc\x00\x00\x01\xff\xff\xe0\x00\x3f\xc0\x00\x00\x7f\xff\xff\x00\x00\x0f\xff\xff\xff\xc0\x00\x0f\xff\xf1\xff\xff\xff\xf8\x00\x0f\xff\xff\xfc\x00\x00\x07\xff\xff\xff\x80\xff\xff\xf8\x07\xf0\x00\x00\x00\x1f\xff\xff\xff\xc0\x00\xff\xff\xff\xc0\x00\x0e\xff\xff\xff\xc0\x00\x00\x03\xff\xf8\x00\x00\x03\xff\x80\x00\x00\x00\xe0\x00\x00\x0f\xf8\x00\x00\x3f\xff\xff\xff",
"\x3f\x00\x03\xc0\x1f\x00\x00\x00\x0f\xf0\x00\x07\xff\xff\xef\x00\xfe\x00\x7f\xfe\x00\x00\x3f\xf0\x00\x3f\xc0\x00\x00\x07\xfc\x00\x00\x03\xff\xff\xff\x0f\xff\xff\xff\xc0\x07\xf8\x00\x00\xf8\x00\x00\x3f\x9f\xff\xff\xfc\x00\x00\x3f\xff\xc0\x00\x03\xff\xff\xc0\x00\x00\xff\xfc\x00\x1f\xff\xe0\x00\x00\x07\xff\xe0\x07\x00\x00\x00\x7f\xff\xfe\x00\x00\x00\xff\xff\xfe\x00\x00\x0f\xfc\x00\x07\xff\xf0\x00\x00\x00\x7f\x80\x03\xcf\xff\x80\x00\x01\xff\xff\xe0\x3c\x00\x00\x3f\xff\xff\xff\x80\x00\x00",
"\x30\x00\x00\x00\xff\xf8\x00\x00\xff\xfc\x00\x3f\xff\xff\x80\x00\x00\x0f\xcf\xff\xcf\xff\xff\xc0\xff\xff\xff\x80\x00\x00\x01\xff\xff\xff\xf0\x00\x00\x00\x1f\xff\x00\x03\xff\xfc\x00\x00\x00\x07\xff\x80\x00\x7f\xff\xff\xfe\x0f\xff\xc0\x00\x00\x00\x7f\xf0\xbf\xff\xff\xe0\x00\x00\xff\xf8\x00\x00\x01\xff\xff\xf8\x00\x00\x3e\x00\x00\x07\xff\xe0\x00\x00\x00\x80\x00\x03\x80\x01\xff\xff\xff\xf8\x60\x00\x00\x00\xff\xf0\x00\x1f\xff\xff\xc7\xff\xf0\x40\xff\xff\xfe\x00\x00\x07\xff\xdf\xff\x80\x00\x00",
"\xff\xff\xff\x83\xff\xf8\x1f\xff\x1f\xff\xff\x80\x0f\xff\xff\xe0\x00\x00\x00\x3f\xff\xff\xc0\xf8\x00\x00\x78\x00\x1c\x00\x00\x00\x1f\xe0\x00\x00\x00\xff\xff\xe0\x3f\xff\xff\xfe\x00\x00\x03\xff\xff\xfe\x00\x00\x00\x1f\xff\xfc\x00\x7f\xff\x00\x00\x00\x1f\xff\xff\x00\x02\x00\x00\x3f\xff\xfc\x00\x00\x00\xff\xf0\x1f\xfe\xff\xff\xc0\x01\xff\xff\xff\xf0\x00\x00\x00\xff\xff\xfe\x00\x00\x00\x0f\xff\xc0\x00\x00\x7f\x7f\xc0\x00\x00\x01\xff\xff\xfe\x0f\xff\xff\xff\xc0\x00\x0f\x80\x00\x00\x3f\xff\xff\xff",
"\xff\xff\xff\xfe\x00\x00\x00\xff\xf8\x0f\xff\xff\xf0\x00\x00\x07\xff\xff\xfb\xff\xc0\x00\x07\xfc\x07\xe0\x00\x00\x01\xff\xff\xe0\x00\x7f\xff\xff\xf8\x00\x00\x3f\xff\xf8\x00\x00\x00\x0f\xff\xff\x00\x00\x0f\xc0\x00\x00\x3f\xff\xf0\x00\x00\x01\xff\xff\xfe\x1f\xff\xf8\x00\x00\x20\x00\x00\x00\x3f\xff\xbf\xff\x9f\xff\xff\xfc\x3f\xff\xff\xf0\x00\x00\x07\x80\x00\x00\xff\xff\xe7\xff\xff\xff\xf0\x00\x00\x3f\xff\xff\xff\x00\x00\x07\xff\xff\xe0\xff\xff\xff\xe0\x01\xff\xff\xff\xf8\x00\x00\x00\x7f\xff\xff\xff",
"\x00\x00\x00\x1f\xff\xc0\x00\x0f\xff\xc0\x00\x00\x1f\xff\xc0\x00\x1f\xc0\x00\x00\x03\xff\x80\x00\x00\x07\xff\xff\xff\xc0\x00\x00\x00\x80\x00\x3f\xfc\x00\xc0\x00\x0f\xff\xff\x00\x00\x06\x00\x3f\xfc\x1e\x00\x1f\xff\xff\xf0\x00\x3e\x0f\xff\xff\xf0\x00\x00\x3f\xff\xf0\x00\x00\x00\x1f\xff\xc0\x00\x00\xff\xff\xff\xf8\x00\xe0\x00\x00\xff\xff\xff\xfe\x00\x00\x0f\xff\xff\xfe\x3f\xff\xff\xfc\x07\xfe\x00\x00\x00\xc0\x00\x7f\xff\xff\xfe\x00\xff\xff\xff\xfc\x07\xff\xff\xe0\x00\x3f\xe3\xff\xff\xc0\x00\x00\x3f\xff",
"\x00\x00\x00\x0f\xff\xc0\x00\x0f\xf8\x00\x00\x00\xff\xfc\x00\x00\x0f\xff\xff\xfe\x00\x00\x00\x03\xff\x00\xff\xfc\x07\xff\xf0\x1f\xff\xfe\x0f\xff\xff\xfd\xff\xff\xff\xf0\x1f\xff\xf0\x00\x07\xf8\xff\xf8\x00\x00\x00\xff\xff\xc0\x3f\xff\xff\xff\x80\x00\x00\x0f\xff\xff\xff\x00\x00\x03\xff\xff\xf0\x00\x07\xff\xff\x00\x00\x3f\xff\xf0\x01\xff\xff\xc0\x01\xff\xff\xff\x00\x3f\xff\xf8\x1f\xff\xff\xfe\x1f\xff\xff\xff\xc0\x00\x00\x7f\xe0\x00\x07\xff\xff\xfe\x00\x00\x00\x03\xe0\x07\xff\xc0\x03\xfc\x00\x07\xff\xff\xff",
"\xff\xff\xff\x00\x00\x03\xc0\x00\x00\x01\xff\xff\xf8\x00\x00\x00\x0f\xff\xff\xff\x00\x00\x0f\x00\x00\xff\xff\xf8\x80\x00\xf8\x00\x0f\xc0\x00\x00\x00\xe0\x00\x00\x00\xff\xff\xff\xf8\x0f\xff\xff\xfe\x00\x00\x18\x00\x00\x7f\xff\xff\xff\x00\x00\x03\xff\xff\xff\x00\x7f\xff\xff\xfc\x00\x03\xc0\x00\x00\x0f\xff\xff\xff\xf0\x00\x07\xff\xff\x80\x01\xff\xff\xff\xe0\x00\x0f\xff\xfe\x07\xff\xff\xf8\x00\xff\xff\xff\xc0\x00\x00\x03\xe0\x00\x07\xff\xf0\x0f\xff\xf0\x00\x00\xff\xff\xf8\x7f\xc0\x03\xc0\x3f\xff\xe0\x00\x00\x00",
/* These are zero-terminated strings */
"abc",
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
};
static uschar *hashes[] = {
"DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
"3CDF2936DA2FC556BFA533AB1EB59CE710AC80E5",
"19C1E2048FA7393CFBF2D310AD8209EC11D996E5",
"CA775D8C80FAA6F87FA62BECA6CA6089D63B56E5",
"71AC973D0E4B50AE9E5043FF4D615381120A25A0",
"A6B5B9F854CFB76701C3BDDBF374B3094EA49CBA",
"D87A0EE74E4B9AD72E6847C87BDEEB3D07844380",
"1976B8DD509FE66BF09C9A8D33534D4EF4F63BFD",
"5A78F439B6DB845BB8A558E4CEB106CD7B7FF783",
"F871BCE62436C1E280357416695EE2EF9B83695C",
"62B243D1B780E1D31CF1BA2DE3F01C72AEEA0E47",
"1698994A273404848E56E7FDA4457B5900DE1342",
"056F4CDC02791DA7ED1EB2303314F7667518DEEF",
"9FE2DA967BD8441EEA1C32DF68DDAA9DC1FC8E4B",
"73A31777B4ACE9384EFA8BBEAD45C51A71ABA6DD",
"3F9D7C4E2384EDDABFF5DD8A31E23DE3D03F42AC",
"4814908F72B93FFD011135BEE347DE9A08DA838F",
"0978374B67A412A3102C5AA0B10E1A6596FC68EB",
"44AD6CB618BD935460D46D3F921D87B99AB91C1E",
"02DC989AF265B09CF8485640842128DCF95E9F39",
"67507B8D497B35D6E99FC01976D73F54AECA75CF",
"1EAE0373C1317CB60C36A42A867B716039D441F5",
"9C3834589E5BFFAC9F50950E0199B3EC2620BEC8",
"209F7ABC7F3B878EE46CDF3A1FBB9C21C3474F32",
"05FC054B00D97753A9B3E2DA8FBBA3EE808CEF22",
"0C4980EA3A46C757DFBFC5BAA38AC6C8E72DDCE7",
"96A460D2972D276928B69864445BEA353BDCFFD2",
"F3EF04D8FA8C6FA9850F394A4554C080956FA64B",
"F2A31D875D1D7B30874D416C4D2EA6BAF0FFBAFE",
"F4942D3B9E9588DCFDC6312A84DF75D05F111C20",
"310207DF35B014E4676D30806FA34424813734DD",
"4DA1955B2FA7C7E74E3F47D7360CE530BBF57CA3",
"74C4BC5B26FB4A08602D40CCEC6C6161B6C11478",
"0B103CE297338DFC7395F7715EE47539B556DDB6",
"EFC72D99E3D2311CE14190C0B726BDC68F4B0821",
"660EDAC0A8F4CE33DA0D8DBAE597650E97687250",
"FE0A55A988B3B93946A63EB36B23785A5E6EFC3E",
"0CBDF2A5781C59F907513147A0DE3CC774B54BF3",
"663E40FEE5A44BFCB1C99EA5935A6B5BC9F583B0",
"00162134256952DD9AE6B51EFB159B35C3C138C7",
"CEB88E4736E354416E2010FC1061B3B53B81664B",
"A6A2C4B6BCC41DDC67278F3DF4D8D0B9DD7784EF",
"C23D083CD8820B57800A869F5F261D45E02DC55D",
"E8AC31927B78DDEC41A31CA7A44EB7177165E7AB",
"E864EC5DBAB0F9FF6984AB6AD43A8C9B81CC9F9C",
"CFED6269069417A84D6DE2347220F4B858BCD530",
"D9217BFB46C96348722C3783D29D4B1A3FEDA38C",
"DEC24E5554F79697218D317315FA986229CE3350",
"83A099DF7071437BA5495A5B0BFBFEFE1C0EF7F3",
"AA3198E30891A83E33CE3BFA0587D86A197D4F80",
"9B6ACBEB4989CBEE7015C7D515A75672FFDE3442",
"B021EB08A436B02658EAA7BA3C88D49F1219C035",
"CAE36DAB8AEA29F62E0855D9CB3CD8E7D39094B1",
"02DE8BA699F3C1B0CB5AD89A01F2346E630459D7",
"88021458847DD39B4495368F7254941859FAD44B",
"91A165295C666FE85C2ADBC5A10329DAF0CB81A0",
"4B31312EAF8B506811151A9DBD162961F7548C4B",
"3FE70971B20558F7E9BAC303ED2BC14BDE659A62",
"93FB769D5BF49D6C563685954E2AECC024DC02D6",
"BC8827C3E614D515E83DEA503989DEA4FDA6EA13",
"E83868DBE4A389AB48E61CFC4ED894F32AE112AC",
"55C95459CDE4B33791B4B2BCAAF840930AF3F3BD",
"36BB0E2BA438A3E03214D9ED2B28A4D5C578FCAA",
"3ACBF874199763EBA20F3789DFC59572ACA4CF33",
"86BE037C4D509C9202020767D860DAB039CADACE",
"51B57D7080A87394EEC3EB2E0B242E553F2827C9",
"1EFBFA78866315CE6A71E457F3A750A38FACAB41",
"57D6CB41AEEC20236F365B3A490C61D0CFA39611",
"C532CB64B4BA826372BCCF2B4B5793D5B88BB715",
"15833B5631032663E783686A209C6A2B47A1080E",
"D04F2043C96E10CD83B574B1E1C217052CD4A6B2",
"E8882627C64DB743F7DB8B4413DD033FC63BEB20",
"CD2D32286B8867BC124A0AF2236FC74BE3622199",
"019B70D745375091ED5C7B218445EC986D0F5A82",
"E5FF5FEC1DADBAED02BF2DAD4026BE6A96B3F2AF",
"6F4E23B3F2E2C068D13921FE4E5E053FFED4E146",
"25E179602A575C915067566FBA6DA930E97F8678",
"67DED0E68E235C8A523E051E86108EEB757EFBFD",
"AF78536EA83C822796745556D62A3EE82C7BE098",
"64D7AC52E47834BE72455F6C64325F9C358B610D",
"9D4866BAA3639C13E541F250FFA3D8BC157A491F",
"2E258811961D3EB876F30E7019241A01F9517BEC",
"8E0EBC487146F83BC9077A1630E0FB3AB3C89E63",
"CE8953741FFF3425D2311FBBF4AB481B669DEF70",
"789D1D2DAB52086BD90C0E137E2515ED9C6B59B5",
"B76CE7472700DD68D6328B7AA8437FB051D15745",
"F218669B596C5FFB0B1C14BD03C467FC873230A0",
"1FF3BDBE0D504CB0CDFAB17E6C37ABA6B3CFFDED",
"2F3CBACBB14405A4652ED52793C1814FD8C4FCE0",
"982C8AB6CE164F481915AF59AAED9FFF2A391752",
"5CD92012D488A07ECE0E47901D0E083B6BD93E3F",
"69603FEC02920851D4B3B8782E07B92BB2963009",
"3E90F76437B1EA44CF98A08D83EA24CECF6E6191",
"34C09F107C42D990EB4881D4BF2DDDCAB01563AE",
"474BE0E5892EB2382109BFC5E3C8249A9283B03D",
"A04B4F75051786682483252438F6A75BF4705EC6",
"BE88A6716083EB50ED9416719D6A247661299383",
"C67E38717FEE1A5F65EC6C7C7C42AFC00CD37F04",
"959AC4082388E19E9BE5DE571C047EF10C174A8D",
"BAA7AA7B7753FA0ABDC4A541842B5D238D949F0A",
"351394DCEBC08155D100FCD488578E6AE71D0E9C",
"AB8BE94C5AF60D9477EF1252D604E58E27B2A9EE",
"3429EC74A695FDD3228F152564952308AFE0680A",
"907FA46C029BC67EAA8E4F46E3C2A232F85BD122",
"2644C87D1FBBBC0FC8D65F64BCA2492DA15BAAE4",
"110A3EEB408756E2E81ABAF4C5DCD4D4C6AFCF6D",
"CD4FDC35FAC7E1ADB5DE40F47F256EF74D584959",
"8E6E273208AC256F9ECCF296F3F5A37BC8A0F9F7",
"FE0606100BDBC268DB39B503E0FDFE3766185828",
"6C63C3E58047BCDB35A17F74EEBA4E9B14420809",
"BCC2BD305F0BCDA8CF2D478EF9FE080486CB265F",
"CE5223FD3DD920A3B666481D5625B16457DCB5E8",
"948886776E42E4F5FAE1B2D0C906AC3759E3F8B0",
"4C12A51FCFE242F832E3D7329304B11B75161EFB",
"C54BDD2050504D92F551D378AD5FC72C9ED03932",
"8F53E8FA79EA09FD1B682AF5ED1515ECA965604C",
"2D7E17F6294524CE78B33EAB72CDD08E5FF6E313",
"64582B4B57F782C9302BFE7D07F74AA176627A3A",
"6D88795B71D3E386BBD1EB830FB9F161BA98869F",
"86AD34A6463F12CEE6DE9596ABA72F0DF1397FD1",
"7EB46685A57C0D466152DC339C8122548C757ED1",
"E7A98FB0692684054407CC221ABC60C199D6F52A",
"34DF1306662206FD0A5FC2969A4BEEC4EB0197F7",
"56CF7EBF08D10F0CB9FE7EE3B63A5C3A02BCB450",
"3BAE5CB8226642088DA760A6F78B0CF8EDDEA9F1",
"6475DF681E061FA506672C27CBABFA9AA6DDFF62",
"79D81991FA4E4957C8062753439DBFD47BBB277D",
"BAE224477B20302E881F5249F52EC6C34DA8ECEF",
"EDE4DEB4293CFE4138C2C056B7C46FF821CC0ACC",
"A9993E364706816ABA3E25717850C26C9CD0D89D",
"84983E441C3BD26EBAAE4AA1F95129E5E54670F1"
};
static uschar *atest = "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F";
int main(void)
{
sha1 base;
int j;
int i = 0x01020304;
uschar *ctest = (uschar *)(&i);
uschar buffer[256];
uschar digest[20];
uschar s[41];
printf("Checking sha1: %s-endian\n\n", (ctest[0] == 0x04)? "little" : "big");
for (i = 0; i < sizeof(tests)/sizeof(uschar *); i ++)
{
printf("%d.\nShould be: %s\n", i, hashes[i]);
sha1_start(&base);
sha1_end(&base, tests[i], (i <= 128)? i : strlen(tests[i]), digest);
for (j = 0; j < 20; j++) sprintf(s+2*j, "%02X", digest[j]);
printf("Computed: %s\n", s);
if (strcmp(s, hashes[i]) != 0) printf("*** No match ***\n");
printf("\n");
}
/* 1 000 000 repetitions of "a" */
ctest = malloc(1000000);
memset(ctest, 'a', 1000000);
printf("1 000 000 repetitions of 'a'\n");
printf("Should be: %s\n", atest);
sha1_start(&base);
sha1_end(&base, ctest, 1000000, digest);
for (j = 0; j < 20; j++) sprintf(s+2*j, "%02X", digest[j]);
printf("Computed: %s\n", s);
if (strcmp(s, atest) != 0) printf("*** No match ***\n");
}
#endif
/* End of sha1.c */
Index: spa.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/spa.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file, which provides support for Microsoft's Secure Password
Authentication, was contributed by Marc Prud'hommeaux. Tom Kistner added SPA
server support. I (PH) have only modified it in very trivial ways.
References:
http://www.innovation.ch/java/ntlm.html
http://www.kuro5hin.org/story/2002/4/28/1436/66154
* It seems that some systems have existing but different definitions of some
* of the following types. I received a complaint about "int16" causing
* compilation problems. So I (PH) have renamed them all, to be on the safe
* side, by adding 'x' on the end. See auths/auth-spa.h.
* typedef signed short int16;
* typedef unsigned short uint16;
* typedef unsigned uint32;
* typedef unsigned char uint8;
07-August-2003: PH: Patched up the code to avoid assert bombouts for stupid
input data. Find appropriate comment by grepping for "PH".
*/
#include "../exim.h"
#include "spa.h"
/* #define DEBUG_SPA */
#ifdef DEBUG_SPA
#define DSPA(x,y,z) debug_printf(x,y,z)
#else
#define DSPA(x,y,z)
#endif
/* Options specific to the spa authentication mechanism. */
optionlist auth_spa_options[] = {
{ "client_domain", opt_stringptr,
(void *)(offsetof(auth_spa_options_block, spa_domain)) },
{ "client_password", opt_stringptr,
(void *)(offsetof(auth_spa_options_block, spa_password)) },
{ "client_username", opt_stringptr,
(void *)(offsetof(auth_spa_options_block, spa_username)) },
{ "server_password", opt_stringptr,
(void *)(offsetof(auth_spa_options_block, spa_serverpassword)) }
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
int auth_spa_options_count =
sizeof(auth_spa_options)/sizeof(optionlist);
/* Default private options block for the contidion authentication method. */
auth_spa_options_block auth_spa_option_defaults = {
NULL, /* spa_password */
NULL, /* spa_username */
NULL, /* spa_domain */
NULL /* spa_serverpassword (for server side use) */
};
/*************************************************
* Initialization entry point *
*************************************************/
/* Called for each instance, after its options have been read, to
enable consistency checks to be done, or anything else that needs
to be set up. */
void
auth_spa_init(auth_instance *ablock)
{
auth_spa_options_block *ob =
(auth_spa_options_block *)(ablock->options_block);
/* The public name defaults to the authenticator name */
if (ablock->public_name == NULL) ablock->public_name = ablock->name;
/* Both username and password must be set for a client */
if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:\n "
"one of client_username and client_password cannot be set without "
"the other", ablock->name);
ablock->client = ob->spa_username != NULL;
/* For a server we have just one option */
ablock->server = ob->spa_serverpassword != NULL;
}
/*************************************************
* Server entry point *
*************************************************/
/* For interface, see auths/README */
#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
int
auth_spa_server(auth_instance *ablock, uschar *data)
{
auth_spa_options_block *ob = (auth_spa_options_block *)(ablock->options_block);
uint8x lmRespData[24];
uint8x ntRespData[24];
SPAAuthRequest request;
SPAAuthChallenge challenge;
SPAAuthResponse response;
SPAAuthResponse *responseptr = &response;
uschar msgbuf[2048];
uschar *clearpass;
/* send a 334, MS Exchange style, and grab the client's request */
if (auth_get_no64_data(&data, US"NTLM supported") != OK)
{
/* something borked */
return FAIL;
}
if (spa_base64_to_bits((char *)(&request), (const char *)(data)) < 0)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
"request: %s\n", data);
return FAIL;
}
/* create a challenge and send it back */
spa_build_auth_challenge(&request,&challenge);
spa_bits_to_base64 (msgbuf, (unsigned char*)&challenge,
spa_request_length(&challenge));
if (auth_get_no64_data(&data, msgbuf) != OK)
{
/* something borked */
return FAIL;
}
/* dump client response */
if (spa_base64_to_bits((char *)(&response), (const char *)(data)) < 0)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
"response: %s\n", data);
return FAIL;
}
/* get username and put it in $1 */
/***************************************************************
PH 07-Aug-2003: The original code here was this:
Ustrcpy(msgbuf, unicodeToString(((char*)responseptr) +
IVAL(&responseptr->uUser.offset,0),
SVAL(&responseptr->uUser.len,0)/2) );
However, if the response data is too long, unicodeToString bombs out on
an assertion failure. It uses a 1024 fixed buffer. Bombing out is not a good
idea. It's too messy to try to rework that function to return an error because
it is called from a number of other places in the auth-spa.c module. Instead,
since it is a very small function, I reproduce its code here, with a size check
that causes failure if the size of msgbuf is exceeded. ****/
{
int i;
char *p = ((char*)responseptr) + IVAL(&responseptr->uUser.offset,0);
int len = SVAL(&responseptr->uUser.len,0)/2;
if (len + 1 >= sizeof(msgbuf)) return FAIL;
for (i = 0; i < len; ++i)
{
msgbuf[i] = *p & 0x7f;
p += 2;
}
msgbuf[i] = 0;
}
/***************************************************************/
expand_nstring[1] = msgbuf;
expand_nlength[1] = Ustrlen(msgbuf);
expand_nmax = 1;
/* look up password */
clearpass = expand_string(ob->spa_serverpassword);
if (clearpass == NULL)
{
if (expand_string_forcedfail)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
"expanding spa_serverpassword\n");
return FAIL;
}
else
{
DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
"spa_serverpassword: %s\n", expand_string_message);
return DEFER;
}
}
/* create local hash copy */
spa_smb_encrypt (clearpass, challenge.challengeData, lmRespData);
spa_smb_nt_encrypt (clearpass, challenge.challengeData, ntRespData);
/* compare NT hash (LM may not be available) */
if (memcmp(ntRespData,
((unsigned char*)responseptr)+IVAL(&responseptr->ntResponse.offset,0),
24) == 0)
/* success. we have a winner. */
return OK;
return FAIL;
}
/*************************************************
* Client entry point *
*************************************************/
/* For interface, see auths/README */
int
auth_spa_client(
auth_instance *ablock, /* authenticator block */
smtp_inblock *inblock, /* connection inblock */
smtp_outblock *outblock, /* connection outblock */
int timeout, /* command timeout */
uschar *buffer, /* buffer for reading response */
int buffsize) /* size of buffer */
{
auth_spa_options_block *ob =
(auth_spa_options_block *)(ablock->options_block);
SPAAuthRequest request;
SPAAuthChallenge challenge;
SPAAuthResponse response;
char msgbuf[2048];
char *domain = NULL;
char *username, *password;
if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n",
ablock->public_name) < 0)
return FAIL_SEND;
/* wait for the 3XX OK message */
if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
return FAIL;
/* Code added by PH to expand the options */
username = CS expand_string(ob->spa_username);
if (username == NULL)
{
string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
"authenticator: %s", ob->spa_username, ablock->name,
expand_string_message);
return ERROR;
}
password = CS expand_string(ob->spa_password);
if (password == NULL)
{
string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
"authenticator: %s", ob->spa_password, ablock->name,
expand_string_message);
return ERROR;
}
if (ob->spa_domain != NULL)
{
domain = CS expand_string(ob->spa_domain);
if (domain == NULL)
{
string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
"authenticator: %s", ob->spa_domain, ablock->name,
expand_string_message);
return ERROR;
}
}
/* Original code */
DSPA("\n\n%s authenticator: using domain %s\n\n",
ablock->name, domain);
spa_build_auth_request (&request, CS username, domain);
spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
spa_request_length(&request));
DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name,
msgbuf);
/* send the encrypted password */
if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
return FAIL_SEND;
/* wait for the auth challenge */
if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
return FAIL;
/* convert the challenge into the challenge struct */
DSPA("\n\n%s authenticator: challenge (%s)\n\n",
ablock->name, buffer + 4);
spa_base64_to_bits ((char *)(&challenge), (const char *)(buffer + 4));
spa_build_auth_response (&challenge, &response,
CS username, CS password);
spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
spa_request_length(&response));
DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name,
msgbuf);
/* send the challenge response */
if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
return FAIL_SEND;
/* If we receive a success response from the server, authentication
has succeeded. There may be more data to send, but is there any point
in provoking an error here? */
if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
return OK;
/* Not a success response. If errno != 0 there is some kind of transmission
error. Otherwise, check the response code in the buffer. If it starts with
'3', more data is expected. */
if (errno != 0 || buffer[0] != '3')
return FAIL;
return FAIL;
}
/* End of spa.c */
Index: spa.h
====================================================================
/* $Cambridge: exim/exim-src/src/auths/spa.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file, which provides support for Microsoft's Secure Password
Authentication, was contributed by Marc Prud'hommeaux. */
#include "auth-spa.h"
/* Private structure for the private options. */
typedef struct {
uschar *spa_username;
uschar *spa_password;
uschar *spa_domain;
uschar *spa_serverpassword;
} auth_spa_options_block;
/* Data for reading the private options. */
extern optionlist auth_spa_options[];
extern int auth_spa_options_count;
/* Block containing default values. */
extern auth_spa_options_block auth_spa_option_defaults;
/* The entry points for the mechanism */
extern void auth_spa_init(auth_instance *);
extern int auth_spa_server(auth_instance *, uschar *);
extern int auth_spa_client(auth_instance *, smtp_inblock *,
smtp_outblock *, int, uschar *, int);
/* End of spa.h */
Index: xtextdecode.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/xtextdecode.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Decode byte-string in xtext *
*************************************************/
/* This function decodes a string in xtextformat as defined in RFC 1891 and
required by the SMTP AUTH extension (RFC 2554). We put the result in a piece of
store of equal length - it cannot be longer than this. Although in general the
result of decoding an xtext may be binary, in the context in which it is used
by Exim (for decoding the value of AUTH on a MAIL command), the result is
expected to be an addr-spec. We therefore add on a terminating zero, for
convenience.
Arguments:
code points to the coded string, zero-terminated
ptr where to put the pointer to the result, which is in
dynamic store
Returns: the number of bytes in the result, excluding the final zero;
-1 if the input is malformed
*/
int
auth_xtextdecode(uschar *code, uschar **ptr)
{
register int x;
uschar *result = store_get(Ustrlen(code) + 1);
*ptr = result;
while ((x = (*code++)) != 0)
{
if (x < 33 || x > 127 || x == '=') return -1;
if (x == '+')
{
register int y;
if (!isxdigit((x = (*code++)))) return -1;
y = ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10)) << 4;
if (!isxdigit((x = (*code++)))) return -1;
*result++ = y | ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10));
}
else *result++ = x;
}
*result = 0;
return result - *ptr;
}
/* End of xtextdecode.c */
Index: xtextencode.c
====================================================================
/* $Cambridge: exim/exim-src/src/auths/xtextencode.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/*************************************************
* Encode byte-string in xtext *
*************************************************/
/* This function encodes a string of bytes, containing any values whatsoever,
as "xtext", as defined in RFC 1891 and required by the SMTP AUTH extension (RFC
2554).
Arguments:
clear points to the clear text bytes
len the number of bytes to encode
Returns: a pointer to the zero-terminated xtext string, which
is in working store
*/
uschar *
auth_xtextencode(uschar *clear, int len)
{
uschar *code;
uschar *p = (uschar *)clear;
uschar *pp;
int c = len;
int count = 1;
register int x;
/* We have to do a prepass to find out how many specials there are,
in order to get the right amount of store. */
while (c -- > 0)
count += ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')? 3 : 1;
pp = code = store_get(count);
p = (uschar *)clear;
c = len;
while (c-- > 0)
{
if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')
{
sprintf(CS pp, "+%.02x", x); /* There's always room */
pp += 3;
}
else *pp++ = x;
}
*pp = 0;
return code;
}
/* End of xtextencode.c */
Index: Makefile
====================================================================
# $Cambridge: exim/exim-src/src/lookups/Makefile,v 1.1 2004/10/07 13:10:01 ph10 Exp $
# Make file for building a library containing all the available lookups and
# calling it lookups.a. This is called from the main make file, after cd'ing
# to the lookups subdirectory. When the relevant LOOKUP_ macros are not
# defined, dummy modules get compiled.
OBJ = cdb.o dbmdb.o dnsdb.o dsearch.o ibase.o ldap.o lsearch.o mysql.o nis.o \
nisplus.o oracle.o passwd.o pgsql.o testdb.o whoson.o lf_check_file.o \
lf_quote.o
lookups.a: $(OBJ)
/bin/rm -f lookups.a
$(AR) lookups.a $(OBJ)
$(RANLIB) $@
/bin/rm -rf ../drtables.o
.SUFFIXES: .o .c
.c.o:; $(CC) -c $(CFLAGS) $(INCLUDE) $*.c
lf_check_file.o: $(HDRS) lf_check_file.c lf_functions.h
lf_quote.o: $(HDRS) lf_quote.c lf_functions.h
cdb.o: $(HDRS) cdb.c cdb.h
dbmdb.o: $(HDRS) dbmdb.c dbmdb.h
dnsdb.o: $(HDRS) dnsdb.c dnsdb.h
dsearch.o: $(HDRS) dsearch.c dsearch.h
ibase.o: $(HDRS) ibase.c ibase.h
ldap.o: $(HDRS) ldap.c ldap.h
lsearch.o: $(HDRS) lsearch.c lsearch.h
mysql.o: $(HDRS) mysql.c mysql.h
nis.o: $(HDRS) nis.c nis.h
nisplus.o: $(HDRS) nisplus.c nisplus.h
oracle.o: $(HDRS) oracle.c oracle.h
passwd.o: $(HDRS) passwd.c passwd.h
pgsql.o: $(HDRS) pgsql.c pgsql.h
testdb.o: $(HDRS) testdb.c testdb.h
whoson.o: $(HDRS) whoson.c whoson.h
# End
Index: README
====================================================================
$Cambridge: exim/exim-src/src/lookups/README,v 1.1 2004/10/07 13:10:01 ph10 Exp $
LOOKUPS
-------
Each lookup type is implemented by 6 functions, xxx_open(), xxx_check(),
xxx_find(), xxx_close(), xxx_tidy(), and xxx_quote(), where xxx is the name of
the lookup type (e.g. lsearch, dbm, or whatever).
The xxx_check(), xxx_close(), xxx_tidy(), and xxx_quote() functions need not
exist. There is a table in drtables.c which links the lookup names to the
various sets of functions, with NULL entries for any that don't exist. When
the code for a lookup type is omitted from the binary, all its entries are
NULL.
One of the fields in the table contains flags describing the kind of lookup.
These are
lookup_querystyle
This is a "query style" lookup without a file name, as opposed to the "single
key" style, where the key is associated with a "file name".
lookup_absfile
For single key lookups, this means that the file name must be an absolute path.
It is set for lsearch and dbm, but not for NIS, for example.
When a single-key lookup file is opened, the handle returned by the xxx_open()
function is saved, along with the file name and lookup type, in a tree. The
xxx_close() function is not called when the first lookup is completed. If there
are subsequent lookups of the same type that quote the same file name,
xxx_open() isn't called; instead the cached handle is re-used.
Exim calls the function search_tidyup() at strategic points in its processing
(e.g. after all routing and directing has been done) and this function walks
the tree and calls the xxx_close() functions for all the cached handles.
Query-style lookups don't have the concept of an open file that can be cached
this way. Of course, the local code for the lookup can manage its own caching
information in any way it pleases. This means that the xxx_close()
function, even it it exists, is never called. However, if an xxx_tidy()
function exists, it is called once whenever Exim calls search_tidyup().
A single-key lookup type may also have an xxx_tidy() function, which is called
by search_tidyup() after all cached handles have been closed via the
xxx_close() function.
The lookup functions are wrapped into a special store pool (POOL_SEARCH). You
can safely use store_get to allocate store for your handle caching. The store
will be reset after all xxx_tidy() functions are called.
The function interfaces are as follows:
xxx_open()
----------
This function is called to initiate the lookup. For things that involve files
it should do a real open; for other kinds of lookup it may do nothing at all.
The arguments are:
uschar *filename the name of the "file" to open, for non-query-style
lookups; NULL for query-style lookups
uschar **errmsg where to put an error message if there is a problem
The yield of xxx_open() is a void * value representing the open file or
database. For real files is is normally the FILE or DBM value. For other
kinds of lookup, if there is no natural value to use, (-1) is recommended.
The value should not be NULL (or 0) as that is taken to indicate failure of
the xxx_open() function. For single-key lookups, the handle is cached along
with the filename and type, and may be used for several lookups.
xxx_check()
-----------
If this function exists, it is called after a successful open to check on the
ownership and mode of the file(s). The arguments are:
void *handle the handle passed back from xxx_open()
uschar *filename the filename passed to xxx_open()
int modemask mode bits that must not be set
int *owners permitted owners of the file(s)
int *owngroups permitted group owners of the file(s)
uschar **errmsg where to put an error message if there is a problem
In the owners and owngroups vectors, the first element is the count of the
remaining elements. There is a common function that can be called to check
a file:
int search_check_file(int fd, char *filename, int modemask, int *owners,
int *owngroups, char *type, char **errmsg);
If fd is >= 0, it is checked using fstat(), and filename is used only in
error messages. If fd is < 0 then filename is checked using stat(). The yield
is zero if all is well, +1 if the mode or uid or gid is wrong, or -1 if the
stat() fails.
The yield of xxx_check() is TRUE if all is well, FALSE otherwise. The
function should not close the file(s) on failure. That is done from outside
by calling the xxx_close() function.
xxx_find()
----------
This is called to search an open file/database. The result is OK, FAIL, or
DEFER. The arguments are:
void *handle the handle passed back from xxx_open()
uschar *filename the filename passed to xxx_open() (NULL for querystyle)
uschar *keyquery the key to look up, or query to process, zero-terminated
int length the length of the key
uschar **result point to the yield, in dynamic store, on success
uschar **errmsg where to put an error message on failure;
this is initially set to "", and should be left
as that for a standard "entry not found" error
BOOL *do_cache the lookup should set this to FALSE when it changes data.
This is TRUE by default. When set to FALSE the cache tree
of the current search handle will be cleaned and the
current result will NOT be cached. Currently the mysql
and pgsql lookups use this when UPDATE/INSERT queries are
executed.
Even though the key is zero-terminated, the length is passed because in the
common case it has been computed already and is often needed.
xxx_close()
-----------
This is called for single-key lookups when a file is finished with. There is no
yield, and the only argument is the handle that was passed back from
xxx_open(). It is not called for query style lookups.
xxx_tidy()
----------
This function is called once at the end of search_tidyup() for every lookup
type for which it exists.
xxx_quote()
-----------
This is called by the string expansion code for expansions of the form
${quote_xxx:<string>}, if it exists. If not, the expansion code makes no change
to the string. The function must apply any quoting rules that are specific to
the lookup, and return a pointer to the revised string. If quoting is not
needed, it can return its single argument, which is a uschar *. This function
does NOT use the POOL_SEARCH store, because it's usually never called from any
lookup code.
****
Index: cdb.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/cdb.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/*
* Exim - CDB database lookup module
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Copyright (c) 1998 Nigel Metheringham, Planet Online Ltd
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* --------------------------------------------------------------
* Modified by PH for Exim 4:
* Changed over to using unsigned chars
* Makes use of lf_check_file() for file checking
* --------------------------------------------------------------
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*
* This code implements Dan Bernstein's Constant DataBase (cdb) spec.
* Information, the spec and sample code for cdb can be obtained from
* http://www.pobox.com/~djb/cdb.html
*
* This implementation borrows some code from Dan Bernstein's
* implementation (which has no license restrictions applied to it).
* This (read-only) implementation is completely contained within
* cdb.[ch] it does *not* link against an external cdb library.
*
*
* There are 2 varients included within this code. One uses MMAP and
* should give better performance especially for multiple lookups on a
* modern machine. The other is the default implementation which is
* used in the case where the MMAP fails or if MMAP was not compiled
* in. this implementation is the same as the original reference cdb
* implementation. The MMAP version is compiled in if the HAVE_MMAP
* preprocessor define is defined - this should be set in the system
* specific os.h file.
*
*/
#include "../exim.h"
#include "lf_functions.h"
#include "cdb.h"
#ifdef HAVE_MMAP
# include <sys/mman.h>
/* Not all implementations declare MAP_FAILED */
# ifndef MAP_FAILED
# define MAP_FAILED ((void *) -1)
# endif /* MAP_FAILED */
#endif /* HAVE_MMAP */
#define CDB_HASH_SPLIT 256 /* num pieces the hash table is split into */
#define CDB_HASH_MASK 255 /* mask to and off split value */
#define CDB_HASH_ENTRY 8 /* how big each offset it */
#define CDB_HASH_TABLE (CDB_HASH_SPLIT * CDB_HASH_ENTRY)
/* State information for cdb databases that are open NB while the db
* is open its contents will not change (cdb dbs are normally updated
* atomically by renaming). However the lifetime of one of these
* state structures should be limited - ie a long running daemon
* that opens one may hit problems....
*/
struct cdb_state {
int fileno;
off_t filelen;
uschar *cdb_map;
uschar *cdb_offsets;
};
/* 32 bit unsigned type - this is an int on all modern machines */
typedef unsigned int uint32;
/*
* cdb_hash()
* Internal function to make hash value */
static uint32
cdb_hash(uschar *buf, unsigned int len)
{
uint32 h;
h = 5381;
while (len) {
--len;
h += (h << 5);
h ^= (uint32) *buf++;
}
return h;
}
/*
* cdb_bread()
* Internal function to read len bytes from disk, coping with oddities */
static int
cdb_bread(int fd,
uschar *buf,
int len)
{
int r;
while (len > 0) {
do
r = Uread(fd,buf,len);
while ((r == -1) && (errno == EINTR));
if (r == -1) return -1;
if (r == 0) { errno = EIO; return -1; }
buf += r;
len -= r;
}
return 0;
}
/*
* cdb_bread()
* Internal function to parse 4 byte number (endian independant) */
static uint32
cdb_unpack(uschar *buf)
{
uint32 num;
num = buf[3]; num <<= 8;
num += buf[2]; num <<= 8;
num += buf[1]; num <<= 8;
num += buf[0];
return num;
}
void *
cdb_open(uschar *filename,
uschar **errmsg)
{
int fileno;
struct cdb_state *cdbp;
struct stat statbuf;
void * mapbuf;
fileno = Uopen(filename, O_RDONLY, 0);
if (fileno == -1) {
int save_errno = errno;
*errmsg = string_open_failed(errno, "%s for cdb lookup", filename);
errno = save_errno;
return NULL;
}
if (fstat(fileno, &statbuf) == 0) {
/* If this is a valid file, then it *must* be at least
* CDB_HASH_TABLE bytes long */
if (statbuf.st_size < CDB_HASH_TABLE) {
int save_errno = errno;
*errmsg = string_open_failed(errno,
"%s to short for cdb lookup",
filename);
errno = save_errno;
return NULL;
}
} else {
int save_errno = errno;
*errmsg = string_open_failed(errno,
"fstat(%s) failed - cannot do cdb lookup",
filename);
errno = save_errno;
return NULL;
}
/* Having got a file open we need the structure to put things in */
cdbp = store_get(sizeof(struct cdb_state));
/* store_get() does not return if memory was not available... */
/* preload the structure.... */
cdbp->fileno = fileno;
cdbp->filelen = statbuf.st_size;
cdbp->cdb_map = NULL;
cdbp->cdb_offsets = NULL;
/* if we are allowed to we use mmap here.... */
#ifdef HAVE_MMAP
mapbuf = mmap(NULL,
statbuf.st_size,
PROT_READ,
MAP_SHARED,
fileno,
0);
if (mapbuf != MAP_FAILED) {
/* We have an mmap-ed section. Now we can just use it */
cdbp->cdb_map = mapbuf;
/* The offsets can be set to the same value since they should
* effectively be cached as well
*/
cdbp->cdb_offsets = mapbuf;
/* Now return the state struct */
return(cdbp);
} else {
/* If we got here the map failed. Basically we can ignore
* this since we fall back to slower methods....
* However lets debug log it...
*/
DEBUG(D_lookup) debug_printf("cdb mmap failed - %d\n", errno);
}
#endif /* HAVE_MMAP */
/* In this case we have either not got MMAP allowed, or it failed */
/* get a buffer to stash the basic offsets in - this should speed
* things up a lot - especially on multiple lookups */
cdbp->cdb_offsets = store_get(CDB_HASH_TABLE);
/* now fill the buffer up... */
if (cdb_bread(fileno, cdbp->cdb_offsets, CDB_HASH_TABLE) == -1) {
/* read of hash table failed, oh dear, oh.....
* time to give up I think....
* call the close routine (deallocs the memory), and return NULL */
*errmsg = string_open_failed(errno,
"cannot read header from %s for cdb lookup",
filename);
cdb_close(cdbp);
return NULL;
}
/* Everything else done - return the cache structure */
return cdbp;
}
/*************************************************
* Check entry point *
*************************************************/
BOOL
cdb_check(void *handle,
uschar *filename,
int modemask,
uid_t *owners,
gid_t *owngroups,
uschar **errmsg)
{
struct cdb_state * cdbp = handle;
return lf_check_file(cdbp->fileno,
filename,
S_IFREG,
modemask,
owners,
owngroups,
"cdb",
errmsg) == 0;
}
/*************************************************
* Find entry point *
*************************************************/
int
cdb_find(void *handle,
uschar *filename,
uschar *keystring,
int key_len,
uschar **result,
uschar **errmsg,
BOOL *do_cache)
{
struct cdb_state * cdbp = handle;
uint32 item_key_len,
item_dat_len,
key_hash,
item_hash,
item_posn,
cur_offset,
end_offset,
hash_offset_entry,
hash_offset,
hash_offlen,
hash_slotnm;
int loop;
/* Keep picky compilers happy */
do_cache = do_cache;
key_hash = cdb_hash((uschar *)keystring, key_len);
hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK);
hash_offset = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry);
hash_offlen = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry + 4);
/* If the offset length is zero this key cannot be in the file */
if (hash_offlen == 0) {
return FAIL;
}
hash_slotnm = (key_hash >> 8) % hash_offlen;
/* check to ensure that the file is not corrupt
* if the hash_offset + (hash_offlen * CDB_HASH_ENTRY) is longer
* than the file, then we have problems.... */
if ((hash_offset + (hash_offlen * CDB_HASH_ENTRY)) > cdbp->filelen) {
*errmsg = string_sprintf("cdb: corrupt cdb file %s (too short)",
filename);
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
return DEFER;
}
cur_offset = hash_offset + (hash_slotnm * CDB_HASH_ENTRY);
end_offset = hash_offset + (hash_offlen * CDB_HASH_ENTRY);
/* if we are allowed to we use mmap here.... */
#ifdef HAVE_MMAP
/* make sure the mmap was OK */
if (cdbp->cdb_map != NULL) {
uschar * cur_pos = cur_offset + cdbp->cdb_map;
uschar * end_pos = end_offset + cdbp->cdb_map;
for (loop = 0; (loop < hash_offlen); ++loop) {
item_hash = cdb_unpack(cur_pos);
cur_pos += 4;
item_posn = cdb_unpack(cur_pos);
cur_pos += 4;
/* if the position is zero then we have a definite miss */
if (item_posn == 0)
return FAIL;
if (item_hash == key_hash) {
/* matching hash value */
uschar * item_ptr = cdbp->cdb_map + item_posn;
item_key_len = cdb_unpack(item_ptr);
item_ptr += 4;
item_dat_len = cdb_unpack(item_ptr);
item_ptr += 4;
/* check key length matches */
if (item_key_len == key_len) {
/* finally check if key matches */
if (Ustrncmp(keystring, item_ptr, key_len) == 0) {
/* we have a match....
* make item_ptr point to data */
item_ptr += item_key_len;
/* ... and the returned result */
*result = store_get(item_dat_len + 1);
memcpy(*result, item_ptr, item_dat_len);
(*result)[item_dat_len] = 0;
return OK;
}
}
}
/* handle warp round of table */
if (cur_pos == end_pos)
cur_pos = cdbp->cdb_map + hash_offset;
}
/* looks like we failed... */
return FAIL;
}
#endif /* HAVE_MMAP */
for (loop = 0; (loop < hash_offlen); ++loop) {
uschar packbuf[8];
if (lseek(cdbp->fileno, (off_t) cur_offset,SEEK_SET) == -1) return DEFER;
if (cdb_bread(cdbp->fileno, packbuf,8) == -1) return DEFER;
item_hash = cdb_unpack(packbuf);
item_posn = cdb_unpack(packbuf + 4);
/* if the position is zero then we have a definite miss */
if (item_posn == 0)
return FAIL;
if (item_hash == key_hash) {
/* matching hash value */
if (lseek(cdbp->fileno, (off_t) item_posn, SEEK_SET) == -1) return DEFER;
if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
item_key_len = cdb_unpack(packbuf);
/* check key length matches */
if (item_key_len == key_len) {
/* finally check if key matches */
uschar * item_key = store_get(key_len);
if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
if (Ustrncmp(keystring, item_key, key_len) == 0) {
/* Reclaim some store */
store_reset(item_key);
/* matches - get data length */
item_dat_len = cdb_unpack(packbuf + 4);
/* then we build a new result string */
*result = store_get(item_dat_len + 1);
if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
return DEFER;
(*result)[item_dat_len] = 0;
return OK;
}
/* Reclaim some store */
store_reset(item_key);
}
}
cur_offset += 8;
/* handle warp round of table */
if (cur_offset == end_offset)
cur_offset = hash_offset;
}
return FAIL;
}
/*************************************************
* Close entry point *
*************************************************/
/* See local README for interface description */
void
cdb_close(void *handle)
{
struct cdb_state * cdbp = handle;
#ifdef HAVE_MMAP
if (cdbp->cdb_map) {
munmap(CS cdbp->cdb_map, cdbp->filelen);
if (cdbp->cdb_map == cdbp->cdb_offsets)
cdbp->cdb_offsets = NULL;
}
#endif /* HAVE_MMAP */
close(cdbp->fileno);
}
/* End of lookups/cdb.c */
Index: cdb.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/cdb.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/*
* $Id: cdb.h,v 1.2.2.1 1998/05/29 16:21:36 cvs Exp $
*
* Exim - CDB database lookup module
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Copyright (c) 1998 Nigel Metheringham, Planet Online Ltd
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*
* This code implements Dan Bernstein's Constant DataBase (cdb) spec.
* Information, the spec and sample code for cdb can be obtained from
* http://www.pobox.com/~djb/cdb.html
*
* This implementation borrows some code from Dan Bernstein's
* implementation (which has no license restrictions applied to it).
* This (read-only) implementation is completely contained within
* cdb.[ch] it does *not* link against an external cdb library.
*
*
* There are 2 varients included within this code. One uses MMAP and
* should give better performance especially for multiple lookups on a
* modern machine. The other is the default implementation which is
* used in the case where the MMAP fails or if MMAP was not compiled
* in. this implementation is the same as the original reference cdb
* implementation.
*
*/
/* Functions for reading exim cdb files */
extern void *cdb_open(uschar *, uschar **);
extern BOOL cdb_check(void *, uschar *, int, uid_t *, gid_t *, uschar **);
extern int cdb_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern void cdb_close(void *);
/* End of cdb.h */
Index: dbmdb.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dbmdb.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
#include "dbmdb.h"
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description */
void *
dbmdb_open(uschar *filename, uschar **errmsg)
{
EXIM_DB *yield;
EXIM_DBOPEN(filename, O_RDONLY, 0, &yield);
if (yield == NULL)
{
int save_errno = errno;
*errmsg = string_open_failed(errno, "%s as a %s file", filename, EXIM_DBTYPE);
errno = save_errno;
}
return yield;
}
/*************************************************
* Check entry point *
*************************************************/
/* This needs to know more about the underlying files than is good for it!
We need to know what the real file names are in order to check the owners and
modes. If USE_DB is set, we know it is Berkeley DB, which uses an unmodified
file name. If USE_TDB or USE_GDBM is set, we know it is tdb or gdbm, which do
the same. Otherwise, for safety, we have to check for x.db or x.dir and x.pag.
*/
BOOL
dbmdb_check(void *handle, uschar *filename, int modemask, uid_t *owners,
gid_t *owngroups, uschar **errmsg)
{
int rc;
handle = handle; /* Keep picky compilers happy */
#if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
rc = lf_check_file(-1, filename, S_IFREG, modemask, owners, owngroups,
"dbm", errmsg);
#else
{
uschar filebuffer[256];
sprintf(CS filebuffer, "%.250s.db", filename);
rc = lf_check_file(-1, filebuffer, S_IFREG, modemask, owners, owngroups,
"dbm", errmsg);
if (rc < 0) /* stat() failed */
{
sprintf(CS filebuffer, "%.250s.dir", filename);
rc = lf_check_file(-1, filebuffer, S_IFREG, modemask, owners, owngroups,
"dbm", errmsg);
if (rc == 0) /* x.dir was OK */
{
sprintf(CS filebuffer, "%.250s.pag", filename);
rc = lf_check_file(-1, filebuffer, S_IFREG, modemask, owners, owngroups,
"dbm", errmsg);
}
}
}
#endif
return rc == 0;
}
/*************************************************
* Find entry point *
*************************************************/
/* See local README for interface description. This function adds 1 to
the keylength in order to include the terminating zero. */
int
dbmdb_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
EXIM_DB *d = (EXIM_DB *)handle;
EXIM_DATUM key, data;
filename = filename; /* Keep picky compilers happy */
errmsg = errmsg;
do_cache = do_cache;
EXIM_DATUM_INIT(key); /* Some DBM libraries require datums to */
EXIM_DATUM_INIT(data); /* be cleared before use. */
EXIM_DATUM_DATA(key) = CS keystring;
EXIM_DATUM_SIZE(key) = length + 1;
if (EXIM_DBGET(d, key, data))
{
*result = string_copyn(US EXIM_DATUM_DATA(data), EXIM_DATUM_SIZE(data));
EXIM_DATUM_FREE(data); /* Some DBM libraries need a free() call */
return OK;
}
return FAIL;
}
/*************************************************
* Find entry point - no zero on key *
*************************************************/
/* See local README for interface description */
int
dbmnz_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
return dbmdb_find(handle, filename, keystring, length-1, result, errmsg,
do_cache);
}
/*************************************************
* Close entry point *
*************************************************/
/* See local README for interface description */
void
dbmdb_close(void *handle)
{
EXIM_DBCLOSE((EXIM_DB *)handle);
}
/* End of lookups/dbmdb.c */
Index: dbmdb.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dbmdb.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the dbm lookup. Use dbmdb in the code to avoid name
clashes with external library names. */
extern void *dbmdb_open(uschar *, uschar **);
extern BOOL dbmdb_check(void *, uschar *, int, uid_t *, gid_t *, uschar **);
extern int dbmdb_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern int dbmnz_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern void dbmdb_close(void *);
/* End of lookups/dbmdb.h */
Index: dnsdb.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dnsdb.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
#include "dnsdb.h"
/* Ancient systems (e.g. SunOS4) don't appear to have T_TXT defined in their
header files. */
#ifndef T_TXT
#define T_TXT 16
#endif
/* Table of recognized DNS record types and their integer values. */
static char *type_names[] = {
"a",
#if HAVE_IPV6
"aaaa",
#ifdef SUPPORT_A6
"a6",
#endif
#endif
"cname",
"mx",
"ns",
"ptr",
"srv",
"txt" };
static int type_values[] = {
T_A,
#if HAVE_IPV6
T_AAAA,
#ifdef SUPPORT_A6
T_A6,
#endif
#endif
T_CNAME,
T_MX,
T_NS,
T_PTR,
T_SRV,
T_TXT };
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. */
void *
dnsdb_open(uschar *filename, uschar **errmsg)
{
filename = filename; /* Keep picky compilers happy */
errmsg = errmsg; /* Ditto */
return (void *)(-1); /* Any non-0 value */
}
/*************************************************
* Find entry point for dnsdb *
*************************************************/
/* See local README for interface description. */
int
dnsdb_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
int rc;
int size = 256;
int ptr = 0;
int type = T_TXT;
uschar *orig_keystring = keystring;
uschar *equals = Ustrchr(keystring, '=');
uschar buffer[256];
/* Because we're the working in the search pool, we try to reclaim as much
store as possible later, so we preallocate the result here */
uschar *yield = store_get(size);
dns_record *rr;
dns_answer dnsa;
dns_scan dnss;
handle = handle; /* Keep picky compilers happy */
filename = filename;
length = length;
do_cache = do_cache;
/* If the keystring contains an = this is preceded by a type name. */
if (equals != NULL)
{
int i;
int len = equals - keystring;
for (i = 0; i < sizeof(type_names)/sizeof(uschar *); i++)
{
if (len == Ustrlen(type_names[i]) &&
strncmpic(keystring, US type_names[i], len) == 0)
{
type = type_values[i];
break;
}
}
if (i >= sizeof(type_names)/sizeof(uschar *))
{
*errmsg = US"unsupported DNS record type";
return DEFER;
}
keystring += len + 1;
}
/* If the type is PTR, we have to construct the relevant magic lookup
key. This code is now in a separate function. */
if (type == T_PTR)
{
dns_build_reverse(keystring, buffer);
keystring = buffer;
}
DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", keystring);
/* Initialize the resolver, in case this is the first time it is used
in this run. Then do the lookup and sort out the result. */
dns_init(FALSE, FALSE);
rc = dns_lookup(&dnsa, keystring, type, NULL);
if (rc == DNS_NOMATCH || rc == DNS_NODATA) return FAIL;
if (rc != DNS_SUCCEED) return DEFER;
for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
rr != NULL;
rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
{
if (rr->type != type) continue;
/* There may be several addresses from an A6 record. Put newlines between
them, just as for between several records. */
if (type == T_A ||
#ifdef SUPPORT_A6
type == T_A6 ||
#endif
type == T_AAAA)
{
dns_address *da;
for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next)
{
if (ptr != 0) yield = string_cat(yield, &size, &ptr, US"\n", 1);
yield = string_cat(yield, &size, &ptr, da->address, Ustrlen(da->address));
}
continue;
}
/* Other kinds of record just have one piece of data each. */
if (ptr != 0) yield = string_cat(yield, &size, &ptr, US"\n", 1);
if (type == T_TXT)
{
yield = string_cat(yield, &size, &ptr, (uschar *)(rr->data+1),
(rr->data)[0]);
}
else /* T_CNAME, T_MX, T_NS, T_PTR */
{
uschar s[264];
uschar *p = (uschar *)(rr->data);
if (type == T_MX)
{
int num;
GETSHORT(num, p); /* pointer is advanced */
sprintf(CS s, "%d ", num);
yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
}
else if (type == T_SRV)
{
int num, weight, port;
GETSHORT(num, p); /* pointer is advanced */
GETSHORT(weight, p);
GETSHORT(port, p);
sprintf(CS s, "%d %d %d ", num, weight, port);
yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
}
rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
(DN_EXPAND_ARG4_TYPE)(s), sizeof(s));
/* If an overlong response was received, the data will have been
truncated and dn_expand may fail. */
if (rc < 0)
{
log_write(0, LOG_MAIN, "host name alias list truncated for %s",
orig_keystring);
break;
}
else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
}
}
yield[ptr] = 0;
store_reset(yield + ptr + 1); /* Reclaim unused */
*result = yield;
return OK;
}
/* End of lookups/dnsdb.c */
Index: dnsdb.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dnsdb.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the dnsdb lookup */
extern void *dnsdb_open(uschar *, uschar **);
extern int dnsdb_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
/* End of lookups/dnsdb.h */
Index: dsearch.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dsearch.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* The idea for this code came from Matthew Byng-Maddick, but his original has
been heavily reworked a lot for Exim 4 (and it now uses stat() rather than a
directory scan). */
#include "../exim.h"
#include "lf_functions.h"
#include "dsearch.h"
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. We open the directory to test
whether it exists and whether it is searchable. However, we don't need to keep
it open, because the "search" can be done by a call to stat() rather than
actually scanning through the list of files. */
void *
dsearch_open(uschar *dirname, uschar **errmsg)
{
DIR *dp = opendir(CS dirname);
if (dp == NULL)
{
int save_errno = errno;
*errmsg = string_open_failed(errno, "%s for directory search", dirname);
errno = save_errno;
return NULL;
}
closedir(dp);
return (void *)(-1);
}
/*************************************************
* Check entry point *
*************************************************/
/* The handle will always be (void *)(-1), but don't try casting it to an
integer as this gives warnings on 64-bit systems. */
BOOL
dsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners,
gid_t *owngroups, uschar **errmsg)
{
handle = handle;
return lf_check_file(-1, filename, S_IFDIR, modemask, owners, owngroups,
"dsearch", errmsg) == 0;
}
/*************************************************
* Find entry point *
*************************************************/
/* See local README for interface description. We use stat() instead of
scanning the directory, as it is hopefully faster to let the OS do the scanning
for us. */
int
dsearch_find(void *handle, uschar *dirname, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
struct stat statbuf;
int save_errno;
uschar filename[PATH_MAX];
handle = handle; /* Keep picky compilers happy */
length = length;
do_cache = do_cache;
if (Ustrchr(keystring, '/') != 0)
{
*errmsg = string_sprintf("key for dsearch lookup contains a slash: %s",
keystring);
return DEFER;
}
if (!string_format(filename, sizeof(filename), "%s/%s", dirname, keystring))
{
*errmsg = US"path name too long";
return DEFER;
}
if (Ustat(filename, &statbuf) >= 0)
{
*result = string_copy(keystring);
return OK;
}
if (errno == ENOENT) return FAIL;
save_errno = errno;
*errmsg = string_sprintf("%s: stat failed", filename);
errno = save_errno;
return DEFER;
}
/*************************************************
* Close entry point *
*************************************************/
/* See local README for interface description */
void
dsearch_close(void *handle)
{
handle = handle; /* Avoid compiler warning */
}
/* End of lookups/dsearch.c */
Index: dsearch.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/dsearch.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the dsearch lookup */
extern void *dsearch_open(uschar *, uschar **);
extern BOOL dsearch_check(void *, uschar *, int, uid_t *, gid_t *, uschar **);
extern int dsearch_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern void dsearch_close(void *);
/* End of lookups/dsearch.h */
Index: ibase.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/ibase.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* The code in this module was contributed by Ard Biesheuvel. */
#include "../exim.h"
#include "lf_functions.h"
#include "ibase.h"
#ifndef LOOKUP_IBASE
static void dummy(int x)
{
dummy(x - 1);
}
#else
#include <ibase.h> /* The system header */
/* Structure and anchor for caching connections. */
typedef struct ibase_connection {
struct ibase_connection *next;
uschar *server;
isc_db_handle dbh;
isc_tr_handle transh;
} ibase_connection;
static ibase_connection *ibase_connections = NULL;
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. */
void *ibase_open(uschar * filename, uschar ** errmsg)
{
return (void *) (1); /* Just return something non-null */
}
/*************************************************
* Tidy entry point *
*************************************************/
/* See local README for interface description. */
void ibase_tidy(void)
{
ibase_connection *cn;
ISC_STATUS status[20];
while ((cn = ibase_connections) != NULL) {
ibase_connections = cn->next;
DEBUG(D_lookup) debug_printf("close Interbase connection: %s\n",
cn->server);
isc_commit_transaction(status, &cn->transh);
isc_detach_database(status, &cn->dbh);
}
}
static int fetch_field(char *buffer, int buffer_size, XSQLVAR * var)
{
if (buffer_size < var->sqllen)
return 0;
switch (var->sqltype & ~1) {
case SQL_VARYING:
strncpy(buffer, &var->sqldata[2], *(short *) var->sqldata);
return *(short *) var->sqldata;
case SQL_TEXT:
strncpy(buffer, var->sqldata, var->sqllen);
return var->sqllen;
case SQL_SHORT:
return sprintf(buffer, "%d", *(short *) var->sqldata);
case SQL_LONG:
return sprintf(buffer, "%ld", *(ISC_LONG *) var->sqldata);
#ifdef SQL_INT64
case SQL_INT64:
return sprintf(buffer, "%lld", *(ISC_INT64 *) var->sqldata);
#endif
default:
/* not implemented */
return 0;
}
}
/*************************************************
* Internal search function *
*************************************************/
/* This function is called from the find entry point to do the search for a
single server.
Arguments:
query the query string
server the server string
resultptr where to store the result
errmsg where to point an error message
defer_break TRUE if no more servers are to be tried after DEFER
The server string is of the form "host:dbname|user|password". The host can be
host:port. This string is in a nextinlist temporary buffer, so can be
overwritten.
Returns: OK, FAIL, or DEFER
*/
static int
perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
uschar ** errmsg, BOOL * defer_break)
{
isc_stmt_handle stmth = NULL;
XSQLDA *out_sqlda;
XSQLVAR *var;
char buffer[256];
ISC_STATUS status[20], *statusp = status;
int i;
int ssize = 0;
int offset = 0;
int yield = DEFER;
uschar *result = NULL;
ibase_connection *cn;
uschar *server_copy = NULL;
uschar *sdata[3];
/* Disaggregate the parameters from the server argument. The order is host,
database, user, password. We can write to the string, since it is in a
nextinlist temporary buffer. The copy of the string that is used for caching
has the password removed. This copy is also used for debugging output. */
for (i = 2; i > 0; i--) {
uschar *pp = Ustrrchr(server, '|');
if (pp == NULL) {
*errmsg =
string_sprintf("incomplete Interbase server data: %s",
(i == 3) ? server : server_copy);
*defer_break = TRUE;
return DEFER;
}
*pp++ = 0;
sdata[i] = pp;
if (i == 2)
server_copy = string_copy(server); /* sans password */
}
sdata[0] = server; /* What's left at the start */
/* See if we have a cached connection to the server */
for (cn = ibase_connections; cn != NULL; cn = cn->next) {
if (Ustrcmp(cn->server, server_copy) == 0) {
break;
}
}
/* Use a previously cached connection ? */
if (cn != NULL) {
static char db_info_options[] = { isc_info_base_level };
/* test if the connection is alive */
if (isc_database_info
(status, &cn->dbh, sizeof(db_info_options), db_info_options,
sizeof(buffer), buffer)) {
/* error occurred: assume connection is down */
DEBUG(D_lookup)
debug_printf
("Interbase cleaning up cached connection: %s\n",
cn->server);
isc_detach_database(status, &cn->dbh);
} else {
DEBUG(D_lookup)
debug_printf("Interbase using cached connection for %s\n",
server_copy);
}
} else {
cn = store_get(sizeof(ibase_connection));
cn->server = server_copy;
cn->dbh = NULL;
cn->transh = NULL;
cn->next = ibase_connections;
ibase_connections = cn;
}
/* If no cached connection, we must set one up. */
if (cn->dbh == NULL || cn->transh == NULL) {
char *dpb, *p;
short dpb_length;
static char trans_options[] =
{ isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
isc_tpb_rec_version
};
/* Construct the database parameter buffer. */
dpb = buffer;
*dpb++ = isc_dpb_version1;
*dpb++ = isc_dpb_user_name;
*dpb++ = strlen(sdata[1]);
for (p = sdata[1]; *p;)
*dpb++ = *p++;
*dpb++ = isc_dpb_password;
*dpb++ = strlen(sdata[2]);
for (p = sdata[2]; *p;)
*dpb++ = *p++;
dpb_length = dpb - buffer;
DEBUG(D_lookup)
debug_printf("new Interbase connection: database=%s user=%s\n",
sdata[0], sdata[1]);
/* Connect to the database */
if (isc_attach_database
(status, 0, sdata[0], &cn->dbh, dpb_length, buffer)) {
isc_interprete(buffer, &statusp);
*errmsg =
string_sprintf("Interbase attach() failed: %s", buffer);
*defer_break = FALSE;
goto IBASE_EXIT;
}
/* Now start a read-only read-committed transaction */
if (isc_start_transaction
(status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
trans_options)) {
isc_interprete(buffer, &statusp);
isc_detach_database(status, &cn->dbh);
*errmsg =
string_sprintf("Interbase start_transaction() failed: %s",
buffer);
*defer_break = FALSE;
goto IBASE_EXIT;
}
}
/* Run the query */
if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) {
isc_interprete(buffer, &statusp);
*errmsg =
string_sprintf("Interbase alloc_statement() failed: %s",
buffer);
*defer_break = FALSE;
goto IBASE_EXIT;
}
out_sqlda = store_get(XSQLDA_LENGTH(1));
out_sqlda->version = SQLDA_VERSION1;
out_sqlda->sqln = 1;
if (isc_dsql_prepare
(status, &cn->transh, &stmth, 0, query, 1, out_sqlda)) {
isc_interprete(buffer, &statusp);
store_reset(out_sqlda);
out_sqlda = NULL;
*errmsg =
string_sprintf("Interbase prepare_statement() failed: %s",
buffer);
*defer_break = FALSE;
goto IBASE_EXIT;
}
/* re-allocate the output structure if there's more than one field */
if (out_sqlda->sqln < out_sqlda->sqld) {
XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
if (isc_dsql_describe
(status, &stmth, out_sqlda->version, new_sqlda)) {
isc_interprete(buffer, &statusp);
isc_dsql_free_statement(status, &stmth, DSQL_drop);
store_reset(out_sqlda);
out_sqlda = NULL;
*errmsg =
string_sprintf("Interbase describe_statement() failed: %s",
buffer);
*defer_break = FALSE;
goto IBASE_EXIT;
}
out_sqlda = new_sqlda;
}
/* allocate storage for every returned field */
for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) {
switch (var->sqltype & ~1) {
case SQL_VARYING:
var->sqldata =
(char *) store_get(sizeof(char) * var->sqllen + 2);
break;
case SQL_TEXT:
var->sqldata =
(char *) store_get(sizeof(char) * var->sqllen);
break;
case SQL_SHORT:
var->sqldata = (char *) store_get(sizeof(short));
break;
case SQL_LONG:
var->sqldata = (char *) store_get(sizeof(ISC_LONG));
break;
#ifdef SQL_INT64
case SQL_INT64:
var->sqldata = (char *) store_get(sizeof(ISC_INT64));
break;
#endif
case SQL_FLOAT:
var->sqldata = (char *) store_get(sizeof(float));
break;
case SQL_DOUBLE:
var->sqldata = (char *) store_get(sizeof(double));
break;
#ifdef SQL_TIMESTAMP
case SQL_DATE:
var->sqldata = (char *) store_get(sizeof(ISC_QUAD));
break;
#else
case SQL_TIMESTAMP:
var->sqldata = (char *) store_get(sizeof(ISC_TIMESTAMP));
break;
case SQL_TYPE_DATE:
var->sqldata = (char *) store_get(sizeof(ISC_DATE));
break;
case SQL_TYPE_TIME:
var->sqldata = (char *) store_get(sizeof(ISC_TIME));
break;
#endif
}
if (var->sqltype & 1) {
var->sqlind = (short *) store_get(sizeof(short));
}
}
/* finally, we're ready to execute the statement */
if (isc_dsql_execute
(status, &cn->transh, &stmth, out_sqlda->version, NULL)) {
isc_interprete(buffer, &statusp);
*errmsg =
string_sprintf("Interbase describe_statement() failed: %s",
buffer);
isc_dsql_free_statement(status, &stmth, DSQL_drop);
*defer_break = FALSE;
goto IBASE_EXIT;
}
while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) !=
100L) {
/* check if an error occurred */
if (status[0] & status[1]) {
isc_interprete(buffer, &statusp);
*errmsg =
string_sprintf("Interbase fetch() failed: %s", buffer);
isc_dsql_free_statement(status, &stmth, DSQL_drop);
*defer_break = FALSE;
goto IBASE_EXIT;
}
if (result != NULL)
result = string_cat(result, &ssize, &offset, US "\n", 1);
/* Find the number of fields returned. If this is one, we don't add field
names to the data. Otherwise we do. */
if (out_sqlda->sqld == 1) {
if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */
result =
string_cat(result, &ssize, &offset, US buffer,
fetch_field(buffer, sizeof(buffer),
&out_sqlda->sqlvar[0]));
}
else
for (i = 0; i < out_sqlda->sqld; i++) {
int len = fetch_field(buffer, sizeof(buffer),
&out_sqlda->sqlvar[i]);
result =
string_cat(result, &ssize, &offset,
US out_sqlda->sqlvar[i].aliasname,
out_sqlda->sqlvar[i].aliasname_length);
result = string_cat(result, &ssize, &offset, US "=", 1);
/* Quote the value if it contains spaces or is empty */
if (*out_sqlda->sqlvar[i].sqlind == -1) { /* NULL value */
result =
string_cat(result, &ssize, &offset, US "\"\"", 2);
}
else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) {
int j;
result =
string_cat(result, &ssize, &offset, US "\"", 1);
for (j = 0; j < len; j++) {
if (buffer[j] == '\"' || buffer[j] == '\\')
result =
string_cat(result, &ssize, &offset,
US "\\", 1);
result =
string_cat(result, &ssize, &offset,
US buffer + j, 1);
}
result =
string_cat(result, &ssize, &offset, US "\"", 1);
} else {
result =
string_cat(result, &ssize, &offset, US buffer,
len);
}
result = string_cat(result, &ssize, &offset, US " ", 1);
}
}
/* If result is NULL then no data has been found and so we return FAIL.
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */
if (result == NULL) {
yield = FAIL;
*errmsg = US "Interbase: no data found";
} else {
result[offset] = 0;
store_reset(result + offset + 1);
}
/* Get here by goto from various error checks. */
IBASE_EXIT:
if (stmth != NULL)
isc_dsql_free_statement(status, &stmth, DSQL_drop);
/* Non-NULL result indicates a sucessful result */
if (result != NULL) {
*resultptr = result;
return OK;
} else {
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
return yield; /* FAIL or DEFER */
}
}
/*************************************************
* Find entry point *
*************************************************/
/* See local README for interface description. The handle and filename
arguments are not used. Loop through a list of servers while the query is
deferred with a retryable error. */
int
ibase_find(void *handle, uschar * filename, uschar * query, int length,
uschar ** result, uschar ** errmsg, BOOL *do_cache)
{
int sep = 0;
uschar *server;
uschar *list = ibase_servers;
uschar buffer[512];
/* Keep picky compilers happy */
do_cache = do_cache;
DEBUG(D_lookup) debug_printf("Interbase query: %s\n", query);
while ((server =
string_nextinlist(&list, &sep, buffer,
sizeof(buffer))) != NULL) {
BOOL defer_break = FALSE;
int rc = perform_ibase_search(query, server, result, errmsg,
&defer_break);
if (rc != DEFER || defer_break)
return rc;
}
if (ibase_servers == NULL)
*errmsg = US "no Interbase servers defined (ibase_servers option)";
return DEFER;
}
/*************************************************
* Quote entry point *
*************************************************/
/* The only characters that need to be quoted (with backslash) are newline,
tab, carriage return, backspace, backslash itself, and the quote characters.
Percent, and underscore and not escaped. They are only special in contexts
where they can be wild cards, and this isn't usually the case for data inserted
from messages, since that isn't likely to be treated as a pattern of any kind.
Sadly, MySQL doesn't seem to behave like other programs. If you use something
like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
can't quote "on spec".
Arguments:
s the string to be quoted
opt additional option text or NULL if none
Returns: the processed string or NULL for a bad option
*/
uschar *ibase_quote(uschar * s, uschar * opt)
{
register int c;
int count = 0;
uschar *t = s;
uschar *quoted;
if (opt != NULL)
return NULL; /* No options recognized */
while ((c = *t++) != 0)
if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
count++;
if (count == 0)
return s;
t = quoted = store_get(Ustrlen(s) + count + 1);
while ((c = *s++) != 0) {
if (Ustrchr("'", c) != NULL) {
*t++ = '\'';
*t++ = '\'';
/* switch(c)
{
case '\n': *t++ = 'n';
break;
case '\t': *t++ = 't';
break;
case '\r': *t++ = 'r';
break;
case '\b': *t++ = 'b';
break;
default: *t++ = c;
break;
}*/
} else
*t++ = c;
}
*t = 0;
return quoted;
}
#endif
/* End of lookups/ibase.c */
Index: ibase.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/ibase.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the Interbase lookup functions */
extern void *ibase_open(uschar *, uschar **);
extern int ibase_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern void ibase_tidy(void);
extern uschar *ibase_quote(uschar *, uschar *);
/* End of lookups/ibase.h */
Index: ldap.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/ldap.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Many thanks to Stuart Lynne for contributing the original code for this
driver. Further contibutions from Michael Haardt, Brian Candler, Barry
Pederson, Peter Savitch and Christian Kellner. Particular thanks to Brian for
researching how to handle the different kinds of error. */
#include "../exim.h"
#include "lf_functions.h"
#include "ldap.h"
/* We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the LDAP headers
available for compiling. Therefore, compile these functions only if LOOKUP_LDAP
is defined. However, some compilers don't like compiling empty modules, so keep
them happy with a dummy when skipping the rest. Make it reference itself to
stop picky compilers complaining that it is unused, and put in a dummy argument
to stop even pickier compilers complaining about infinite loops. */
#ifndef LOOKUP_LDAP
static void dummy(int x) { dummy(x-1); }
#else
/* Include LDAP headers */
#include <lber.h>
#include <ldap.h>
/* Annoyingly, the different LDAP libraries handle errors in different ways,
and some other things too. There doesn't seem to be an automatic way of
distinguishing between them. Local/Makefile should contain a setting of
LDAP_LIB_TYPE, which in turn causes appropriate macros to be defined for the
different kinds. Those that matter are:
LDAP_LIB_NETSCAPE
LDAP_LIB_SOLARIS with synonym LDAP_LIB_SOLARIS7
LDAP_LIB_OPENLDAP2
These others may be defined, but are in fact the default, so are not tested:
LDAP_LIB_UMICHIGAN
LDAP_LIB_OPENLDAP1
*/
#if defined(LDAP_LIB_SOLARIS7) && ! defined(LDAP_LIB_SOLARIS)
#define LDAP_LIB_SOLARIS
#endif
/* Just in case LDAP_NO_LIMIT is not defined by some of these libraries. */
#ifndef LDAP_NO_LIMIT
#define LDAP_NO_LIMIT 0
#endif
/* Just in case LDAP_DEREF_NEVER is not defined */
#ifndef LDAP_DEREF_NEVER
#define LDAP_DEREF_NEVER 0
#endif
/* For libraries without TCP connect timeouts */
#ifndef LDAP_X_IO_TIMEOUT_NO_TIMEOUT
#define LDAP_X_IO_TIMEOUT_NO_TIMEOUT (-1)
#endif
/* Four types of LDAP search are implemented */
#define SEARCH_LDAP_MULTIPLE 0 /* Get attributes from multiple entries */
#define SEARCH_LDAP_SINGLE 1 /* Get attributes from one entry only */
#define SEARCH_LDAP_DN 2 /* Get just the DN from one entry */
#define SEARCH_LDAP_AUTH 3 /* Just checking for authentication */
/* In all 4 cases, the DN is left in $ldap_dn (which post-dates the
SEARCH_LDAP_DN lookup). */
/* Structure and anchor for caching connections. */
typedef struct ldap_connection {
struct ldap_connection *next;
uschar *host;
uschar *user;
uschar *password;
BOOL bound;
int port;
LDAP *ld;
} LDAP_CONNECTION;
static LDAP_CONNECTION *ldap_connections = NULL;
/*************************************************
* Internal search function *
*************************************************/
/* This is the function that actually does the work. It is called (indirectly
via control_ldap_search) from eldap_find(), eldapauth_find(), eldapdn_find(),
and eldapm_find(), with a difference in the "search_type" argument.
The case of eldapauth_find() is special in that all it does is do
authentication, returning OK or FAIL as appropriate. This isn't used as a
lookup. Instead, it is called from expand.c as an expansion condition test.
The DN from a successful lookup is placed in $ldap_dn. This feature postdates
the provision of the SEARCH_LDAP_DN facility for returning just the DN as the
data.
Arguments:
ldap_url the URL to be looked up
server server host name, when URL contains none
s_port server port, used when URL contains no name
search_type SEARCH_LDAP_MULTIPLE allows values from multiple entries
SEARCH_LDAP_SINGLE allows values from one entry only
SEARCH_LDAP_DN gets the DN from one entry
res set to point at the result (not used for ldapauth)
errmsg set to point a message if result is not OK
defer_break set TRUE if no more servers to be tried after a DEFER
user user name for authentication, or NULL
password password for authentication, or NULL
sizelimit max number of entries returned, or 0 for no limit
timelimit max time to wait, or 0 for no limit
tcplimit max time to connect, or NULL for OS default
deference the dereference option, which is one of
LDAP_DEREF_{NEVER,SEARCHING,FINDING,ALWAYS}
Returns: OK or FAIL or DEFER
FAIL is given only if a lookup was performed successfully, but
returned no data.
*/
static int
perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type,
uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password,
int sizelimit, int timelimit, int tcplimit, int dereference)
{
LDAPURLDesc *ludp = NULL;
LDAPMessage *result = NULL;
BerElement *ber;
LDAP_CONNECTION *lcp;
struct timeval timeout;
struct timeval *timeoutptr = NULL;
uschar *attr;
uschar **attrp;
uschar *data = NULL;
uschar *dn = NULL;
uschar *host;
uschar **values;
uschar **firstval;
uschar porttext[16];
uschar *error1 = NULL; /* string representation of errcode (static) */
uschar *error2 = NULL; /* error message from the server */
uschar *matched = NULL; /* partially matched DN */
int attr_count = 0;
int error_yield = DEFER;
int msgid;
int rc;
int port;
int ptr = 0;
int rescount = 0;
int size = 0;
BOOL attribute_found = FALSE;
BOOL ldapi = FALSE;
DEBUG(D_lookup)
debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
"sizelimit=%d timelimit=%d tcplimit=%d\n",
(search_type == SEARCH_LDAP_MULTIPLE)? "m" :
(search_type == SEARCH_LDAP_DN)? "dn" :
(search_type == SEARCH_LDAP_AUTH)? "auth" : "",
ldap_url, server, s_port, sizelimit, timelimit, tcplimit);
/* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP
library that is in use doesn't recognize, say, "ldapi", it will barf here. */
if (!ldap_is_ldap_url(CS ldap_url))
{
*errmsg = string_sprintf("ldap_is_ldap_url: not an LDAP url \"%s\"\n",
ldap_url);
goto RETURN_ERROR_BREAK;
}
/* Parse the URL */
if ((rc = ldap_url_parse(CS ldap_url, &ludp)) != 0)
{
*errmsg = string_sprintf("ldap_url_parse: (error %d) parsing \"%s\"\n", rc,
ldap_url);
goto RETURN_ERROR_BREAK;
}
/* If the host name is empty, take it from the separate argument, if one is
given. OpenLDAP 2.0.6 sets an unset hostname to "" rather than empty, but
expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP
2.0.11 this has changed (it uses NULL). */
if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL)
{
host = server;
port = s_port;
}
else
{
host = US ludp->lud_host;
if (host != NULL && host[0] == 0) host = NULL;
port = ludp->lud_port;
}
DEBUG(D_lookup) debug_printf("after ldap_url_parse: host=%s port=%d\n",
host, port);
if (port == 0) port = LDAP_PORT; /* Default if none given */
sprintf(CS porttext, ":%d", port); /* For messages */
/* If the "host name" is actually a path, we are going to connect using a Unix
socket, regardless of whether "ldapi" was actually specified or not. This means
that a Unix socket can be declared in eldap_default_servers, and "traditional"
LDAP queries using just "ldap" can be used ("ldaps" is similarly overridden).
The path may start with "/" or it may already be escaped as "%2F" if it was
actually declared that way in eldap_default_servers. (I did it that way the
first time.) If the host name is not a path, the use of "ldapi" causes an
error, except in the default case. (But lud_scheme doesn't seem to exist in
older libraries.) */
if (host != NULL)
{
if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0))
{
ldapi = TRUE;
porttext[0] = 0; /* Remove port from messages */
}
#if defined LDAP_LIB_OPENLDAP2
else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0)
{
*errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)",
host);
goto RETURN_ERROR;
}
#endif
}
/* Count the attributes; we need this later to tell us how to format results */
for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
attr_count++;
/* See if we can find a cached connection to this host. The port is not
relevant for ldapi. The host name pointer is set to NULL if no host was given
(implying the library default), rather than to the empty string. Note that in
this case, there is no difference between ldap and ldapi. */
for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
{
if ((host == NULL) != (lcp->host == NULL) ||
(host != NULL && strcmpic(lcp->host, host) != 0))
continue;
if (ldapi || port == lcp->port) break;
}
/* If no cached connection found, we must open a connection to the server. If
the server name is actually an absolute path, we set ldapi=TRUE above. This
requests connection via a Unix socket. However, as far as I know, only OpenLDAP
supports the use of sockets, and the use of ldap_initialize(). */
if (lcp == NULL)
{
LDAP *ld;
/* --------------------------- OpenLDAP ------------------------ */
/* There seems to be a preference under OpenLDAP for ldap_initialize()
instead of ldap_init(), though I have as yet been unable to find
documentation that says this. (OpenLDAP documentation is sparse to
non-existent). So we handle OpenLDAP differently here. Also, support for
ldapi seems to be OpenLDAP-only at present. */
#ifdef LDAP_LIB_OPENLDAP2
/* We now need an empty string for the default host. Get some store in which
to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger
than (9 + 3*Ustrlen(shost)), whereas in the other cases it can't be bigger
than the host name + "ldaps:///" plus : and a port number, say 20 + the
length of the host name. What we get should accommodate both, easily. */
uschar *shost = (host == NULL)? US"" : host;
uschar *init_url = store_get(20 + 3 * Ustrlen(shost));
uschar *init_ptr;
/* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to
contain the path name, with slashes escaped as %2F. */
if (ldapi)
{
int ch;
init_ptr = init_url + 8;
Ustrcpy(init_url, "ldapi://");
while ((ch = *shost++) != 0)
{
if (ch == '/')
{
Ustrncpy(init_ptr, "%2F", 3);
init_ptr += 3;
}
else *init_ptr++ = ch;
}
*init_ptr = 0;
}
/* This is not an ldapi call. Just build a URI with the protocol type, host
name, and port. */
else
{
init_ptr = Ustrchr(ldap_url, '/');
Ustrncpy(init_url, ldap_url, init_ptr - ldap_url);
init_ptr = init_url + (init_ptr - ldap_url);
sprintf(CS init_ptr, "//%s:%d/", shost, port);
}
/* Call ldap_initialize() and check the result */
DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
rc = ldap_initialize(&ld, CS init_url);
if (rc != LDAP_SUCCESS)
{
*errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
rc, init_url);
goto RETURN_ERROR;
}
store_reset(init_url); /* Might as well save memory when we can */
/* ------------------------- Not OpenLDAP ---------------------- */
/* For libraries other than OpenLDAP, use ldap_init(). */
#else /* LDAP_LIB_OPENLDAP2 */
ld = ldap_init(CS host, port);
#endif /* LDAP_LIB_OPENLDAP2 */
/* -------------------------------------------------------------- */
/* Handle failure to initialize */
if (ld == NULL)
{
*errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s",
host, porttext, strerror(errno));
goto RETURN_ERROR;
}
/* Set the TCP connect time limit if available. This is something that is
in Netscape SDK v4.1; I don't know about other libraries. */
#ifdef LDAP_X_OPT_CONNECT_TIMEOUT
ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&tcplimit);
#endif
/* I could not get TLS to work until I set the version to 3. That version
seems to be the default nowadays. The RFC is dated 1997, so I would hope
that all the LDAP libraries support it. Therefore, if eldap_version hasn't
been set, go for v3 if we can. */
if (eldap_version < 0)
{
#ifdef LDAP_VERSION3
eldap_version = LDAP_VERSION3;
#else
eldap_version = 2;
#endif
}
#ifdef LDAP_OPT_PROTOCOL_VERSION
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
#endif
DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
eldap_version, host, porttext);
/* If not using ldapi and TLS is available, set appropriate TLS options: hard
for "ldaps" and soft otherwise. */
#ifdef LDAP_OPT_X_TLS
if (!ldapi)
{
int tls_option;
if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
{
tls_option = LDAP_OPT_X_TLS_HARD;
DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n");
}
else
{
tls_option = LDAP_OPT_X_TLS_TRY;
DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n");
}
ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
}
#endif /* LDAP_OPT_X_TLS */
/* Now add this connection to the chain of cached connections */
lcp = store_get(sizeof(LDAP_CONNECTION));
lcp->host = (host == NULL)? NULL : string_copy(host);
lcp->bound = FALSE;
lcp->user = NULL;
lcp->password = NULL;
lcp->port = port;
lcp->ld = ld;
lcp->next = ldap_connections;
ldap_connections = lcp;
}
/* Found cached connection */
else
{
DEBUG(D_lookup)
debug_printf("re-using cached connection to LDAP server %s%s\n",
host, porttext);
}
/* Bind with the user/password supplied, or an anonymous bind if these values
are NULL, unless a cached connection is already bound with the same values. */
if (!lcp->bound ||
(lcp->user == NULL && user != NULL) ||
(lcp->user != NULL && user == NULL) ||
(lcp->user != NULL && user != NULL && Ustrcmp(lcp->user, user) != 0) ||
(lcp->password == NULL && password != NULL) ||
(lcp->password != NULL && password == NULL) ||
(lcp->password != NULL && password != NULL &&
Ustrcmp(lcp->password, password) != 0))
{
DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
(lcp->bound)? "re-" : "", user, password);
if ((rc = ldap_bind_s(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
!= LDAP_SUCCESS)
{
/* Invalid credentials when just checking credentials returns FAIL. This
stops any further servers being tried. */
if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
{
DEBUG(D_lookup)
debug_printf("Invalid credentials: ldapauth returns FAIL\n");
error_yield = FAIL;
goto RETURN_ERROR_NOMSG;
}
/* Otherwise we have a problem that doesn't stop further servers from being
tried. */
*errmsg = string_sprintf("failed to bind the LDAP connection to server "
"%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc));
goto RETURN_ERROR;
}
/* Successful bind */
lcp->bound = TRUE;
lcp->user = (user == NULL)? NULL : string_copy(user);
lcp->password = (password == NULL)? NULL : string_copy(password);
}
/* If we are just checking credentials, return OK. */
if (search_type == SEARCH_LDAP_AUTH)
{
DEBUG(D_lookup) debug_printf("Bind succeeded: ldapauth returns OK\n");
goto RETURN_OK;
}
/* Before doing the search, set the time and size limits (if given). Here again
the different implementations of LDAP have chosen to do things differently. */
#if defined(LDAP_OPT_SIZELIMIT)
ldap_set_option(lcp->ld, LDAP_OPT_SIZELIMIT, (void *)&sizelimit);
ldap_set_option(lcp->ld, LDAP_OPT_TIMELIMIT, (void *)&timelimit);
#else
lcp->ld->ld_sizelimit = sizelimit;
lcp->ld->ld_timelimit = timelimit;
#endif
/* Similarly for dereferencing aliases. Don't know if this is possible on
an LDAP library without LDAP_OPT_DEREF. */
#if defined(LDAP_OPT_DEREF)
ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference);
#endif
/* Start the search on the server. */
DEBUG(D_lookup) debug_printf("Start search\n");
msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
ludp->lud_attrs, 0);
if (msgid == -1)
{
*errmsg = string_sprintf("ldap search initiation failed");
goto RETURN_ERROR;
}
/* Loop to pick up results as they come in, setting a timeout if one was
given. */
if (timelimit > 0)
{
timeout.tv_sec = timelimit;
timeout.tv_usec = 0;
timeoutptr = &timeout;
}
while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
LDAP_RES_SEARCH_ENTRY)
{
LDAPMessage *e;
DEBUG(D_lookup) debug_printf("ldap_result loop\n");
for(e = ldap_first_entry(lcp->ld, result);
e != NULL;
e = ldap_next_entry(lcp->ld, e))
{
uschar *new_dn;
BOOL insert_space = FALSE;
DEBUG(D_lookup) debug_printf("LDAP entry loop\n");
rescount++; /* Count results */
/* Results for multiple entries values are separated by newlines. */
if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);
/* Get the DN from the last result. */
new_dn = US ldap_get_dn(lcp->ld, e);
if (new_dn != NULL)
{
if (dn != NULL)
{
#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
ldap_memfree(dn);
#else /* OPENLDAP 1, UMich, Solaris */
free(dn);
#endif
}
/* Save for later */
dn = new_dn;
}
/* If the data we want is actually the DN rather than any attribute values,
(an "ldapdn" search) add it to the data string. If there are multiple
entries, the DNs will be concatenated, but we test for this case below, as
for SEARCH_LDAP_SINGLE, and give an error. */
if (search_type == SEARCH_LDAP_DN) /* Do not amalgamate these into one */
{ /* condition, because of the else */
if (new_dn != NULL) /* below, that's for the first only */
{
data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn));
data[ptr] = 0;
attribute_found = TRUE;
}
}
/* Otherwise, loop through the entry, grabbing attribute values. If there's
only one attribute being retrieved, no attribute name is given, and the
result is not quoted. Multiple values are separated by (comma, space).
If more than one attribute is being retrieved, the data is given as a
sequence of name=value pairs, with the value always in quotes. If there are
multiple values, they are given within the quotes, comma separated. */
else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
attr != NULL;
attr = US ldap_next_attribute(lcp->ld, e, ber))
{
if (attr[0] != 0)
{
/* Get array of values for this attribute. */
if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
!= NULL)
{
if (attr_count != 1)
{
if (insert_space)
data = string_cat(data, &size, &ptr, US" ", 1);
else
insert_space = TRUE;
data = string_cat(data, &size, &ptr, attr, Ustrlen(attr));
data = string_cat(data, &size, &ptr, US"=\"", 2);
}
while (*values != NULL)
{
uschar *value = *values;
int len = Ustrlen(value);
DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
if (values != firstval)
data = string_cat(data, &size, &ptr, US", ", 2);
/* For multiple attributes, the data is in quotes. We must escape
internal quotes, backslashes, newlines. */
if (attr_count != 1)
{
int j;
for (j = 0; j < len; j++)
{
if (value[j] == '\n')
data = string_cat(data, &size, &ptr, US"\\n", 2);
else
{
if (value[j] == '\"' || value[j] == '\\')
data = string_cat(data, &size, &ptr, US"\\", 1);
data = string_cat(data, &size, &ptr, value+j, 1);
}
}
}
/* For single attributes, copy the value verbatim */
else data = string_cat(data, &size, &ptr, value, len);
/* Move on to the next value */
values++;
attribute_found = TRUE;
}
/* Closing quote at the end of the data for a named attribute. */
if (attr_count != 1)
data = string_cat(data, &size, &ptr, US"\"", 1);
/* Free the values */
ldap_value_free(CSS firstval);
}
}
#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
/* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need
to be freed. UMich LDAP stores them in static storage and does not require
this. */
ldap_memfree(attr);
#endif
} /* End "for" loop for extracting attributes from an entry */
} /* End "for" loop for extracting entries from a result */
/* Free the result */
ldap_msgfree(result);
result = NULL;
} /* End "while" loop for multiple results */
/* Terminate the dynamic string that we have built and reclaim unused store */
if (data != NULL)
{
data[ptr] = 0;
store_reset(data + ptr + 1);
}
/* Copy the last dn into eldap_dn */
if (dn != NULL)
{
eldap_dn = string_copy(dn);
#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
ldap_memfree(dn);
#else /* OPENLDAP 1, UMich, Solaris */
free(dn);
#endif
}
DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);
if (rc == 0)
{
*errmsg = US"ldap_result timed out";
goto RETURN_ERROR;
}
/* A return code of -1 seems to mean "ldap_result failed internally or couldn't
provide you with a message". Other error states seem to exist where
ldap_result() didn't give us any message from the server at all, leaving result
set to NULL. Apparently, "the error parameters of the LDAP session handle will
be set accordingly". That's the best we can do to retrieve an error status; we
can't use functions like ldap_result2error because they parse a message from
the server, which we didn't get.
Annoyingly, the different implementations of LDAP have gone for different
methods of handling error codes and generating error messages. */
if (rc == -1 || result == NULL)
{
int err;
DEBUG(D_lookup) debug_printf("ldap_result failed\n");
#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
*errmsg = string_sprintf("ldap_result failed: %d, %s",
err, ldap_err2string(err));
#elif defined LDAP_LIB_NETSCAPE
/* Dubious (surely 'matched' is spurious here?) */
(void)ldap_get_lderrno(lcp->ld, &matched, &error1);
*errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched);
#else /* UMich LDAP aka OpenLDAP 1.x */
*errmsg = string_sprintf("ldap_result failed: %d, %s",
lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno));
#endif
goto RETURN_ERROR;
}
/* A return code that isn't -1 doesn't necessarily mean there were no problems
with the search. The message must be an LDAP_RES_SEARCH_RESULT or else it's
something we can't handle. */
if (rc != LDAP_RES_SEARCH_RESULT)
{
*errmsg = string_sprintf("ldap_result returned unexpected code %d", rc);
goto RETURN_ERROR;
}
/* We have a result message from the server. This doesn't yet mean all is well.
We need to parse the message to find out exactly what's happened. */
#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
if (ldap_parse_result(lcp->ld, result, &rc, CSS &matched, CSS &error2, NULL,
NULL, 0) < 0)
{
*errmsg = US"ldap_parse_result failed";
goto RETURN_ERROR;
}
error1 = US ldap_err2string(rc);
#elif defined LDAP_LIB_NETSCAPE
/* Dubious (it doesn't reference 'result' at all!) */
rc = ldap_get_lderrno(lcp->ld, &matched, &error1);
#else /* UMich LDAP aka OpenLDAP 1.x */
rc = ldap_result2error(lcp->ld, result, 0);
error1 = ldap_err2string(rc);
error2 = lcp->ld->ld_error;
matched = lcp->ld->ld_matched;
#endif
/* Process the status as follows:
(1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the
truncated result list.
(2) The range of errors defined by LDAP_NAME_ERROR generally mean "that
object does not, or cannot, exist in the database". For those cases we
fail the lookup.
(3) All other non-successes here are treated as some kind of problem with
the lookup, so return DEFER (which is the default in error_yield).
*/
DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
rc, ldap_err2string(rc));
if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED)
{
*errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
rc,
(error1 != NULL)? error1 : US"",
(error2 != NULL && error2[0] != 0)? US"/" : US"",
(error2 != NULL)? error2 : US"",
(matched != NULL && matched[0] != 0)? US"/" : US"",
(matched != NULL)? matched : US"");
#if defined LDAP_NAME_ERROR
if (LDAP_NAME_ERROR(rc))
#elif defined NAME_ERROR /* OPENLDAP1 calls it this */
if (NAME_ERROR(rc))
#else
if (rc == LDAP_NO_SUCH_OBJECT)
#endif
{
DEBUG(D_lookup) debug_printf("lookup failure forced\n");
error_yield = FAIL;
}
goto RETURN_ERROR;
}
/* The search succeeded. Check if we have too many results */
if (search_type != SEARCH_LDAP_MULTIPLE && rescount > 1)
{
*errmsg = string_sprintf("LDAP search: more than one entry (%d) was returned "
"(filter not specific enough?)", rescount);
goto RETURN_ERROR_BREAK;
}
/* Check if we have too few (zero) entries */
if (rescount < 1)
{
*errmsg = string_sprintf("LDAP search: no results");
error_yield = FAIL;
goto RETURN_ERROR_BREAK;
}
/* If an entry was found, but it had no attributes, we behave as if no entries
were found, that is, the lookup failed. */
if (!attribute_found)
{
*errmsg = US"LDAP search: found no attributes";
error_yield = FAIL;
goto RETURN_ERROR;
}
/* Otherwise, it's all worked */
DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data);
*res = data;
RETURN_OK:
if (result != NULL) ldap_msgfree(result);
ldap_free_urldesc(ludp);
return OK;
/* Error returns */
RETURN_ERROR_BREAK:
*defer_break = TRUE;
RETURN_ERROR:
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
RETURN_ERROR_NOMSG:
if (result != NULL) ldap_msgfree(result);
if (ludp != NULL) ldap_free_urldesc(ludp);
#if defined LDAP_LIB_OPENLDAP2
if (error2 != NULL) ldap_memfree(error2);
if (matched != NULL) ldap_memfree(matched);
#endif
return error_yield;
}
/*************************************************
* Internal search control function *
*************************************************/
/* This function is called from eldap_find(), eldapauth_find(), eldapdn_find(),
and eldapm_find() with a difference in the "search_type" argument. It controls
calls to perform_ldap_search() which actually does the work. We call that
repeatedly for certain types of defer in the case when the URL contains no host
name and eldap_default_servers is set to a list of servers to try. This gives
more control than just passing over a list of hosts to ldap_open() because it
handles other kinds of defer as well as just a failure to open. Note that the
URL is defined to contain either zero or one "hostport" only.
Parameter data in addition to the URL can be passed as preceding text in the
string, as items of the form XXX=yyy. The URL itself can be detected because it
must begin "ldapx://", where x is empty, s, or i.
Arguments:
ldap_url the URL to be looked up, optionally preceded by other parameter
settings
search_type SEARCH_LDAP_MULTIPLE allows values from multiple entries
SEARCH_LDAP_SINGLE allows values from one entry only
SEARCH_LDAP_DN gets the DN from one entry
res set to point at the result
errmsg set to point a message if result is not OK
Returns: OK or FAIL or DEFER
*/
static int
control_ldap_search(uschar *ldap_url, int search_type, uschar **res,
uschar **errmsg)
{
BOOL defer_break = FALSE;
int timelimit = LDAP_NO_LIMIT;
int sizelimit = LDAP_NO_LIMIT;
int tcplimit = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
int dereference = LDAP_DEREF_NEVER;
int sep = 0;
uschar *url = ldap_url;
uschar *p;
uschar *user = NULL;
uschar *password = NULL;
uschar *server, *list;
uschar buffer[512];
while (isspace(*url)) url++;
/* Until the string begins "ldap", search for the other parameter settings that
are recognized. They are of the form NAME=VALUE, with the value being
optionally double-quoted. There must still be a space after it, however. No
NAME has the value "ldap". */
while (strncmpic(url, US"ldap", 4) != 0)
{
uschar *name = url;
while (*url != 0 && *url != '=') url++;
if (*url == '=')
{
int namelen;
uschar *value;
namelen = ++url - name;
value = string_dequote(&url);
if (isspace(*url))
{
if (strncmpic(name, US"USER=", namelen) == 0) user = value;
else if (strncmpic(name, US"PASS=", namelen) == 0) password = value;
else if (strncmpic(name, US"SIZE=", namelen) == 0) sizelimit = Uatoi(value);
else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value) * 1000;
/* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
#ifdef LDAP_OPT_DEREF
else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0)
{
if (strcmpic(value, US"never") == 0) dereference = LDAP_DEREF_NEVER;
else if (strcmpic(value, US"searching") == 0)
dereference = LDAP_DEREF_SEARCHING;
else if (strcmpic(value, US"finding") == 0)
dereference = LDAP_DEREF_FINDING;
if (strcmpic(value, US"always") == 0) dereference = LDAP_DEREF_ALWAYS;
}
#else
else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0)
{
*errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP "
"library - cannot use \"dereference\"");
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
return DEFER;
}
#endif
else
{
*errmsg =
string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL",
namelen, name);
DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
return DEFER;
}
while (isspace(*url)) url++;
continue;
}
}
*errmsg = US"malformed parameter setting precedes LDAP URL";
DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
return DEFER;
}
/* If user is set, de-URL-quote it. Some LDAP libraries do this for themselves,
but it seems that not all behave like this. The DN for the user is often the
result of ${quote_ldap_dn:...} quoting, which does apply URL quoting, because
that is needed when the DN is used as a base DN in a query. Sigh. This is all
far too complicated. */
if (user != NULL)
{
uschar *s;
uschar *t = user;
for (s = user; *s != 0; s++)
{
int c, d;
if (*s == '%' && isxdigit(c=s[1]) && isxdigit(d=s[2]))
{
c = tolower(c);
d = tolower(d);
*t++ =
(((c >= 'a')? (10 + c - 'a') : c - '0') << 4) |
((d >= 'a')? (10 + d - 'a') : d - '0');
s += 2;
}
else *t++ = *s;
}
*t = 0;
}
DEBUG(D_lookup)
debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
"dereference=%d\n", user, password, sizelimit, timelimit, tcplimit,
dereference);
/* If the request is just to check authentication, some credentials must
be given. The password must not be empty because LDAP binds with an empty
password are considered anonymous, and will succeed on most installations. */
if (search_type == SEARCH_LDAP_AUTH)
{
if (user == NULL || password == NULL)
{
*errmsg = US"ldapauth lookups must specify the username and password";
return DEFER;
}
if (password[0] == 0)
{
DEBUG(D_lookup) debug_printf("Empty password: ldapauth returns FAIL\n");
return FAIL;
}
}
/* Check for valid ldap url starters */
p = url + 4;
if (tolower(*p) == 's' || tolower(*p) == 'i') p++;
if (Ustrncmp(p, "://", 3) != 0)
{
*errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", "
"\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url);
DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
return DEFER;
}
/* No default servers, or URL contains a server name: just one attempt */
if (eldap_default_servers == NULL || p[3] != '/')
{
return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
&defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
}
/* Loop through the default servers until OK or FAIL */
list = eldap_default_servers;
while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
{
int rc;
int port = 0;
uschar *colon = Ustrchr(server, ':');
if (colon != NULL)
{
*colon = 0;
port = Uatoi(colon+1);
}
rc = perform_ldap_search(url, server, port, search_type, res, errmsg,
&defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
if (rc != DEFER || defer_break) return rc;
}
return DEFER;
}
/*************************************************
* Find entry point *
*************************************************/
/* See local README for interface description. The different kinds of search
are handled by a common function, with a flag to differentiate between them.
The handle and filename arguments are not used. */
int
eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
/* Keep picky compilers happy */
do_cache = do_cache;
return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
}
int
eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
/* Keep picky compilers happy */
do_cache = do_cache;
return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
}
int
eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
/* Keep picky compilers happy */
do_cache = do_cache;
return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
}
int
eldapauth_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
/* Keep picky compilers happy */
do_cache = do_cache;
return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg));
}
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. */
void *
eldap_open(uschar *filename, uschar **errmsg)
{
return (void *)(1); /* Just return something non-null */
}
/*************************************************
* Tidy entry point *
*************************************************/
/* See local README for interface description.
Make sure that eldap_dn does not refer to reclaimed or worse, freed store */
void
eldap_tidy(void)
{
LDAP_CONNECTION *lcp = NULL;
eldap_dn = NULL;
while ((lcp = ldap_connections) != NULL)
{
DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host,
lcp->port);
ldap_unbind(lcp->ld);
ldap_connections = lcp->next;
}
}
/*************************************************
* Quote entry point *
*************************************************/
/* LDAP quoting is unbelievably messy. For a start, two different levels of
quoting have to be done: LDAP quoting, and URL quoting. The current
specification is the result of a suggestion by Brian Candler. It recognizes
two separate cases:
(1) For text that appears in a search filter, the following escapes are
required (see RFC 2254):
* -> \2A
( -> \28
) -> \29
\ -> \5C
NULL -> \00
Then the entire filter text must be URL-escaped. This kind of quoting is
implemented by ${quote_ldap:....}. Note that we can never have a NULL
in the input string, because that's a terminator.
(2) For a DN that is part of a URL (i.e. the base DN), the characters
, + " \ < > ;
must be quoted by backslashing. See RFC 2253. Leading and trailing spaces
must be escaped, as must a leading #. Then the string must be URL-quoted.
This type of quoting is implemented by ${quote_ldap_dn:....}.
For URL quoting, the only characters that need not be quoted are the
alphamerics and
! $ ' ( ) * + - . _
All the others must be hexified and preceded by %. This includes the
backslashes used for LDAP quoting.
For a DN that is given in the USER parameter for authentication, we need the
same initial quoting as (2) but in this case, the result must NOT be
URL-escaped, because it isn't a URL. The way this is handled is by
de-URL-quoting the text when processing the USER parameter in
control_ldap_search() above. That means that the same quote operator can be
used. This has the additional advantage that spaces in the DN won't cause
parsing problems. For example:
USER=cn=${quote_ldap_dn:$1},%20dc=example,%20dc=com
should be safe if there are spaces in $1.
Arguments:
s the string to be quoted
opt additional option text or NULL if none
only "dn" is recognized
Returns: the processed string or NULL for a bad option
*/
/* The characters in this string, together with alphanumerics, never need
quoting in any way. */
#define ALWAYS_LITERAL "!$'-._"
/* The special characters in this string do not need to be URL-quoted. The set
is a bit larger than the general literals. */
#define URL_NONQUOTE ALWAYS_LITERAL "()*+"
/* The following macros define the characters that are quoted by quote_ldap and
quote_ldap_dn, respectively. */
#define LDAP_QUOTE "*()\\"
#define LDAP_DN_QUOTE ",+\"\\<>;"
uschar *
eldap_quote(uschar *s, uschar *opt)
{
register int c;
int count = 0;
int len = 0;
BOOL dn = FALSE;
uschar *t = s;
uschar *quoted;
/* Test for a DN quotation. */
if (opt != NULL)
{
if (Ustrcmp(opt, "dn") != 0) return NULL; /* No others recognized */
dn = TRUE;
}
/* Compute how much extra store we need for the string. This doesn't have to be
exact as long as it isn't an underestimate. The worst case is the addition of 5
extra bytes for a single character. This occurs for certain characters in DNs,
where, for example, < turns into %5C%3C. For simplicity, we just add 5 for each
possibly escaped character. The really fast way would be just to test for
non-alphanumerics, but it is probably better to spot a few others that are
never escaped, because if there are no specials at all, we can avoid copying
the string. */
while ((c = *t++) != 0)
{
len++;
if (!isalnum(c) && Ustrchr(ALWAYS_LITERAL, c) == NULL) count += 5;
}
if (count == 0) return s;
/* Get sufficient store to hold the quoted string */
t = quoted = store_get(len + count + 1);
/* Handle plain quote_ldap */
if (!dn)
{
while ((c = *s++) != 0)
{
if (!isalnum(c))
{
if (Ustrchr(LDAP_QUOTE, c) != NULL)
{
sprintf(CS t, "%%5C%02X", c); /* e.g. * => %5C2A */
t += 5;
continue;
}
if (Ustrchr(URL_NONQUOTE, c) == NULL) /* e.g. ] => %5D */
{
sprintf(CS t, "%%%02X", c);
t += 3;
continue;
}
}
*t++ = c; /* unquoted character */
}
}
/* Handle quote_ldap_dn */
else
{
uschar *ss = s + len;
/* Find the last char before any trailing spaces */
while (ss > s && ss[-1] == ' ') ss--;
/* Quote leading spaces and sharps */
for (; s < ss; s++)
{
if (*s != ' ' && *s != '#') break;
sprintf(CS t, "%%5C%%%02X", *s);
t += 6;
}
/* Handle the rest of the string, up to the trailing spaces */
while (s < ss)
{
c = *s++;
if (!isalnum(c))
{
if (Ustrchr(LDAP_DN_QUOTE, c) != NULL)
{
Ustrncpy(t, "%5C", 3); /* insert \ where needed */
t += 3; /* fall through to check URL */
}
if (Ustrchr(URL_NONQUOTE, c) == NULL) /* e.g. ] => %5D */
{
sprintf(CS t, "%%%02X", c);
t += 3;
continue;
}
}
*t++ = c; /* unquoted character, or non-URL quoted after %5C */
}
/* Handle the trailing spaces */
while (*ss++ != 0)
{
Ustrncpy(t, "%5C%20", 6);
t += 6;
}
}
/* Terminate the new string and return */
*t = 0;
return quoted;
}
#endif /* LOOKUP_LDAP */
/* End of lookups/ldap.c */
Index: ldap.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/ldap.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the ldap lookups */
extern void *eldap_open(uschar *, uschar **);
extern int eldap_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern int eldapauth_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern int eldapdn_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern int eldapm_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern void eldap_tidy(void);
extern uschar *eldap_quote(uschar *, uschar *);
/* End of lookups/ldap.h */
Index: lf_check_file.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/lf_check_file.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
/*************************************************
* Check a file's credentials *
*************************************************/
/* fstat can normally be expected to work on an open file, but there are some
NFS states where it may not.
Arguments:
fd an open file descriptor or -1
filename a file name if fd is -1
s_type type of file (S_IFREG or S_IFDIR)
modemask a mask specifying mode bits that must *not* be set
owners NULL or a list of of allowable uids, count in the first item
owngroups NULL or a list of allowable gids, count in the first item
type name of lookup type for putting in error message
errmsg where to put an error message
Returns: -1 stat() or fstat() failed
0 OK
+1 something didn't match
Side effect: sets errno to ERRNO_BADUGID, ERRNO_NOTREGULAR or ERRNO_BADMODE for
bad uid/gid, not a regular file, or bad mode; otherwise leaves it
to what fstat set it to.
*/
int
lf_check_file(int fd, uschar *filename, int s_type, int modemask, uid_t *owners,
gid_t *owngroups, char *type, uschar **errmsg)
{
int i;
struct stat statbuf;
if ((fd >= 0 && fstat(fd, &statbuf) != 0) ||
(fd < 0 && Ustat(filename, &statbuf) != 0))
{
int save_errno = errno;
*errmsg = string_sprintf("%s: stat failed", filename);
errno = save_errno;
return -1;
}
if ((statbuf.st_mode & S_IFMT) != s_type)
{
if (s_type == S_IFREG)
{
*errmsg = string_sprintf("%s is not a regular file (%s lookup)",
filename, type);
errno = ERRNO_NOTREGULAR;
}
else
{
*errmsg = string_sprintf("%s is not a directory (%s lookup)",
filename, type);
errno = ERRNO_NOTDIRECTORY;
}
return +1;
}
if ((statbuf.st_mode & modemask) != 0)
{
*errmsg = string_sprintf("%s (%s lookup): file mode %.4o should not contain "
"%.4o", filename, type, statbuf.st_mode & 07777,
statbuf.st_mode & modemask);
errno = ERRNO_BADMODE;
return +1;
}
if (owners != NULL)
{
BOOL uid_ok = FALSE;
for (i = 1; i <= (int)owners[0]; i++)
if (owners[i] == statbuf.st_uid) { uid_ok = TRUE; break; }
if (!uid_ok)
{
*errmsg = string_sprintf("%s (%s lookup): file has wrong owner", filename,
type);
errno = ERRNO_BADUGID;
return +1;
}
}
if (owngroups != NULL)
{
BOOL gid_ok = FALSE;
for (i = 1; i <= (int)owngroups[0]; i++)
if (owngroups[i] == statbuf.st_gid) { gid_ok = TRUE; break; }
if (!gid_ok)
{
*errmsg = string_sprintf("%s (%s lookup): file has wrong group", filename,
type);
errno = ERRNO_BADUGID;
return +1;
}
}
return 0;
}
/* End of lf_check_file.c */
Index: lf_functions.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/lf_functions.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the functions that are shared by the lookups */
extern int lf_check_file(int, uschar *, int, int, uid_t *, gid_t *, char *,
uschar **);
extern uschar *lf_quote(uschar *, uschar *, int, uschar *, int *, int *);
/* End of lf_functions.h */
Index: lf_quote.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/lf_quote.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
/*************************************************
* Add string to result, quoting if necessary *
*************************************************/
/* This function is called by some lookups that create name=value result
strings, to handle the quoting of the data. It adds "name=" to the result,
followed by appropriately quoted data, followed by a single space.
Arguments:
name the field name
value the data value
vlength the data length
result the result pointer
asize points to the size variable
aoffset points to the offset variable
Returns: the result pointer (possibly updated)
*/
uschar *
lf_quote(uschar *name, uschar *value, int vlength, uschar *result, int *asize,
int *aoffset)
{
result = string_append(result, asize, aoffset, 2, name, US"=");
/* NULL is handled as an empty string */
if (value == NULL) value = US"";
/* Quote the value if it is empty, contains white space, or starts with a quote
character. */
if (value[0] == 0 || Ustrpbrk(value, " \t\n\r") != NULL || value[0] == '\"')
{
int j;
result = string_cat(result, asize, aoffset, US"\"", 1);
for (j = 0; j < vlength; j++)
{
if (value[j] == '\"' || value[j] == '\\')
result = string_cat(result, asize, aoffset, US"\\", 1);
result = string_cat(result, asize, aoffset, US value+j, 1);
}
result = string_cat(result, asize, aoffset, US"\"", 1);
}
else
{
result = string_cat(result, asize, aoffset, US value, vlength);
}
return string_cat(result, asize, aoffset, US" ", 1);
}
/* End of lf_quote.c */
Index: lsearch.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/lsearch.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
#include "lsearch.h"
/* Codes for the different kinds of lsearch that are supported */
enum {
LSEARCH_PLAIN, /* Literal keys */
LSEARCH_WILD, /* Wild card keys, expanded */
LSEARCH_NWILD, /* Wild card keys, not expanded */
LSEARCH_IP /* IP addresses and networks */
};
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description */
void *
lsearch_open(uschar *filename, uschar **errmsg)
{
FILE *f = Ufopen(filename, "rb");
if (f == NULL)
{
int save_errno = errno;
*errmsg = string_open_failed(errno, "%s for linear search", filename);
errno = save_errno;
return NULL;
}
return f;
}
/*************************************************
* Check entry point *
*************************************************/
BOOL
lsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners,
gid_t *owngroups, uschar **errmsg)
{
return lf_check_file(fileno((FILE *)handle), filename, S_IFREG, modemask,
owners, owngroups, "lsearch", errmsg) == 0;
}
/*************************************************
* Internal function for the various lsearches *
*************************************************/
/* See local README for interface description, plus:
Extra argument:
type one of the values LSEARCH_PLAIN, LSEARCH_WILD, LSEARCH_NWILD, or
LSEARCH_IP
There is some messy logic in here to cope with very long data lines that do not
fit into the fixed sized buffer. Most of the time this will never be exercised,
but people do occasionally do weird things. */
static int
internal_lsearch_find(void *handle, uschar *filename, uschar *keystring,
int length, uschar **result, uschar **errmsg, int type)
{
FILE *f = (FILE *)handle;
BOOL last_was_eol = TRUE;
BOOL this_is_eol = TRUE;
int old_pool = store_pool;
void *reset_point = NULL;
uschar buffer[4096];
/* Wildcard searches may use up some store, because of expansions. We don't
want them to fill up our search store. What we do is set the pool to the main
pool and get a point to reset to later. Wildcard searches could also issue
lookups, but internal_search_find will take care of that, and the cache will be
safely stored in the search pool again. */
if(type == LSEARCH_WILD || type == LSEARCH_NWILD)
{
store_pool = POOL_MAIN;
reset_point = store_get(0);
}
filename = filename; /* Keep picky compilers happy */
errmsg = errmsg;
rewind(f);
for (last_was_eol = TRUE;
Ufgets(buffer, sizeof(buffer), f) != NULL;
last_was_eol = this_is_eol)
{
int ptr, size;
int p = Ustrlen(buffer);
int linekeylength;
uschar *yield;
uschar *s = buffer;
/* Check whether this the final segment of a line. If it follows an
incomplete part-line, skip it. */
this_is_eol = p > 0 && buffer[p-1] == '\n';
if (!last_was_eol) continue;
/* We now have the start of a physical line. If this is a final line segment,
remove trailing white space. */
if (this_is_eol)
{
while (p > 0 && isspace((uschar)buffer[p-1])) p--;
buffer[p] = 0;
}
/* If the buffer is empty it might be (a) a complete empty line, or (b) the
start of a line that begins with so much white space that it doesn't all fit
in the buffer. In both cases we want to skip the entire physical line.
If the buffer begins with # it is a comment line; if it begins with white
space it is a logical continuation; again, we want to skip the entire
physical line. */
if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue;
/* We assume that they key will fit in the buffer. If the key starts with ",
read it as a quoted string. We don't use string_dequote() because that uses
new store for the result, and we may be doing this many times in a long file.
We know that the dequoted string must be shorter than the original, because
we are removing the quotes, and also any escape sequences always turn two or
more characters into one character. Therefore, we can store the new string in
the same buffer. */
if (*s == '\"')
{
uschar *t = s++;
while (*s != 0 && *s != '\"')
{
if (*s == '\\') *t++ = string_interpret_escape(&s);
else *t++ = *s;
s++;
}
if (*s != 0) s++; /* Past terminating " */
linekeylength = t - buffer;
}
/* Otherwise it is terminated by a colon or white space */
else
{
while (*s != 0 && *s != ':' && !isspace(*s)) s++;
linekeylength = s - buffer;
}
/* The matching test depends on which kind of lsearch we are doing */
switch(type)
{
/* A plain lsearch treats each key as a literal */
case LSEARCH_PLAIN:
if (linekeylength != length || strncmpic(buffer, keystring, length) != 0)
continue;
break; /* Key matched */
/* A wild lsearch treats each key as a possible wildcarded string; no
expansion is done for nwildlsearch. */
case LSEARCH_WILD:
case LSEARCH_NWILD:
{
int rc;
int save = buffer[linekeylength];
uschar *list = buffer;
buffer[linekeylength] = 0;
rc = match_isinlist(keystring,
&list,
UCHAR_MAX+1, /* Single-item list */
NULL, /* No anchor */
NULL, /* No caching */
MCL_STRING + ((type == LSEARCH_WILD)? 0:MCL_NOEXPAND),
TRUE, /* Caseless */
NULL);
buffer[linekeylength] = save;
if (rc == FAIL) continue;
if (rc == DEFER) return DEFER;
}
break; /* Key matched */
/* Compare an ip address against a list of network/ip addresses. We have to
allow for the "*" case specially. */
case LSEARCH_IP:
if (linekeylength == 1 && buffer[0] == '*')
{
if (length != 1 || keystring[0] != '*') continue;
}
else if (length == 1 && keystring[0] == '*') continue;
else
{
int maskoffset;
int save = buffer[linekeylength];
buffer[linekeylength] = 0;
if (!string_is_ip_address(buffer, &maskoffset) ||
!host_is_in_net(keystring, buffer, maskoffset)) continue;
buffer[linekeylength] = save;
}
break; /* Key matched */
}
/* The key has matched. Skip spaces after the key, and allow an optional
colon after the spaces. This is an odd specification, but it's for
compatibility. */
while (isspace((uschar)*s)) s++;
if (*s == ':')
{
s++;
while (isspace((uschar)*s)) s++;
}
/* Reset dynamic store, if we need to, and revert to the search pool */
if (reset_point != NULL)
{
store_reset(reset_point);
store_pool = old_pool;
}
/* Now we want to build the result string to contain the data. There can be
two kinds of continuation: (a) the physical line may not all have fitted into
the buffer, and (b) there may be logical continuation lines, for which we
must convert all leading white space into a single blank.
Initialize, and copy the first segment of data. */
size = 100;
ptr = 0;
yield = store_get(size);
if (*s != 0)
yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
/* Now handle continuations */
for (last_was_eol = this_is_eol;
Ufgets(buffer, sizeof(buffer), f) != NULL;
last_was_eol = this_is_eol)
{
s = buffer;
p = Ustrlen(buffer);
this_is_eol = p > 0 && buffer[p-1] == '\n';
/* Remove trailing white space from a physical line end */
if (this_is_eol)
{
while (p > 0 && isspace((uschar)buffer[p-1])) p--;
buffer[p] = 0;
}
/* If this is not a physical line continuation, skip it entirely if it's
empty or starts with #. Otherwise, break the loop if it doesn't start with
white space. Otherwise, replace leading white space with a single blank. */
if (last_was_eol)
{
if (buffer[0] == 0 || buffer[0] == '#') continue;
if (!isspace((uschar)buffer[0])) break;
while (isspace((uschar)*s)) s++;
*(--s) = ' ';
}
/* Join a physical or logical line continuation onto the result string. */
yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
}
yield[ptr] = 0;
store_reset(yield + ptr + 1);
*result = yield;
return OK;
}
/* Reset dynamic store, if we need to */
if (reset_point != NULL)
{
store_reset(reset_point);
store_pool = old_pool;
}
return FAIL;
}
/*************************************************
* Find entry point for lsearch *
*************************************************/
/* See local README for interface description */
int
lsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
do_cache = do_cache; /* Keep picky compilers happy */
return internal_lsearch_find(handle, filename, keystring, length, result,
errmsg, LSEARCH_PLAIN);
}
/*************************************************
* Find entry point for wildlsearch *
*************************************************/
/* See local README for interface description */
int
wildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
do_cache = do_cache; /* Keep picky compilers happy */
return internal_lsearch_find(handle, filename, keystring, length, result,
errmsg, LSEARCH_WILD);
}
/*************************************************
* Find entry point for nwildlsearch *
*************************************************/
/* See local README for interface description */
int
nwildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
do_cache = do_cache; /* Keep picky compilers happy */
return internal_lsearch_find(handle, filename, keystring, length, result,
errmsg, LSEARCH_NWILD);
}
/*************************************************
* Find entry point for iplsearch *
*************************************************/
/* See local README for interface description */
int
iplsearch_find(void *handle, uschar *filename, uschar *keystring, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
do_cache = do_cache; /* Keep picky compilers happy */
if ((length == 1 && keystring[0] == '*') ||
string_is_ip_address(keystring, NULL))
{
return internal_lsearch_find(handle, filename, keystring, length, result,
errmsg, LSEARCH_IP);
}
else
{
*errmsg = string_sprintf("\"%s\" is not a valid iplsearch key (an IP "
"address, with optional CIDR mask, is wanted): "
"in a host list, use net-iplsearch as the search type", keystring);
return DEFER;
}
}
/*************************************************
* Close entry point *
*************************************************/
/* See local README for interface description */
void
lsearch_close(void *handle)
{
fclose((FILE *)handle);
}
/* End of lookups/lsearch.c */
Index: lsearch.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/lsearch.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the lsearch and wildlsearch lookups */
extern void *lsearch_open(uschar *, uschar **);
extern BOOL lsearch_check(void *, uschar *, int, uid_t *, gid_t *, uschar **);
extern int lsearch_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern void lsearch_close(void *);
extern int wildlsearch_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern int nwildlsearch_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
extern int iplsearch_find(void *, uschar *, uschar *, int, uschar **,
uschar **, BOOL *);
/* End of lookups/lsearch.h */
Index: mysql.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/mysql.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Thanks to Paul Kelly for contributing the original code for these
functions. */
#include "../exim.h"
#include "lf_functions.h"
#include "mysql.h" /* The local header */
/* We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the MYSQL header
available for compiling. Therefore, compile these functions only if
LOOKUP_MYSQL is defined. However, some compilers don't like compiling empty
modules, so keep them happy with a dummy when skipping the rest. Make it
reference itself to stop picky compilers complaining that it is unused, and put
in a dummy argument to stop even pickier compilers complaining about infinite
loops. */
#ifndef LOOKUP_MYSQL
static void dummy(int x) { dummy(x-1); }
#else
#include <mysql.h> /* The system header */
/* Structure and anchor for caching connections. */
typedef struct mysql_connection {
struct mysql_connection *next;
uschar *server;
MYSQL *handle;
} mysql_connection;
static mysql_connection *mysql_connections = NULL;
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. */
void *
mysql_open(uschar *filename, uschar **errmsg)
{
return (void *)(1); /* Just return something non-null */
}
/*************************************************
* Tidy entry point *
*************************************************/
/* See local README for interface description. */
void
mysql_tidy(void)
{
mysql_connection *cn;
while ((cn = mysql_connections) != NULL)
{
mysql_connections = cn->next;
DEBUG(D_lookup) debug_printf("close MYSQL connection: %s\n", cn->server);
mysql_close(cn->handle);
}
}
/*************************************************
* Internal search function *
*************************************************/
/* This function is called from the find entry point to do the search for a
single server.
Arguments:
query the query string
server the server string
resultptr where to store the result
errmsg where to point an error message
defer_break TRUE if no more servers are to be tried after DEFER
do_cache set false if data is changed
The server string is of the form "host/dbname/user/password". The host can be
host:port. This string is in a nextinlist temporary buffer, so can be
overwritten.
Returns: OK, FAIL, or DEFER
*/
static int
perform_mysql_search(uschar *query, uschar *server, uschar **resultptr,
uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
{
MYSQL *mysql_handle = NULL; /* Keep compilers happy */
MYSQL_RES *mysql_result = NULL;
MYSQL_ROW mysql_row_data;
MYSQL_FIELD *fields;
int i;
int ssize = 0;
int offset = 0;
int yield = DEFER;
unsigned int num_fields;
uschar *result = NULL;
mysql_connection *cn;
uschar *server_copy = NULL;
uschar *sdata[4];
/* Disaggregate the parameters from the server argument. The order is host,
database, user, password. We can write to the string, since it is in a
nextinlist temporary buffer. The copy of the string that is used for caching
has the password removed. This copy is also used for debugging output. */
for (i = 3; i > 0; i--)
{
uschar *pp = Ustrrchr(server, '/');
if (pp == NULL)
{
*errmsg = string_sprintf("incomplete MySQL server data: %s",
(i == 3)? server : server_copy);
*defer_break = TRUE;
return DEFER;
}
*pp++ = 0;
sdata[i] = pp;
if (i == 3) server_copy = string_copy(server); /* sans password */
}
sdata[0] = server; /* What's left at the start */
/* See if we have a cached connection to the server */
for (cn = mysql_connections; cn != NULL; cn = cn->next)
{
if (Ustrcmp(cn->server, server_copy) == 0)
{
mysql_handle = cn->handle;
break;
}
}
/* If no cached connection, we must set one up. Mysql allows for a host name
and port to be specified. It also allows the name of a Unix socket to be used.
Unfortunately, this contains slashes, but its use is expected to be rare, so
the rather cumbersome syntax shouldn't inconvenience too many people. We use
this: host:port(socket) where all the parts are optional. */
if (cn == NULL)
{
uschar *p;
uschar *socket = NULL;
int port = 0;
if ((p = Ustrchr(sdata[0], '(')) != NULL)
{
*p++ = 0;
socket = p;
while (*p != 0 && *p != ')') p++;
*p = 0;
}
if ((p = Ustrchr(sdata[0], ':')) != NULL)
{
*p++ = 0;
port = Uatoi(p);
}
if (Ustrchr(sdata[0], '/') != NULL)
{
*errmsg = string_sprintf("unexpected slash in MySQL server hostname: %s",
sdata[0]);
*defer_break = TRUE;
return DEFER;
}
/* If the database is the empty string, set it NULL - the query must then
define it. */
if (sdata[1][0] == 0) sdata[1] = NULL;
DEBUG(D_lookup)
debug_printf("MYSQL new connection: host=%s port=%d socket=%s "
"database=%s user=%s\n", sdata[0], port, socket, sdata[1], sdata[2]);
/* Get store for a new handle, initialize it, and connect to the server */
mysql_handle = store_get(sizeof(MYSQL));
mysql_init(mysql_handle);
if (mysql_real_connect(mysql_handle,
/* host user passwd database */
CS sdata[0], CS sdata[2], CS sdata[3], CS sdata[1],
port, CS socket, 0) == NULL)
{
*errmsg = string_sprintf("MYSQL connection failed: %s",
mysql_error(mysql_handle));
*defer_break = FALSE;
goto MYSQL_EXIT;
}
/* Add the connection to the cache */
cn = store_get(sizeof(mysql_connection));
cn->server = server_copy;
cn->handle = mysql_handle;
cn->next = mysql_connections;
mysql_connections = cn;
}
/* Else use a previously cached connection */
else
{
DEBUG(D_lookup)
debug_printf("MYSQL using cached connection for %s\n", server_copy);
}
/* Run the query */
if (mysql_query(mysql_handle, CS query) != 0)
{
*errmsg = string_sprintf("MYSQL: query failed: %s\n",
mysql_error(mysql_handle));
*defer_break = FALSE;
goto MYSQL_EXIT;
}
/* Pick up the result. If the query was not of the type that returns data,
namely INSERT, UPDATE, or DELETE, an error occurs here. However, this situation
can be detected by calling mysql_field_count(). If its result is zero, no data
was expected (this is all explained clearly in the MySQL manual). In this case,
we return the number of rows affected by the command. In this event, we do NOT
want to cache the result; also the whole cache for the handle must be cleaned
up. Setting do_cache FALSE requests this. */
if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
{
if ( mysql_field_count(mysql_handle) == 0 )
{
DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
result = string_sprintf("%d", mysql_affected_rows(mysql_handle));
*do_cache = FALSE;
goto MYSQL_EXIT;
}
*errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
mysql_error(mysql_handle));
*defer_break = FALSE;
goto MYSQL_EXIT;
}
/* Find the number of fields returned. If this is one, we don't add field
names to the data. Otherwise we do. */
num_fields = mysql_num_fields(mysql_result);
/* Get the fields and construct the result string. If there is more than one
row, we insert '\n' between them. */
fields = mysql_fetch_fields(mysql_result);
while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
{
unsigned long *lengths = mysql_fetch_lengths(mysql_result);
if (result != NULL)
result = string_cat(result, &ssize, &offset, US"\n", 1);
if (num_fields == 1)
{
if (mysql_row_data[0] != NULL) /* NULL value yields nothing */
result = string_cat(result, &ssize, &offset, US mysql_row_data[0],
lengths[0]);
}
else for (i = 0; i < num_fields; i++)
{
result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
result, &ssize, &offset);
}
}
/* If result is NULL then no data has been found and so we return FAIL.
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */
if (result == NULL)
{
yield = FAIL;
*errmsg = US"MYSQL: no data found";
}
else
{
result[offset] = 0;
store_reset(result + offset + 1);
}
/* Get here by goto from various error checks and from the case where no data
was read (e.g. an update query). */
MYSQL_EXIT:
/* Free mysal store for any result that was got; don't close the connection, as
it is cached. */
if (mysql_result != NULL) mysql_free_result(mysql_result);
/* Non-NULL result indicates a sucessful result */
if (result != NULL)
{
*resultptr = result;
return OK;
}
else
{
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
return yield; /* FAIL or DEFER */
}
}
/*************************************************
* Find entry point *
*************************************************/
/* See local README for interface description. The handle and filename
arguments are not used. Loop through a list of servers while the query is
deferred with a retryable error. */
int
mysql_find(void *handle, uschar *filename, uschar *query, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
int sep = 0;
uschar *server;
uschar *list = mysql_servers;
uschar buffer[512];
DEBUG(D_lookup) debug_printf("MYSQL query: %s\n", query);
while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
{
BOOL defer_break = FALSE;
int rc = perform_mysql_search(query, server, result, errmsg, &defer_break,
do_cache);
if (rc != DEFER || defer_break) return rc;
}
if (mysql_servers == NULL)
*errmsg = US"no MYSQL servers defined (mysql_servers option)";
return DEFER;
}
/*************************************************
* Quote entry point *
*************************************************/
/* The only characters that need to be quoted (with backslash) are newline,
tab, carriage return, backspace, backslash itself, and the quote characters.
Percent, and underscore and not escaped. They are only special in contexts
where they can be wild cards, and this isn't usually the case for data inserted
from messages, since that isn't likely to be treated as a pattern of any kind.
Sadly, MySQL doesn't seem to behave like other programs. If you use something
like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
can't quote "on spec".
Arguments:
s the string to be quoted
opt additional option text or NULL if none
Returns: the processed string or NULL for a bad option
*/
uschar *
mysql_quote(uschar *s, uschar *opt)
{
register int c;
int count = 0;
uschar *t = s;
uschar *quoted;
if (opt != NULL) return NULL; /* No options recognized */
while ((c = *t++) != 0)
if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;
if (count == 0) return s;
t = quoted = store_get(Ustrlen(s) + count + 1);
while ((c = *s++) != 0)
{
if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
{
*t++ = '\\';
switch(c)
{
case '\n': *t++ = 'n';
break;
case '\t': *t++ = 't';
break;
case '\r': *t++ = 'r';
break;
case '\b': *t++ = 'b';
break;
default: *t++ = c;
break;
}
}
else *t++ = c;
}
*t = 0;
return quoted;
}
#endif /* MYSQL_LOOKUP */
/* End of lookups/mysql.c */
Index: mysql.h
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/mysql.h,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the mysql lookup functions */
extern void *mysql_open(uschar *, uschar **);
extern int mysql_find(void *, uschar *, uschar *, int, uschar **, uschar **,
BOOL *);
extern void mysql_tidy(void);
extern uschar *mysql_quote(uschar *, uschar *);
/* End of lookups/mysql.h */
Index: nis.c
====================================================================
/* $Cambridge: exim/exim-src/src/lookups/nis.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2004 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "lf_functions.h"
#include "nis.h"
/* We can't just compile this code and allow the library mechanism to omit the
functions if they are not wanted, because we need to have the NIS header
available for compiling. Therefore, compile these functions only if LOOKUP_NIS
is defined. However, some compilers don't like compiling empty modules, so keep
them happy with a dummy when skipping the rest. Make it reference itself to
stop picky compilers complaining that it is unused, and put in a dummy argument
to stop even pickier compilers complaining about infinite loops. */
#ifndef LOOKUP_NIS
static void dummy(int x) { dummy(x-1); }
#else
#include <rpcsvc/ypclnt.h>
/*************************************************
* Open entry point *
*************************************************/
/* See local README for interface description. This serves for both
the "nis" and "nis0" lookup types. */
----------------------------------------------
Diff block truncated. (Max lines = 10000)
----------------------------------------------