istream.c 17.6 KB
Newer Older
1
/* $Id: istream.c,v 1.27 2010/07/18 13:43:23 htrb Exp $ */
2 3 4 5 6 7 8
#include "fm.h"
#include "myctype.h"
#include "istream.h"
#include <signal.h>
#ifdef USE_SSL
#include <openssl/x509v3.h>
#endif
9 10 11
#ifdef __MINGW32_VERSION
#include <winsock.h>
#endif
12 13 14 15 16 17 18 19 20 21 22 23 24

#define	uchar		unsigned char

#define STREAM_BUF_SIZE 8192
#define SSL_BUF_SIZE	1536

#define MUST_BE_UPDATED(bs) ((bs)->stream.cur==(bs)->stream.next)

#define POP_CHAR(bs) ((bs)->iseos?'\0':(bs)->stream.buf[(bs)->stream.cur++])

static void basic_close(int *handle);
static int basic_read(int *handle, char *buf, int len);

25 26
static void file_close(struct io_file_handle *handle);
static int file_read(struct io_file_handle *handle, char *buf, int len);
27 28 29 30 31 32 33 34 35 36 37

static int str_read(Str handle, char *buf, int len);

#ifdef USE_SSL
static void ssl_close(struct ssl_handle *handle);
static int ssl_read(struct ssl_handle *handle, char *buf, int len);
#endif

static int ens_read(struct ens_handle *handle, char *buf, int len);
static void ens_close(struct ens_handle *handle);

38 39
static void memchop(char *p, int *len);

40 41 42 43 44
static void
do_update(BaseStream base)
{
    int len;
    base->stream.cur = base->stream.next = 0;
45
    len = (*base->read) (base->handle, base->stream.buf, base->stream.size);
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    if (len <= 0)
	base->iseos = TRUE;
    else
	base->stream.next += len;
}

static int
buffer_read(StreamBuffer sb, char *obuf, int count)
{
    int len = sb->next - sb->cur;
    if (len > 0) {
	if (len > count)
	    len = count;
	bcopy((const void *)&sb->buf[sb->cur], obuf, len);
	sb->cur += len;
    }
    return len;
}

static void
init_buffer(BaseStream base, char *buf, int bufsize)
{
    StreamBuffer sb = &base->stream;
    sb->size = bufsize;
    sb->cur = 0;
71
    sb->buf = NewWithoutGC_N(uchar, bufsize);
72
    if (buf) {
73
	memcpy(sb->buf, buf, bufsize);
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
	sb->next = bufsize;
    }
    else {
	sb->next = 0;
    }
    base->iseos = FALSE;
}

static void
init_base_stream(BaseStream base, int bufsize)
{
    init_buffer(base, NULL, bufsize);
}

static void
init_str_stream(BaseStream base, Str s)
{
    init_buffer(base, s->ptr, s->length);
}

InputStream
newInputStream(int des)
{
    InputStream stream;
    if (des < 0)
	return NULL;
100
    stream = NewWithoutGC(union input_stream);
101 102
    init_base_stream(&stream->base, STREAM_BUF_SIZE);
    stream->base.type = IST_BASIC;
103
    stream->base.handle = NewWithoutGC(int);
104 105 106 107 108 109 110 111 112 113 114 115
    *(int *)stream->base.handle = des;
    stream->base.read = (int (*)())basic_read;
    stream->base.close = (void (*)())basic_close;
    return stream;
}

InputStream
newFileStream(FILE * f, void (*closep) ())
{
    InputStream stream;
    if (f == NULL)
	return NULL;
116
    stream = NewWithoutGC(union input_stream);
117 118
    init_base_stream(&stream->base, STREAM_BUF_SIZE);
    stream->file.type = IST_FILE;
119
    stream->file.handle = NewWithoutGC(struct io_file_handle);
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    stream->file.handle->f = f;
    if (closep)
	stream->file.handle->close = closep;
    else
	stream->file.handle->close = (void (*)())fclose;
    stream->file.read = (int (*)())file_read;
    stream->file.close = (void (*)())file_close;
    return stream;
}

