Example #1
0
char *htmlquoted(char *s)
{
	/*
	 * This routine converts a plain string into an html-quoted string
	 */

	static strbuffer_t *result = NULL;
	char *inp, *endp;
	char c;

	if (!result) result = newstrbuffer(4096);
	clearstrbuffer(result);

	inp = s;
	do {
		endp = inp + strcspn(inp, "\"&<> ");
		c = *endp;
		if (endp > inp) addtobufferraw(result, inp, endp-inp);
		switch (c) {
		  case '"': addtobuffer(result, "&quot;"); break;
		  case '&': addtobuffer(result, "&amp;"); break;
		  case '<': addtobuffer(result, "&lt;"); break;
		  case '>': addtobuffer(result, "&gt;"); break;
		  case ' ': addtobuffer(result, "&nbsp;"); break;
		  default: break;
		}
		inp = (c == '\0') ? NULL : endp+1;
	} while (inp);

	return STRBUF(result);
}
Example #2
0
enum conn_cbresult_t tcp_standard_callback(tcpconn_t *connection, enum conn_callback_t id, void *userdata)
{
	int res = CONN_CBRESULT_OK;
	int n, advancestep;
	size_t used;
	time_t start, expire;
	int keysize;
	char *certsubject, *issuer, *fulltext;
	myconn_t *rec = (myconn_t *)userdata;

	dbgprintf("CB: %s\n", conn_callback_names[id]);

