/* OS/2 REXX: 8bitMIME mail submission + CRAM-MD5 authentication */
/* Usage: rxmailto
*/
/* ADDR one receiver for SMTP RCPT TO + header To: */
/* PASS for preconfigured CRAM-MD5 SMTP AUTH server */
/* BODY text mail body, ASCII or the local 8bit CODE */
/* SUBJ mail subject, max. ASCII substring length 75 */
/* Configuration (edit the four corresponding variables below): */
/* HELO fully qualified domain name for SMTP EHLO and the */
/* Message-ID. HELO should resolve to the actual IP. */
/* FROM is the sender for SMTP MAIL FROM + header From: */
/* SMTP the mail submit agent (server) using port 587 for */
/* the specified FROM address. */
/* CODE local charset used for 8bitMIME, e.g. windows-1252 */
/* Bugs and features: */
/* - The input file with the body has to be a file, not a pipe. */
/* - The script doesn't support spaces in ADDR, PASS, and FILE. */
/* - The script tries to use 8bitMIME if needed, even if the PEER */
/* doesn't support it. Users are supposed to know their MSA. */
/* - The script tries to use CRAM-MD5 authentication even if the */
/* MSA doesn't support it. Again, users should know their MSA. */
/* - If the password is specified as dot the script switches to */
/* unauthenticated 'direct-to-MX' mode for tests with GETMX(). */
/* - If the 1st non-blank char. in the body is not '<' the mail */
/* is sent as text/plain, otherwise as text/html. 8bit char.s */
/* in the body trigger charset=windows-1252 (see variable CODE) */
/* and body=8bitMIME, else the mail is sent as 7bit us-ascii. */
/* - The SUB64() encoder can emit lines with more than 76 char.s, */
/* not permitted by RfC 2047. If you consider to fix this make */
/* sure that it supports CODE='utf-8' without splitting UTF-8. */
/* Procedures (for info): */
/* INTRO wrapper for initial TRANS(), shows server greeting */
/* AUTHX wrapper for optional CRAM-MD5 challenge + response */
/* USAGE usage message, if given show an erroneous argument */
/* INPUT get mail body with dot-stuffing and CRLF lineends */
/* B64.O B64 encoding of Content-MD5 determined by INPUT() */
/* SUB64 B64 encoding of non-ASCII subwords in SUBJ */
/* GETMX parse 'nslookup -q=mx' for direct-to-MX mail tests */
/* TRANS send command to SMTP, expect reply code 2xy or 3x4 */
/* CRAM CRAM-MD5 (RfC 2195) SASL-mechanism for SMTP AUTH */
/* HMAC hashed MAC (RfC 2104) used by CRAM */
/* MD5 MD5 digest (RfC 1321) used by HMAC */
/* TOPEN open TCP connection to server at given port */
/* TREAD read data from open socket */
/* TSEND send data to open socket (or close sender side) */
/* RXMSG show an error message (message window or stderr) */
/* TFAIL show socket error RXMSG, close socket, catch trap */
/* TRAP process trap */
/* Prerequisites: (Frank Ellermann, 2007) */
/* 1 - This source, see */
/* 2 - OS/2 RxSock.dll available from IBM (REXX IPv4 sockets API) */
/* 3 - OS/2 nslookup.exe with a working DNS server configured in */
/* %ETC%\resolv2 - on my system the file D:\MPTN\ETC\RESOLV2, */
/* for another tool modify procedure GETMX(), e.g. use "dig". */
/* 4 - Please modify HELO, FROM, SMTP, and CODE for your box: */
HELO = 'yourbox.example.org' /* should be FQDN of sending box */
FROM = 'sender@isp.example' /* sender address used by script */
SMTP = 'smtp.isp.example' /* mail submit agent for sender */
CODE = 'windows-1252' /* local 8bit charset for mails */
signal on novalue name TRAP ; signal on syntax name TRAP
signal on failure name TRAP ; signal on halt name TRAP
CRLF = x2c( 0D0A ) ; EXPO = 'TSOCK CRLF INFO'
parse arg ADDR PASS BODY SUBJ ; parse var ADDR . '@' PEER
if PASS = '' then exit USAGE()
if SUBJ = '' then exit USAGE( 'missing subject for mail' )
if PEER = '' then exit USAGE( 'malformed address' ADDR )
PURL = 'http://purl.net/xyzzy/src/rxmailto.cmd'
PURL = 'rxmailto.cmd/0.1 (+' || PURL || ')'
SUBJ = SUB64( SUBJ, CODE, length( 'subject:' ))
BODY = INPUT( BODY ) /* sets also Content-MD5 in INFO */
HEAD = 'Content-Transfer-Encoding:'
if sign( verify( BODY, xrange( x2c( 0 ), x2c( 7F ))))
then parse value CODE HEAD '8bit' with TYPE HEAD
else parse value 'us-ascii' HEAD '7bit' with TYPE HEAD
if abbrev( strip( BODY, 'Leading' ), '<' )
then TEXT = 'text/html;' 'charset=' || TYPE
else TEXT = 'text/plain;' 'charset=' || TYPE
if PASS <> '.' /* if password is no dot use MSA */
then parse value SMTP 587 with PEER PORT
else parse value GETMX( PEER ) 25 with PEER PORT
DATE = left( date( 'W' ), 3 ) || ',' date() time() '-0000'
MESS = '<' || date( 'S' ) || time( 'S' ) || '@' || HELO || '>'
HEAD = 'Content-Type:' TEXT || CRLF || HEAD
HEAD = 'Content-MD5:' INFO || CRLF || HEAD
HEAD = 'MIME-Version:' 1.0 || CRLF || HEAD
HEAD = 'User-Agent:' PURL || CRLF || HEAD
HEAD = 'Subject:' SUBJ || CRLF || HEAD
HEAD = 'Message-ID:' MESS || CRLF || HEAD
HEAD = 'Date:' DATE || CRLF || HEAD
HEAD = 'To:' ADDR || CRLF || HEAD
HEAD = 'From:' FROM || CRLF || HEAD
if 0 then do ; say HEAD ; exit 1 ; end /* if 1: debug */
signal on novalue name TFAIL ; signal on syntax name TFAIL
signal on failure name TFAIL ; signal on halt name TFAIL
TEXT = '<' || FROM || '>' ; ADDR = '<' || ADDR || '>'
if TYPE <> 'us-ascii' then TEXT = TEXT 'body=8bitMIME'
select /* normal SMTP terminated by NOP */
when TOPEN( PEER, PORT ) then exit 1
when INTRO( /* shows greeting */ ) then nop
when TRANS( 'ehlo' HELO ) then nop
when AUTHX( 0 /* challenge */ ) then nop
when AUTHX( 1 /* response */ ) then nop
when TRANS( 'mail from:' || TEXT ) then nop
when TRANS( 'rcpt to:' || ADDR ) then nop
when TRANS( 'data' ) then nop
when TSEND( HEAD || CRLF ) then exit TFAIL()
when TSEND( CRLF || BODY ) then exit TFAIL()
when TRANS( '.' ) then nop
otherwise /* 0 after transmission success */
call TRANS 'quit' ; call SockClose TSOCK ; exit 0
end /* 1 after any transaction error */
call TRANS 'quit' ; call SockClose TSOCK ; exit 1
INTRO: /* reads variables PEER and PORT */
say date( 'S' ) time() 'TCP connection with' PEER || ':' || PORT
return TRANS() /* show time() + server greeting */
AUTHX: /* write and read access on INFO */
if PASS = '.' then return 0 /* handle CRAM-MD5 if PASS given */
if arg( 1 ) then return TRANS( CRAM( FROM, PASS, INFO ))
else return TRANS( 'auth CRAM-MD5' )
USAGE: procedure expose (EXPO) /* show usage or error message */
parse source . . THIS /* (sorry, only SAY implemented) */
TEXT = 'usage:' THIS 'address password body-file subject'
if arg( 1, 'e' ) then TEXT = 'error:' arg( 1 ) || CRLF || TEXT
say TEXT ; return 1
INPUT: procedure expose (EXPO) /* read mail body in given file */
parse arg FILE ; signal on notready name TRAP
BODY = '' ; EOL = x2c( 0A )
INFO = MD5( /* init. */, '' ) /* init. context for Content-MD5 */
TEXT = charin( FILE,, chars( FILE ))
do while TEXT <> '' /* dot-stuffing + CRLF lineends: */
parse var TEXT HEAD (EOL) TEXT
if abbrev( CRLF, right( HEAD, 1 )) then HEAD = HEAD || EOL
else HEAD = HEAD || CRLF
INFO = MD5( HEAD, INFO ) /* update MD5 w/out dot stuffing */
if abbrev( HEAD, '.' ) then HEAD = '.' || HEAD
BODY = BODY || HEAD /* input file locked until exit, */
end /* intentionally not closed here */
INFO = B64.O( x2c( MD5( /* final */, INFO )))
return BODY
B64.O : procedure /* string to (unlimited) base64: */
B64 = 'abcdefghijklmnopqrstuvwxyz'
B64 = translate( B64 ) || B64 || '0123456789+/'
SRC = x2b( c2x( arg( 1 ))) ; DST = ''
ADD = ( length( SRC ) / 4 ) // 3
SRC = SRC || copies( '00', ADD )
do while SRC <> ''
parse var SRC N 7 SRC ; N = x2d( b2x( N ))
DST = DST || substr( B64, N + 1, 1 )
end
return DST || copies( '=', ADD )
SUB64: procedure expose (EXPO) /* unstructured RFC 2047 encoder */
parse arg SRC, CODE, OFS ; CODE = '=?' || CODE || '?B?'
W = words( SRC ) ; DST = '' ; X = 0 ; S = 1 ; E = 0
VCHAR = xrange( x2c( 21 ), x2c( 7E ))
do N = 1 to W + 1
V = sign( verify( word( SRC, N ), VCHAR ))
if V = X & N <= W then E = E + 1 ; else do
SUB = subword( SRC, S, 1 + E - S )
if X then do /* B64 encode non-ASCII SUBwords */
X = wordindex( '*' SRC, S + 1 ) - 2
do while substr( SRC, X, 1 ) == ' '
SUB = ' ' || SUB ; X = X - 1
end /* include extra leading spaces */
X = wordindex( SRC '*', E + 1 ) - 2
do while substr( SRC, X, 1 ) == ' '
SUB = SUB || ' ' ; X = X - 1
end /* include extra trailing spaces */
SUB = CODE || B64.O( SUB ) || '?='
end
X = 1 + length( SUB ) ; OFS = X + OFS
if OFS > 76 then do /* not strictly 2047 conforming: */
DST = DST || CRLF ; OFS = X
end /* chunks > 76 silently accepted */
DST = DST SUB ; X = V
S = N ; E = N
end
end
return substr( DST, 2 )
GETMX: procedure expose (EXPO) /* parse `nslookup -q=mx` output */
signal on error name TRAP ; RXQ = rxqueue( 'GET' )
arg NAME ; PRI = copies( 9, digits())
address CMD '@nslookup -q=mx' NAME '2>&1 | rxqueue' RXQ
do while sign( queued())
pull . ' = ' NEW ',' . ' = ' MX
select
when MX = '' | MX = '.' then nop
when datatype( NEW, 'w' ) = 0 then nop
when NEW > PRI then nop
otherwise NAME = MX ; PRI = NEW
end
end
return strip( NAME )
TRANS: procedure expose (EXPO) /* send stuff and get an answer: */
arg VERB . ; MORE = ''
if VERB <> '' then do
if TSEND( arg( 1 ) || CRLF ) then exit TFAIL()
if VERB = 'QUIT' then call TSEND
say substr( time( 'L' ), 4, 8 ) arg( 1 )
end
DATA = TREAD()
do while DATA \== ''
MORE = MORE || DATA
do while sign( pos( CRLF, MORE ))
parse var MORE DATA (CRLF) MORE
say substr( time( 'L' ), 4, 8 ) DATA
end
if MORE == '' then do
parse var DATA DATA INFO
select
when length( DATA ) <> 3 then nop
when sign( verify( DATA, '0123456789' )) then nop
when VERB = 'AUTH' then return ( DATA <> 334 )
when VERB = 'DATA' then return ( DATA <> 354 )
otherwise return 1 - abbrev( DATA, '2' )
end /* 0: 2xx ok., 1: not 2xx is bad */
end
DATA = TREAD()
end
exit TFAIL()
/* -------------------------------------------------------------- */
/* To use CRAM-MD5 copy procedures CRAM, HMAC, and the MD5 stuff. */
/* CRAM( USER, PASS, CHALLENGE ) returns a base64 response for a */
/* base64 challenge. The challenge can have the form '+ base64'. */
CRAM : procedure /* for CRAM details see RfC 2195 */
B64 = 'abcdefghijklmnopqrstuvwxyz'
B64 = translate( B64 ) || B64 || '0123456789+/'
DST = '' ; SRC = arg( 3 ) /* strip IMAP or POP3 AUTH '+ ': */
if abbrev( SRC, '+ ' ) then SRC = substr( SRC, 3 )
do while abbrev( '==', SRC ) = 0
parse var SRC ADD 2 SRC /* if no B64 force REXX error 40 */
ADD = d2x( pos( ADD, B64 ) - 1 )
DST = DST || right( x2b( ADD ), 6, 0 )
end
SRC = x2c( b2x( left( DST, length( DST ) - 2 * length( SRC ))))
SRC = x2b( c2x( arg( 1 ) HMAC( arg( 2 ), SRC )))
DST = ''
ADD = ( length( SRC ) / 4 ) // 3
SRC = SRC || copies( '00', ADD )
do while SRC <> ''
parse var SRC N 7 SRC ; N = x2d( b2x( N ))
DST = DST || substr( B64, N + 1, 1 )
end
return DST || copies( '=', ADD )
/* -------------------------------------------------------------- */
/* To use HMAC-MD5 copy procedure HMAC and all MD5 procedures. */
/* The typical key length is 16 bytes, e.g. x2c( MD5( secret )). */
/* Use left( x2c( HMAC( key, msg )), 12 ) for the first 96 bits. */
HMAC : procedure /* for HMAC details see RfC 2104 */
parse arg KEY, MSG /* also known as KEYED-MD5 */
if length( KEY ) > 64 then KEY = x2c( MD5( KEY ))
OPAD = bitxor( KEY, copies( '5C'x, 64 ), '00'x )
IPAD = bitxor( KEY, copies( '36'x, 64 ), '00'x )
return MD5( OPAD || x2c( MD5( IPAD || MSG )))
/* -------------------------------------------------------------- */
/* Credits: RSA Data Security, Inc. MD5 Message-Digest Algorithm, */
/* for an MD5 test suite see */
/* hash = MD5( bytes ) => MD5 of an octet string */
/* ctxt = MD5( bytes, '' ) => init. new MD5 context */
/* ctxt = MD5( bytes, ctxt ) => update old MD5 context */
/* hash = MD5( /**/ , ctxt ) => finalize MD5 context */
/* hash = MD5( bytes, /**/, n ) => MD5 of n zero-fill bits */
/* ctxt = MD5( bytes, '' , n ) => init. MD5 bit context */
/* ctxt = MD5( bytes, ctxt, n ) => update MD5 bit context */
MD5 : procedure /* for MD5 details see RfC 1321 */
if arg( 2 ) = '' then do /* no or empty context => init. */
A = '67452301' ; B = 'EFCDAB89' ; LEN = 0
C = '98BADCFE' ; D = '10325476' ; BIN = ''
end
else parse value arg( 2 ) with A B C D LEN BIN
numeric digits 20 /* 20 digits for max. 2**64 bits */
ADD = 8 * length( arg( 1 )) /* use length ADD if no arg( 3 ) */
NEW = length( BIN ) /* BIN = remaining bits, mod 512 */
if arg( 3 ) = '' & NEW // 8 = 0 then do
MSG = x2c( b2x( BIN )) || arg( 1 )
NEW = NEW + ADD ; ADD = NEW // 512
NEW = NEW - ADD ; BIN = substr( MSG, NEW / 8 + 1 )
LEN = LEN + NEW ; MSG = left( MSG, NEW / 8 )
BIN = x2b( c2x( BIN )) /* save up to 511 remaining bits */
end /* code above is good for octets */
else do /* code below is for bit-strings */
if arg( 3 ) <> '' then ADD = arg( 3 )
BIN = BIN || left( x2b( c2x( arg( 1 ))), ADD, 0 )
NEW = NEW + ADD ; ADD = NEW // 512
NEW = NEW - ADD ; MSG = left( BIN, NEW )
LEN = LEN + NEW ; BIN = substr( BIN, NEW + 1 )
MSG = x2c( b2x( MSG )) /* caveat, for the 3rd argument */
end /* you'll get what you paid for */
if arg( 2, 'o' ) | ( arg( 1, 'o' ) & arg( 2 ) <> '' ) then do
LEN = LEN + ADD /* compute total length in bits, */
NEW = NEW + ADD /* note NEW bits for final loop, */
ADD = 448 - ADD /* pad to length 448 modulo 512 */
if ADD <= 0 then ADD = ADD + 512
BIN = b2x( BIN || left( 1, ADD, 0 ))
MSG = MSG || x2c( BIN ) || reverse( x2c( d2x( LEN, 16 )))
NEW = NEW + ADD + 64 /* reverse little endian length, */
LEN = 'EOF' /* using 16 * 4 = 512 - 448 bits */
end
call trace 'O' ; trace 'N' /* disable interactive MD5 trace */
do N = 1 to NEW / 512 /* for MSG with N * 512 NEW bits */
A = x2c( A ) ; AA = A ; B = x2c( B ) ; BB = B
C = x2c( C ) ; CC = C ; D = x2c( D ) ; DD = D
K = N * 64 - 63 /* fetch next 64 * 8 = 512 bits, */
do J = 0 to 15 /* 512 = 16 * 32 bits to decimal */
M.J = c2d( reverse( substr( MSG, K + J * 4, 4 )))
end J
A = MD5.1( A, B, C, D, 7, M.0 + 3614090360 ) /* 1 */
D = MD5.1( D, A, B, C, 12, M.1 + 3905402710 ) /* 2 */
C = MD5.1( C, D, A, B, 17, M.2 + 606105819 ) /* 3 */
B = MD5.1( B, C, D, A, 22, M.3 + 3250441966 ) /* 4 */
A = MD5.1( A, B, C, D, 7, M.4 + 4118548399 ) /* 5 */
D = MD5.1( D, A, B, C, 12, M.5 + 1200080426 ) /* 6 */
C = MD5.1( C, D, A, B, 17, M.6 + 2821735955 ) /* 7 */
B = MD5.1( B, C, D, A, 22, M.7 + 4249261313 ) /* 8 */
A = MD5.1( A, B, C, D, 7, M.8 + 1770035416 ) /* 9 */
D = MD5.1( D, A, B, C, 12, M.9 + 2336552879 ) /* 10 */
C = MD5.1( C, D, A, B, 17, M.10 + 4294925233 ) /* 11 */
B = MD5.1( B, C, D, A, 22, M.11 + 2304563134 ) /* 12 */
A = MD5.1( A, B, C, D, 7, M.12 + 1804603682 ) /* 13 */
D = MD5.1( D, A, B, C, 12, M.13 + 4254626195 ) /* 14 */
C = MD5.1( C, D, A, B, 17, M.14 + 2792965006 ) /* 15 */
B = MD5.1( B, C, D, A, 22, M.15 + 1236535329 ) /* 16 */
A = MD5.2( A, B, C, D, 5, M.1 + 4129170786 ) /* 17 */
D = MD5.2( D, A, B, C, 9, M.6 + 3225465664 ) /* 18 */
C = MD5.2( C, D, A, B, 14, M.11 + 643717713 ) /* 19 */
B = MD5.2( B, C, D, A, 20, M.0 + 3921069994 ) /* 20 */
A = MD5.2( A, B, C, D, 5, M.5 + 3593408605 ) /* 21 */
D = MD5.2( D, A, B, C, 9, M.10 + 38016083 ) /* 22 */
C = MD5.2( C, D, A, B, 14, M.15 + 3634488961 ) /* 23 */
B = MD5.2( B, C, D, A, 20, M.4 + 3889429448 ) /* 24 */
A = MD5.2( A, B, C, D, 5, M.9 + 568446438 ) /* 25 */
D = MD5.2( D, A, B, C, 9, M.14 + 3275163606 ) /* 26 */
C = MD5.2( C, D, A, B, 14, M.3 + 4107603335 ) /* 27 */
B = MD5.2( B, C, D, A, 20, M.8 + 1163531501 ) /* 28 */
A = MD5.2( A, B, C, D, 5, M.13 + 2850285829 ) /* 29 */
D = MD5.2( D, A, B, C, 9, M.2 + 4243563512 ) /* 30 */
C = MD5.2( C, D, A, B, 14, M.7 + 1735328473 ) /* 31 */
B = MD5.2( B, C, D, A, 20, M.12 + 2368359562 ) /* 32 */
A = MD5.3( A, B, C, D, 4, M.5 + 4294588738 ) /* 33 */
D = MD5.3( D, A, B, C, 11, M.8 + 2272392833 ) /* 34 */
C = MD5.3( C, D, A, B, 16, M.11 + 1839030562 ) /* 35 */
B = MD5.3( B, C, D, A, 23, M.14 + 4259657740 ) /* 36 */
A = MD5.3( A, B, C, D, 4, M.1 + 2763975236 ) /* 37 */
D = MD5.3( D, A, B, C, 11, M.4 + 1272893353 ) /* 38 */
C = MD5.3( C, D, A, B, 16, M.7 + 4139469664 ) /* 39 */
B = MD5.3( B, C, D, A, 23, M.10 + 3200236656 ) /* 40 */
A = MD5.3( A, B, C, D, 4, M.13 + 681279174 ) /* 41 */
D = MD5.3( D, A, B, C, 11, M.0 + 3936430074 ) /* 42 */
C = MD5.3( C, D, A, B, 16, M.3 + 3572445317 ) /* 43 */
B = MD5.3( B, C, D, A, 23, M.6 + 76029189 ) /* 44 */
A = MD5.3( A, B, C, D, 4, M.9 + 3654602809 ) /* 45 */
D = MD5.3( D, A, B, C, 11, M.12 + 3873151461 ) /* 46 */
C = MD5.3( C, D, A, B, 16, M.15 + 530742520 ) /* 47 */
B = MD5.3( B, C, D, A, 23, M.2 + 3299628645 ) /* 48 */
A = MD5.4( A, B, C, D, 6, M.0 + 4096336452 ) /* 49 */
D = MD5.4( D, A, B, C, 10, M.7 + 1126891415 ) /* 50 */
C = MD5.4( C, D, A, B, 15, M.14 + 2878612391 ) /* 51 */
B = MD5.4( B, C, D, A, 21, M.5 + 4237533241 ) /* 52 */
A = MD5.4( A, B, C, D, 6, M.12 + 1700485571 ) /* 53 */
D = MD5.4( D, A, B, C, 10, M.3 + 2399980690 ) /* 54 */
C = MD5.4( C, D, A, B, 15, M.10 + 4293915773 ) /* 55 */
B = MD5.4( B, C, D, A, 21, M.1 + 2240044497 ) /* 56 */
A = MD5.4( A, B, C, D, 6, M.8 + 1873313359 ) /* 57 */
D = MD5.4( D, A, B, C, 10, M.15 + 4264355552 ) /* 58 */
C = MD5.4( C, D, A, B, 15, M.6 + 2734768916 ) /* 59 */
B = MD5.4( B, C, D, A, 21, M.13 + 1309151649 ) /* 60 */
A = MD5.4( A, B, C, D, 6, M.4 + 4149444226 ) /* 61 */
D = MD5.4( D, A, B, C, 10, M.11 + 3174756917 ) /* 62 */
C = MD5.4( C, D, A, B, 15, M.2 + 718787259 ) /* 63 */
B = MD5.4( B, C, D, A, 21, M.9 + 3951481745 ) /* 64 */
A = d2x( c2d( AA ) + c2d( A ), 8 )
B = d2x( c2d( BB ) + c2d( B ), 8 )
C = d2x( c2d( CC ) + c2d( C ), 8 )
D = d2x( c2d( DD ) + c2d( D ), 8 )
end N
if LEN = 'EOF' then do /* return lower case c2x( hash ) */
MSG = reverse( x2c( D || C || B || A ))
return translate( c2x( MSG ), 'abcdef', 'ABCDEF' )
end /* caller uses x2c for real hash */
else return A B C D LEN BIN /* return an updated MD5 context */
MD5.1 : procedure /* function used in MD5 round 1: */
parse arg A, B, C, D, S, M
C = bitor( bitand( B, C ), bitand( D, bitxor( B, 'FFFFFFFF'x )))
signal MD5.. /* = return MD5..(), common part */
MD5.2 : procedure /* function used in MD5 round 2: */
parse arg A, B, C, D, S, M
C = bitor( bitand( B, D ), bitand( C, bitxor( D, 'FFFFFFFF'x )))
signal MD5.. /* = return MD5..(), common part */
MD5.3 : procedure /* function used in MD5 round 3: */
parse arg A, B, C, D, S, M
C = bitxor( B, bitxor( C, D ))
signal MD5.. /* = return MD5..(), common part */
MD5.4 : procedure /* function used in MD5 round 4: */
parse arg A, B, C, D, S, M
C = bitxor( C, bitor( B, bitxor( D, 'FFFFFFFF'x )))
MD5.. : /* common part incl. S rotation: */
C = x2b( d2x( c2d( A ) + c2d( C ) + M, 8 ))
C = b2x( right( C || left( C, S ), 32 ))
return x2c( d2x( x2d( C ) + c2d( B ), 8 ))
/* -------------------------------------------------------------- */
/* RXsock.dll interface (TOPEN, TREAD, TSEND, TFAIL) + gen. RXMSG */
TOPEN: procedure expose (EXPO) /* TCP connect with HOST at PORT */
if RxFuncQuery( 'SockLoadFuncs' ) then do
call RxFuncAdd 'SockLoadFuncs', 'RXSOCK', 'SockLoadFuncs'
call SockLoadFuncs 'N' /* TRAP if RXSOCK.DLL not found */
end
if datatype( value( 'TSOCK' ), 'w' ) then call SockClose TSOCK
if sign( verify( arg( 1 ), '0.123456789' )) = 0 then do
if SockGetHostByAddr( arg( 1 ), 'PEER.' ) = 0 then do
PEER.ADDR = arg( 1 ) ; PEER.HOST = arg( 1 )
end /* support IP without host name: */
end /* SockConnect() handles bad IP */
else if SockGetHostByName( arg( 1 ), 'PEER.' ) = 0
then return RXMSG( 'unknown host' arg( 1 ) value( 'h_errno' ))
PEER.PORT = arg( 2 ) ; PEER.FAMILY = 'AF_INET'
TSOCK = SockSocket( PEER.FAMILY, 'SOCK_STREAM', 'IPPROTO_TCP' )
if 0 <= TSOCK then do
if SockConnect( TSOCK, 'PEER.' ) = 0 then return 0
end /* 0: okay, connected with TSOCK */
return TFAIL() /* 1: error shown, socket closed */
TREAD: procedure expose (EXPO) /* TCP read line (or data block) */
READ = ''
do until N < 2000 | sign( pos( x2c( 0A ), READ ))
N = SockRecv( TSOCK, 'DATA', 2000 )
if N > 0 then READ = READ || left( DATA, N )
end
return READ
TSEND: procedure expose (EXPO) /* TCP send complete data block */
if arg( 1, 'e' ) /* 1: any error, 0: sent / close */
then return length( arg( 1 )) <> SockSend( TSOCK, arg( 1 ))
else return SockShutDown( TSOCK, 1 ) <> 0
RXMSG: procedure expose (EXPO) /* show error message & return 1 */
parse source . . THIS ; signal on syntax name RXMSG.TRAP
call RxMessageBox arg( 1 ), THIS, /**/, 'HAND' ; return 1
RXMSG.TRAP: call SockPSock_Errno arg( 1 ) ; return 1
TFAIL: /* close sockets and handle TRAP */
signal on novalue name TRAP ; signal on syntax name TRAP
signal on failure name TRAP ; signal on halt name TRAP
TRAP = RXMSG( 'socket error' SockSock_Errno() value( 'errno' ))
if symbol( 'TSOCK' ) = 'VAR' then TRAP = SockClose( TSOCK )
if condition() = '' then return 1
/* drop into normal TRAP handler, 'sigl' + 'result' preserved: */
/* see , (c) F. Ellermann */
TRAP: /* select REXX exception handler */
call trace 'O' ; trace N /* don't trace interactive */
parse source TRAP /* source on separate line */
TRAP = x2c( 0D ) || right( '+++', 10 ) TRAP || x2c( 0D0A )
TRAP = TRAP || right( '+++', 10 ) /* = standard trace prefix */
TRAP = TRAP strip( condition( 'c' ) 'trap:' condition( 'd' ))
select
when wordpos( condition( 'c' ), 'ERROR FAILURE' ) > 0 then do
if condition( 'd' ) > '' /* need an additional line */
then TRAP = TRAP || x2c( 0D0A ) || right( '+++', 10 )
TRAP = TRAP '(RC' rc || ')' /* any system error codes */
if condition( 'c' ) = 'FAILURE' then rc = -3
end
when wordpos( condition( 'c' ), 'HALT SYNTAX' ) > 0 then do
if condition( 'c' ) = 'HALT' then rc = 4
if condition( 'd' ) > '' & condition( 'd' ) <> rc then do
if condition( 'd' ) <> errortext( rc ) then do
TRAP = TRAP || x2c( 0D0A ) || right( '+++', 10 )
TRAP = TRAP errortext( rc )
end /* future condition( 'd' ) */
end /* may use errortext( rc ) */
else TRAP = TRAP errortext( rc )
rc = -rc /* rc < 0: REXX error code */
end
when condition( 'c' ) = 'NOVALUE' then rc = -2 /* dubious */
when condition( 'c' ) = 'NOTREADY' then rc = -1 /* dubious */
otherwise /* force non-zero whole rc */
if datatype( value( 'RC' ), 'W' ) = 0 then rc = 1
if rc = 0 then rc = 1
if condition() = '' then TRAP = TRAP arg( 1 )
end /* direct: TRAP( message ) */
TRAP = TRAP || x2c( 0D0A ) || format( sigl, 6 )
signal on syntax name TRAP.SIGL /* throw syntax error 3... */
if 0 < sigl & sigl <= sourceline() /* if no handle for source */
then TRAP = TRAP '*-*' strip( sourceline( sigl ))
else TRAP = TRAP '+++ (source line unavailable)'
TRAP.SIGL: /* ...catch syntax error 3 */
if abbrev( right( TRAP, 2 + 6 ), x2c( 0D0A )) then do
TRAP = TRAP '+++ (source line unreadable)' ; rc = -rc
end
select
when 1 then do /* in pipes STDERR: output */
parse version TRAP.REXX /* REXX/Personal: \dev\con */
if abbrev( TRAP.REXX, 'REXXSAA ' ) | /**/ ,
6 <= word( TRAP.REXX, 2 ) then TRAP.REXX = 'STDERR'
else TRAP.REXX = '\dev\con'
signal on syntax name TRAP.FAIL
call lineout TRAP.REXX , TRAP /* fails if no more handle */
end
when 0 then do /* OS/2 PM or ooREXX on NT */
signal on syntax name TRAP.FAIL
call RxMessageBox translate( TRAP, ' ', x2c( 0D )), /**/ ,
'Trap' time(),, 'ERROR'
end
otherwise say TRAP ; trace ?L /* interactive Label trace */
end
if condition() = 'SIGNAL' then signal TRAP.EXIT
TRAP.CALL: return rc /* continue after CALL ON */
TRAP.FAIL: say TRAP ; rc = 0 - rc /* force TRAP error output */
TRAP.EXIT: exit rc /* exit for any SIGNAL ON */