InputStream
newStrStream(Str s)
{
    InputStream stream;
    if (s == NULL)
	return NULL;
136
    stream = NewWithoutGC(union input_stream);
137 138
    init_str_stream(&stream->base, s);
    stream->str.type = IST_STR;
139
    stream->str.handle = NULL;
140 141 142 143 144 145 146 147 148 149 150 151
    stream->str.read = (int (*)())str_read;
    stream->str.close = NULL;
    return stream;
}

#ifdef USE_SSL
InputStream
newSSLStream(SSL * ssl, int sock)
{
    InputStream stream;
    if (sock < 0)
	return NULL;
152
    stream = NewWithoutGC(union input_stream);
153 154
    init_base_stream(&stream->base, SSL_BUF_SIZE);
    stream->ssl.type = IST_SSL;
155
    stream->ssl.handle = NewWithoutGC(struct ssl_handle);
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    stream->ssl.handle->ssl = ssl;
    stream->ssl.handle->sock = sock;
    stream->ssl.read = (int (*)())ssl_read;
    stream->ssl.close = (void (*)())ssl_close;
    return stream;
}
#endif

InputStream
newEncodedStream(InputStream is, char encoding)
{
    InputStream stream;
    if (is == NULL || (encoding != ENC_QUOTE && encoding != ENC_BASE64 &&
		       encoding != ENC_UUENCODE))
	return is;
171
    stream = NewWithoutGC(union input_stream);
172 173
    init_base_stream(&stream->base, STREAM_BUF_SIZE);
    stream->ens.type = IST_ENCODED;
174
    stream->ens.handle = NewWithoutGC(struct ens_handle);
175 176 177
    stream->ens.handle->is = is;
    stream->ens.handle->pos = 0;
    stream->ens.handle->encoding = encoding;
178
    growbuf_init_without_GC(&stream->ens.handle->gb);
179 180 181 182 183 184 185 186 187 188 189 190 191
    stream->ens.read = (int (*)())ens_read;
    stream->ens.close = (void (*)())ens_close;
    return stream;
}

int
ISclose(InputStream stream)
{
    MySignalHandler(*prevtrap) ();
    if (stream == NULL || stream->base.close == NULL ||
	stream->base.type & IST_UNCLOSE)
	return -1;
    prevtrap = mySignal(SIGINT, SIG_IGN);
192
    stream->base.close (stream->base.handle);
193
    mySignal(SIGINT, prevtrap);
194 195
    xfree(stream->base.stream.buf);
    xfree(stream);
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    return 0;
}

int
ISgetc(InputStream stream)
{
    BaseStream base;
    if (stream == NULL)
	return '\0';
    base = &stream->base;
    if (!base->iseos && MUST_BE_UPDATED(base))
	do_update(base);
    return POP_CHAR(base);
}

int
ISundogetc(InputStream stream)
{
    StreamBuffer sb;
    if (stream == NULL)
	return -1;
    sb = &stream->base.stream;
    if (sb->cur > 0) {
	sb->cur--;
	return 0;
    }
    return -1;
}

Str
226
StrISgets2(InputStream stream, char crnl)
227
{
228
    struct growbuf gb;
229 230

    if (stream == NULL)
231 232 233 234
	return NULL;
    growbuf_init(&gb);
    ISgets_to_growbuf(stream, &gb, crnl);
    return growbuf_to_Str(&gb);
235 236
}

