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

/****************************************************************************
 * @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
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *																			*
 * 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									*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* ANSI */
23 24
#include <stdlib.h>	/* malloc/realloc/free */
#include <string.h>	/* strlen */
25 26 27

/* SMB-specific */
#include "smblib.h"
28
#include "base64.h"
29
#include "lzh.h"
30

31
char* smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
32
{
33
	char*	buf;
34
	char*	preamble;
35 36
	char*	lzhbuf;
	char*	p;
37
	char*	str;
deuce's avatar
64-bit  
deuce committed
38
	uint16_t	xlat;
39 40
	uint 	i;
	int		lzh;	/* BOOL */
41 42
	long	l=0,lzhlen,length;

43
	if((buf=(char*)malloc(sizeof(char)))==NULL) {
44
		safe_snprintf(smb->last_error, sizeof(smb->last_error)
45 46
			,"%s malloc failure of %" XP_PRIsize_t "u bytes for buffer"
			,__FUNCTION__, sizeof(char));
47 48 49 50
		return(NULL);
	}
	*buf=0;

51 52 53 54 55 56 57
	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) {
58
				safe_snprintf(smb->last_error, sizeof(smb->last_error)
59 60
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
61 62
				free(buf);
				return(NULL);
63 64 65
			}
			buf=p;
			l+=sprintf(buf+l,"%s\r\n",str);
66
		}
rswindell's avatar
rswindell committed
67 68
		if(l) {	/* Add a blank line after comments */
			if((p=(char*)realloc(buf,l+3))==NULL) {
69
				safe_snprintf(smb->last_error, sizeof(smb->last_error)
70 71
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+3);
rswindell's avatar
rswindell committed
72 73 74 75 76 77 78 79 80 81 82 83 84
				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) {
85
				safe_snprintf(smb->last_error, sizeof(smb->last_error)
86 87
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
88 89 90 91 92 93 94 95
				free(buf);
				return(NULL);
			}
			buf=p;
			memcpy(buf+l, tmp, length);
			l += length;
			buf[l] = 0;
		}
96
	}
97
	preamble = strdup(buf);
98

99
	for(i=0;i<(uint)msg->hdr.total_dfields;i++) {
100
		if(msg->dfield[i].length<=sizeof(xlat))
101
			continue;
102 103 104 105 106 107 108 109 110 111 112 113
		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;
		}
114 115
		fseek(smb->sdt_fp,msg->hdr.offset+msg->dfield[i].offset
			,SEEK_SET);
116 117
		if(fread(&xlat, 1, sizeof(xlat), smb->sdt_fp) != sizeof(xlat))
			continue;
118 119 120
		lzh=0;
		if(xlat==XLAT_LZH) {
			lzh=1;
121 122
			if(fread(&xlat, 1, sizeof(xlat), smb->sdt_fp) != sizeof(xlat))
				continue;
123 124 125 126
		}
		if(xlat!=XLAT_NONE) 	/* no other translations currently supported */
			continue;

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

189 190
	if(mode&GETMSGTXT_PLAIN) {
		char* plaintext = smb_getplaintext(msg, buf);
191 192 193 194 195 196 197 198 199 200
		if(plaintext != NULL) {
			buf = malloc(strlen(preamble) + strlen(plaintext) + 1);
			if(buf == NULL)
				buf = plaintext;
			else {
				strcpy(buf, preamble);
				strcat(buf, plaintext);
				free(plaintext);
			}
		}
201
	}
202
	free(preamble);
203 204 205
	return(buf);
}

206
void smb_freemsgtxt(char* buf)
207 208
{
	if(buf!=NULL)
209
		free(buf);
210
}
rswindell's avatar
rswindell committed
211

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
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;

227
	for(;*p != 0; p++) {
228
		if(*p==' ' || (*p>='!' && *p<='~' && *p!='=') || *p=='\t'|| *p=='\r'|| *p=='\n')
229 230 231
			*dest++=*p;
		else if(*p=='=') {
			p++;
232 233 234
			if(*p == '\r')	/* soft link break */
				p++;
			if(*p == 0)
235
				break;
236 237
			if(*p == '\n')
				continue;
238
			if(IS_HEXDIGIT(*p) && IS_HEXDIGIT(*(p+1))) {
239
				uchar ch = HEX_CHAR_TO_INT(*p) << 4;
240
				p++;
241 242 243
				ch |= HEX_CHAR_TO_INT(*p);
				if(ch == '\t' || ch >= ' ')
					*dest++=ch;
244 245 246 247 248 249 250 251 252 253
			} else {	/* bad encoding */
				*dest++='=';
				*dest++=*p;
			}
		}
	}
	*dest=0;
	return buf;
}

