/* Copyright (c) 2017, Computer History Museum All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sendmail.h" #include "myssl.h" #define FILE_NUM 34 /* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */ /************************************************************************ * functions for dealing with a sendmail (or other?) smtp server ************************************************************************/ #pragma segment SMTP typedef enum { helo=1, mail, rcpt, data, rset, send, soml, saml, vrfy, expn, help, noop, quit, turn, ehlo, auth, starttls } SMTPEnum; typedef enum {k1342LWSP,k1342Word,k1342Plain,k1342End} Enum1342; typedef struct { Str255 word; Enum1342 wordType; } Token1342, *Token1342Ptr; typedef struct { Boolean sawGreeting; long maxSize; Boolean mime8bit; Boolean pipeline; SASLEnum saslMech; Boolean starttls; Str255 digest; } EhloStuff, *EhloStuffPtr, **EhloStuffHandle; EhloStuffHandle Ehlo; #define RingNext(pointer,array,size) ((array) + (((pointer)-(array))+1)%(size)) #define kSpecialSendDidntPanOut -1 #define CMD_BUFFER 1024 typedef struct wdsEntry *WDSPtr; /************************************************************************ * declarations for private routines ************************************************************************/ OSErr SendPtrHead(TransStream stream, Ptr label, long labelLen, Ptr body, long bodyLen, Boolean allowQP,short tid); OSErr SendRawMIME(TransStream stream,FSSpecPtr spec); OSErr SendEnriched(TransStream stream,UHandle text,DecoderFunc *encoder); void EhloLine(UPtr line,long size); int DoIntroductions(TransStream stream); OSErr DoSMTPAuth(TransStream stream); int SayByeBye(TransStream stream); int SendCmd(TransStream stream, int cmd, UPtr args, AccuPtr argsAcc); int SendCmdGetReply(TransStream stream, int cmd, UPtr args, Boolean chatter,MessHandle messH); int SendHeaderLine(TransStream stream,MessHandle messH,short index,Boolean allowQP,short tid); int SendAttachments(TransStream stream,MessHandle messH,long flags,PStr boundary,short tableID,short idBase); OSErr SendNewsGroups(TransStream stream,AccuPtr newsGroupAcc,short tid); OSErr SendCID(TransStream stream,MessHandle messH,long part,short n); int SMTPCmdError(int cmd, UPtr args, UPtr message); void PrimeProgress(MessHandle messH); OSErr SendDigest(TransStream stream,FSSpecPtr spec); int WannaSend(MyWindowPtr win); short SendXSender(TransStream stream,MessHandle messH); OSErr SendMIMEHeaders(TransStream stream,MessHandle messH,UHandle enriched,PStr boundary,emsMIMEHandle *tlMIME,Boolean isRelated); OSErr SendContentType(TransStream stream,Handle text1, long offset1, Handle text2, long offset2, short tableID,long *flags,long *opts,PStr name,emsMIMEHandle *tlMIME,PStr subType); long DecideEncoding(Handle text1, Handle text2,Boolean anyfunny,short etid,long flags); Boolean SevenBitTable(short tableID); Boolean Any2022(Handle text,long offset); PStr NameCharset(PStr charset,short tid,emsMIMEHandle *tlMIME); Boolean LongerThan(Handle text, short len); Boolean LongerWordThan(Handle text, short len); Handle Encode1342(UPtr source,long len,short lineLimit,short *charsOnLine,PStr nl,short tid); void Encode1342String(PStr s,short tid); void Next1342Word(UPtr *startP,UPtr end,Token1342Ptr current,PStr delim,Boolean *wasQuote,Boolean *encQuote); OSErr SendAnonFTP(TransStream stream,FSSpecPtr spec); OSErr SendSpecial(TransStream stream,FSSpecPtr spec,AttMapPtr amp); OSErr SendAddressHead(TransStream stream,PETEHandle pte,HSPtr hs,Boolean allowQP,short tid); OSErr SendNormalHead(TransStream stream,PETEHandle pte,HSPtr hs,Boolean allowQP,short tid); OSErr SendSubjectHead(TransStream stream,PETEHandle pte,HSPtr hs, Boolean allowQP,short tid); //OSErr SendPipeRCPT(TransStream stream,PStr newRecip,AccuPtr pipe,Boolean chatter); OSErr SendPipeRCPT(TransStream stream,PStr newRecip,AccuPtr pipe,Boolean chatter, MessHandle messH); OSErr PeriodEncoder(CallType callType,DecoderPBPtr pb); long StuffPeriods(UPtr in, long inLen, UPtr out, PStr newLine, short *nlStatePtr); int SendRelatedParts(TransStream stream,MessHandle messH,long flags,StackHandle parts,PStr boundary); int SendAnAttachment(TransStream stream,MessHandle messH,long flags,Boolean canQP,Boolean plainText,short tableID,PStr boundary,FSSpecPtr spec,short multiID,short partID); int SendAttachmentFolder(TransStream stream,MessHandle messH,long flags,Boolean canQP,Boolean plainText,short tableID,PStr boundary,FSSpecPtr folderSpec,short multiID,short partID,short *partBase,CInfoPBRec *hfi); int sErr; void ConvertPictPart(FSSpecPtr origSpec,FSSpecPtr spec); OSErr FlattenAndSpool(FSSpecPtr spec); OSErr FlattenQTMovie(FSSpecPtr inSpec,FSSpecPtr outSpec); OSErr AllAttachOnBoard(MessHandle messH); OSErr TransmitMessageMixed(TransmitPBPtr pb,Boolean topLevel); OSErr TransmitMessageRelated(TransmitPBPtr pb,Boolean topLevel); OSErr TransmitMessageText(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitMessageBody(TransmitPBPtr pb,Boolean withClosure); OSErr TransmitMessageBodyHeaders(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitTopHeaders(TransmitPBPtr pb); OSErr TransmitMultiHeaders(TransmitPBPtr pb,short subType,PStr boundary,short otherParm,PStr otherVal); OSErr TransmitMessageTextBloat(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitMessageTextStrip(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitMessageTextPlain(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitMessageTextRich(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel); OSErr TransmitMessageSig(TransmitPBPtr pb); OSErr TransmitMessageSigBloat(TransmitPBPtr pb); OSErr TransmitMessageSigBody(TransmitPBPtr pb,Boolean withHeaders); OSErr TransmitMessageSigBodyPlain(TransmitPBPtr pb); /************************************************************************ * Public routines ************************************************************************/ /************************************************************************ * StartSMTP - initiate a connection with the specified SMTP server ************************************************************************/ int StartSMTP(TransStream stream, UPtr serverName, long port) { if (UUPCOut) sErr=UUPCPrime(serverName); else if (!(sErr=ConnectTrans(stream,serverName,port,False,GetRLong(OPEN_TIMEOUT)))) sErr=DoIntroductions(stream); return(sErr); } /************************************************************************ * MySendMessage - send a message to the SMTP server ************************************************************************/ int MySendMessage(TransStream stream,TOCHandle tocH,int sumNum,CSpecHandle specList) { WindowPtr messWinWP; Str255 buffer; Str255 param; MessHandle messH; Accumulator newsGroupAcc; Zero(newsGroupAcc); /* * handle open, dirty windows */ if (!(messH=SaveB4Send(tocH,sumNum))) return(1); messWinWP = GetMyWindowWindowPtr((*messH)->win); /* * Log, if we must */ GetWTitle(messWinWP,buffer); ComposeLogR(LOG_SEND,nil,SENDING,buffer); #ifdef TWO if (PrefIsSet(PREF_POP_SEND)) { long size=sizeof(buffer); *buffer = '+'; if (POPCmdGetReply(stream,kpcXmit,"",buffer,&size) || *buffer!='+') { if (*buffer=='-') POPCmdError(kpcXmit,"",buffer); return(1); } } else #endif { /* * reset SMTP */ sErr = SendCmdGetReply(stream,rset,nil,True,messH); if (sErr/100 != 2) return(sErr); sErr = 0; /* * envelope */ MessReturnAddr(messH,buffer); if (Ehlo && *(*Ehlo)->digest) { ComposeString(param,"\p %r=%p",EsmtpStrn+esmtpAmd5,LDRef(Ehlo)->digest); PSCat(buffer,param); UL(Ehlo); } if (Ehlo && (*Ehlo)->maxSize) { ComposeString(param,"\p %r=%d",EsmtpStrn+esmtpSize,ApproxMessageSize(messH) K); PSCat(buffer,param); } if (Ehlo && (*Ehlo)->mime8bit && PrefIsSet(PREF_ALLOW_8BITMIME)) { ComposeRString(param,BODY_EQUALS,EsmtpStrn+esmtp8BMIME); PSCat(buffer,param); } sErr = SendCmdGetReply(stream,mail,buffer,True,messH); if (Ehlo && *(*Ehlo)->digest && IsAddrErr(sErr)) InvalidatePasswords(False,True,False); if (sErr/100 != 2) return(sErr); sErr = 0; if (WrapWrong) OffsetWindow(messWinWP); if (sErr=DoRcptTos(stream,messH,True,specList,&newsGroupAcc)) goto done; } (*messH)->newsGroupAcc = newsGroupAcc; if (sErr=TransmitMessageHi(stream,messH,True,!PrefIsSet(PREF_POP_SEND))) goto done; AccuZap(newsGroupAcc); Zero((*messH)->newsGroupAcc); //TimeStamp(tocH,sumNum,GMTDateTime(),ZoneSecs()); done: (void)ComposeLogR(LOG_SEND,nil,sErr?LOG_FAILED:LOG_SUCCEEDED,sErr); AccuZap(newsGroupAcc); return(sErr); } /********************************************************************** * MessReturnAddr - get the right return addr for this message; if addr * is in "Me" nickname, returns from addr. Else returns configured addr **********************************************************************/ PStr MessReturnAddr(MessHandle messH,PStr buffer) { Str255 from; Str255 returnAddr; short template = MessOptIsSet(messH,OPT_RECEIPT) && MessOptIsSet(messH,OPT_BULK) ? EMPTY_MFROM:NORMAL_MFROM; // return address usually GetReturnAddr(returnAddr,False); // or contents of from field if (!CompHeadGetStr(messH,FROM_HEAD,from) && !StringSame(returnAddr,from) && IsMe(from)) PCopy(returnAddr,from); // strip <>'s ShortAddr(returnAddr,returnAddr); // and compose the argument ComposeRString(buffer,template,returnAddr); return(buffer); } /************************************************************************ * EndSMTP - done talking to SMTP ************************************************************************/ int EndSMTP(TransStream stream) { if (UUPCOut) UUPCDry(stream); else { SilenceTrans(stream,True); // ignore all TCP/IP errors from here on out -jdboyd 030113 if ((!sErr || (sErr<600 && sErr>=400)) && !(sErr=SayByeBye(stream))) DisTrans(stream); sErr = DestroyTrans(stream); } ZapHandle(eSignature); ZapHandle(RichSignature); ZapHandle(HTMLSignature); ZapHandle(Ehlo); return(sErr); } /************************************************************************ * SMTPError - return the last SMTP error ************************************************************************/ int SMTPError(TransStream stream) { return (sErr); } /************************************************************************ * Private routines ************************************************************************/ /************************************************************************ * DoIntroductions - take care of the beginning of the SMTP protocol ************************************************************************/ int DoIntroductions(TransStream stream) { Str255 buffer; Ehlo = NewZH(EhloStuff); /* * get banner from the remote end */ sErr = GetReply(stream,buffer,sizeof(buffer),True,False); if (sErr/100 != 2) return(sErr); sErr = 0; SayHello : /* * tell it who we are */ sErr = SendCmdGetReply(stream,ehlo,WhoAmI(stream,buffer),True,nil); if (sErr>=400) sErr = SendCmdGetReply(stream,helo,WhoAmI(stream,buffer),True,nil); if (sErr/100 == 2) { #ifdef ESSL if( ShouldUseSSL(stream) && !(stream->ESSLSetting & esslSSLInUse)) { if (!(*Ehlo)->starttls) { if(!(stream->ESSLSetting & esslOptional)) { sErr = 502; // SMTPCmdError(starttls,nil,GetRString(buffer,SSL_ERR_STRING)+1); ComposeStdAlert ( Note, ALRTStringsStrn+NO_SERVER_SSL ); } } else { OSStatus sslErr; int tempErr; tempErr = SendCmdGetReply(stream,starttls,nil,(stream->ESSLSetting & esslOptional) != 0,nil); if(tempErr/100 == 2) { sslErr = ESSLStartSSL(stream); if(sslErr) { if(!(stream->ESSLSetting & esslOptional)) { sErr = 554; SMTPCmdError(starttls,nil,GetRString(buffer,SSL_ERR_STRING)+1); } } else if(stream->ESSLSetting & esslSSLInUse) { ZeroHandle(Ehlo); goto SayHello; } } else if(!(stream->ESSLSetting & esslOptional)) { sErr = tempErr; } } } #endif if ((sErr/100 == 2)) { if (!PrefIsSet(PREF_SMTP_AUTH_NOTOK)) { short (*authfunc)(TransStream stream) = nil; if (!(*Ehlo)->saslMech) { ComposeLogS(LOG_PROTO,nil,"\pSMTP auth not available, disabling"); SetPref(PREF_SMTP_GAVE_530,NoStr); SetPref(PREF_SMTP_DOES_AUTH,NoStr); } else { SetPref(PREF_SMTP_DOES_AUTH,YesStr); if (PrefIsSet(PREF_KERBEROS) || *(*CurPers)->password) { ComposeLogS(LOG_PROTO,nil,"\pSMTP auth %r under way...",SASLStrn+(*Ehlo)->saslMech); // We have everything we need to attempt authentication sErr = DoSMTPAuth(stream); } else ComposeLogS(LOG_PROTO,nil,"\pSMTP auth %r not being attempted, no credentials",SASLStrn+(*Ehlo)->saslMech); } } else if ((*Ehlo)->saslMech) ComposeLogS(LOG_PROTO,nil,"\pSMTP auth %r available but forbidden",SASLStrn+(*Ehlo)->saslMech); } } return((sErr/100 != 2) ? sErr : (sErr=0)); } /************************************************************************ * DoSMTPAuth - authenticate for SMTP ************************************************************************/ OSErr DoSMTPAuth(TransStream stream) { Accumulator chalAcc, respAcc; short rounds = 0; Str63 service; long state; Zero(chalAcc); Zero(respAcc); // put auth command in initial response AccuAddRes(&respAcc,EsmtpStrn+esmtpAuth); AccuAddChar(&respAcc,' '); // grab service name for kerberos GetRString(service,K5_SMTP_SERVICE); // run the mechanism do { // Build the response if (SASLDo(service,(*Ehlo)->saslMech,rounds++,&state,&chalAcc,&respAcc)) sErr = 601; else { // Send the response if (SendCmd(stream,0,nil,&respAcc)) sErr = 601; else { // get the reply sErr = GetReplyLo(stream,nil,0,&respAcc,false,false); chalAcc.offset = 0; if (sErr==334) { UPtr spot; // extract the challenge token AccuAddChar(&respAcc,0); spot = *respAcc.data; while (*spot && !IsWhite(*spot)) spot++; // skip code // rest will be base64 or whitespace, the decoder won't care AccuAddFromHandle(&chalAcc,respAcc.data,spot-*respAcc.data,respAcc.offset-(spot-*respAcc.data)-1); } } } // the response needs no pre-loading anymore. respAcc.offset = 0; } while (sErr/100 == 3); // if the server rejects our auth, but doesn't actually // require auth for anything, pretend like nothing happened if (sErr/100==5 && !ShouldSMTPAuth()) sErr = 0; // Kill the accumulators AccuZap(chalAcc); AccuZap(respAcc); // Let the sasl mechanism know how it all came out SASLDone(service,(*Ehlo)->saslMech,rounds,&state,sErr); return sErr; } /************************************************************************ * SayByeBye - take care of the end of the SMTP protocol ************************************************************************/ int SayByeBye(TransStream stream) { sErr = SendCmdGetReply(stream,quit,nil,False,nil); return((sErr/100 != 2) ? sErr : (sErr=0)); } /************************************************************************ * SendCmd - send an smtp command, with optional arguments ************************************************************************/ int SendCmd(TransStream stream, int cmd, UPtr args, AccuPtr argsAcc) { Byte buffer[CMD_BUFFER]; GetRString(buffer,SMTP_STRN+cmd); if (args && *args) PCat(buffer,args); if (cmd) ProgressMessage(kpMessage,buffer); if (!argsAcc) { PCat(buffer,NewLine); if (sErr=SendPString(stream,buffer)) return(sErr); } else { // send the command if (cmd) PCatC(buffer,' '); if (sErr=SendPString(stream,buffer)) return(sErr); // add a newline to the accumulator AccuAddStr(argsAcc,NewLine); // send the data sErr = SendTrans(stream,LDRef(argsAcc->data),argsAcc->offset,nil); // erase what we did to the accumulator UL(argsAcc->data); argsAcc->offset -= *NewLine; if (sErr) return sErr; } return(noErr); } /************************************************************************ * SMTPCmdError - report an error for an SMTP command ************************************************************************/ int SMTPCmdError(int cmd, UPtr args, UPtr message) { Str255 theCmd; Str255 theError; int err; GetRString(theCmd,2800+cmd); if (args && *args) PCat(theCmd,args); strcpy(theError+1,message); *theError = strlen(theError+1); if (theError[*theError]=='\012') (*theError)--; if (theError[*theError]=='\015') (*theError)--; MyParamText(theCmd,theError,"\pSMTP",""); err = ReallyDoAnAlert(PROTO_ERR_ALRT,Note); return(err); } /************************************************************************ * SendCmdGetReply - send an smtp command, with optional arguments, and * wait for the reply. Returns reply code. ************************************************************************/ int SendCmdGetReply(TransStream stream, int cmd, UPtr args, Boolean chatter,MessHandle messH) { char buffer[CMD_BUFFER]; if (sErr=SendCmd(stream,cmd,args,nil)) return(601); /* error in transmission */ sErr = GetReply(stream,buffer,sizeof(buffer),False,cmd==ehlo); if (cmd==rcpt && sErr>499 && sErr<600) sErr = 550; if (sErr>399 && sErr<=600 && (IsAddrErr(sErr)||cmd!=rcpt) && cmd!=ehlo && chatter) SMTPCmdError(cmd?cmd:auth,(cmd&&cmd!=auth)?args:nil,buffer); if (messH && IsAddrErr(sErr) && cmd!=rcpt) { if (strchr(buffer,'\015')) *strchr(buffer,'\015') = 0; AddOutgoingMesgError((*messH)->sumNum,SumOf(messH)->uidHash,sErr,BAD_XMIT_ERR_TEXT,"",buffer); } return(sErr); } /************************************************************************ * EhloLine - process an Ehlo return ************************************************************************/ void EhloLine(UPtr line,long size) { Str63 directive; Str255 value,digest; UPtr start,end,stop; long longVal; if (!Ehlo) return; if ((*Ehlo)->sawGreeting) { (*Ehlo)->sawGreeting = True; return; } // Stupid AUTH= nonsense GetRString(value,EsmtpStrn+esmtpAuth); PCatC(value,'='); if (start=PPtrFindSub(value,line,size)) start[4] = ' '; /* * grab the Ehlo directive */ end = line+size; start = line+4; while (startmaxSize = 0x7fffffff; if (*value <= 9) // avoid rilly big numbers { StringToNum(value,&longVal); if (longVal > 1 K) (*Ehlo)->maxSize = longVal; ComposeLogS(LOG_PROTO,nil,"\pESMTP size %d",longVal); } else ComposeLogS(LOG_PROTO,nil,"\pESMTP size invalid (%p)",value); break; case esmtp8BMIME: (*Ehlo)->mime8bit = True; ComposeLogS(LOG_PROTO,nil,"\pESMTP mime8bit"); break; case esmtpAuth: { SASLEnum mech = (*Ehlo)->saslMech; UPtr spot = value+1; Str31 service; GetRString(service,K5_SMTP_SERVICE); while (PToken(value,directive,&spot," \011\012\015")) mech = SASLFind(service,directive, mech); (*Ehlo)->saslMech = mech; if (mech) ComposeLogS(LOG_PROTO,nil,"\pESMTP SASL mech %r",SASLStrn+mech); } break; case esmtpAmd5: SetPref(PREF_SMTP_DOES_AUTH,YesStr); PSCopy(directive,(*CurPers)->password); GenDigest(value,directive,digest); PCopy((*Ehlo)->digest,digest); ComposeLogS(LOG_PROTO,nil,"\pESMTP AMD5 auth found"); break; case esmtpPipeline: (*Ehlo)->pipeline = True; ComposeLogS(LOG_PROTO,nil,"\pESMTP pipeline"); break; case esmtpStartTLS: (*Ehlo)->starttls = True; ComposeLogS(LOG_PROTO,nil,"\pESMTP starttls"); break; default: ComposeLogS(LOG_PROTO,nil,"\pESMTP has NFI what Ò%pÓ is supposed to mean",directive); break; } } /************************************************************************ * DoRcptTos - tell the remote sendmail who is getting the message ************************************************************************/ int DoRcptTos(TransStream stream,MessHandle messH, Boolean chatter, CSpecHandle fccList,AccuPtr newsGroupAcc) { sErr=DoRcptTosFrom(stream,messH,TO_HEAD,chatter,fccList,newsGroupAcc); if (sErr) return(sErr); sErr=DoRcptTosFrom(stream,messH,BCC_HEAD,chatter,fccList,newsGroupAcc); if (sErr) return(sErr); sErr=DoRcptTosFrom(stream,messH,CC_HEAD,chatter,fccList,newsGroupAcc); return(sErr); } /************************************************************************ * DoRcptTosFrom - do the Rcpt to's from a particular TERec ************************************************************************/ int DoRcptTosFrom(TransStream stream, MessHandle messH, short index, Boolean chatter,CSpecHandle fccSpecs,AccuPtr newsGroupAcc) { Str255 toWhom; UHandle addresses=nil; UHandle rawAddresses; UPtr address; HeadSpec hs; UHandle text; Boolean evilSendmail=PrefIsSet(PREF_EVIL_SENDMAIL); UPtr spot; Str255 server; long junk; Accumulator pipe; short err; Zero(pipe); if (evilSendmail) { GetSMTPInfo(server); if (DotToNum(server,&junk)) { /* ip address; turn into domain literal */ PInsert(server,sizeof(server),"\p[",server+1); PCatC(server,']'); } } sErr = 550; if (CompHeadFind(messH,index,&hs) && !CompHeadGetText(TheBody,&hs,&text)) { if (!(sErr=SuckAddresses(&rawAddresses,text,False,True,False,nil))) { sErr=200; if (**rawAddresses) { ExpandAliases(&addresses,rawAddresses,0,False); ZapHandle(rawAddresses); if (!addresses) { AddOutgoingMesgError ((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, BAD_ADDRESS); return(sErr=550); } for (address=LDRef(addresses); *address; address += *address + 2) { /* * skip groups */ if (address[*address]==':' || address[1]==';') continue; /* skip group identifiers */ /* * handle Fcc's */ //Folder Carbon Copy - do not support FCC in Light if (HasFeature (featureFcc)) { if (IsFCCAddr(address)) { if (fccSpecs) { if (sErr=AddFccToList(address,fccSpecs)) break; } continue; } else if (IsNewsgroupAddr(address)) { if (newsGroupAcc) { sErr = noErr; if (newsGroupAcc->offset) sErr = AccuAddRes(newsGroupAcc,COMMA_SPACE); if (!sErr) sErr=AccuAddPtr(newsGroupAcc,address+2,*address-1); if (sErr) break; } continue; } } if (*address > MAX_ALIAS) { sErr = 550; AddOutgoingMesgError ((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, BAD_ADDRESS); break; } if (UUPCOut) { if (sErr=UUPCWriteAddr(address)) break; } else { toWhom[0] = 1; toWhom[1] = '<'; if (evilSendmail && *toWhom+*server+6address;spot--) if (*spot=='@') *spot = '%'; } PCat(toWhom,address); if (evilSendmail && *toWhom+*server+6'); if (Ehlo && (*Ehlo)->pipeline) sErr = SendPipeRCPT(stream,toWhom,&pipe,chatter, messH); else { char buffer[CMD_BUFFER]; if (sErr=SendCmd(stream,rcpt,toWhom,nil)) sErr = 601; /* error in transmission */ sErr = GetReply(stream,buffer,sizeof(buffer),False,false); if (sErr>499 && sErr<600) sErr = 550; if (IsAddrErr(sErr)) { c2pstr(buffer); if (buffer[*buffer]=='\r') --*buffer; AddOutgoingMesgError ((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, BAD_ADDRESS_ERR_TEXT,address,buffer); } } if (sErr/100 != 2) {chatter = False;break;} } } ZapHandle(addresses); if (Ehlo && (*Ehlo)->pipeline) { err = SendPipeRCPT(stream,nil,&pipe,chatter, messH); if (sErr/100==2 || !sErr) sErr = err; } } else ZapHandle(rawAddresses); } else { sErr = 550; AddOutgoingMesgError ((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, BAD_ADDRESS); } ZapHandle(text); } return(sErr/100!=2 ? sErr : (sErr=0)); } /************************************************************************ * SendPipeRCPT - send rcpt to commands in a pipeline ************************************************************************/ OSErr SendPipeRCPT(TransStream stream,PStr newRecip,AccuPtr pipe,Boolean chatter, MessHandle messH) { OSErr err = 200; OSErr firstErr = 200; char buffer[CMD_BUFFER]; Str255 address; /* * reap outstanding rcpts if need be */ while (!newRecip && pipe->offset || pipe->offset > 3 K) { PCopy(address,*pipe->data); err = GetReply(stream,buffer,sizeof(buffer),chatter,false); if (err>499 && err<600) err = 550; if (firstErr/100==2) firstErr = err; if (err/100 != 2) { if (chatter && IsAddrErr(err)) { c2pstr(buffer); if (buffer[*buffer]=='\r') --*buffer; AddOutgoingMesgError ((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, err, BAD_ADDRESS_ERR_TEXT,address,buffer); } chatter = False; } // take the first address out of the accumulator BMD(*pipe->data+*address+1,*pipe->data,pipe->offset-*address-1); pipe->offset -= *address+1; } /* * send current address */ if (err/100 == 2 && newRecip) { if (err=SendCmd(stream,rcpt,newRecip,nil)) return(601); /* error in transmission */ if (err=AccuAddPtr(pipe,newRecip,*newRecip+1)) { WarnUser(MEM_ERR,err); return(601); } } return(firstErr); } /********************************************************************** * AddFccToList - add a mailbox to the fcc list. **********************************************************************/ OSErr AddFccToList(PStr fcc,CSpecHandle list) { FSSpec spec; Str255 name; Str15 prefix; OSErr err; UseFeature (featureFcc); PSCopy(name,fcc); if (name[1]=='"') { BMD(name+2,name+1,*name-1); *name -= 2; } TrimPrefix(name,GetRString(prefix,FCC_PREFIX)); if (err=BoxSpecByName(&spec,name)) return(FileSystemError(NOT_MAILBOX,name,err)); AddSpecToList(&spec,list); if (err=MemError()) WarnUser(MEM_ERR,err); return(err); } /************************************************************************ * TransmitMessage - send a message to the remote sendmail ************************************************************************/ int TransmitMessage(TransStream stream, MessHandle messH, Boolean chatter,Boolean mime,Boolean others,emsMIMEHandle *tlMIME,Boolean sendDataCmd) { return TransmitMessageLo(stream, messH, chatter, mime, others, tlMIME, sendDataCmd, !UUPCOut, true); } /************************************************************************ * TransmitMessageLo - send a message to the remote sendmail ************************************************************************/ int TransmitMessageLo(TransStream stream, MessHandle messH, Boolean chatter,Boolean mime,Boolean others,emsMIMEHandle *tlMIME,Boolean sendDataCmd,Boolean finishSMTP,Boolean doTopLevel) { TransmitPB pb; FSSpec spec; Str255 scratch; FSSpec errSpec; Zero(pb); pb.messH = messH; pb.mime = mime; pb.others = others; pb.receipt = MessOptIsSet(messH,OPT_BULK) && MessOptIsSet(messH,OPT_RECEIPT); pb.parts = nil; pb.isRelated = false; pb.hasAttachments = 1!=GetIndAttachment(messH,1,&spec,&pb.hs); pb.flags = SumOf(messH)->flags; pb.opts = SumOf(messH)->opts; pb.stream = stream; pb.tlMIME = tlMIME; pb.html = MessOptIsSet(messH,OPT_HTML); pb.rich = MessFlagIsSet(messH,FLAG_RICH); pb.allLWSP = !pb.hasAttachments && !pb.html && !pb.rich && IsAllLWSPMess(messH); pb.hasSig = !pb.allLWSP && (SumOf(messH)->sigId!=-1) && !MessOptIsSet(messH,OPT_INLINE_SIG) && eSignature && *eSignature && GetHandleSize(eSignature); pb.strip = !pb.allLWSP && MessOptIsSet(messH,OPT_STRIP) || MessOptIsSet(messH,OPT_JUST_EXCERPT); pb.bloat = !pb.allLWSP && MessOptIsSet(messH,OPT_BLOAT); if (pb.allLWSP) pb.flags &= ~FLAG_WRAP_OUT; Zero(pb.enriched); if (tlMIME && NewTLMIME(tlMIME)) goto fail; PrimeProgress(messH); //Make sure we have all the attachments if (sErr = AllAttachOnBoard(messH)) goto fail; CompHeadFind(messH,0,&pb.hs); // Add back in the inline sig if removed... pb.hs.stop = PeteLen(TheBody); /* * rich? */ if (pb.mime && !pb.strip && pb.html) { if (sErr=AccuInit(&pb.enriched)) goto fail; if (sErr=HTMLPreamble(&pb.enriched,PCopy(scratch,SumOf(messH)->subj),0,False)) goto fail; if (sErr=StackInit(sizeof(FSSpec),&pb.parts)) goto fail; Zero(errSpec); if (sErr=BuildHTML(&pb.enriched,TheBody,nil,pb.hs.stop,pb.hs.value,nil,nil,1,CompGetMID(messH,scratch),pb.parts,&errSpec)) { if (errSpec.vRefNum) { // Report error with graphic file AddOutgoingMesgError((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, GRAPHIC_FILE_ERR,sErr,errSpec.name); } sErr = 543; goto fail; } AccuTrim(&pb.enriched); pb.isRelated = pb.parts && (*pb.parts)->elCount > 0; } else if (pb.mime && !pb.strip && pb.rich) { if (sErr=AccuInit(&pb.enriched)) goto fail; if (BuildEnriched(&pb.enriched,TheBody,nil,pb.hs.stop,pb.hs.value,nil,False)) goto fail; } if (sendDataCmd) { sErr = SendCmdGetReply(stream,data,nil,True,messH); if (sErr && sErr/100!=3) goto fail; } if (pb.hasAttachments) sErr = TransmitMessageMixed(&pb,doTopLevel); else if (pb.isRelated) sErr = TransmitMessageRelated(&pb,doTopLevel); else sErr = TransmitMessageText(&pb,true,doTopLevel); if (sErr) goto fail; if (pb.mime) if (finishSMTP) sErr = FinishSMTP(stream,pb.messH); done: ZapHandle(pb.enriched.data); ZapHandle(pb.parts); return(sErr/100 != 2 ? sErr : (sErr=0)); fail: if (IsAddrErr(sErr)) SumOf(messH)->state = MESG_ERR; else sErr = 600; if (tlMIME) ZapTLMIME(*tlMIME); goto done; } /************************************************************************ * AllAttachOnBoardLo - do we have all the attachments? No error reporting. ************************************************************************/ OSErr AllAttachOnBoardLo(MessHandle messH, Boolean errReport) { short index; FSSpec spec; OSErr err = noErr; for (index=1;!err;index++) { if (err=GetIndAttachment(messH,index,&spec,nil)) if ((err!=1) && errReport) { AddOutgoingMesgError((*messH)->sumNum, (*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, err, ATTACH_MESS_ERR,err,spec.name); FileSystemError(BINHEX_OPEN,spec.name,err); } } if (err==1) err = noErr; else if (err) err = 543; return(err); } /************************************************************************ * AllAttachOnBoard - do we have all the attachments? ************************************************************************/ OSErr AllAttachOnBoard(MessHandle messH) { return AllAttachOnBoardLo(messH, true); } /************************************************************************ * TransmitMessageMixed - transmit a multipart/mixed message ************************************************************************/ OSErr TransmitMessageMixed(TransmitPBPtr pb,Boolean topLevel) { OSErr err; Str127 boundary; // build the boundary BuildBoundary(pb->messH,boundary,""); // mime-version first of all if (topLevel) if (err=TransmitMimeVersion(pb)) return(err); // send the non-mime headers if (topLevel) if (err=TransmitTopHeaders(pb)) return(err); // send the mime headers if (pb->mime && (err=TransmitMultiHeaders(pb,MIME_MIXED,boundary,0,nil))) return(err); if (pb->mime) { // send the header/body separator if (err=SendPString(pb->stream,NewLine)) return(err); // send the first boundary if (err=SendBoundary(pb->stream)) return(err); // send the message itself if (pb->isRelated) err = TransmitMessageRelated(pb,false); else err = TransmitMessageText(pb,false,false); if (err) return(err); // send the attachments err = SendAttachments(pb->stream,pb->messH,pb->flags,boundary,SumOf(pb->messH)->tableId,pb->isRelated ? (*pb->parts)->elCount : 0); if (err) return(err); // signature? if (pb->hasSig) { // attachment/signature boundary if (err=SendBoundary(pb->stream)) return(err); if (err=TransmitMessageSig(pb)) return(err); } // and the final boundary PCat(boundary,"\p--"); if (err=SendBoundary(pb->stream)) return(err); } return(noErr); } /************************************************************************ * TransmitMessageRelated - transmit a multipart/related message ************************************************************************/ OSErr TransmitMessageRelated(TransmitPBPtr pb,Boolean topLevel) { OSErr err; Str127 boundary; Str31 textSlashHtml; // build the boundary BuildBoundary(pb->messH,boundary,"\pmr"); // mime-version first of all if (topLevel) if (err=TransmitMimeVersion(pb)) return(err); // send the non-mime headers if (topLevel) if (err=TransmitTopHeaders(pb)) return(err); // send the mime headers if (pb->mime) if (err=TransmitMultiHeaders(pb,MIME_RELATED,boundary,AttributeStrn+aType,ComposeRString(textSlashHtml,THING_SLASH_THING,MIME_TEXT,HTMLTagsStrn+htmlTag))) return(err); if (pb->mime) { // send the header/body separator if (err=SendPString(pb->stream,NewLine)) return(err); // send the first boundary if (err=SendBoundary(pb->stream)) return(err); // send the message itself err = TransmitMessageText(pb,!pb->hasAttachments,false); if (err) return(err); // send the parts err = SendRelatedParts(pb->stream,pb->messH,pb->flags,pb->parts,boundary); if (err) return(err); // and the final boundary PCat(boundary,"\p--"); if (err=SendBoundary(pb->stream)) return(err); } return(noErr); } /************************************************************************ * TransmitMessageText - transmit body and sig, with or without bloat ************************************************************************/ OSErr TransmitMessageText(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel) { OSErr err; if (pb->bloat && !pb->strip && (pb->html||pb->rich)) { // The user wants multipart/alternative if (err = TransmitMessageTextBloat(pb,sigToo,topLevel)) return(err); } else if (!pb->strip && (pb->html||pb->rich)) { // The user wants the rich version if (err=TransmitMessageTextRich(pb,sigToo,topLevel)) return(err); } else { // The user is chicken if (err=TransmitMessageTextStrip(pb,sigToo,topLevel)) return(err); } return(noErr); } /************************************************************************ * TransmitMessageTextBloat - transmit body and possibly sig with bloat ************************************************************************/ OSErr TransmitMessageTextBloat(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel) { OSErr err; Str127 boundary; // we'll need a boundary BuildBoundary(pb->messH,boundary,"\pma"); // mime-version first of all if (topLevel) if (err=TransmitMimeVersion(pb)) return(err); // send the non-mime headers if (topLevel) if (err=TransmitTopHeaders(pb)) return(err); // send the part headers, header/body separator, and initial boundary if (pb->mime) if (err=TransmitMultiHeaders(pb,MIME_ALTERNATIVE,boundary,0,nil)) return(err); if (pb->mime) { // now the header/body separator and the initial boundary if (err=SendPString(pb->stream,NewLine)) return(err); if (err=SendBoundary(pb->stream)) return(err); // send the stripped version, not top-level if (err=TransmitMessageTextStrip(pb,sigToo,false)) return(err); // now the mid boundary if (err=SendBoundary(pb->stream)) return(err); // send the rich version, not top-level if (err=TransmitMessageTextRich(pb,sigToo,false)) return(err); // and the final boundary PCat(boundary,"\p--"); if (err=SendBoundary(pb->stream)) return(err); } return(noErr); } /************************************************************************ * TransmitMessageTextStrip - strip styles before sending ************************************************************************/ OSErr TransmitMessageTextStrip(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel) { Boolean oldFlat = Flatten!=nil; OSErr err; if ((pb->opts&OPT_BLOAT) || MessOptIsSet(pb->messH,FLAG_WRAP_OUT)) pb->flags |= FLAG_WRAP_OUT; // force wrapping on plain part of m/a if ((pb->opts&OPT_BLOAT) && !Flatten) Flatten = GetFlatten(); // force flattening ConvertExcerpt((*pb->messH)->bodyPTE,pb->hs.value,0x7fffffff,nil,nil); // and convert the excerpts pb->hs.stop = PETEGetTextLen(PETE,(*pb->messH)->bodyPTE); PeteCleanList((*pb->messH)->bodyPTE); (*pb->messH)->win->isDirty = false; // send it if (err=TransmitMessageTextPlain(pb,sigToo,topLevel)) return(err); // put flatten back if (!oldFlat) ZapPtr(Flatten); return(err); } /************************************************************************ * TransmitMessageTextPlain - send plaintext version of message & sig ************************************************************************/ OSErr TransmitMessageTextPlain(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel) { OSErr err; // save off some stuff Boolean oldStrip = pb->strip; // save the old strip value long oldOpts = pb->opts; long oldFlags = pb->flags; // we be strippin' pb->strip = true; pb->opts |= OPT_STRIP; // mime-version first of all if (topLevel) if (err=TransmitMimeVersion(pb)) return(err); // send the non-mime headers if (topLevel) if (err=TransmitTopHeaders(pb)) return(err); // send the mime headers if (pb->mime && !pb->receipt) if (err=TransmitMessageBodyHeaders(pb,sigToo,topLevel)) return(err); if (pb->mime) { // send the header/body separator if (!pb->receipt) if (err=SendPString(pb->stream,NewLine)) return(err); // send the message itself err = TransmitMessageBody(pb,false); if (err) return(err); // signature? Do not generate headers. if (sigToo && pb->hasSig) if (err=TransmitMessageSigBody(pb,false)) return(err); } // put stuff back pb->strip = oldStrip; pb->opts = oldOpts; pb->flags = oldFlags; return(noErr); } /************************************************************************ * TransmitMessageTextRich - send rich version of message & sig ************************************************************************/ OSErr TransmitMessageTextRich(TransmitPBPtr pb,Boolean sigToo,Boolean topLevel) { OSErr err; // we don't wrap rich text pb->flags &= ~FLAG_WRAP_OUT; // mime-version first of all if (topLevel) if (err=TransmitMimeVersion(pb)) return(err); // send the non-mime headers if (topLevel) if (err=TransmitTopHeaders(pb)) return(err); // send the mime headers if (pb->mime) if (err=TransmitMessageBodyHeaders(pb,sigToo,topLevel)) return(err); if (pb->mime) { // send the header/body separator if (err=SendPString(pb->stream,NewLine)) return(err); // send the message itself; close out html if no signature err = TransmitMessageBody(pb,!pb->hasSig); if (err) return(err); // signature? Do not generate headers & preamble if (sigToo && pb->hasSig) if (err=TransmitMessageSigBody(pb,false)) return(err); } return(noErr); } /************************************************************************ * TransmitMimeVersion - transmit the silly mime-version header ************************************************************************/ OSErr TransmitMimeVersion(TransmitPBPtr pb) { OSErr err; if (err = ComposeRTrans(pb->stream,MIME_V_FMT,InterestHeadStrn+hMimeVersion,MIME_VERSION,NewLine)) return(err); return(noErr); } /************************************************************************ * TransmitTopHeaders - transmit the non-MIME headers at the top of the message ************************************************************************/ OSErr TransmitTopHeaders(TransmitPBPtr pb) { OSErr sErr=noErr; Str255 buffer; Str255 scratch; short header; short tid = EffectiveTID(SumOf(pb->messH)->tableId); short priority; Accumulator newsGroupAcc; if (!pb->others) return(noErr); /* * The dreaded X-Sender: */ if (sErr=SendXSender(pb->stream,pb->messH)) return(sErr); /* * extra headers saved with message */ BufferSendRelease(pb->stream); if ((*pb->messH)->extras.data) SendExtras(pb->stream,(*pb->messH)->extras.data,(pb->flags&FLAG_CAN_ENC)!=0,tid); BSCLOSE(pb->stream,nil); /* * newsgroups */ newsGroupAcc = (*pb->messH)->newsGroupAcc; if (newsGroupAcc.offset) { BufferSendRelease(pb->stream); SendNewsGroups(pb->stream,&newsGroupAcc,tid); BSCLOSE(pb->stream,nil); } /* * Return Receipts */ if (MessFlagIsSet(pb->messH,FLAG_RR) && !MessOptIsSet(pb->messH,OPT_RECEIPT)) { UseFeature (featureReturnReceiptTo); if (PrefIsSet(PREF_RRT)) { if (sErr=ComposeRTrans(pb->stream,RRT_FMT,GetReturnAddr(scratch,true),NewLine)) return(sErr); } if (sErr=ComposeRTrans(pb->stream,MIME_P_FMT,InterestHeadStrn+hMDN,GetReturnAddr(scratch,true),NewLine)) return(sErr); } /* * Bulk? */ if (MessOptIsSet(pb->messH,OPT_BULK)) if (sErr=ComposeRTrans(pb->stream,IMPORTANCE_FMT,TOCHeaderStrn+tchPrecedence,BULK,NewLine)) return(sErr); /* * extra static headers */ if (GetResource_('STR#',EX_HEADERS_STRN)) { for (header=1;;header++) { if (*GetRString(buffer,EX_HEADERS_STRN+header)) { if (sErr=SendTrans(pb->stream,buffer+1,*buffer,NewLine+1,*NewLine,nil)) return(sErr); } else break; } } /* * Registration headers */ if (MessOptIsSet(pb->messH,OPT_SEND_REGINFO)) { UserStateType state = GetNagState (); BufferSendRelease(pb->stream); GetRegFirst(state, scratch); GetRString(buffer, RegCodeHeadStrn+hRegFirst); buffer[++buffer[0]] = ':'; SendPtrHead(pb->stream, buffer+1, *buffer, scratch+1, *scratch, (pb->flags&FLAG_CAN_ENC)!=0, tid); GetRegLast(state, scratch); GetRString(buffer, RegCodeHeadStrn+hRegLast); buffer[++buffer[0]] = ':'; SendPtrHead(pb->stream, buffer+1, *buffer, scratch+1, *scratch, (pb->flags&FLAG_CAN_ENC)!=0, tid); GetRegCode(state, scratch); GetRString(buffer, RegCodeHeadStrn+hRegCode); buffer[++buffer[0]] = ':'; SendPtrHead(pb->stream, buffer+1, *buffer, scratch+1, *scratch, (pb->flags&FLAG_CAN_ENC)!=0, tid); BSCLOSE(pb->stream,nil); } /* * real headers */ // priority priority = SumOf(pb->messH)->priority; priority = Prior2Display(priority); if (priority!=3) { if (!PrefIsSet(PREF_SUP_PRIORITY)) { PriorityHeader(buffer,priority); if (sErr=SendTrans(pb->stream,buffer+1,*buffer,NewLine+1,*NewLine,nil)) return(sErr); } if (PrefIsSet(PREF_GEN_IMPORTANCE)) { ComposeRString(buffer,IMPORTANCE_FMT,TOCHeaderStrn+tchImportance,ImportanceOutStrn+priority,NewLine); if (SendPString(pb->stream,buffer)) return(600); } } // date BuildDateHeader(buffer,SumOf(pb->messH)->seconds); if (*buffer && SendTrans(pb->stream,buffer+1,*buffer,NewLine+1,*NewLine,nil)) return(600); BufferSendRelease(pb->stream); //finally what the user thinks of as the headers... for (header=1;headerstream,pb->messH,header,(pb->flags&FLAG_CAN_ENC)!=0,tid)) return(sErr); BSCLOSE(pb->stream,nil); done: return(sErr/100 != 2 ? sErr : 0); } /************************************************************************ * TransmitMultiHeaders - transmit the headers for multipart/something ************************************************************************/ OSErr TransmitMultiHeaders(TransmitPBPtr pb,short subType,PStr boundary,short otherParam,PStr otherVal) { OSErr err; Str255 scratch; MessHandle messH = pb->messH; UHandle headerContent = nil; // The multipart header if (err = ComposeRTrans(pb->stream,MIME_MP_FMT,InterestHeadStrn+hContentType, MIME_MULTIPART,subType, AttributeStrn+aBoundary,boundary,NewLine)) return(err); if (otherParam && (err = ComposeRTrans(pb->stream,MIME_CT_ANNOTATE,otherParam,otherVal,NewLine))) return(err); if (!GetRHeaderAnywhere(messH,PLUGIN_INFO,&headerContent)) { MakePStr(scratch,*headerContent+2,GetHandleSize(headerContent)-2); // 2 adjusts for colon-space ZapHandle(headerContent); if (err = ComposeRTrans(pb->stream,MIME_CT_ANNOTATE,PLUGIN_INFO,scratch,NewLine)) return err; } // Let the translators know if (pb->tlMIME) { AddTLMIME(*pb->tlMIME,TLMIME_TYPE,GetRString(scratch,MIME_MULTIPART),nil); AddTLMIME(*pb->tlMIME,TLMIME_SUBTYPE,GetRString(scratch,subType),nil); AddTLMIME(*pb->tlMIME,TLMIME_PARAM,GetRString(scratch,AttributeStrn+aBoundary),boundary); } return(noErr); } /************************************************************************ * TransmitMessageBodyHeaders - send the header for the body ************************************************************************/ OSErr TransmitMessageBodyHeaders(TransmitPBPtr pb,Boolean withSignature,Boolean topLevel) { MessHandle messH = pb->messH; // keep macros happy UHandle text; UHandle sig; OSErr err; if (pb->strip) { PETEGetRawText(PETE,TheBody,&text); sig = withSignature ? eSignature : nil; err = SendContentType(pb->stream,text,BodyOffset(text),sig,0,SumOf(messH)->tableId,&pb->flags,&pb->opts,nil,topLevel?pb->tlMIME:nil,nil); } else { sig = pb->html ? HTMLSignature : RichSignature; sig = withSignature ? eSignature : nil; err = SendContentType(pb->stream,pb->enriched.data,0,sig,0,SumOf(messH)->tableId,&pb->flags,&pb->opts,nil,topLevel?pb->tlMIME:nil,nil); } pb->encoder = (pb->receipt || 0==(pb->flags&FLAG_ENCBOD)) ? nil : QPEncoder; return(noErr); } /************************************************************************ * TransmitMessageBody - send the message's body ************************************************************************/ OSErr TransmitMessageBody(TransmitPBPtr pb,Boolean withClosure) { OSErr sErr = noErr; UHandle body; if (pb->strip) { // send the plain text PETEGetRawText(PETE,(*pb->messH)->bodyPTE,&body); sErr = SendBodyLines(pb->stream,body,pb->hs.stop,pb->hs.value,pb->flags,True,nil,0,False,pb->encoder); } else { if (pb->html) { // gotta close out the html? if (withClosure) if (sErr=HTMLPostamble(&pb->enriched,False)) return(sErr); // send the html sErr = SendBodyLines(pb->stream,pb->enriched.data,pb->enriched.offset,0,pb->flags,True,nil,0,False,pb->encoder); } else if (pb->rich) { // send the enriched sErr = SendEnriched(pb->stream,pb->enriched.data,pb->encoder); } // done with that... pb->enriched.offset = 0; AccuTrim(&pb->enriched); } BSCLOSE(pb->stream,pb->encoder); if (withClosure) pb->encoder = nil; done: return(sErr); } /************************************************************************ * TransmitMessageSig - transmit the sig as its own part, possibly bloated ************************************************************************/ OSErr TransmitMessageSig(TransmitPBPtr pb) { OSErr err; if (pb->bloat && SigStyled && !pb->strip && (pb->rich||pb->html)) err = TransmitMessageSigBloat(pb); else if (SigStyled && !pb->strip) err = TransmitMessageSigBody(pb,true); else err = TransmitMessageSigBodyPlain(pb); return(err); } /************************************************************************ * TransmitMessageSigBodyPlain - send the sig, forcing to plain ************************************************************************/ OSErr TransmitMessageSigBodyPlain(TransmitPBPtr pb) { OSErr err; // save off some stuff Boolean oldStrip = pb->strip; // save the old strip value long oldOpts = pb->opts; long oldFlags = pb->flags; Boolean oldSigStyled = SigStyled; // we be strippin' pb->strip = true; pb->opts |= OPT_STRIP; SigStyled = false; // Send the darn thing err = TransmitMessageSigBody(pb,true); // put stuff back pb->strip = oldStrip; pb->opts = oldOpts; pb->flags = oldFlags; SigStyled = oldSigStyled; return(err); } /************************************************************************ * TransmitMessageSigBloat - Send the message's signature, possibly with m/a ************************************************************************/ OSErr TransmitMessageSigBloat(TransmitPBPtr pb) { OSErr err; Str127 boundary; Boolean oldFlat = Flatten!=nil; // we'll need a boundary BuildBoundary(pb->messH,boundary,"\pma"); // send the part headers, header/body separator, and initial boundary if (err=TransmitMultiHeaders(pb,MIME_ALTERNATIVE,boundary,0,nil)) return(err); // now the header/body separator and the initial boundary if (err=SendPString(pb->stream,NewLine)) return(err); if (err=SendBoundary(pb->stream)) return(err); // send the plain version pb->flags |= FLAG_WRAP_OUT; // force wrapping on plain part if (!Flatten) Flatten = GetFlatten(); // force flattening if (err=TransmitMessageSigBodyPlain(pb)) return(err); if (!oldFlat) ZapPtr(Flatten); // now the mid boundary if (err=SendBoundary(pb->stream)) return(err); // send the rich version if (err=TransmitMessageSigBody(pb,true)) return(err); // and the final boundary PCat(boundary,"\p--"); if (err=SendBoundary(pb->stream)) return(err); return(noErr); } /************************************************************************ * TransmitMessageSigBody - Send the message's signature ************************************************************************/ OSErr TransmitMessageSigBody(TransmitPBPtr pb,Boolean withHeaders) { Handle sigTemp=nil; Str63 scratch; // which sig do we want? if (!SigStyled&&withHeaders || pb->strip || !(pb->rich||pb->html)) sigTemp = eSignature; else if (pb->html) sigTemp = HTMLSignature; else if (pb->rich) sigTemp = RichSignature; // copy the sig if (sErr = MyHandToHand(&sigTemp)) {sigTemp = nil; goto done;} // send headers if we must if (withHeaders) { if (sErr = SendContentType(pb->stream,sigTemp,0,nil,0,SumOf(pb->messH)->tableId,&pb->flags,&pb->opts,nil,nil,nil)) goto done; pb->encoder = (pb->receipt || 0==(pb->flags&FLAG_ENCBOD)) ? nil : QPEncoder; // header/body separator if (withHeaders) if (sErr=SendPString(pb->stream,NewLine)) goto done; // preamble if html if (!pb->strip && pb->html) { // generate the preamble AccuZap(pb->enriched); if (sErr = AccuInit(&pb->enriched)) goto done; sErr = HTMLPreamble(&pb->enriched,GetRString(scratch,SIGNATURE),0,False); // add the signature to it if (!sErr) sErr = AccuAddHandle(&pb->enriched,sigTemp); if (sErr) goto done; AccuTrim(&pb->enriched); // now put the accumulator handle in sigTemp and zero the accumulator ZapHandle(sigTemp); sigTemp = pb->enriched.data; Zero(pb->enriched); } } // and finally, send it if (sErr=SendBodyLines(pb->stream,sigTemp,GetHandleSize_(sigTemp),0,pb->flags,True,nil,0,False,pb->encoder)) goto done; BSCLOSE(pb->stream,pb->encoder); pb->encoder = nil; done: ZapHandle(sigTemp); return(sErr); } /********************************************************************** * SendEnriched - send a block of text as text/enriched **********************************************************************/ OSErr SendEnriched(TransStream stream,UHandle text,DecoderFunc *encoder) { UPtr start, space, spot, end; long soft = GetRLong(ENRICHED_SOFT_LINE); Boolean wasNl=True; long lastLen, lastC; short nofill=0; Str31 dir; LDRef(text); end = *text + GetHandleSize(text); for (start=*text;startstart && spot-start>soft) break; } if (!nofill && spot-start>soft && space>start) spot = space; if (!UUPCOut && *start=='.') BS(stream,encoder,".",1); BS(stream,encoder,start,spot-start); if (*start=='<' && spot[-1]=='>') { if (start[1]=='/') MakePStr(dir,start+2,spot-start-3); else MakePStr(dir,start+1,spot-start-2); if (EqualStrRes(dir,EnrichedStrn+enNoFill)) { if (start[1]=='/') nofill = MAX(nofill-1,0); else nofill++; } } BS(stream,encoder,NewLine+1,*NewLine); if (space>start && spot==space && spot 399) SMTPCmdError(data,"",buffer); if (IsAddrErr(sErr)) { if (*buffer) buffer[strlen(buffer)-1] = 0; AddOutgoingMesgError((*messH)->sumNum,(*(*messH)->tocH)->sums[(*messH)->sumNum].uidHash, sErr, BAD_XMIT_ERR_TEXT,"",buffer); } } return(sErr/100 != 2 ? sErr : (sErr=0)); } /************************************************************************ * SendHeaderLine - send a line of header information to sendmail ************************************************************************/ int SendHeaderLine(TransStream stream,MessHandle messH,short header,Boolean allowQP,short tid) { Str63 note; Str63 label; HeadSpec hs; if (!CompHeadFind(messH,header,&hs)) { if (header!=TO_HEAD) return(noErr); else // something wrong. We didn't find the header we should have. This message is toast return fnfErr; } if (hs.stop==hs.value) { if (header==TO_HEAD && *GetRString(note,BCC_ONLY)) { GetRString(label,HEADER_STRN+TO_HEAD); return(sErr=SendPtrHead(stream,label+1,*label,note+1,*note,allowQP,tid)); } else return(noErr); } /* * is it an address header? */ else if (IsAddressHead(header)) sErr = SendAddressHead(stream,TheBody,&hs,allowQP,tid); else sErr = SendNormalHead(stream,TheBody,&hs,allowQP,tid); done: return(sErr); } /************************************************************************ * SendAddressHead - send an address header ************************************************************************/ OSErr SendAddressHead(TransStream stream,PETEHandle pte,HSPtr hs, Boolean allowQP,short tid) { UPtr start; int lineLimit = GetRLong(WRAP_SPOT)-2; UHandle safe=nil; Handle fix = nil; Boolean high, wasHigh; Boolean first = True; short lastLen, lastC; UHandle addresses=nil; UHandle rawAddresses; short inGroup = 0; UHandle text; short charsOnLine = 0; OSErr err; Boolean popSend = PrefIsSet(PREF_POP_SEND); Boolean wasGroup = False; Str31 dontHide; PETEGetRawText(PETE,pte,&text); GetRString(dontHide,GROUP_DONT_HIDE); /* * start by sending the label */ BS(stream,nil,LDRef(text)+hs->start,hs->value-hs->start-1); charsOnLine = hs->value-hs->start; SuckPtrAddresses(&rawAddresses,*text+hs->value,hs->stop-hs->value,True,True,False,nil); UL(text); if (rawAddresses && **rawAddresses) { err = ExpandAliases(&addresses,rawAddresses,0,True); ZapHandle(rawAddresses); if (err) return(err); /* * now we have the fully-expanded address list */ if (addresses) { for (start=LDRef(addresses); *start; start += *start+2) { //Folder Carbon Copy - do no support FCC in Light if (HasFeature (featureFcc)) { if (IsFCCAddr(start)) continue; if (IsNewsgroupAddr(start)) continue; } if (inGroup && start[1]==';') inGroup--; if (popSend || !inGroup) { if (Flatten) TransLit(start+1,*start,Flatten); high = allowQP && AnyHighBits(start+1,*start); /* * put out a return if we need to start a new line, or a comma if we just * need a separator. If it's the very first address, it goes on the * first line, period. If it's the trailing semicolon, it goes on this line, * period. */ if (first || *start==1 && start[1]==';') first = False; else { if (!wasGroup && start[*start]!=';') {BS(stream,nil,",",1); charsOnLine++;} /* send the comma separator */ if ((charsOnLine && *start+charsOnLine>lineLimit) || /* overflow */ (high || wasHigh)) /* 1522 stuff gets own line always */ { BS(stream,nil,NewLine+1,*NewLine); charsOnLine = 0; } } /* * space */ if (start[*start]!=';') {BS(stream,nil," ",1); charsOnLine++;} /* * hey! we can finally send this! */ wasHigh = high; /* make sure 1522 stuff gets own line next time */ /* * need to use RFC 1522 */ if (high) { fix=Encode1342(start+1,*start,lineLimit,&charsOnLine,NewLine,tid); if (fix) BS(stream,nil,LDRef(fix),GetHandleSize_(fix)); } /* * RFC 1522 not used */ if (!fix) { BS(stream,nil,start+1,*start); charsOnLine += *start; } ZapHandle(fix); } if (wasGroup = start[*start]==':') if (!PFindSub(dontHide,start)) inGroup++; } /* * finish off header with newline */ BS(stream,nil,NewLine+1,*NewLine); } ZapHandle(addresses); } else ZapHandle(rawAddresses); done: ZapHandle(fix); return(sErr); } /************************************************************************ * SendNormalHead - send a normal header ************************************************************************/ OSErr SendNormalHead(TransStream stream,PETEHandle pte,HSPtr hs, Boolean allowQP,short tid) { UHandle text; Str15 kiran; if (hs->index == SUBJ_HEAD && *GetRString(kiran,JUST_FOR_KIRAN)) return(SendSubjectHead(stream,pte,hs,allowQP,tid)); PETEGetRawText(PETE,pte,&text); LDRef(text); sErr = SendPtrHead(stream,*text+hs->start,hs->value-hs->start-1,*text+hs->value,hs->stop-hs->value,allowQP,tid); UL(text); return(sErr); } /************************************************************************ * SendSubjectHead - send the subject header ************************************************************************/ OSErr SendSubjectHead(TransStream stream,PETEHandle pte,HSPtr hs, Boolean allowQP,short tid) { UHandle text; Str15 kiran; long offset; long len; long stop; long colon; long k; GetRString(kiran,JUST_FOR_KIRAN); PETEGetRawText(PETE,pte,&text); LDRef(text); len = hs->stop; offset = hs->start; do { // find delimitter k = SearchStrPtr(kiran,*text,offset,len,true,false,nil); // if not found, pretend at end stop = k < 0 ? len : k; if (k>=0) (*text)[k] = '\015'; // replace first delim with newline 'cuz sendptrhead expects it // find colon colon = SearchPtrPtr(":",1,*text,offset,stop,true,false,nil); if (colon<0) sErr = SendPtrHead(stream," ",1,*text+offset,stop-offset,allowQP,tid); else sErr = SendPtrHead(stream,*text+offset,colon-offset+1,*text+colon+2,stop-colon-2,allowQP,tid); // skip delimitter offset = stop + *kiran; if (k>=0) (*text)[k] = kiran[1]; // put char back. } while (stopstart && IsWhite(stop[-1])) { while (stop>start && IsWhite(stop[-1])) --stop; bodyLen = stop-start; start[bodyLen] = '\015'; // pretend we have newline there } if (Flatten) TransLit(start,bodyLen,Flatten); /* yes, tromps on in-memory copy; doesn't matter, will get pitched at close anyway */ if (allowQP && AnyHighBits(start,stop-start)) { if (fix=Encode1342(start,stop-start,lineLimit,nil,NewLine,tid)) { start = LDRef(fix); BS(stream,nil," ",1); BS(stream,nil,start,GetHandleSize_(fix)); start = stop; } } for (;startstart && end >= limit && limit2) MakePStr(label,labelStart,labelStop-labelStart-1); else *label = 0; /* * find the newline */ start=stop+1; if (*start==' ') start++; } for (stop=start;stopdata),newsGroupAcc->offset-1,false,tid)); } /************************************************************************ * SendXSender - construct and send the X-Sender header ************************************************************************/ short SendXSender(TransStream stream,MessHandle messH) { Handle popCanon=nil,returnCanon=nil; Str255 buffer; Str255 from; short err = 0; if ((*CurPers)->popSecure) { CompHeadGetStr(messH,FROM_HEAD,from); /* figure out what the return addr means */ SuckPtrAddresses(&returnCanon,from+1,*from,False,True,False,nil); /* grab the POP account, and figure out what it means */ GetPOPPref(buffer); SuckPtrAddresses(&popCanon,buffer+1,*buffer,False,True,False,nil); } /* if different or no password, send Sender field */ if (!UUPCOut && (!(*CurPers)->popSecure || !popCanon || !returnCanon || !StringSame(LDRef(popCanon),LDRef(returnCanon)))) { if (!(*CurPers)->popSecure) GetRString(buffer,UNVERIFIED); else *buffer = 0; err = ComposeRTrans(stream,XSENDER_FMT,UUPCIn ? 0 : GetPOPPref(from),buffer,NewLine); } ZapHandle(popCanon); ZapHandle(returnCanon); return(err); } /************************************************************************ * SendBodyLines - send the actual body of the message * Don't look at this; it's a mess. * text Handle to the text to send * length length of same * offset offset at which to begin * flags message flags * forceLines should I force a newline at the end of the text? * lineStarts pointer to array for determining wrap (may be nil) * nLines length of same * partial should I listen to the partial information? ************************************************************************/ int SendBodyLines(TransStream stream,UHandle text,long length,long offset,long flags,Boolean forceLines,short *lineStarts,short nLines,Boolean partial,DecoderFunc *encoder) { UPtr start; /* the beginning of the text left to be sent */ UPtr stop; /* the end of the entire text block */ UPtr end; /* one past the last character of a line of text to be sent for complete lines, this will be a return */ UPtr space; /* the last space before end */ int lineLimit; /* # of chars at which to wrap */ int hardLimit; /* limit for hard returns; don't wrap if para < hardLimit */ static short quoteLevel; /* the # of quote chars at start of line */ Byte suspendChar; /* the quote character */ static short partialSize; /* the size of the last line output, if it was an incomplete line */ static Boolean softNewline; /* was the last newline added by us? */ Str31 scratch; short i; UPtr nl; Boolean doWrap = 0!=(flags&FLAG_WRAP_OUT); short lastC, lastLen; short maxQuote = GetRLong(MAX_QUOTE); short curLen; short tab = GetRLong(TAB_DISTANCE); Boolean flowed = doWrap && UseFlowOut; Boolean withSpace; int smtpMaxLen = GetRLong(MAX_SMTP_LINE); Str15 spaceNewLine; if (flowed) { *spaceNewLine = 0; PCatC(spaceNewLine,' '); PCat(spaceNewLine,NewLine); } if (!partial) { softNewline=False; /* the caller has told us not to */ partialSize = 0; /* bother with partial processing */ } start = LDRef(text)+offset; stop = *text + length; /* * gather up important info for wrap calculations */ if (doWrap) { suspendChar = (GetRString(scratch,UseFlowOut ? FLOWED_QUOTE:QUOTE_PREFIX))[1]; withSpace = UseFlowOut ? scratch[0]>1 : false; lineLimit = GetRLong(UseFlowOut ? (encoder?ENCODED_FLOWED_WRAP_SPOT:FLOW_WRAP_SPOT):WRAP_SPOT); hardLimit = GetRLong(UseFlowOut ? FLOW_WRAP_THRESH:WRAP_THRESH); /* * if this is a new line, count the quote level */ if (!partialSize) { for (end = start;end maxQuote) quoteLevel = 0; } } else lineLimit = REAL_BIG; /* * main loop; loop through the buffer, sending one line at a time */ for (; start')) space=nl; if (*nl=='\t') curLen = tab*((curLen+tab)/tab); else curLen++; } // adjust for lines ending in whitespace if (nlstart;end--) { if (*end==' ') curLen--; else if (*end=='\t') curLen = tab*((curLen-tab)/tab); else break; } } end = nl; if (curLen>lineLimit) /* we went over the wrap limit */ { if (space>start) /* and we found a space */ end = space; /* Wrap it! */ else if (curLen>smtpMaxLen) end = start+smtpMaxLen; } } else { for (nl=start;nlwrap limit but < 80 */ if (!softNewline && endstart && ((void*)SendTrans)!=((void*)WrapSendTrans)) { /* escape initial periods, if need be */ if (!UUPCOut && *start=='.') BS(stream,encoder,".",1); /* if doing f=f, space-stuff "From " */ else if (flowed && *start=='F' && end-start>5 && *(uLong*)(start+1)=='rom ') BS(stream,encoder," ",1); } /* * find last non-space character on line, * unless it's the end of the buffer, or not being wrapped, * in which case we'd best not drop spaces */ space = end; // special case for "-- " if (space-start==3 && start[0]==start[1] && start[0]=='-' && start[2]==' ') ; // leave it alone else if (doWrap && endstart && (space[-1]=='\t'||space[-1]==' ')) space--; /* if there is data to send, send it */ if (space>start) { // Ok, we might need to insert an extra space if doing f=f if (flowed && !partialSize && quoteLevel && space-start>quoteLevel && start[quoteLevel]==' ' && ((void*)SendTrans)!=((void*)WrapSendTrans)) { BS(stream,encoder,start,quoteLevel); BS(stream,encoder," ",1); BS(stream,encoder,start+quoteLevel,space-start-quoteLevel); } else { /* if doing f=f, space-stuff initial space */ if (!partialSize && flowed && *start==' ') BS(stream,encoder," ",1); /* Now send the line */ BS(stream,encoder,start,space-start); } } /* * send the newline, unless we've run out of characters and so don't know * if this should be a complete line or not */ if (forceLines || end maxQuote) quoteLevel = 0; } else /* if we wrapped it, prequote the next line */ { for (i=0;i') BS(stream,encoder," ",1); // space-stuff initial > if (quoteLevel && withSpace) {BS(stream,encoder," ",1);partialSize++;} } /* * normally, end points at a newline (for complete lines) or space * (for wrapped ones). So, we normally skip the character end points * to. However, long solid lines or Rong-wrapped lines might not obey * this behavior; adjust end back by one to make up for the increment * we'll do in just a few cycles... */ if (endwin),buff); // ByteProgress(buff,0,CountCompBytes(messH)); Progress(NoChange,NoChange,nil,nil,buff); } /************************************************************************ * WannaSend - find out of the user wants to send a dirty window ************************************************************************/ int WannaSend(MyWindowPtr win) { Str255 title; MyGetWTitle(GetMyWindowWindowPtr(win),title); return(AlertStr(WANNA_SEND_ALRT,Stop,title)); } /************************************************************************ * SendAttachments - send the files the user has attached to his message. ************************************************************************/ int SendAttachments(TransStream stream,MessHandle messH,long flags,PStr boundary,short tableID,short idBase) { FSSpec spec; short index; short err=noErr; Boolean plainText = 0==(flags & FLAG_BX_TEXT); Boolean canQP = 0!=(flags & FLAG_CAN_ENC); Boolean isUU; CInfoPBRec hfi; Str31 name; short aType; aType = AttachOptNumber(flags); isUU = aType+1==atmUU; hfi.hFileInfo.ioNamePtr = name; for (index=1;!err;index++) { if (err=GetIndAttachment(messH,index,&spec,nil)) if (err==1) break; else return(FileSystemError(BINHEX_OPEN,spec.name,err)); IsAlias(&spec,&spec); if (FSpIsItAFolder(&spec)) err = SendAttachmentFolder(stream,messH,flags,canQP,plainText,tableID,boundary,&spec,0,idBase+index-1,&idBase,&hfi); else err = SendAnAttachment(stream,messH,flags,canQP,plainText,tableID,boundary,&spec,0,idBase+index-1); } if (index-1) UpdateNumStat(kStatSentAttach,index-1); if (err==1) err = noErr; return(err); } /************************************************************************ * SendRelatedParts - send the files the user has put in his html ************************************************************************/ int SendRelatedParts(TransStream stream,MessHandle messH,long flags,StackHandle stack,PStr boundary) { FSSpec origSpec,spec; short index; short err=noErr; for (index=(*stack)->elCount;!err && index--;) { if (err = StackItem(&origSpec,index,stack)) err = 1; else { IsAlias(&origSpec,&spec); ConvertPictPart(&origSpec,&spec); if (err=SendAnAttachment(stream,messH,flags,true,true,0,boundary,&spec,1,(*stack)->elCount-index-1)) break; } } if (err==1) err = noErr; return(err); } /************************************************************************ * ConvertPictPart - convert any PICT HTML parts to something more universal ************************************************************************/ void ConvertPictPart(FSSpecPtr origSpec,FSSpecPtr spec) { static OSType exportType; Str255 scratch; Str31 token; GraphicsExportComponent exCI = nil; OSErr err; if (PrefIsSet(PREF_NO_PICT_CONVERSION) || // User doesn't want conversion exportType == -1 || // Exporter not found #if TARGET_RT_MAC_CFM !HaveQuickTime(0x0400) || !GraphicsExportDoExport || // Make sure we have version 4 or greater and QT library #else !HaveQuickTime(0x0400) || #endif FileTypeOf(spec) != 'PICT') return; // Don't convert this one if (!exportType) { // Find best exporter if (GetRString(scratch,EXPORT_PICT_LIST)) { OSType thisType; UPtr spot; for(spot=scratch+1;PToken(scratch,token,&spot,",");) { if (*token==sizeof(thisType)) { BMD(token+1,&thisType,sizeof(thisType)); if (exCI = OpenDefaultComponent(GraphicsExporterComponentType,thisType)) { // Found one! exportType = thisType; break; } } } } if (!exportType) { // Exporter not found exportType = -1; return; } } else exCI = OpenDefaultComponent(GraphicsExporterComponentType,exportType); if (exCI) { // Do conversion GraphicsImportComponent imCI; if (!GetGraphicsImporterForFile(spec,&imCI)) { FSSpec tempSpec; tempSpec = *origSpec; UniqueSpec(&tempSpec,31); GraphicsExportSetOutputFile(exCI,&tempSpec); GraphicsExportSetInputGraphicsImporter(exCI,imCI); if (exportType == 'PNGf') { // Don't allow alpha channel. QuickTime PNG exporter messes up alpha channel with // PICT vector images rendering them invisible when not viewing with QuickTime GraphicsExportSetDepth (exCI,24); } err = GraphicsExportDoExport(exCI,nil); CloseComponent(imCI); CloseComponent(exCI); if (!err) { // Replace old PICT file (or alias) FSpDelete(origSpec); FSpRename(&tempSpec,origSpec->name); *spec = *origSpec; } } else CloseComponent(exCI); } } /************************************************************************ * SendAttachmentFolder - send a folder full of files ************************************************************************/ int SendAttachmentFolder(TransStream stream,MessHandle messH,long flags,Boolean canQP,Boolean plainText,short tableID,PStr boundary,FSSpecPtr folderSpec,short multiID,short partID,short *partBase,CInfoPBRec *hfi) { short err = noErr; short index; FSSpec spec; Str127 ourBoundary; long dirId; // start by sending a boundary for the outer multipart if (err=SendBoundary(stream)) return err; // and a content-id, why not? if (err=SendCID(stream,messH,multiID,partID)) return(err); // now, let's compose our boundary NumToString(partID,spec.name); BuildBoundary(nil,ourBoundary,spec.name); /* * send the multipart header */ if (err = ComposeRTrans(stream,MIME_MP_FMT, InterestHeadStrn+hContentType, MIME_MULTIPART, MIME_X_FOLDER, AttributeStrn+aBoundary, ourBoundary, NewLine)) return(err); /* * content-disposition */ if (!err) err = ComposeRTrans(stream,MIME_CD_FMT, InterestHeadStrn+hContentDisposition, ATTACHMENT, AttributeStrn+aFilename, folderSpec->name, NewLine); // header/body separator if (err=SendPString(stream,NewLine)) return(err); // get our dirID spec.vRefNum = folderSpec->vRefNum; spec.parID = dirId = SpecDirId(folderSpec); if (!spec.parID) return fnfErr; // setup our iterator hfi->hFileInfo.ioNamePtr = spec.name; hfi->hFileInfo.ioFDirIndex = 0; index = 0; // loop through the contents while (!DirIterate(spec.vRefNum,spec.parID,hfi)) { if (partBase) ++*partBase; partID++; if (HFIIsFolder(hfi)) { index = hfi->hFileInfo.ioFDirIndex; // save the index err = SendAttachmentFolder(stream,messH,flags,canQP,plainText,tableID,ourBoundary,&spec,multiID,partID,partBase,hfi); hfi->hFileInfo.ioNamePtr = spec.name; hfi->hFileInfo.ioFDirIndex = index; // restore the index spec.parID = dirId; } else err = SendAnAttachment(stream,messH,flags,canQP,plainText,tableID,ourBoundary,&spec,multiID,partID); if (err) break; } // we need to send our final boundary if (!err) err = SendTrans(stream,"--",2,ourBoundary+1,*ourBoundary,"--",2,NewLine+1,*NewLine,nil); return err; } /************************************************************************ * SendAnAttachment - send a single file ************************************************************************/ int SendAnAttachment(TransStream stream,MessHandle messH,long flags,Boolean canQP,Boolean plainText,short tableID,PStr boundary,FSSpecPtr spec,short multiID,short partID) { short err=noErr; Boolean isUU; CInfoPBRec hfi; Str255 s; Str31 name; short aType; AttMap am; uLong oldTicks; Boolean flat = false; FSSpec local; Boolean noRFork; aType = AttachOptNumber(flags); isUU = aType+1==atmUU; hfi.hFileInfo.ioNamePtr = name; if (err=FSpGetHFileInfo(spec,&hfi)) return(FileSystemError(BINHEX_OPEN,spec->name,err)); ComposeRString(s,BINHEX_PROG_FMT,spec->name); Progress(NoChange,NoChange,nil,nil,s); noRFork = hfi.hFileInfo.ioFlRLgLen==0; oldTicks = TickCount(); if (err=SendBoundary(stream)) return(err); if (err=SendCID(stream,messH,multiID,partID)) return(err); if (err=FindAttMap(spec,&am)) return(err); if (am.mm.specialId=='flat') { local = *spec; if (!FlattenAndSpool(&local)) { spec = &local; // send the spooled copy flat = true; } am.mm.specialId = nil; // special processing done } #ifdef TWO if (plainText && am.mm.specialId && am.mm.specialId != ' ' || am.mm.specialId=='MiME') err = SendSpecial(stream,spec,&am); else #endif err = kSpecialSendDidntPanOut; if (err != kSpecialSendDidntPanOut) ; #ifdef TWO else if (plainText && IsMailbox(spec) && hfi.hFileInfo.ioFlLgLen) err = SendDigest(stream,spec); #endif else if (plainText && (am.isPostScript&&!canQP || EqualStrRes(am.mm.mimetype,MIME_TEXT) && am.isBasic)) err = SendPlain(stream,spec,flags,tableID,&am); else if (!isUU && plainText && (am.isBasic || noRFork)) err = SendDataFork(stream,spec,flags,tableID,&am); else { switch(aType+1) { case atmDouble: err = SendDouble(stream,spec,flags,tableID,&am); break; case atmSingle: err = SendSingle(stream,spec,True,&am); break; #ifdef TWO case atmUU: err = SendUU(stream,spec,&am); break; #endif default: err = SendBinHex(stream,spec,&am); break; } } // if (!err) {Progress(100,NoBar,nil,nil,nil);PopProgress(False);} { long rate = (600*(hfi.hFileInfo.ioFlLgLen+hfi.hFileInfo.ioFlRLgLen))/((TickCount()-oldTicks+1)*1024); ComposeLogS(LOG_TPUT,nil,"\p%p: %d %d.%d KBps",spec->name,aType,rate/10,rate%10); } if (flat) FSpDelete(spec); return(err); } /************************************************************************ * SendCID - send a content-id ************************************************************************/ OSErr SendCID(TransStream stream,MessHandle messH,long part,short n) { Str255 mid, cid; OSErr err=noErr; if (*CompGetMID(messH,mid)) { // compose BuildContentID(cid,mid,part,n); // send err = ComposeRTrans(stream,CID_SEND_FMT,InterestHeadStrn+hContentId,cid,NewLine); } return(err); } /************************************************************************ * BuildContentID - build a content-id, without <>'s or header or newline ************************************************************************/ PStr BuildContentID(PStr into,PStr mid,long part,short i) { return(ComposeRString(into,CID_ONLY_FMT,mid,part,i)); } #ifdef TWO /********************************************************************** * SendDigest - send a mailbox, as a digest **********************************************************************/ OSErr SendDigest(TransStream stream, FSSpecPtr spec) { TOCHandle tocH = TOCBySpec(spec); Str255 boundary; Str63 date; short i; MyWindowPtr win; Boolean newWin; if (!tocH) return(1); /* * build the boundary */ BuildBoundary(nil,boundary,"\pd"); sErr = ComposeRTrans(stream,MIME_MP_FMT, InterestHeadStrn+hContentType, MIME_MULTIPART, MIME_DIGEST, AttributeStrn+aBoundary, boundary, NewLine); if (!sErr) sErr = ComposeRTrans(stream,MIME_CD_FMT, InterestHeadStrn+hContentDisposition, ATTACHMENT, AttributeStrn+aFilename,spec->name, NewLine); if (!sErr && *R822Date(date,AFSpGetMod(spec)-ZoneSecs())) sErr = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aModDate,date,NewLine); if (!sErr) sErr=SendPString(stream,NewLine); if (!sErr) for (i=0;i<(*tocH)->count;i++) { UHandle tSig, tRSig, tHSig; tSig = eSignature; tRSig = RichSignature; tHSig = HTMLSignature; eSignature = nil; RichSignature = nil; HTMLSignature = nil; /* * send a boundary */ if (sErr=SendBoundary(stream)) break; win = GetAMessageLo(tocH, i, nil, nil, false, &newWin); if(win) { WindowPtr winWP = GetMyWindowWindowPtr(win); sErr = TransmitMessageForSpool(stream, Win2MessH(win)); if(newWin) CloseMyWindow(winWP); else NotUsingWindow(winWP); } else sErr = mFulErr; BSCLOSE(stream,nil); /* unfortunate, but gotta do it because of how SendBoundary works */ eSignature = tSig; RichSignature = tRSig; HTMLSignature = tHSig; } done: FlushTOCs(True,False); /* * and the terminal boundary */ if (!sErr) { PCat(boundary,"\p--"); sErr = SendBoundary(stream); } return(sErr); } /************************************************************************ * SendSpecial - send a special attachment type ************************************************************************/ OSErr SendSpecial(TransStream stream,FSSpecPtr spec,AttMapPtr amp) { OSErr err; switch (amp->mm.specialId) { case 'AURL': err = SendAnonFTP(stream,spec); break; case 'MiME': err = SendRawMIME(stream,spec); break; default: WarnUser(INVALID_MAP,0); err = 1; break; } return(err); } #endif /************************************************************************ * GetFlatten - Copy the flatten table into a pointer ************************************************************************/ UPtr GetFlatten(void) { Handle flatH; UPtr flatten; flatten = NuPtr(256); flatH = GetResource_('taBL',ktFlatten); if (flatH) BMD(*flatH,flatten,256); else ZapPtr(flatten); return(flatten); } #ifdef TWO /************************************************************************ * SendAnonFTP - send an 'AURL' doc as an anonymous ftp thingie ************************************************************************/ OSErr SendAnonFTP(TransStream stream,FSSpecPtr spec) { OSErr err; Handle text; Str127 type, ftp, host, dir, name, token; Str255 data; UPtr spot; short size; if (err=Snarf(spec,&text,254)) FileSystemError(BINHEX_READ,spec->name,err); else { size = GetHandleSize_(text); MakePStr(data,*text,size); /* * parse the string */ spot = data+1; if (PToken(data,type,&spot," ") && PToken(data,ftp,&spot,":")) { while (*spot=='/') spot++; if (PToken(data,host,&spot,"/")) { *name = *dir = 0; while (PToken(data,token,&spot,"/")) { if (dir[*dir] != '/') PCatC(dir,'/'); PCat(dir,token); PCopy(name,token); } if (*name) { *dir -= 1+*name; /* * hey; that all parsed */ if (EqualStrRes(type,ANARCHIE_GET) || EqualStrRes(type,ANARCHIE_TXT)) if (EqualStrRes(ftp,ANARCHIE_FTP) && !PPtrFindSub("\p@",host+1,*host)) { /* * and it is even something we can handle. Hurrah! */ err = ComposeRTrans(stream,MIME_TEXTPLAIN, InterestHeadStrn+hContentType, MIME_MESSAGE,EXTERNAL_BODY,"",NewLine); if (!err) err = ComposeRTrans(stream,NQ_ANNOTATE, AttributeStrn+aAccessType, GetRString(data,ANON_FTP), NewLine); if (!err) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aSite, host, NewLine); if (*dir && !err) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aDirectory, dir, NewLine); if (!err) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aName, name, NewLine); if (!err) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aMode, GetRString(data,EqualStrRes(type,ANARCHIE_TXT)?ASCII:IMAGE), NewLine); if (!err) err = SendPString(stream,NewLine); if (!err) err = ComposeRTrans(stream,MIME_MP_FMT, InterestHeadStrn+hContentType, MIME_APPLICATION,MIME_OCTET_STREAM, AttributeStrn+aName, name, NewLine); if (!err) err = ComposeRTrans(stream,MIME_CD_FMT, InterestHeadStrn+hContentDisposition, ATTACHMENT, AttributeStrn+aFilename,name, NewLine); return(err); } } } } return(-1); } return(err); } /************************************************************************ * SendRawMIME - send a raw MIME document ************************************************************************/ OSErr SendRawMIME(TransStream stream,FSSpecPtr spec) { long size = FSpDFSize(spec); Handle buffer = nil; long bSize; long count; long sendCount; OSErr err = noErr; short refN; DecoderFunc *encoder = UUPCOut ? nil : PeriodEncoder; Boolean needNL = false; // we do not need to add a newline bSize = MIN(size,GetRLong(BUFFER_SIZE)); //Debugger(); if (buffer=NewIOBHandle(size/4,size)) { MoveHHi(buffer); HLock(buffer); if (!(err = FSpOpenDF(spec,fsRdWrPerm,&refN))) { // sniff the end of the file for crlf GetEOF(refN,&count); if (count<2) needNL = true; else { SetFPos(refN,fsFromLEOF,-2); count = 2; ARead(refN,&count,*buffer); needNL = (*buffer)[0]!='\015' || (*buffer)[1]!='\012'; SetFPos(refN,fsFromStart,0); } // send the file while (!err && size>0) { count = MIN(size,bSize); if (!(err=ARead(refN,&count,*buffer))) { if (NewLine[0]==1 && NewLine[1]=='\015') sendCount = RemoveChar('\012',*buffer,count); else if (NewLine[0]==1 && NewLine[1]=='\012') sendCount = RemoveChar('\015',*buffer,count); else sendCount = count; err = BufferSend(stream,encoder,*buffer,sendCount,True); } else FileSystemError(BINHEX_READ,spec->name,err); size -= count; } // if the file didn't end with a newline, add one if (!err && needNL) err = BufferSend(stream,encoder,NewLine+1,*NewLine,True); // send any remainder if (!err) err = BufferSend(stream,nil,nil,0,False); BufferSendRelease(stream); FSClose(refN); } ZapHandle(buffer); } else WarnUser(BINHEX_READ, err = MemError()); return(err); } #endif /************************************************************************ * PeriodEncoder - encode periods ************************************************************************/ OSErr PeriodEncoder(CallType callType,DecoderPBPtr pb) { static short nlState; if (pb) { switch (callType) { case kDecodeInit: nlState = *NewLine; break; case kDecodeDone: break; case kDecodeDispose: break; case kDecodeData: if (pb->inlen) pb->outlen = StuffPeriods(pb->input,pb->inlen,pb->output,NewLine,&nlState); else pb->outlen = 0; break; } } return(noErr); } /************************************************************************ * StuffPeriods - byte-stuff periods for SMTP ************************************************************************/ long StuffPeriods(UPtr in, long inLen, UPtr out, PStr newLine, short *nlStatePtr) { short nlState = *nlStatePtr; UPtr end = in + inLen; short newLineLen = *newLine; UPtr origOut = out; for (end=in+inLen;inisPostScript) { if ((flags&FLAG_CAN_ENC)) { encoder = B64Encoder; flags |= FLAG_ENCBOD; } if (err=MIMEFileHeader(stream,amp,POSTSCRIPT,AFSpGetMod(spec))) goto done; if (err = ComposeRTrans(stream,MIME_V_FMT,InterestHeadStrn+hContentEncoding,encoder ? MIME_BASE64 : MIME_BINARY,NewLine)) goto done; DontTranslate = True; flags &= ~FLAG_WRAP_OUT; } else { flags &= ~FLAG_ENCBOD; /* we may not need to encode this; we'll find out later */ Snarf(spec,&taste,GetRLong(TEXT_QP_TASTE)); if (err = SendContentType(stream,taste,0,nil,0,tableId,&flags,nil,ATT_MAP_NAME(amp),nil,amp->mm.subtype)) goto done; ZapHandle(taste); encoder = flags&FLAG_ENCBOD ? QPEncoder : nil; if (err = ComposeRTrans(stream,MIME_CD_FMT, InterestHeadStrn+hContentDisposition, ATTACHMENT, AttributeStrn+aFilename, ATT_MAP_NAME(amp), NewLine)) goto done; if (*R822Date(scratch,AFSpGetMod(spec)-ZoneSecs()) && (err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aModDate,scratch,NewLine))) goto done; } FSpGetFInfo(spec,&info); if (!err && !amp->suppressXMac) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aMacType,Long2Hex(scratch,info.fdType), NewLine); if (!err && !amp->suppressXMac) err = ComposeRTrans(stream,MIME_CT_ANNOTATE, AttributeStrn+aMacCreator,Long2Hex(scratch,info.fdCreator), NewLine); if (err = SendPString(stream,NewLine)) goto done; err = SendTextFile(stream,spec,flags,encoder); done: DontTranslate = False; BufferSendRelease(stream); return(err); } /********************************************************************** * SendTextFile - send text from a file **********************************************************************/ OSErr SendTextFile(TransStream stream,FSSpecPtr spec,long flags,DecoderFunc *encoder) { short refN=0; UHandle dataBuffer=nil; long dataSize; int err; long fileSize,sendSize,readSize; Boolean partial = False; /* * allocate the buffers */ dataSize = GetRLong(BUFFER_SIZE); if (!(dataBuffer=NuHTempOK(dataSize))) {WarnUser(MEM_ERR,err=MemError()); goto done;} /* * open it */ if (err = FSpOpenDF(spec,fsRdPerm,&refN)) {FileSystemError(BINHEX_OPEN,spec->name,err); goto done;} if (err = GetEOF(refN,&fileSize)) {FileSystemError(BINHEX_OPEN,spec->name,err); goto done;} /* * send it */ for (;fileSize;fileSize-=readSize) { readSize=MIN(dataSize,fileSize); sendSize = readSize; if (err=ARead(refN,&sendSize,LDRef(dataBuffer))) {FileSystemError(BINHEX_READ,spec->name,err); goto done;} UL(dataBuffer); if (err = SendBodyLines(stream,dataBuffer,sendSize,0,flags,False,nil,0,partial,encoder)) goto done; partial = (*dataBuffer)[sendSize-1] != '\015'; } if (!err) err=BufferSend(stream,encoder,nil,0,True); if (!err && partial) SendPString(stream,NewLine); done: DontTranslate = False; BufferSendRelease(stream); if (refN) MyFSClose(refN); if (dataBuffer) ZapHandle(dataBuffer); return(err); } /************************************************************************ * BuildDateHeader - build an RFC 822 date header ************************************************************************/ void BuildDateHeader(UPtr buffer,long seconds) { Str63 date; if (*R822Date(date,seconds)) ComposeRString(buffer,DATE_HEADER,HeaderStrn+DATE_HEAD,date); else *buffer = 0; return; } /************************************************************************ * BuildDateHeader - build an RFC 822 date header ************************************************************************/ PStr R822Date(PStr date,long seconds) { DateTimeRec dtr; long delta = ZoneSecs(); Boolean negative; if (delta==-1) {*date=0;return date;} if (seconds) SecondsToDate(seconds+delta,&dtr); else GetTime(&dtr); if (negative=delta<0) delta *= -1; delta /= 60; /* we want minutes */ return(ComposeRString(date,R822_DATE_FMT, WEEKDAY_STRN+dtr.dayOfWeek, dtr.day, MONTH_STRN+dtr.month, dtr.year, dtr.hour/10, dtr.hour%10, dtr.minute/10, dtr.minute%10, dtr.second/10, dtr.second%10, negative ? '-' : '+', delta/600,(delta%600)/60,(delta%60)/10,delta%10)); } /************************************************************************ * SaveB4Send - grab an outgoing message, saving if necessary ************************************************************************/ MessHandle SaveB4Send(TOCHandle tocH,short sumNum) { short which; MessHandle messH = (MessHandle)(*tocH)->sums[sumNum].messH; if (messH && (*messH)->win->isDirty) { which = WannaSend((*messH)->win); if (which==CANCEL_ITEM || which==WANNA_SAVE_CANCEL) return (nil); else if (which == WANNA_SAVE_SAVE && !SaveComp((*messH)->win)) return(nil); else if (which == WANNA_SAVE_DISCARD) { (*messH)->win->isDirty = False; CloseMyWindow(GetMyWindowWindowPtr((*messH)->win)); messH = nil; } } if (!messH) { MyWindowPtr winResult; MyThreadBeginCritical(); // Make sure OpenComp doesn't switch threads winResult = OpenComp(tocH,sumNum,nil,nil,False,False); MyThreadEndCritical(); if (!winResult) return(nil); messH = (MessHandle)(*tocH)->sums[sumNum].messH; } return(messH); } /************************************************************************ * BuildBoundary - build a boundary line for a message ************************************************************************/ void BuildBoundary(MessHandle messH,PStr boundary,PStr middle) { #pragma unused(messH) ComposeRString(boundary,MIME_BOUND1_FMT,GMTDateTime(),middle,MIME_BOUND2); } /************************************************************************ * SendContentType - deduce and send the appropriate content-type * (and CTE) for two blocks of text (body and signature, typically) * text1 - one block of text (may be nil) * text2 - second block of text (may be nil) * tableID - xlate table id * flags - message flags ************************************************************************/ OSErr SendContentType(TransStream stream,Handle text1, long offset1, Handle text2, long offset2, short tableID,long *flags,long *opts,PStr name,emsMIMEHandle *tlMIME,PStr subtype) { short etid = EffectiveTID(tableID); Str127 scratch; Str63 flowed; #ifdef ETL Str63 s2; #endif Boolean anyfunny; Boolean any2022; short err; short encId; Boolean strip = 0!=(opts && (*opts & OPT_STRIP)); Boolean rich = !strip && 0!=(flags && (*flags & FLAG_RICH)); Boolean html = !strip && 0!=(opts && (*opts & OPT_HTML)); short computedSubType = html?HTMLTagsStrn+htmlTag:(rich?MIME_RICHTEXT:MIME_PLAIN); if (rich || html) *flags &= ~FLAG_WRAP_OUT; anyfunny = !text1 || AnyFunny(text1,offset1) || text2 && AnyFunny(text2,offset2); any2022 = !PrefIsSet(PREF_NO_2022) && (text1 && Any2022(text1,offset1) || text2 && Any2022(text2,offset2)); /* * figure out proper charset */ if (anyfunny) NameCharset(scratch,etid,tlMIME); else if (any2022) NameCharset(scratch,kt2022,tlMIME); else NameCharset(scratch,ktMacUS,tlMIME); if (0!=(*flags&FLAG_WRAP_OUT) && UseFlowOut) { if (tlMIME) AddTLMIME(*tlMIME,TLMIME_PARAM,GetRString(flowed,AttributeStrn+aFormat),GetRString(s2,FORMAT_FLOWED)); ComposeRString(flowed,MIME_CT_ANNOTATE,AttributeStrn+aFormat,GetRString(s2,FORMAT_FLOWED),""); PCat(scratch,flowed); } /* * send the content type */ if (!name) { if (subtype) err = ComposeRTrans(stream,MIME_TEXTNOTPLAIN, InterestHeadStrn+hContentType, MIME_TEXT, subtype, scratch, NewLine); else err = ComposeRTrans(stream,MIME_TEXTPLAIN, InterestHeadStrn+hContentType, MIME_TEXT, computedSubType, scratch, NewLine); #ifdef ETL if (tlMIME) { AddTLMIME(*tlMIME,TLMIME_TYPE,GetRString(scratch,MIME_TEXT),nil); AddTLMIME(*tlMIME,TLMIME_SUBTYPE,subtype?subtype:GetRString(scratch,computedSubType),nil); } #endif } else { PCat(scratch,NewLine); if (subtype) err = ComposeRTrans(stream,MIME_TEXT_SUBTYPE_FMT, InterestHeadStrn+hContentType, MIME_TEXT, subtype, AttributeStrn+aName, name, scratch); else err = ComposeRTrans(stream,MIME_MP_FMT, InterestHeadStrn+hContentType, MIME_TEXT, computedSubType, AttributeStrn+aName, name, scratch); #ifdef ETL if (tlMIME) { AddTLMIME(*tlMIME,TLMIME_TYPE,GetRString(scratch,MIME_TEXT),nil); AddTLMIME(*tlMIME,TLMIME_SUBTYPE,subtype?subtype:GetRString(scratch,computedSubType),nil); } #endif } if (err) return(err); /* * content-transfer-encoding, if any */ if (0==(*flags&FLAG_ENCBOD)) /* set manually? */ *flags = DecideEncoding(text1,text2,anyfunny,etid,*flags); /* determine automatically */ if (0!=(*flags&FLAG_ENCBOD)) { if ((*flags&FLAG_CAN_ENC) && (UUPCOut || (Ehlo && !(*Ehlo)->mime8bit) || !PrefIsSet(PREF_ALLOW_8BITMIME))) encId = MIME_QP; else { encId = MIME_8BIT; *flags &= ~FLAG_ENCBOD; } if (err = ComposeRTrans(stream,MIME_V_FMT, InterestHeadStrn+hContentEncoding, encId, NewLine)) return(err); #ifdef ETL if (tlMIME) { AddTLMIME(*tlMIME,TLMIME_TYPE,GetRString(scratch,MIME_TEXT),nil); AddTLMIME(*tlMIME,TLMIME_PARAM,GetRString(scratch,MIME_CTE),GetRString(s2,encId)); } #endif } return(noErr); } /************************************************************************ * DecideEncoding - decide which encoding (if any) to use ************************************************************************/ long DecideEncoding(Handle text1, Handle text2,Boolean anyfunny,short etid,long flags) { if (etid==ktMacUS) anyfunny = False; if (anyfunny) flags |= FLAG_ENCBOD; /* encode funny chars in QP */ else if (0==(flags&FLAG_WRAP_OUT) && !(flags&FLAG_RICH)) { if (!text1 || LongerThan(text1,GetRLong(MAX_SMTP_LINE)) || text2 && LongerThan(text2,GetRLong(MAX_SMTP_LINE))) flags |= FLAG_ENCBOD; } else if (0==(flags&FLAG_ENCBOD) && (flags&FLAG_RICH)) { if (!text1 || LongerWordThan(text1,GetRLong(ENRICHED_MAX_WORD)) || text2 && LongerWordThan(text1,GetRLong(ENRICHED_MAX_WORD))) flags |= FLAG_ENCBOD; } return(flags); } /************************************************************************ * SevenBitTable - is the table in question full of only 7-bit chars? ************************************************************************/ Boolean SevenBitTable(short tableID) { Handle table; UPtr spot,end; if (!tableID || !(table = GetResource_('taBL',tableID))) return(False); for (spot=*table+127,end=*table+256;spot126) return(False); return(True); } /************************************************************************ * AnyFunny - does a block of text contain funny chars? ************************************************************************/ Boolean AnyFunny(Handle text,long offset) { UPtr spot,end; Str255 line; if (!text || !*text) return(True); // check for high bits if (Flatten) { for (spot=*text+offset,end=spot+GetHandleSize_(text)-offset; spot126) return(True); } else { for (spot=*text+offset,end=spot+GetHandleSize_(text)-offset; spot126) return(True); } // check for uucp envelopes for (spot=*text+offset;spotlen) return(True); nl = spot; } } return(False); } /************************************************************************ * LongerWordThan - is there a "word" longer than some number of chars? ************************************************************************/ Boolean LongerWordThan(Handle text, short len) { UPtr spot,end,nl; spot = *text; end = spot + GetHandleSize_(text); nl = spot-1; for (;spotlen) return(True); nl = spot; } } return(False); } /************************************************************************ * Next1342Word - parse a word from a 1342 stream ************************************************************************/ void Next1342Word(UPtr *startP,UPtr end,Token1342Ptr current,PStr delim,Boolean *wasQuote,Boolean *encQuote) { Str63 word; short wordLim = 48; Enum1342 wordType; Byte c; UPtr source = *startP; Boolean justSpace; Boolean newWasQuote; UPtr qSpot; /* * are we off the end? */ if (source>=end) { wordType = k1342End; *word = 0; } else { c = *source++; *word = 1; word[1] = c; if (*wasQuote || c=='"') /* collect a quote */ { if (!*wasQuote) { /* search to end of quote to see if quote contains any high bits */ for (qSpot=source;qSpot=0x80) wordLim -= 2; /* allow space for encoding */ if (*source++ == '"' && word[*word-1]!='\\') { newWasQuote = False; break; /* closing " */ } } wordType = *encQuote ? k1342Word : k1342Plain; *wasQuote = newWasQuote; } else if (strchr(delim+1,c)) /* collect a string of delimiters */ { justSpace = c==' '; while (source=0x80) wordLim -= 2; /* allow space for encoding */ PCatC(word,*source); source++; } // did we end with chars left? If not, and if we have a prior space, // back up to that prior space if (*word >= wordLim && lastSP) { source = lastSP; wordLim = oldWordLim; *word = oldWordSize; } wordType = AnyHighBits(word+1,*word) ? k1342Word : k1342Plain; // trim trailing spaces from encoded words if (wordType == k1342Word) while (*word && word[*word]==' ') { --*word; --source; } } } PCopy(current->word,word); /* copy word and type into current buffer */ current->wordType = wordType; *startP = source; /* mark new position in string */ } /************************************************************************ * Encode1342 - encode a header line ala RFC 1342 (1522, actually) ************************************************************************/ Handle Encode1342(UPtr source,long len,short lineLimit,short *charsOnLine,PStr nl,short tid) { Token1342 tokens[3]; Token1342Ptr prev, curr, next; Str31 dl1342; Boolean continueQuote = False; Boolean encQuote = False; UPtr spot = source; short line; Handle encoded = NuHandle(0); Boolean wrapped; Byte c; if (!encoded) goto fail; /* * grab delimiter list */ GetRString(dl1342,RFC1342_DELIMS); /* * prime initial line length */ line = charsOnLine ? *charsOnLine : 0; /* * initialize token buffer */ tokens[0].wordType = k1342End; *tokens[0].word = 0; /* * read first token */ Next1342Word(&spot,source+len,tokens+1,dl1342,&continueQuote,&encQuote); /* * main loop */ for (curr = tokens+1;curr->wordType!=k1342End;curr = next) { next = RingNext(curr,tokens,3); prev = RingNext(next,tokens,3); Next1342Word(&spot,source+len,next,dl1342,&continueQuote,&encQuote); /* * whitespace between two encoded words gets elided. Therefore, if we * are looking at whitespace and both the previous and next words are * encoded, we must encode the current word. * * if either the previous or next words are not encoded, then we don't * need to do anything special */ if (curr->wordType==k1342LWSP && prev->wordType==k1342Word && next->wordType==k1342Word) curr->wordType = k1342Word; /* * if we are encoding, get with it */ if (curr->wordType == k1342Word) Encode1342String(curr->word,tid); /* * worry about line wrapping */ wrapped = (prev->wordType!=k1342Plain || curr->wordType!=k1342Plain) && (lineLimit && line+*curr->word>=lineLimit); if (wrapped) { if (PtrPlusHand_(nl+1,encoded,*nl)) goto fail; if (PtrPlusHand_(" ",encoded,1)) goto fail; line = 1; } /* * if we are outputting an encoded word and the previous thing was an * encoded word, or if the previous thing was a regular word that did * NOT end with a space or a ')', then we must prepend a space to the * encoded word */ c = prev->word[*prev->word]; if (!wrapped && curr->wordType==k1342Word && (prev->wordType==k1342Word || prev->wordType==k1342Plain && c!=' ' && c!=')')) { BMD(curr->word+1,curr->word+2,*curr->word); ++*curr->word; curr->word[1] = ' '; } /* * if we are outputting an encoded word and the next thing is a * plain word, then we must append a space to the encoded word */ c = next->word[1]; if (curr->wordType==k1342Word && next->wordType==k1342Plain) PCatC(curr->word,' '); /* * stick the word on the end */ if (PtrPlusHand_(curr->word+1,encoded,*curr->word)) goto fail; line += *curr->word; } if (charsOnLine) *charsOnLine = line; return(encoded); fail: ZapHandle(encoded); return(nil); } /************************************************************************ * Encode1342String - encode a string in 1342-speak ************************************************************************/ void Encode1342String(PStr s,short tid) { Str255 encoded; Str63 name; UPtr from, to, end; Byte c; /* * first, we translit to ISO-latin1 */ if (tid) TransLitRes(s+1,*s,tid); /* * now, we encode */ to = encoded+1; end = s + *s + 1; for (from=s+1;fromencoded+sizeof(encoded)-20) return; /* OVERFLOW */ } *encoded = to-encoded-1; ComposeRString(s,RFC1342_FMT,SimpleNameCharset(name,tid),encoded); } #pragma segment SMTPUtil /************************************************************************ * SendPString - send a pascal string ************************************************************************/ OSErr SendPString(TransStream stream,PStr string) { return(SendTrans(stream,string+1,*string,nil)); } /************************************************************************ * TimeStamp - put a time stamp on a message ************************************************************************/ void TimeStamp(TOCHandle tocH,short sumNum,uLong when,long delta) { PtrTimeStamp(LDRef(tocH)->sums+sumNum,when,delta); UL(tocH); #ifdef NEVER CalcSumLengths(tocH,sumNum); #endif InvalSum(tocH,sumNum); TOCSetDirty(tocH,true); } /************************************************************************ * PtrTimeStamp - timestamp, but into a sum directly ************************************************************************/ void PtrTimeStamp(MSumPtr sum,uLong when,long delta) { sum->seconds = when; sum->origZone = delta/60; } PStr FormatZone(PStr string,long delta) { Boolean neg = delta<0; if (neg) delta *= -1; delta /= 60; /* minutes*/ ComposeString(string,"\p %c%d%d%d%d",neg?'-':'+', delta/600,(delta%600)/60,(delta%60)/10,delta%10); return(string); } /************************************************************************ * BufferSend - send a buffer of (possibly encoded) data * encoder - function to call for encoding * data - data to encode/send (or nil to send remaining data and close) * dataLen - length of data to encode/send ************************************************************************/ OSErr BufferSend(TransStream stream, DecoderFunc *encoder, UPtr data, long dataLen, Boolean text) { short err = noErr; static long used; long consumed; UPtr spot,end; long bSize; long progBytes; if (EncoderGlobalsOldEncoder && EncoderGlobalsOldEncoder!=encoder) { BufferSendRelease(stream); EncoderGlobalsOldEncoder = encoder; } /* * are we being fed data? */ if (data) { /* * do we need to initialize? */ if (!EncoderGlobalsBuffers[0]) { bSize = 3*GetRLong(BUFFER_SIZE); while (!(EncoderGlobalsBuffers[0] = NuHTempOK(bSize)) && bSize > 256) bSize /= 2; if (!EncoderGlobalsBuffers[0]) { WarnUser(MEM_ERR,err=MemError()); return(err); } #ifdef DEBUG if (!BUG5) #endif if (AsyncSendTrans) EncoderGlobalsBuffers[1] = NuHTempOK(bSize); EncoderGlobalsBuffer = EncoderGlobalsBuffers[0]; if (encoder) err = (*encoder)(kDecodeInit,&EncoderGlobalsPb); EncoderGlobalsPb.text = text; if (err) {BufferSend(stream,encoder,nil,0,0); return(err);} used = 0; } bSize = GetHandleSize_(EncoderGlobalsBuffer); if (!DontTranslate && Flatten) for (spot=data,end=data+dataLen;spot 4*dataLen)) { EncoderGlobalsPb.output = LDRef(EncoderGlobalsBuffer)+used; consumed = EncoderGlobalsPb.inlen = MIN((bSize-used)/4,dataLen); EncoderGlobalsPb.input = data; err = (*encoder)(kDecodeData,&EncoderGlobalsPb); UL(EncoderGlobalsBuffer); if (err) return(err); used += EncoderGlobalsPb.outlen; progBytes = EncoderGlobalsPb.outlen; } } /* * no, just copy */ else { consumed = MIN(bSize-used,dataLen); BMD(data,*EncoderGlobalsBuffer+used,consumed); used += consumed; } /* * send */ if (consumed < dataLen) { if (AsyncSendTrans && EncoderGlobalsBuffers[1]) { err = AsyncSendTrans(stream,LDRef(EncoderGlobalsBuffer),used); EncoderGlobalsBuffer = EncoderGlobalsBuffer == EncoderGlobalsBuffers[0] ? EncoderGlobalsBuffers[1] : EncoderGlobalsBuffers[0]; } else { err = SendTrans(stream,LDRef(EncoderGlobalsBuffer),used,nil); UL(EncoderGlobalsBuffer); } if (err) return(err); used = 0; } dataLen -= consumed; data += consumed; if (progBytes) ByteProgress(nil,-progBytes,0); } } /* * no data; clear out encoder */ else { if (AsyncSendTrans) err = AsyncSendTrans(stream,nil,-1); if (!err && used && !dataLen && EncoderGlobalsBuffer) err = SendTrans(stream,LDRef(EncoderGlobalsBuffer),used,nil); if (encoder && EncoderGlobalsPb.refCon) { if (!err) { EncoderGlobalsPb.output = LDRef(EncoderGlobalsBuffer); err = (*encoder)(kDecodeDone,&EncoderGlobalsPb); if (!err && EncoderGlobalsPb.outlen && !dataLen) err = SendTrans(stream,*EncoderGlobalsBuffer,EncoderGlobalsPb.outlen,nil); } (*encoder)(kDecodeDispose,&EncoderGlobalsPb); } WriteZero(&EncoderGlobalsPb,sizeof(EncoderGlobalsPb)); used = 0; ZapHandle(EncoderGlobalsBuffers[0]); ZapHandle(EncoderGlobalsBuffers[1]); EncoderGlobalsBuffer = nil; } return(err); } /************************************************************************ * GetIndAttachment - get a particular attacment ************************************************************************/ OSErr GetIndAttachment(MessHandle messH,short index,FSSpecPtr spec,HSPtr where) { OSErr err=1; Handle text=nil; HeadSpec hs; if (CompHeadFind(messH,ATTACH_HEAD,&hs)) { if (!(err = PETEGetRawText(PETE,TheBody,&text))) err = GetIndAttachmentLo(text,index,spec,where,&hs); } return(err); } /************************************************************************ * GetIndAttachmentLo - get a particular attacment ************************************************************************/ OSErr GetIndAttachmentLo(Handle text,short index,FSSpecPtr spec,HSPtr where,HeadSpec *hs) { short colons[4]; short onColon; Str31 name; Str31 volName; long id; int onChar; OSErr err; onColon = 0; for (onChar=hs->value;onCharstop;onChar++) if ((*text)[onChar] == ':') { colons[onColon] = onChar; if (++onColon==sizeof(colons)/sizeof(short)) { index--; onColon = 0; if (!index) { BMD((*text)+colons[0]+1,volName+1,colons[1]-colons[0]); *volName = colons[1]-colons[0]; id = Atoi(LDRef(text)+colons[1]+1); BMD((*text)+colons[2]+1,name+1,colons[3]-colons[2]-1); if (where) { where->start = where->value = colons[0]; where->stop = colons[3]+1; } *name = colons[3]-colons[2]-1; if (err = FSMakeFSSpec(GetMyVR(volName),id,name,spec)) { // This file probably wasn't found. Go ahead and // build spec manually. May need name later on. spec->vRefNum = GetMyVR(volName); spec->parID = id; PStrCopy(spec->name,name,sizeof(spec->name)); } hs->value = onChar+1; return err; } } } hs->value = onChar; return(1); /* no more files */ } /************************************************************************ * PriorityHeader: Build a priority header ************************************************************************/ UPtr PriorityHeader(UPtr buffer,Byte priority) { return(ComposeRString(buffer,PRIORITY_FMT,HEADER_STRN+PRIORITY_HEAD,priority,PRIOR_STRN+priority)); } /************************************************************************ * GetReply - get a reply to an SMTP command ************************************************************************/ int GetReplyLo(TransStream stream, UPtr buffer, int size, AccuPtr bufAcc, Boolean verbose,Boolean isEhlo) { long rSize; Str127 scratch; char *cp; short err; Str255 tempBuffer; // if a buffer was not passed in... if (!buffer) { buffer = tempBuffer; size = 255; } #ifdef TWO if (PrefIsSet(PREF_POP_SEND)) { rSize = size; err = POPCmdGetReply(stream,-1,"",buffer,&rSize); if (err) return(602); if (*buffer=='+') BMD("200 ",buffer,4); else BMD("550 ",buffer,4); cp = buffer; } else #endif do { Boolean partialBuffer; rSize = size; if (bufAcc) bufAcc->offset = 0; if (CommandPeriod || (sErr=RecvLine(stream,buffer,&rSize))) return(602); /* error receiving */ if (bufAcc) AccuAddPtr(bufAcc,buffer,rSize); partialBuffer = rSize && buffer[rSize-1]!='\r'; // if we've been given an accumulator, // accumulate the whole response if this one is incomplete if (bufAcc && partialBuffer) { while(buffer[rSize-1]!='\r') { rSize = size; sErr = RecvLine(stream,buffer,&rSize); if (CommandPeriod || sErr) return 602; AccuAddPtr(bufAcc,buffer,rSize); } // pretend that what we got was just that first bufferful rSize = min(size,bufAcc->offset); BMD(*bufAcc->data,buffer,rSize); } else if (partialBuffer) { // Ick - not all of the reply will fit in the buffer, and we // weren't given an accumulator to keep it in. Throw stuff away until // we find the end of the line Str63 dumpBuffer; long dumpSize; do { dumpSize = sizeof(dumpBuffer); sErr = RecvLine(stream,dumpBuffer,&dumpSize); if (CommandPeriod || sErr) return 602; CarefulLog(LOG_PROTO,DISCARD_LOG_FMT,dumpBuffer,dumpSize); } while (dumpBuffer[dumpSize-1]!='\r'); } if (verbose) { *scratch = MIN(127,rSize); strncpy(scratch+1,buffer,*scratch); ProgressMessage(kpMessage,scratch); } for (cp=buffer;cp'~');cp++); if (isEhlo && cp[0]=='2' && cp[1]=='5' && cp[2]=='0') EhloLine(buffer,rSize); rSize -= cp-buffer; } while (rSize<3 || !isdigit(cp[0])||!isdigit(cp[1])||!isdigit(cp[2]) || rSize>3 && cp[3]=='-'); cp[rSize] = 0; err = Atoi(cp); if (verbose && err>399 && err<600) SMTPCmdError(nil,nil,buffer); if (err==505 || err==530) SetPref(PREF_SMTP_GAVE_530,YesStr); if (err==452) err = 552; // Goddam stupid SMTP spec gave a 4xx series response // to a permanent error code return(err); } /************************************************************************ * FlattenAndSpool - flatten and spool a movie * Changes the filespec passed to it! ************************************************************************/ OSErr FlattenAndSpool(FSSpecPtr spec) { FSSpec tempSpec; OSErr err = NewTempSpec(0,0,spec->name,&tempSpec); if (!err) { if (err=FlattenQTMovie(spec,&tempSpec)) FSpDelete(&tempSpec); else { *spec = tempSpec; FSpKillRFork(spec); } } return(err); } /********************************************************************** * FlattenQTMovie - put movie in data fork of new file **********************************************************************/ OSErr FlattenQTMovie(FSSpecPtr inSpec,FSSpecPtr outSpec) { short movieResFile; Movie theMovie,tempMovie; OSErr err = noErr; if (!HaveQuickTime(0x0100)) return cantOpenHandler; // Don't have QuickTime if (!QTMoviesInited) { if (!EnterMovies()) // Need to do this once QTMoviesInited = true; err = GetMoviesError(); } if (QTMoviesInited && !(err = OpenMovieFile(inSpec,&movieResFile,fsRdPerm))) { short movieResID = 0; /* want first movie */ Boolean wasChanged; err = NewMovieFromFile (&theMovie,movieResFile,&movieResID,nil,0,&wasChanged); CloseMovieFile(movieResFile); if (!err) { tempMovie = FlattenMovieData(theMovie,flattenAddMovieToDataFork,outSpec,FileCreatorOf(inSpec),smSystemScript,createMovieFileDeleteCurFile); err = GetMoviesError(); if (tempMovie) DisposeMovie(tempMovie); DisposeMovie(theMovie); } } return err; }