237 238
void
ISgets_to_growbuf(InputStream stream, struct growbuf *gb, char crnl)
239
{
240 241 242
    BaseStream base = &stream->base;
    StreamBuffer sb = &base->stream;
    int i;
243

244
    gb->length = 0;
245 246 247 248

    while (!base->iseos) {
	if (MUST_BE_UPDATED(base)) {
	    do_update(base);
249
	    continue;
250
	}
251 252 253 254
	if (crnl && gb->length > 0  && gb->ptr[gb->length - 1] == '\r') {
	    if (sb->buf[sb->cur] == '\n') {
		GROWBUF_ADD_CHAR(gb, '\n');
		++sb->cur;
255
	    }
256 257 258 259 260 261
	    break;
	}
	for (i = sb->cur; i < sb->next; ++i) {
	    if (sb->buf[i] == '\n' || (crnl && sb->buf[i] == '\r')) {
		++i;
		break;
262 263
	    }
	}
264 265 266 267
	growbuf_append(gb, &sb->buf[sb->cur], i - sb->cur);
	sb->cur = i;
	if (gb->length > 0 && gb->ptr[gb->length - 1] == '\n')
	    break;
268 269
    }

270 271 272
    growbuf_reserve(gb, gb->length + 1);
    gb->ptr[gb->length] = '\0';
    return;
273 274
}

275
#ifdef unused
276 277 278
int
ISread(InputStream stream, Str buf, int count)
{
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
    int len;

    if (count + 1 > buf->area_size) {
	char *newptr = GC_MALLOC_ATOMIC(count + 1);
	memcpy(newptr, buf->ptr, buf->length);
	newptr[buf->length] = '\0';
	buf->ptr = newptr;
	buf->area_size = count + 1;
    }
    len = ISread_n(stream, buf->ptr, count);
    buf->length = (len > 0) ? len : 0;
    buf->ptr[buf->length] = '\0';
    return (len > 0) ? 1 : 0;
}
#endif

int
ISread_n(InputStream stream, char *dst, int count)
{
    int len, l;
299 300
    BaseStream base;

301 302 303
    if (stream == NULL || count <= 0)
	return -1;
    if ((base = &stream->base)->iseos)
304 305
	return 0;

306
    len = buffer_read(&base->stream, dst, count);
307
    if (MUST_BE_UPDATED(base)) {
308 309
	l = (*base->read) (base->handle, &dst[len], count - len);
	if (l <= 0) {
310
	    base->iseos = TRUE;
311 312
	} else {
	    len += l;
313 314
	}
    }
315
    return len;
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
}

int
ISfileno(InputStream stream)
{
    if (stream == NULL)
	return -1;
    switch (IStype(stream) & ~IST_UNCLOSE) {
    case IST_BASIC:
	return *(int *)stream->base.handle;
    case IST_FILE:
	return fileno(stream->file.handle->f);
#ifdef USE_SSL
    case IST_SSL:
	return stream->ssl.handle->sock;
#endif
    case IST_ENCODED:
	return ISfileno(stream->ens.handle->is);
    default:
	return -1;
    }
}

int
ISeos(InputStream stream)
{
    BaseStream base = &stream->base;
    if (!base->iseos && MUST_BE_UPDATED(base))
	do_update(base);
    return base->iseos;
}

#ifdef USE_SSL
static Str accept_this_site;

void
ssl_accept_this_site(char *hostname)
{
    if (hostname)
	accept_this_site = Strnew_charp(hostname);
    else
	accept_this_site = NULL;
}

static int
ssl_match_cert_ident(char *ident, int ilen, char *hostname)
{
    /* RFC2818 3.1.  Server Identity
     * Names may contain the wildcard
     * character * which is considered to match any single domain name
     * component or component fragment. E.g., *.a.com matches foo.a.com but
     * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
     */
    int hlen = strlen(hostname);
    int i, c;

    /* Is this an exact match? */
    if ((ilen == hlen) && strncasecmp(ident, hostname, hlen) == 0)
	return TRUE;

    for (i = 0; i < ilen; i++) {
	if (ident[i] == '*' && ident[i + 1] == '.') {
	    while ((c = *hostname++) != '\0')
		if (c == '.')
		    break;
	    i++;
	}
	else {
	    if (ident[i] != *hostname++)
		return FALSE;
	}
    }
    return *hostname == '\0';
}