254 255 256 257 258 259 260 261
static size_t strStartsWith_i(const char* buf, const char* match)
{
	size_t len = strlen(match);
	if (strnicmp(buf, match, len) == 0)
		return len;
	return 0;
}

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
static enum content_transfer_encoding mime_encoding(const char* str)
{
	const char* p = str;

	if(str ==  NULL)
		return CONTENT_TRANFER_ENCODING_NONE;
	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;
}

278
static enum content_transfer_encoding mime_getxferencoding(const char* beg, const char* end)
279
{
280
	const char* p = beg;
281 282 283

	while(p < end) {
		SKIP_WHITESPACE(p);
284 285
		size_t len = strStartsWith_i(p, "content-transfer-encoding:");
		if(len < 1) {
286 287 288
			FIND_CHAR(p, '\n');
			continue;
		}
289
		return mime_encoding(p + len);
290 291 292 293 294 295
	}

	return CONTENT_TRANFER_ENCODING_NONE;
}

/* ToDo: parse and return the "modification-date" value */
296
static BOOL mime_getattachment(const char* beg, const char* end, char* attachment, size_t attachment_len)
297
{
298
	char fname[MAX_PATH+1];
299
	const char* p = beg;
300 301 302

	while(p < end) {
		SKIP_WHITESPACE(p);
303 304
		size_t len = strStartsWith_i(p, "content-disposition:");
		if(len < 1) {
305 306 307
			FIND_CHAR(p, '\n');
			continue;
		}
308
		p += len;
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
		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, '"');
324 325 326
		} else {
			char* wsp = filename;
			FIND_WHITESPACE(wsp);
327
			term = strchr(filename, ';');
328 329 330
			if(term > wsp)
				term = wsp;
		}
331 332 333 334
		if(term == NULL) {
			term = filename;
			FIND_WHITESPACE(term);
		}
335 336
		if(term - filename >= sizeof(fname))
			term = filename + sizeof(fname) - 1;
337 338
		memcpy(fname, filename, term - filename);
		fname[term - filename] = 0;
339 340
		if(attachment != NULL && attachment_len > 0) {
			strncpy(attachment, getfname(fname), attachment_len);
341
			attachment[attachment_len - 1] = '\0';
342
		}
343 344 345 346 347
		return TRUE;
	}
	return FALSE;
}

348
// Parses a MIME text/* content-type header field
349
void smb_parse_content_type(const char* content_type, char** subtype, char** charset)
350 351 352 353 354 355 356 357 358 359 360 361
{
	if(subtype != NULL) {
		FREE_AND_NULL(*subtype);
	}
	if(charset != NULL) {
		FREE_AND_NULL(*charset);
	}
	if(content_type == NULL)
		return;
	char buf[512];
	SAFECOPY(buf, content_type);
	char* p;
362 363
	if((p = strstr(buf, "\r\n\r\n")) != NULL)	/* Don't parse past the end of header */
		*p = 0;
364 365 366
	size_t len = strStartsWith_i(buf, "text/");
	if(len > 0) {
		p = buf + len;
367
		if(subtype != NULL) {
368 369 370 371 372 373 374 375
			if((*subtype = strdup(p)) != NULL) {
				char* tp = *subtype;
				FIND_WHITESPACE(tp);
				*tp = 0;
				tp = *subtype;
				FIND_CHAR(tp, ';');
				*tp = 0;
			}
376
		}
377 378
		char* parms = p;
		if(charset != NULL && ((p = strcasestr(parms, " charset=")) != NULL || (p = strcasestr(parms, ";charset=")) != NULL)) {
379 380 381 382
			BOOL quoted = FALSE;
			p += 9;
			if(*p == '"') {
				quoted = TRUE;
383
				p++;
384
			}
385 386 387 388
			char* tp = p;
			FIND_WHITESPACE(tp);
			*tp = 0;
			tp = p;
389 390 391 392 393
			if(quoted) {
				FIND_CHAR(tp, '"');
			} else {
				FIND_CHAR(tp, ';');
			}
394 395 396 397 398 399
			*tp = 0;
			*charset = strdup(p);
		}
	}
}

