From: simsong (Simson L. Garfinkel)
Newsgroups: comp.sys.next.programmer
Subject: Cracking NeXTmail (with source)
Date: 13 Apr 92 12:28:22 GMT
Reply-To: simsong@next.cambridge.ma.us
Organization: NeXTWORLD Magazine

I'm writing an incredible address-book program (due out in a few months) and  
one of the things that I have to be able to do is to send out NeXTmail, both  
regular NeXTmail and NeXTmail with attachments.

Cracking the attachments format was easy; I had already done that a long time  
ago and posted a few shell scripts to help out with the task.

Cracking Non-NeXT mail format was much harder, though.  You see, it isn't just  
plain ASCII.  NeXTmail hides stuff in the data so that when Non-NeXT mail is  
read by a NeXT mail reader, it will automatically put back together the  
paragraph breaks so that your paragraphs will properly flow.

After spending a night working on this problem, I came up with the methods,  
shown below.

Basically, NeXTmail looks for th presence of a magic header (Received: by NeXT  
Mailer) to know that it is message that originated from NeXTmail.  It then  
combines the lines into paragraphs that have more than one space at the end of  
the line; furthermore, the number of characters in the line has to be divisible  
by two.

Anyway, here is the code.  Please DO NOT SEND ME QUESTIONS about the format,  
becuase I don't have any documentation either.  But if you find bugs in my  
code, please post them to comp.sys.next.programmer.

CODE FOLLOWS:

#import "Subprocess.h"

#import <appkit/appkit.h>
#import <stdlib.h>
#import <string.h>
#import <libc.h>

@implementation Logger

- sendNextMailToProc:mailProc forText:mailText
{
	/* NeXT mail */
	char	dir[256];
	char	fname[256];
	char	*outname;
	char	outfile[256];
	char	command[1024];
	char	uufile[1024];
	char	NextMailLine[1024];
	struct	stat	sbuf;
	FILE	*f;
	NXStream	*str;
	
	strcpy(dir,"/tmp/LogdirXXXXXX");
	mktemp(dir);
	strcpy(uufile,"/tmp/uufileXXXXXX");
	mktemp(uufile);
	
	if(mkdir(dir,0700)){
		NXRunAlertPanel(0,"Could not create %s: %s",
				0,0,0,dir,strerror(errno));
		[window	setTitle:"mail failed"];
		return self;
	}
	strcpy(fname,dir);
	strcat(fname,"//");
	strcat(fname,"index.rtf");
	
	strcpy(outfile,"/tmp/.tar.XXXXXX.fromLogger.attach");
	NXGetTempFilename(outfile,10);
	outname	= index(outfile,'.');
	
	str	= NXOpenMemory(0,0,NX_WRITEONLY);
	[mailText	writeRichText:str];
	NXSaveToFile(str,fname);
	NXCloseMemory(str,NX_FREEBUFFER);
	
	sprintf(command,
		"cd %s;tar cf - index.rtf|compress|uuencode %s > %s",
		dir,outname,uufile);
	system(command);
	if(stat(uufile,&sbuf)){
		NXRunAlertPanel(0,"stat: %s:%s",
				0,0,0,uufile,strerror(errno));
		[window	setTitle:"mail failed"];
		return self;
	}
	
	sprintf(NextMailLine,"Next-Attachment: %s, %d, 1/1, 660, 0",
		outname,sbuf.st_size);
	
	[mailProc	send:NextMailLine];
	[mailProc	send:""];
	
	/* Now send the file */
	
	f	= fopen(uufile,"r");
	if(!f){
		NXRunAlertPanel(0,"%s:%s",0,0,0,strerror(errno));
		[window	setTitle:"mail failed"];
		return self;
	}
	while(!feof(f)){
		char	buf[1024];
		
		buf[0]	= 0;
		fgets(buf,sizeof(buf),f);
		if(buf[0]){
			[mailProc	send:buf withNewline:NO];
		}
	}
	return self;
}