static Str
ssl_check_cert_ident(X509 * x, char *hostname)
{
    int i;
    Str ret = NULL;
    int match_ident = FALSE;
    /*
     * All we need to do here is check that the CN matches.
     *
     * From RFC2818 3.1 Server Identity:
     * If a subjectAltName extension of type dNSName is present, that MUST
     * be used as the identity. Otherwise, the (most specific) Common Name
     * field in the Subject field of the certificate MUST be used. Although
     * the use of the Common Name is existing practice, it is deprecated and
     * Certification Authorities are encouraged to use the dNSName instead.
     */
    i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
    if (i >= 0) {
	X509_EXTENSION *ex;
	STACK_OF(GENERAL_NAME) * alt;

	ex = X509_get_ext(x, i);
	alt = X509V3_EXT_d2i(ex);
	if (alt) {
	    int n;
	    GENERAL_NAME *gn;
	    X509V3_EXT_METHOD *method;
	    Str seen_dnsname = NULL;

	    n = sk_GENERAL_NAME_num(alt);
	    for (i = 0; i < n; i++) {
		gn = sk_GENERAL_NAME_value(alt, i);
		if (gn->type == GEN_DNS) {
424
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
425
		    char *sn = ASN1_STRING_data(gn->d.ia5);
426
#else
Tatsuya Kinoshita's avatar
Tatsuya Kinoshita committed
427
		    char *sn = ASN1_STRING_get0_data(gn->d.ia5);
428
#endif
429 430 431 432
		    int sl = ASN1_STRING_length(gn->d.ia5);

		    if (!seen_dnsname)
			seen_dnsname = Strnew();
433 434 435 436 437 438 439 440
		    /* replace \0 to make full string visible to user */
		    if (sl != strlen(sn)) {
			int i;
			for (i = 0; i < sl; ++i) {
			    if (!sn[i])
				sn[i] = '!';
			}
		    }
441
		    Strcat_m_charp(seen_dnsname, sn, " ", NULL);
442 443
		    if (sl == strlen(sn) /* catch \0 in SAN */
			&& ssl_match_cert_ident(sn, sl, hostname))
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
			break;
		}
	    }
	    method = X509V3_EXT_get(ex);
	    sk_GENERAL_NAME_free(alt);
	    if (i < n)		/* Found a match */
		match_ident = TRUE;
	    else if (seen_dnsname)
		/* FIXME: gettextize? */
		ret = Sprintf("Bad cert ident from %s: dNSName=%s", hostname,
			      seen_dnsname->ptr);
	}
    }

    if (match_ident == FALSE && ret == NULL) {
	X509_NAME *xn;
	char buf[2048];
461
	int slen;
462 463 464

	xn = X509_get_subject_name(x);

465 466
	slen = X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf));
	if ( slen == -1)
467 468
	    /* FIXME: gettextize? */
	    ret = Strnew_charp("Unable to get common name from peer cert");
469 470 471 472 473 474 475 476 477 478
	else if (slen != strlen(buf)
		|| !ssl_match_cert_ident(buf, strlen(buf), hostname)) {
	    /* replace \0 to make full string visible to user */
	    if (slen != strlen(buf)) {
		int i;
		for (i = 0; i < slen; ++i) {
		    if (!buf[i])
			buf[i] = '!';
		}
	    }
479 480
	    /* FIXME: gettextize? */
	    ret = Sprintf("Bad cert ident %s from %s", buf, hostname);
481
	}
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 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 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
    }
    return ret;
}