	switch (id) {
	  case CONN_CB_CONNECT_START:          /* Client mode: New outbound connection start */
		break;

	  case CONN_CB_CONNECT_FAILED:         /* Client mode: New outbound connection failed */
		rec->talkresult = TALK_CONN_FAILED;
		rec->textlog = newstrbuffer(0);
		addtobuffer(rec->textlog, strerror(connection->errcode));
		conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_CONNECT_COMPLETE:       /* Client mode: New outbound connection succeded */
		rec->textlog = newstrbuffer(0);
		rec->talkresult = TALK_OK;		/* Will change below if we fail later */
		rec->readbufsz = USERBUFSZ;
		rec->readbuf = rec->readp = malloc(rec->readbufsz);
		*(rec->readbuf) = '\0';
		rec->writebuf = rec->writep = malloc(USERBUFSZ);
		*(rec->writebuf) = '\0';
		break;

	  case CONN_CB_SSLHANDSHAKE_OK:        /* Client/server mode: SSL handshake completed OK (peer certificate ready) */
		certsubject = conn_peer_certificate(connection, &start, &expire, &keysize, &issuer, &fulltext);
		if (certsubject) {
			rec->peercertificate = certsubject;	/* certsubject is malloc'ed by conn_peer_certificate */
			rec->peercertificateissuer = issuer;	/* ditto issuer */
			rec->peercertificatestart = start;
			rec->peercertificateexpiry = expire;
			rec->peercertificatekeysize = keysize;
			rec->peercertificatedetails = fulltext;
		}
		if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_SSLHANDSHAKE_FAILED:    /* Client/server mode: SSL handshake failed (connection will close) */
		rec->talkresult = TALK_BADSSLHANDSHAKE;
		conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_READCHECK:              /* Client/server mode: Check if application wants to read data */
		if (!rec->dialog[rec->step])
			res = CONN_CBRESULT_FAILED;
		else if (rec->istelnet > 0)
			res = CONN_CBRESULT_OK;
		else
			res = ((strncasecmp(rec->dialog[rec->step], "EXPECT:", 7) == 0) || (strncasecmp(rec->dialog[rec->step], "READ", 4) == 0)) ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED;
		break;

	  case CONN_CB_READ:                   /* Client/server mode: Ready for application to read data w/ conn_read() */
		/* Make sure we have some buffer space */
		used = (rec->readp - rec->readbuf);
		if ((rec->readbufsz - used) < USERBUFSZ) {
			rec->readbufsz += USERBUFSZ;
			rec->readbuf = (char *)realloc(rec->readbuf, rec->readbufsz);
			rec->readp = rec->readbuf + used;
		}
		/* Read the data */
		n = conn_read(connection, rec->readp, (rec->readbufsz - used - 1));
		if (n <= 0) return CONN_CBRESULT_OK;	/* n == 0 happens during SSL handshakes, n < 0 means connection will close */
		rec->bytesread += n;

		/* Process data for some protocols */
		if (rec->istelnet) n = telnet_datahandler(rec, n);
		if (rec->talkprotocol == TALK_PROTO_HTTP) n = http_datahandler(rec, n, 0, &advancestep);

		/* Save the data */
		if (n > 0) {
			*(rec->readp + n) = '\0';
			if (rec->talkprotocol == TALK_PROTO_PLAIN) addtobuffer(rec->textlog, rec->readp);
			rec->readp += n;
		}

		/* See how the dialog is progressing */
		if (strncasecmp(rec->dialog[rec->step], "EXPECT:", 7) == 0) {
			int explen, expstart;

			expstart = 7 + strspn(rec->dialog[rec->step] + 7, " \t");
			explen = strlen(rec->dialog[rec->step] + expstart);

			if ((n < explen) && (strncasecmp(rec->readbuf, rec->dialog[rec->step] + expstart, n) == 0)) {
				/* 
				 * Got the right data so far, but not the complete amount.
				 * Do nothing, we'll just keep reading until we have all of the data
				 */
			}
			else if (strncasecmp(rec->readbuf, rec->dialog[rec->step] + expstart, explen) == 0) {
				/* Got the expected data, go to next step */
				rec->step++;
				rec->readp = rec->readbuf;
				*rec->readp = '\0';
			}
			else {
				/* Got some unexpected data, give up */
				rec->talkresult = TALK_BADDATA;
				conn_close_connection(connection, NULL);
			}
		}
		else if (strcasecmp(rec->dialog[rec->step], "READALL") == 0) {
			/* No need to save the data twice (we store it in rec->textlog), so reset the readp to start of our readbuffer */
			rec->readp = rec->readbuf;
			*(rec->readp) = '\0';
			if (advancestep) rec->step++;
		}
		else if (strcasecmp(rec->dialog[rec->step], "READ") == 0) {
			rec->step++;
		}

		/* See if we have reached a point where we switch to TLS mode */
		if (rec->dialog[rec->step] && (strcasecmp(rec->dialog[rec->step], "STARTTLS") == 0)) {
			res = CONN_CBRESULT_STARTTLS;
			rec->step++;
		}

		/* See if we're done */
		if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_WRITECHECK:             /* Client/server mode: Check if application wants to write data */
		if (!rec->dialog[rec->step])
			res = CONN_CBRESULT_FAILED;
		else if (rec->istelnet != 0)
			res = (rec->istelnet < 0) ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED;
		else {
			if ((*rec->writep == '\0') && (strncasecmp(rec->dialog[rec->step], "SEND:", 5) == 0)) {
				char *sendstart = rec->dialog[rec->step] + 5;
				sendstart += strspn(sendstart, " \t");
				strcpy(rec->writebuf, sendstart);
				rec->writep = rec->writebuf;
			}
			res = (*rec->writep != '\0') ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED;
		}
		break;

	  case CONN_CB_WRITE:                  /* Client/server mode: Ready for application to write data w/ conn_write() */
		if (rec->istelnet < 0) {
			n = conn_write(connection, rec->writep, -(rec->istelnet));
			if (n <= 0) return CONN_CBRESULT_OK;	/* n == 0 happens during SSL handshakes, n < 0 means connection will close */
			rec->writep += n;
			rec->istelnet += n; if (rec->istelnet == 0) rec->istelnet = 1;
		}
		else {
			n = conn_write(connection, rec->writep, strlen(rec->writep));
			if (n <= 0) return CONN_CBRESULT_OK;	/* n == 0 happens during SSL handshakes, n < 0 means connection will close */
			if (n > 0) {
				rec->byteswritten += n;
				switch (rec->talkprotocol) {
				  case TALK_PROTO_PLAIN:
				  case TALK_PROTO_HTTP:
					addtobufferraw(rec->textlog, rec->writep, n);
					break;
				  default:
					break;
				}
				rec->writep += n;
				if (*rec->writep == '\0') {
					rec->step++;	/* Next step */
					if (last_write_step(rec)) {
						conn_close_connection(connection, "w");
					}
				}
			}
		}

		/* See if we have reached a point where we switch to TLS mode */
		if (rec->dialog[rec->step] && (strcasecmp(rec->dialog[rec->step], "STARTTLS") == 0)) {
			res = CONN_CBRESULT_STARTTLS;
			rec->step++;
		}

		/* See if we're done */
		if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_TIMEOUT:
		rec->talkresult = TALK_CONN_TIMEOUT;
		conn_close_connection(connection, NULL);
		break;

	  case CONN_CB_CLOSED:                 /* Client/server mode: Connection has been closed */
		/* See if we need to report an error from closing the connection unexpectedly */
		if ((rec->talkresult == TALK_OK) && rec->dialog[rec->step]) {
			/*
			 * We should only close if
			 * - we hit a CLOSE command
			 * - we hit the end of the command list (NULL dialog step)
			 * - peer disconnects during a READ step
			 * So if the current step is NOT a CLOSE or a READALL step, then 
			 * the close was unexpected - so flag it as an error.
			 */
			if ((strcasecmp(rec->dialog[rec->step], "CLOSE") != 0) && (strcasecmp(rec->dialog[rec->step], "READALL") != 0))
				rec->talkresult = TALK_INTERRUPTED;
		}
		rec->elapsedus = connection->elapsedus;
		return 0;

	  case CONN_CB_CLEANUP:                /* Client/server mode: Connection cleanup */
		if (rec->readbuf) xfree(rec->readbuf);
		if (rec->writebuf) xfree(rec->writebuf);
		connection->userdata = NULL;
		test_is_done(rec);
		return 0;

	  default:
		break;
	}