400 401
/* Find the specified content-type in a multi-part MIME-encoded message body, recursively */
static const char* mime_getpart(const char* buf, const char* content_type, const char* content_match
402
	,int depth, enum content_transfer_encoding* encoding, char** charset, char* attachment, size_t attachment_len, int index)
rswindell's avatar
rswindell committed
403
{
404
	const char*	txt;
rswindell's avatar
rswindell committed
405 406
	char*	p;
	char	boundary[256];
407 408 409
	char	match1[128];
	char	match2[128];
	int		match_len = 0;
410
	int		found = 0;
411

412
	if(content_match != NULL) {
413 414
		match_len = sprintf(match1, "%s;", content_match);
					sprintf(match2, "%s\r", content_match);
415
	}
rswindell's avatar
rswindell committed
416

417 418
	if(depth > 2)
		return NULL;
rswindell's avatar
rswindell committed
419
	if(content_type == NULL)	/* Not MIME-encoded */
420
		return NULL;
421 422 423 424 425
	size_t len;
	if(((len = strStartsWith_i(content_type, "multipart/alternative;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/mixed;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/report;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/")) < 1))
426
		return NULL;
427 428
	content_type += len;
	p = strcasestr(content_type, "boundary=");
rswindell's avatar
rswindell committed
429
	if(p == NULL)
430
		return NULL;
431 432 433 434 435 436
	p += 9;
	if(*p == '"')
		p++;
	SAFEPRINTF(boundary, "--%s", p);
	if((p = strchr(boundary,'"')) != NULL)
		*p = 0;
rswindell's avatar
rswindell committed
437 438 439
	txt = buf;
	while((p = strstr(txt, boundary)) != NULL) {
		txt = p+strlen(boundary);
440 441
		if(strncmp(txt, "--\r\n", 4) == 0)
			break;
rswindell's avatar
rswindell committed
442 443 444 445
		SKIP_WHITESPACE(txt);
		p = strstr(txt, "\r\n\r\n");	/* End of header */
		if(p==NULL)
			continue;
446 447
		for(content_type = txt; content_type < p; content_type++) {
			SKIP_WHITESPACE(content_type);
448 449
			if((len = strStartsWith_i(content_type, "Content-Type:")) > 0) {
				content_type += len;
450
				SKIP_WHITESPACE(content_type);
451
				break;
452
			}
453 454 455 456
			FIND_CHAR(content_type, '\r');
		}
		if(content_type >= p)
			continue;
457
		const char* cp;
458
		if((match_len && strnicmp(content_type, match1, match_len) && strnicmp(content_type, match2, match_len))
459
			|| (attachment != NULL && !mime_getattachment(txt, p, attachment, attachment_len))) {
460
			if((cp = mime_getpart(p, content_type, content_match, depth + 1, encoding, charset, attachment, attachment_len, index)) != NULL)
461
				return cp;
462 463 464
			continue;
		}
		if(found++ != index) {
465
			if((cp = mime_getpart(p, content_type, content_match, depth + 1, encoding, charset, attachment, attachment_len, index)) != NULL)
466
				return cp;
467 468
			continue;
		}
469 470
		if(encoding != NULL)
			*encoding = mime_getxferencoding(txt, p);
471 472
		if(charset != NULL)
			smb_parse_content_type(content_type, NULL, charset);
473

474
		txt = p + 4;	// strlen("\r\n\r\n")
rswindell's avatar
rswindell committed
475 476 477
		SKIP_WHITESPACE(txt);
		if((p = strstr(txt, boundary)) != NULL)
			*p = 0;
478
		return txt;
rswindell's avatar
rswindell committed
479
	}
480 481 482
	return NULL;
}