Str
ssl_get_certificate(SSL * ssl, char *hostname)
{
    BIO *bp;
    X509 *x;
    X509_NAME *xn;
    char *p;
    int len;
    Str s;
    char buf[2048];
    Str amsg = NULL;
    Str emsg;
    char *ans;

    if (ssl == NULL)
	return NULL;
    x = SSL_get_peer_certificate(ssl);
    if (x == NULL) {
	if (accept_this_site
	    && strcasecmp(accept_this_site->ptr, hostname) == 0)
	    ans = "y";
	else {
	    /* FIXME: gettextize? */
	    emsg = Strnew_charp("No SSL peer certificate: accept? (y/n)");
	    ans = inputAnswer(emsg->ptr);
	}
	if (ans && TOLOWER(*ans) == 'y')
	    /* FIXME: gettextize? */
	    amsg = Strnew_charp
		("Accept SSL session without any peer certificate");
	else {
	    /* FIXME: gettextize? */
	    char *e = "This SSL session was rejected "
		"to prevent security violation: no peer certificate";
	    disp_err_message(e, FALSE);
	    free_ssl_ctx();
	    return NULL;
	}
	if (amsg)
	    disp_err_message(amsg->ptr, FALSE);
	ssl_accept_this_site(hostname);
	/* FIXME: gettextize? */
	s = amsg ? amsg : Strnew_charp("valid certificate");
	return s;
    }
#ifdef USE_SSL_VERIFY
    /* check the cert chain.
     * The chain length is automatically checked by OpenSSL when we
     * set the verify depth in the ctx.
     */
    if (ssl_verify_server) {
	long verr;
	if ((verr = SSL_get_verify_result(ssl))
	    != X509_V_OK) {
	    const char *em = X509_verify_cert_error_string(verr);
	    if (accept_this_site
		&& strcasecmp(accept_this_site->ptr, hostname) == 0)
		ans = "y";
	    else {
		/* FIXME: gettextize? */
		emsg = Sprintf("%s: accept? (y/n)", em);
		ans = inputAnswer(emsg->ptr);
	    }
	    if (ans && TOLOWER(*ans) == 'y') {
		/* FIXME: gettextize? */
		amsg = Sprintf("Accept unsecure SSL session: "
			       "unverified: %s", em);
	    }
	    else {
		/* FIXME: gettextize? */
		char *e =
		    Sprintf("This SSL session was rejected: %s", em)->ptr;
		disp_err_message(e, FALSE);
		free_ssl_ctx();
		return NULL;
	    }
	}
    }
#endif
    emsg = ssl_check_cert_ident(x, hostname);
    if (emsg != NULL) {
	if (accept_this_site
	    && strcasecmp(accept_this_site->ptr, hostname) == 0)
	    ans = "y";
	else {
	    Str ep = Strdup(emsg);
	    if (ep->length > COLS - 16)
		Strshrink(ep, ep->length - (COLS - 16));
	    Strcat_charp(ep, ": accept? (y/n)");
	    ans = inputAnswer(ep->ptr);
	}
	if (ans && TOLOWER(*ans) == 'y') {
	    /* FIXME: gettextize? */
	    amsg = Strnew_charp("Accept unsecure SSL session:");
	    Strcat(amsg, emsg);
	}
	else {
	    /* FIXME: gettextize? */
	    char *e = "This SSL session was rejected "
		"to prevent security violation";
	    disp_err_message(e, FALSE);
	    free_ssl_ctx();
	    return NULL;
	}
    }
    if (amsg)
	disp_err_message(amsg->ptr, FALSE);
    ssl_accept_this_site(hostname);
    /* FIXME: gettextize? */
    s = amsg ? amsg : Strnew_charp("valid certificate");
    Strcat_charp(s, "\n");
    xn = X509_get_subject_name(x);
    if (X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)) == -1)
	Strcat_charp(s, " subject=<unknown>");
    else
	Strcat_m_charp(s, " subject=", buf, NULL);
    xn = X509_get_issuer_name(x);
    if (X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)) == -1)
	Strcat_charp(s, ": issuer=<unknown>");
    else
	Strcat_m_charp(s, ": issuer=", buf, NULL);
    Strcat_charp(s, "\n\n");

    bp = BIO_new(BIO_s_mem());
    X509_print(bp, x);
    len = (int)BIO_ctrl(bp, BIO_CTRL_INFO, 0, (char *)&p);
    Strcat_charp_n(s, p, len);
    BIO_free_all(bp);
    X509_free(x);
    return s;
}
#endif

/* Raw level input stream functions */

static void
basic_close(int *handle)
{
624 625 626
#ifdef __MINGW32_VERSION
    closesocket(*(int *)handle);
#else
627
    close(*(int *)handle);
628
#endif
629
    xfree(handle);
630 631 632 633 634
}