- sendMailToProc:mailProc forText:mailText
{
	int		line;
	int		pos;
	NXBreakArray	*theBreaks=0;
	int		nbreaks;
	NXLineDesc	*breaks;
	int		length = [mailText textLength];

	[mailProc	send:""];

	object_getInstanceVariable(mailText,"theBreaks",(void **)&theBreaks);

	nbreaks	= theBreaks->chunk.used;
	breaks  = theBreaks->breaks;

	for(pos=0,line=0;line<nbreaks && pos<length;line++){
		int	lineChange;
		int	endParagraph;
		int	len,nlen;
		char	*buf;
		int	measured_len;

		lineChange	= breaks[line] & 0x8000;
		endParagraph	= breaks[line] & 0x4000;
		
		
		len	= breaks[line] & 0x2fff;
		buf	= malloc(len+16);
		
		nlen = [mailText getSubstring:buf start:pos length:len];
		buf[nlen] = '\000';

		measured_len	 = strlen(buf);
		if(measured_len>0){
			if(buf[measured_len-1]=='\n'){
				buf[measured_len-1]='\000';
			}
		}
		strcat(buf," ");
		while((strlen(buf) % 2) !=1 ){ /* the NeXTmail secret */
			strcat(buf," ");
		}
		
		[mailProc send:buf];

		free(buf);
		pos	+= len;

		if(lineChange!=0){
			/* "if the line change bit is set, the descriptor is
			 * the first field of a NXHeightChange...."
			 */
			line	+=  sizeof(NXHeightInfo)/sizeof(NXLineDesc);
		}
	}
	return self;
}

- log:sender
{
	[window	setTitle:"Sending mail"];

	proc = [[Subprocess alloc] init:"/usr/lib/sendmail -t"
		      withDelegate:self
		      andPtySupport:NO
		      andStdErr:YES];

	[proc	send:"From: " withNewline:NO];
	[proc	send:[fromCell stringValue]];

        [proc	send:"Received: by NeXT Mailer (1.63)"];

	[proc	send:"To: " withNewline:NO];
	[proc	send:[toCell stringValue]];

	[proc	send:"Subject: " withNewline:NO];
	[proc	send:[subjectCell stringValue]];

	if([nextMailSwitch intValue]==1){
		[self	sendNextMailToProc:proc forText:text];
	}
	else{
		[self	sendMailToProc:proc forText:text];
	}
	[proc	terminateInput];

	return self;
}
			
- setScroller:aScroller
{
	scroller 	= aScroller;
	text		= [scroller docView];
	[text 		setDelegate:self];
	[text		setSelProp:NX_LEFTMARGIN to:5.0];
	return self;
}





/* Delegate methods */

- makeRich
{
	[nextMailSwitch	setIntValue:1];
	return self;
}

- textDidChange:sender
{
	[[sender window] setDocEdited:YES];
	return self;
}

- textWillConvert:sender fromFont:fnt toFont:fnt2
{
	[self	makeRich];
	return fnt2;
}


- nextMailSwitchChanged:sender
{
	/* If we are changing from NeXTmail to non-NeXTmail, get rid of
	 * all font information in the scroller...
	 */
	if([sender intValue]==0){
		NXStream	*str;
		char		*addr;
		int		len,maxlen;


		str	= NXOpenMemory(0,0,NX_READWRITE);
		[text	writeText:str];
		NXGetMemoryBuffer(str,&addr,&len,&maxlen);
		[text	setText:""];
		[text	setText:addr];
		[text 	display];
		
		NXCloseMemory(str,NX_FREEBUFFER);
	}
	return self;
}


/* Subprocess delegate methods */
- subprocessDone
{
	[window	performClose:self];
	return self;
}

- subprocessOut:(char *)buf
{
	NXRunAlertPanel("Logger","sendmail: %s\n",0,0,0,buf);
	return self;
}

- subprocessError:(char *)buf
{
	NXRunAlertPanel("Error","sendmail: %s\n",0,0,0,buf);
	return self;
}

@end
-- 

................................................................simson