	return res;
}
Example #3
0
static int http_datahandler(myconn_t *rec, int iobytes, int startoffset, int *advancestep)
{
	char *endofhdrs;
	int httpmajorver, httpminorver;
	char *xferencoding;
	int len = iobytes;
	char *bol, *buf;
	int hdrbytes, bodybytes = 0, bodyoffset, initialhdrbuflen, n;

	*advancestep = 0;

	switch (rec->httpdatastate) {
	  case HTTPDATA_HEADERS:
		initialhdrbuflen = STRBUFLEN(rec->httpheaders);
		addtobufferraw(rec->httpheaders, rec->readbuf+startoffset, (iobytes - startoffset));

check_for_endofheaders:
		/* 
		 * Now see if we have the end-of-headers delimiter.
		 * This SHOULD be <cr><lf><cr><lf>, but RFC 2616 says
		 * you SHOULD recognize just plain <lf><lf>.
		 * So try the second form, if the first one is not there.
		 */
		endofhdrs = strstr(STRBUF(rec->httpheaders), "\r\n\r\n");
		if (endofhdrs) {
			endofhdrs += 4;
		}
		else {
			endofhdrs = strstr(STRBUF(rec->httpheaders), "\n\n");
			if (endofhdrs) {
				endofhdrs += 2;
			}
		}

		if (!endofhdrs) {
			/* No more to do for now, but pass the databyte-count back to the caller for further processing. */
			return iobytes;
		}
		else {
			/* Chop the non-header section of data from the headers */
			strbufferchop(rec->httpheaders, strlen(endofhdrs));
		}


		/* We have an end-of-header delimiter, but it could be just a "100 Continue" response */
		sscanf(STRBUF(rec->httpheaders), "HTTP/%d.%d %d", &httpmajorver, &httpminorver, &rec->httpstatus);
		if (rec->httpstatus == 100) {
			/* 
			 * It's a "100"  continue-status.
			 * Just drop this set of headers, and re-do the end-of-headers check.
			 */
			strbuffer_t *newhdrbuf = newstrbuffer(0);
			addtobuffer(newhdrbuf, endofhdrs);
			freestrbuffer(rec->httpheaders);
			rec->httpheaders = newhdrbuf;
			goto check_for_endofheaders;
		}

		/* Have all the http headers now */
		rec->httpdatastate = HTTPDATA_BODY;


		/* 
		 * Find the "Transfer-encoding: " header (if there is one) to see if the transfer uses chunks,
		 * and grab "Content-Length:" to get the length of the body.
		 */
		xferencoding = NULL;
		bol = STRBUF(rec->httpheaders);
		while (bol && !xferencoding && !rec->httpcontentleft) {
			if (strncasecmp(bol, "Transfer-encoding:", 18) == 0) {
				bol += 18; bol += strspn(bol, " ");
				xferencoding = bol;
			}
			else if (strncasecmp(bol, "Content-Length:", 15) == 0) {
				bol += 15; bol += strspn(bol, " ");
				rec->httpcontentleft = atoi(bol);
			}
			else {
				bol = strchr(bol, '\n'); if (bol) bol++;
			}
		}

		if (xferencoding && (strncasecmp(xferencoding, "chunked", 7) == 0)) 
			rec->httpchunkstate = HTTP_CHUNK_INIT;
		else {
			rec->httpchunkstate = (rec->httpcontentleft > 0) ? HTTP_CHUNK_NOTCHUNKED : HTTP_CHUNK_NOTCHUNKED_NOCLEN;
		}

		/* Done with all the http header processing. Call ourselves to handle any remaining data we got after the headers */

		/* 
		 * To figure out how this works, here is the layout of rec->httpheaders. The first 
		 * (initialhdrbuflen) part is what we had before this call to http_datahandler, the 
		 * last (iobytes) part has been copied over from the current rec->buf. 
		 * endofhdrs points into rec->httpheaders. bodyoffset and bodybytes are relative,
		 * so even though the body data is in rec->buf and NOT in rec->httpheaders, we can
		 * calculate the offset and length of the body data.
		 *
		 *                                         endofhdrs
		 *                                              !
		 * !-----------------------------!----------------------------!
		 *
		 * <......initialhdrbuflen.......>
		 *                               <.........iobytes............>
		 * <...............hdrbytes.....................>
		 *                               <..bodyoffset..>
		 *                                              <..bodybytes..>
		 */
		hdrbytes = (endofhdrs - STRBUF(rec->httpheaders));
		bodyoffset = hdrbytes - initialhdrbuflen;
		bodybytes = iobytes - bodyoffset;
		http_datahandler(rec, bodybytes, bodyoffset, advancestep); 
		break;


	  case HTTPDATA_BODY:
		buf = rec->readbuf+startoffset;
		while (len > 0) {
			bodybytes = 0;

			switch (rec->httpchunkstate) {
			  case HTTP_CHUNK_NOTCHUNKED:
			  case HTTP_CHUNK_NOTCHUNKED_NOCLEN:
				bodybytes = len;
				break;

			  case HTTP_CHUNK_INIT:
				/* We're about to pick up a chunk length */
				rec->httpleftinchunk = 0;
				rec->httpchunkstate = HTTP_CHUNK_GETLEN;
				break;

			  case HTTP_CHUNK_GETLEN:
				/* We are collecting the length of the chunk */
				n = hexvalue(*buf);
				if (n == -1) {
					rec->httpchunkstate = HTTP_CHUNK_SKIPLENCR;
				}
				else {
					rec->httpleftinchunk = rec->httpleftinchunk*16 + n;
					buf++; len--;
				}
				break;
				
			  case HTTP_CHUNK_SKIPLENCR:
				/* We've got the length, now skip to the next LF */
				if (*buf == '\n') {
					buf++; len--; 
					rec->httpchunkstate = ((rec->httpleftinchunk > 0) ? HTTP_CHUNK_DATA : HTTP_CHUNK_NOMORE);
				}
				else if ((*buf == '\r') || (*buf == ' ')) {
					buf++; len--;
				}
				else {
					errprintf("Yikes - strange data following chunk len. Saw a '%c'\n", *buf);
					buf++; len--;
				}
				break;

			  case HTTP_CHUNK_DATA:
				/* Passing off the data */
				bodybytes = (len > rec->httpleftinchunk) ? rec->httpleftinchunk : len;
				rec->httpleftinchunk -= bodybytes;
				if (rec->httpleftinchunk == 0) rec->httpchunkstate = HTTP_CHUNK_SKIPENDCR;
				break;

			  case HTTP_CHUNK_SKIPENDCR:
				/* Skip CR/LF after a chunk */
				if (*buf == '\n') {
					buf++; len--; rec->httpchunkstate = HTTP_CHUNK_DONE;
				}
				else if (*buf == '\r') {
					buf++; len--;
				}
				else {
					errprintf("Yikes - strange data following chunk data. Saw a '%c'\n", *buf);
					buf++; len--;
				}
				break;

			  case HTTP_CHUNK_DONE:
				/* One chunk is done, continue with the next */
				rec->httpchunkstate = HTTP_CHUNK_GETLEN;
				break;

			  case HTTP_CHUNK_NOMORE:
				/* All chunks done. Skip the rest (trailers) */
				len = 0;
				break;
			}

			/* bodybytes holds the number of bytes data from buf that should go to userspace */
			if (bodybytes > 0) {
				addtobufferraw(rec->httpbody, buf, bodybytes);
				buf += bodybytes;
				len -= bodybytes;
				if ((rec->httpcontentleft > 0) && (rec->httpcontentleft >= bodybytes)) rec->httpcontentleft -= bodybytes;
				dbgprintf("HTTP bodybytes %d, %d bytes left\n", bodybytes, rec->httpcontentleft);
			}
		}

		/* Done processing body content. Now see if we have all of it - if we do, then proceed to next step. */
		dbgprintf("http chunkstate: %d\n",rec->httpchunkstate);
		switch (rec->httpchunkstate) {
		  case HTTP_CHUNK_NOTCHUNKED:
			if (rec->httpcontentleft <= 0) *advancestep = 1;
			break;

		  case HTTP_CHUNK_NOTCHUNKED_NOCLEN:
			/* We have no content-length: header, so keep going until we do two NULL-reads */
			if ((rec->httplastbodyread == 0) && (bodybytes == 0))
				*advancestep = 1;
			else
				rec->httplastbodyread = bodybytes;
			break;

		  case HTTP_CHUNK_NOMORE:
			*advancestep = 1;
			break;

		  default:
			break;
		}

		break;
	}


	return iobytes;
}