static int
basic_read(int *handle, char *buf, int len)
{
635 636 637
#ifdef __MINGW32_VERSION
    return recv(*(int *)handle, buf, len, 0);
#else
638
    return read(*(int *)handle, buf, len);
639
#endif
640 641 642
}

static void
643
file_close(struct io_file_handle *handle)
644 645
{
    handle->close(handle->f);
646
    xfree(handle);
647 648 649
}

static int
650
file_read(struct io_file_handle *handle, char *buf, int len)
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
{
    return fread(buf, 1, len, handle->f);
}

static int
str_read(Str handle, char *buf, int len)
{
    return 0;
}

#ifdef USE_SSL
static void
ssl_close(struct ssl_handle *handle)
{
    close(handle->sock);
    if (handle->ssl)
	SSL_free(handle->ssl);
668
    xfree(handle);
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
}

static int
ssl_read(struct ssl_handle *handle, char *buf, int len)
{
    int status;
    if (handle->ssl) {
#ifdef USE_SSL_VERIFY
	for (;;) {
	    status = SSL_read(handle->ssl, buf, len);
	    if (status > 0)
		break;
	    switch (SSL_get_error(handle->ssl, status)) {
	    case SSL_ERROR_WANT_READ:
	    case SSL_ERROR_WANT_WRITE:	/* reads can trigger write errors; see SSL_get_error(3) */
		continue;
	    default:
		break;
	    }
	    break;
	}
#else				/* if !defined(USE_SSL_VERIFY) */
	status = SSL_read(handle->ssl, buf, len);
#endif				/* !defined(USE_SSL_VERIFY) */
    }
    else
	status = read(handle->sock, buf, len);
    return status;
}
#endif				/* USE_SSL */

static void
ens_close(struct ens_handle *handle)
{
    ISclose(handle->is);
704 705
    growbuf_clear(&handle->gb);
    xfree(handle);
706 707 708 709 710
}

static int
ens_read(struct ens_handle *handle, char *buf, int len)
{
711
    if (handle->pos == handle->gb.length) {
712
	char *p;
713 714 715 716
	struct growbuf gbtmp;

	ISgets_to_growbuf(handle->is, &handle->gb, TRUE);
	if (handle->gb.length == 0)
717 718
	    return 0;
	if (handle->encoding == ENC_BASE64)
719
	    memchop(handle->gb.ptr, &handle->gb.length);
720
	else if (handle->encoding == ENC_UUENCODE) {
721 722 723 724
	    if (handle->gb.length >= 5 &&
		!strncmp(handle->gb.ptr, "begin", 5))
		ISgets_to_growbuf(handle->is, &handle->gb, TRUE);
	    memchop(handle->gb.ptr, &handle->gb.length);
725
	}
726 727
	growbuf_init_without_GC(&gbtmp);
	p = handle->gb.ptr;
728
	if (handle->encoding == ENC_QUOTE)
729
	    decodeQP_to_growbuf(&gbtmp, &p);
730
	else if (handle->encoding == ENC_BASE64)
731
	    decodeB_to_growbuf(&gbtmp, &p);
732
	else if (handle->encoding == ENC_UUENCODE)
733 734 735
	    decodeU_to_growbuf(&gbtmp, &p);
	growbuf_clear(&handle->gb);
	handle->gb = gbtmp;
736 737 738
	handle->pos = 0;
    }

739 740
    if (len > handle->gb.length - handle->pos)
	len = handle->gb.length - handle->pos;
741

742
    memcpy(buf, &handle->gb.ptr[handle->pos], len);
743 744 745
    handle->pos += len;
    return len;
}
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760

static void
memchop(char *p, int *len)
{
    char *q;

    for (q = p + *len; q > p; --q) {
	if (q[-1] != '\n' && q[-1] != '\r')
	    break;
    }
    if (q != p + *len)
	*q = '\0';
    *len = q - p;
    return;
}