smbtxt.c 11.7 KB
Newer Older
1 2 3 4 5 6 7 8
/* Synchronet message base (SMB) message text library routines */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
rswindell's avatar
rswindell committed
9
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* ANSI */
37 38
#include <stdlib.h>	/* malloc/realloc/free */
#include <string.h>	/* strlen */
39 40 41

/* SMB-specific */
#include "smblib.h"
42
#include "base64.h"
43

44
char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
45
{
46 47 48
	char*	buf;
	char*	lzhbuf;
	char*	p;
49
	char*	str;
deuce's avatar
64-bit  
deuce committed
50
	uint16_t	xlat;
51 52
	uint 	i;
	int		lzh;	/* BOOL */
53 54
	long	l=0,lzhlen,length;

55
	if((buf=(char*)malloc(sizeof(char)))==NULL) {
56
		sprintf(smb->last_error
57 58
			,"%s malloc failure of %" XP_PRIsize_t "u bytes for buffer"
			,__FUNCTION__, sizeof(char));
59 60 61 62
		return(NULL);
	}
	*buf=0;

63 64 65 66 67 68 69 70
	if(!(mode&GETMSGTXT_NO_HFIELDS)) {
		for(i=0;i<(uint)msg->total_hfields;i++) {			/* comment headers are part of text */
			if(msg->hfield[i].type!=SMB_COMMENT && msg->hfield[i].type!=SMTPSYSMSG)
				continue;
			str=(char*)msg->hfield_dat[i];
			length=strlen(str)+2;	/* +2 for crlf */
			if((p=(char*)realloc(buf,l+length+1))==NULL) {
				sprintf(smb->last_error
71 72
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
73 74
				free(buf);
				return(NULL);
75 76 77
			}
			buf=p;
			l+=sprintf(buf+l,"%s\r\n",str);
78
		}
rswindell's avatar
rswindell committed
79 80 81
		if(l) {	/* Add a blank line after comments */
			if((p=(char*)realloc(buf,l+3))==NULL) {
				sprintf(smb->last_error
82 83
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+3);
rswindell's avatar
rswindell committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97
				free(buf);
				return(NULL);
			}
			buf=p;
			l+=sprintf(buf+l,"\r\n");
		}
		unsigned answers = 0;
		for(i=0;i<(uint)msg->total_hfields;i++) {			/* Poll Answers are part of text */
			if(msg->hfield[i].type!=SMB_POLL_ANSWER)
				continue;
			char tmp[128];
			length = safe_snprintf(tmp, sizeof(tmp), "%2u: %s\r\n", ++answers, (char*)msg->hfield_dat[i]);
			if((p=(char*)realloc(buf,l+length+1))==NULL) {
				sprintf(smb->last_error
98 99
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
100 101 102 103 104 105 106 107
				free(buf);
				return(NULL);
			}
			buf=p;
			memcpy(buf+l, tmp, length);
			l += length;
			buf[l] = 0;
		}
108 109
	}

110
	for(i=0;i<(uint)msg->hdr.total_dfields;i++) {
111
		if(msg->dfield[i].length<=sizeof(xlat))
112
			continue;
113 114 115 116 117 118 119 120 121 122 123 124
		switch(msg->dfield[i].type) {
			case TEXT_BODY:
				if(mode&GETMSGTXT_NO_BODY)
					continue;
				break;
			case TEXT_TAIL:
				if(!(mode&GETMSGTXT_TAILS))
					continue;
				break;
			default:	/* ignore other data types */
				continue;
		}
125 126
		fseek(smb->sdt_fp,msg->hdr.offset+msg->dfield[i].offset
			,SEEK_SET);
127
		fread(&xlat,sizeof(xlat),1,smb->sdt_fp);
128 129 130
		lzh=0;
		if(xlat==XLAT_LZH) {
			lzh=1;
131
			fread(&xlat,sizeof(xlat),1,smb->sdt_fp); 
132 133 134 135
		}
		if(xlat!=XLAT_NONE) 	/* no other translations currently supported */
			continue;

136
		length=msg->dfield[i].length-sizeof(xlat);
137
		if(lzh) {
138
			length-=sizeof(xlat);
139 140
			if(length<1)
				continue;
141
			if((lzhbuf=(char*)malloc(length))==NULL) {
142
				sprintf(smb->last_error
143 144
					,"%s malloc failure of %ld bytes for LZH buffer"
					, __FUNCTION__, length);
rswindell's avatar
rswindell committed
145 146
				free(buf);
				return(NULL);
147
			}
148
			smb_fread(smb,lzhbuf,length,smb->sdt_fp);
deuce's avatar
deuce committed
149
			lzhlen=*(int32_t*)lzhbuf;
150
			if((p=(char*)realloc(buf,l+lzhlen+3L))==NULL) {
151
				sprintf(smb->last_error
152 153
					,"%s realloc failure of %ld bytes for text buffer"
					, __FUNCTION__, l+lzhlen+3L);
154
				free(lzhbuf);
rswindell's avatar
rswindell committed
155 156
				free(buf);
				return(NULL); 
157 158
			}
			buf=p;
deuce's avatar
deuce committed
159
			lzh_decode((uint8_t *)lzhbuf,length,(uint8_t *)buf+l);
160
			free(lzhbuf);
161 162 163
			l+=lzhlen; 
		}
		else {
164
			if((p=(char*)realloc(buf,l+length+3L))==NULL) {
165
				sprintf(smb->last_error
166 167
					,"%s realloc failure of %ld bytes for text buffer"
					, __FUNCTION__, l+length+3L);
rswindell's avatar
rswindell committed
168 169
				free(buf);
				return(NULL);
170 171 172 173 174 175 176 177 178 179
			}
			buf=p;
			p=buf+l;
			l+=fread(p,1,length,smb->sdt_fp);
		}
		if(!l)
			continue;
		l--;
		while(l && buf[l]==0) l--;
		l++;
180
		*(buf+l)='\r';	/* CR */
181
		l++;
182
		*(buf+l)='\n';	/* LF */
183 184 185
		l++;
		*(buf+l)=0; 
	}
186

rswindell's avatar
rswindell committed
187 188
	if(mode&GETMSGTXT_PLAIN)
		buf = smb_getplaintext(msg, buf);
189 190 191
	return(buf);
}

192
void SMBCALL smb_freemsgtxt(char* buf)
193 194
{
	if(buf!=NULL)
195
		free(buf);
196
}
rswindell's avatar
rswindell committed
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
enum content_transfer_encoding {
	CONTENT_TRANFER_ENCODING_NONE,
	CONTENT_TRANFER_ENCODING_BASE64,
	CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE,
	CONTENT_TRANFER_ENCODING_OTHER
};

/* Decode quoted-printable content-transfer-encoded text */
/* Ignores (strips) unsupported ctrl chars and non-ASCII chars */
/* Does not enforce 76 char line length limit */
char* qp_decode(char* buf)
{
	uchar*	p=(uchar*)buf;
	uchar*	dest=p;

	for(;;p++) {
		if(*p==0) {
			*dest++='\r';
			*dest++='\n';
			break;
		}
219
		if(*p==' ' || (*p>='!' && *p<='~' && *p!='=') || *p=='\t'|| *p=='\r'|| *p=='\n')
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
			*dest++=*p;
		else if(*p=='=') {
			p++;
			if(*p==0) 	/* soft link break */
				break;
			if(isxdigit(*p) && isxdigit(*(p+1))) {
				char hex[3];
				hex[0]=*p;
				hex[1]=*(p+1);
				hex[2]=0;
				/* ToDo: what about encoded NULs and the like? */
				*dest++=(uchar)strtoul(hex,NULL,16);
				p++;
			} else {	/* bad encoding */
				*dest++='=';
				*dest++=*p;
			}
		}
	}
	*dest=0;
	return buf;
}

static enum content_transfer_encoding mime_getxferencoding(char* beg, char* end)
{
	char* p = beg;

	while(p < end) {
		SKIP_WHITESPACE(p);
		if(strnicmp(p, "content-transfer-encoding:", 26) != 0) {
			FIND_CHAR(p, '\n');
			continue;
		}
		p += 26;
		SKIP_WHITESPACE(p);
		if(strnicmp(p, "base64", 6) == 0)
			return CONTENT_TRANFER_ENCODING_BASE64;
		if(strnicmp(p, "quoted-printable", 16) == 0)
			return CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE;
		if(strnicmp(p, "7bit", 4) == 0 || strnicmp(p, "8bit", 4) == 0 || strnicmp(p, "binary", 6) == 0)
			return CONTENT_TRANFER_ENCODING_NONE;
		return CONTENT_TRANFER_ENCODING_OTHER;
	}

	return CONTENT_TRANFER_ENCODING_NONE;
}

/* ToDo: parse and return the "modification-date" value */
static BOOL mime_getattachment(char* beg, char* end, char* attachment)
{
	char* p = beg;

	while(p < end) {
		SKIP_WHITESPACE(p);
		if(strnicmp(p, "content-disposition:", 20) != 0) {
			FIND_CHAR(p, '\n');
			continue;
		}
		p += 20;
		SKIP_WHITESPACE(p);
		if(strnicmp(p, "inline", 6) == 0) {
			FIND_CHAR(p, '\n');
			continue;
		}
		char* filename = strstr(p, "filename=");
		if(filename == NULL) {
			FIND_CHAR(p, '\n');
			continue;
		}
		filename += 9;
		char* term;
		if(*filename == '"') {
			filename++;
			term = strchr(filename, '"');
		} else
			term = strchr(filename, ';');
		if(term == NULL) {
			term = filename;
			FIND_WHITESPACE(term);
		}
		*term = 0;
		strncpy(attachment, getfname(filename), MAX_PATH);
		return TRUE;
	}
	return FALSE;
}

/* Find the specified content-type in a MIME-encoded message body, recursively */
static char* mime_getcontent(char* buf, const char* content_type, const char* content_match
	,int depth, enum content_transfer_encoding* encoding, char* attachment)
rswindell's avatar
rswindell committed
310 311 312 313
{
	char*	txt;
	char*	p;
	char	boundary[256];
314 315 316 317 318 319 320 321
	char	match1[128];
	char	match2[128];
	int		match_len = 0;
	
	if(content_match != NULL) {
		match_len = sprintf(match1, "Content-Type: %s;", content_match);
					sprintf(match2, "Content-Type: %s\r", content_match);
	}
rswindell's avatar
rswindell committed
322

323 324
	if(depth > 2)
		return NULL;
rswindell's avatar
rswindell committed
325
	if(content_type == NULL)	/* Not MIME-encoded */
326
		return NULL;
rswindell's avatar
rswindell committed
327 328 329 330 331 332 333
	content_type += 13;
	SKIP_WHITESPACE(content_type);
	if(strstr(content_type, "multipart/alternative;") == content_type)
		content_type += 22;
	else if(strstr(content_type, "multipart/mixed;") == content_type)
		content_type +=16;
	else
334
		return NULL;
335
	p = strstr(content_type, "boundary=");
rswindell's avatar
rswindell committed
336
	if(p == NULL)
337
		return NULL;
338 339 340 341 342 343
	p += 9;
	if(*p == '"')
		p++;
	SAFEPRINTF(boundary, "--%s", p);
	if((p = strchr(boundary,'"')) != NULL)
		*p = 0;
rswindell's avatar
rswindell committed
344 345 346 347
	txt = buf;
	while((p = strstr(txt, boundary)) != NULL) {
		txt = p+strlen(boundary);
		SKIP_WHITESPACE(txt);
348
		if(strncmp(txt, "Content-Type:", 13) != 0)
rswindell's avatar
rswindell committed
349 350 351 352
			continue;
		p = strstr(txt, "\r\n\r\n");	/* End of header */
		if(p==NULL)
			continue;
353 354 355
		if((match_len && strncmp(txt, match1, match_len) && strncmp(txt, match2, match_len))
			|| (attachment != NULL && !mime_getattachment(txt, p, attachment))) {
			if((p = mime_getcontent(p, txt, content_match, depth + 1, encoding, attachment)) != NULL)
356 357 358
				return p;
			continue;
		}
359 360
		if(encoding != NULL)
			*encoding = mime_getxferencoding(txt, p);
rswindell's avatar
rswindell committed
361 362 363 364
		txt = p;
		SKIP_WHITESPACE(txt);
		if((p = strstr(txt, boundary)) != NULL)
			*p = 0;
365
		return txt;
rswindell's avatar
rswindell committed
366
	}
367 368 369 370 371 372 373 374 375
	return NULL;
}

/* Get just the plain-text portion of a MIME-encoded message body */
char* SMBCALL smb_getplaintext(smbmsg_t* msg, char* buf)
{
	int		i;
	char*	txt;
	char*	content_type = NULL;
376
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;
377 378 379 380 381 382 383 384 385

	for(i=0;i<msg->total_hfields;i++) { 
		if(msg->hfield[i].type==RFC822HEADER) { 
			if(strnicmp((char*)msg->hfield_dat[i],"Content-Type:",13)==0) {
				content_type=msg->hfield_dat[i];
				break;
			}
        }
    }
386 387 388 389
	if(content_type == NULL)	/* not MIME */
		return buf;
	txt = mime_getcontent(buf, content_type, "text/plain", 0, &xfer_encoding, /* attachment: */NULL);
	if(txt != NULL) {
390
		memmove(buf, txt, strlen(txt)+1);
391 392
		if(xfer_encoding == CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE)
			qp_decode(buf);
393 394 395 396 397 398 399 400
		else if(xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
			char* decoded = strdup(buf);
			if(decoded == NULL)
				return buf;
			if(b64_decode(decoded, strlen(decoded), buf, strlen(buf)) > 0)
				strcpy(buf, decoded);
			free(decoded);
		}
401 402
	}

rswindell's avatar
rswindell committed
403 404
	return buf;
}
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

/* Get just an attachment (just one) from MIME-encoded message body */
uint8_t* SMBCALL smb_getattachment(smbmsg_t* msg, char* buf, char* filename, uint32_t* filelen)
{
	int		i;
	char*	txt;
	char*	content_type = NULL;
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;

	for(i=0;i<msg->total_hfields;i++) { 
		if(msg->hfield[i].type==RFC822HEADER) { 
			if(strnicmp((char*)msg->hfield_dat[i],"Content-Type:",13)==0) {
				content_type=msg->hfield_dat[i];
				break;
			}
        }
    }
	if(content_type == NULL)	/* not MIME */
		return NULL;
	txt = mime_getcontent(buf, content_type, /* match-type: */NULL, 0, &xfer_encoding, /* attachment: */filename);
	if(txt != NULL && xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
		memmove(buf, txt, strlen(txt)+1);
427 428 429 430
		int result = b64_decode(buf, strlen(buf), buf, strlen(buf));
		if(result < 1)
			return NULL;
		*filelen = result;
431 432 433 434 435
		return buf;
	}

	return NULL;	/* No attachment */
}