smbtxt.c 16.5 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
		sprintf(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
58
	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
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
69
		if(l) {	/* Add a blank line after comments */
			if((p=(char*)realloc(buf,l+3))==NULL) {
				sprintf(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
85
				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
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
				sprintf(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
141
142
143
144
145
			if(smb_fread(smb,lzhbuf,length,smb->sdt_fp) != length) {
				sprintf(smb->last_error
					,"%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
				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
				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
				sprintf(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
			} else {	/* bad encoding */
				*dest++='=';
				*dest++=*p;
			}
		}
	}
250
251
	*dest++='\r';
	*dest++='\n';
252
253
254
255
	*dest=0;
	return buf;
}

256
257
258
259
260
261
262
263
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;
}

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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;
}

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

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

	return CONTENT_TRANFER_ENCODING_NONE;
}

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

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

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

402
403
/* 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
404
	,int depth, enum content_transfer_encoding* encoding, char** charset, char* attachment, size_t attachment_len, int index)
rswindell's avatar
rswindell committed
405
{
406
	const char*	txt;
rswindell's avatar
rswindell committed
407
408
	char*	p;
	char	boundary[256];
409
410
411
	char	match1[128];
	char	match2[128];
	int		match_len = 0;
412
	int		found = 0;
413

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

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

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

485
486
/* 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 */
487
char* smb_getplaintext(smbmsg_t* msg, char* buf)
488
{
489
	size_t len;
490
	const char*	txt;
491
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;
492

493
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
494
		return NULL;
495
496
	if(strStartsWith_i(msg->content_type, "multipart/") > 0) {
		txt = mime_getpart(buf, msg->content_type, "text/plain", 0, &xfer_encoding, &msg->text_charset
497
			,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
498
499
500
501
502
503
504
505
506
507
508
509
510
511
		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);
512
	} else {
513
514
515
516
517
518
519
		/* 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);
520
	}
521
	if(len == 0)	/* No decoding necessary */
522
523
524
525
526
527
528
		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;
529
		if(b64_decode(decoded, len, buf, len) > 0)
530
531
			strcpy(buf, decoded);
		free(decoded);
532
	}
rswindell's avatar
rswindell committed
533
534
	return buf;
}
535

536
537
538
/* 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)
539
{
540
	const char*	txt;
541
542
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;

543
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
544
		return NULL;
545
546
547
	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);
548
		if(txt != NULL && *txt && xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
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
582
583
			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);
584
585
		if(result < 1)
			return NULL;
586
587
		if(filelen != NULL)
			*filelen = result;
588
589
590
	} else {
		if(filelen != NULL)
			*filelen = strlen(buf);
591
	}
592
	return (uint8_t*)buf;
593
}
594
595
596

/* Return number of file attachments contained in MIME-encoded message body */
/* 'body' may be NULL if the body text is not already read/available */
597
ulong smb_countattachments(smb_t* smb, smbmsg_t* msg, const char* body)
598
{
599
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
		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) {
615
616
		char filename[MAX_PATH + 1];
		uint8_t* attachment = smb_getattachment(msg, tmp, filename, sizeof(filename), NULL, count);
617
618
619
620
621
622
623
624
625
		free(tmp);
		if(attachment == NULL)
			break;
		count++;
	}

	free(buf);
	return count;
}