483 484
/* Get just the (first) plain-text or HTML portion of a MIME-encoded multi-part message body */
/* Returns NULL if there is no MIME-encoded plain-text/html portion of the message */
485
char* smb_getplaintext(smbmsg_t* msg, char* buf)
486
{
487
	size_t len;
488
	const char*	txt;
489
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;
490

491
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
492
		return NULL;
493 494
	if(strStartsWith_i(msg->content_type, "multipart/") > 0) {
		txt = mime_getpart(buf, msg->content_type, "text/plain", 0, &xfer_encoding, &msg->text_charset
495
			,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
496 497 498 499 500 501 502 503 504 505 506 507 508 509
		if(txt == NULL) {
			txt = mime_getpart(buf, msg->content_type, "text/html", 0, &xfer_encoding, &msg->text_charset
				,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
			if(txt == NULL)
				return NULL;
			free(msg->text_subtype);
			msg->text_subtype = strdup("html");
		} else {
			free(msg->text_subtype);
			msg->text_subtype = strdup("plain");
		}

		len = strlen(txt);
		memmove(buf, txt, len + 1);
510
	} else {
511 512 513 514 515 516 517
		/* Single-part MIME */
		if(strStartsWith_i(msg->content_type, "text/") < 1) {
			*buf = '\0';
			return buf;
		}
		xfer_encoding = mime_encoding(msg->content_encoding);
		len = strlen(buf);
518
	}
519
	if(len == 0)	/* No decoding necessary */
520 521 522 523 524 525 526
		return buf;
	if(xfer_encoding == CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE)
		qp_decode(buf);
	else if(xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
		char* decoded = strdup(buf);
		if(decoded == NULL)
			return NULL;
527
		if(b64_decode(decoded, len, buf, len) > 0)
528 529
			strcpy(buf, decoded);
		free(decoded);
530
	}
rswindell's avatar
rswindell committed
531 532
	return buf;
}
533

534 535 536
/* Get an attachment (just one) from single-part or multi-part MIME-encoded message body */
/* This function can be destructive (over-write 'buf' with decoded attachment)! */
uint8_t* smb_getattachment(smbmsg_t* msg, char* buf, char* filename, size_t filename_len, uint32_t* filelen, int index)
537
{
538
	const char*	txt;
539 540
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;

541
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
542
		return NULL;
543 544 545
	if(strStartsWith_i(msg->content_type, "multipart/") > 0) {
		txt = mime_getpart(buf, msg->content_type, /* match-type: */NULL, 0, &xfer_encoding, /* charset: */NULL
			,/* attachment: */filename, filename_len, index);
546
		if(txt != NULL && *txt && xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
			size_t len = strlen(txt);
			memmove(buf, txt, len + 1);
			int result = b64_decode(buf, len, buf, len);
			if(result < 1)
				return NULL;
			if(filelen != NULL)
				*filelen = result;
			return (uint8_t*)buf;
		}
		return NULL;	/* No attachment */
	}
	/* Single-part MIME */
	if(index > 0)
		return NULL;
	if(strStartsWith_i(msg->content_type, "text/") > 0)
		return NULL;
	if(filename != NULL) {
		char* fname = strstr(msg->content_type, "name=");
		if(fname == NULL)
			return NULL;
		fname += 5;
		char* term = NULL;
		if(*fname == '"') {
			fname++;
			term = strchr(fname, '"');
		} else
			term = strchr(fname, ';');
		if(term != NULL)
			*term = '\0';
		strncpy(filename, fname, filename_len);
		filename[filename_len - 1] = '\0';
	}
	if(mime_encoding(msg->content_encoding) == CONTENT_TRANFER_ENCODING_BASE64) {
		size_t len = strlen(buf);
		int result = b64_decode(buf, len, buf, len);
582 583
		if(result < 1)
			return NULL;
584 585
		if(filelen != NULL)
			*filelen = result;
586 587 588
	} else {
		if(filelen != NULL)
			*filelen = strlen(buf);
589
	}
590
	return (uint8_t*)buf;
591
}
592 593 594

/* Return number of file attachments contained in MIME-encoded message body */
/* 'body' may be NULL if the body text is not already read/available */
595
ulong smb_countattachments(smb_t* smb, smbmsg_t* msg, const char* body)
596
{
597
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
		return 0;

	ulong count = 0;
	char* buf;

	if(body == NULL)
		buf = smb_getmsgtxt(smb, msg, GETMSGTXT_ALL);
	else
		buf = strdup(body);

	if(buf == NULL)
		return 0;

	char* tmp;
	while((tmp = strdup(buf)) != NULL) {
613 614
		char filename[MAX_PATH + 1];
		uint8_t* attachment = smb_getattachment(msg, tmp, filename, sizeof(filename), NULL, count);
615 616 617 618 619 620 621 622 623
		free(tmp);
		if(attachment == NULL)
			break;
		count++;
	}

	free(buf);
	return count;
}