diff --git a/lib/smtp.c b/lib/smtp.c index 1bd869eb0..3dc50bd6a 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -639,6 +639,7 @@ static CURLcode smtp_done(struct connectdata *conn, CURLcode status, struct SessionHandle *data = conn->data; struct FTP *smtp = data->state.proto.smtp; CURLcode result=CURLE_OK; + ssize_t bytes_written; (void)premature; if(!smtp) @@ -653,6 +654,15 @@ static CURLcode smtp_done(struct connectdata *conn, CURLcode status, conn->bits.close = TRUE; /* marked for closure */ result = status; /* use the already set error code */ } + else + /* TODO: make this work even when the socket is EWOULDBLOCK in this call! */ + + /* write to socket (send away data) */ + result = Curl_write(conn, + conn->writesockfd, /* socket to send to */ + SMTP_EOB, /* buffer pointer */ + SMTP_EOB_LEN, /* buffer size */ + &bytes_written); /* actually sent away */ /* clear these for next connection */ smtp->transfer = FTPTRANSFER_BODY; diff --git a/lib/smtp.h b/lib/smtp.h index 199481a0b..ec0bcfb8b 100644 --- a/lib/smtp.h +++ b/lib/smtp.h @@ -58,4 +58,8 @@ extern const struct Curl_handler Curl_handler_smtps; #define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a" #define SMTP_EOB_LEN 5 +/* if found in data, replace it with this string instead */ +#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" +#define SMTP_EOB_REPL_LEN 4 + #endif /* __SMTP_H */ diff --git a/lib/transfer.c b/lib/transfer.c index 369481b18..ed9e34910 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -784,6 +784,68 @@ static CURLcode readwrite_upload(struct SessionHandle *data, /* store number of bytes available for upload */ data->req.upload_present = nread; +#ifndef CURL_DISABLE_SMTP + if(conn->protocol & PROT_SMTP) { + /* When sending SMTP payload, we must detect CRLF.CRLF sequences in + * the data and make sure it is sent as CRLF..CRLF instead, as + * otherwise it will wrongly be detected as end of data by the server. + */ + struct smtp_conn *smtpc = &conn->proto.smtpc; + + if(data->state.scratch == NULL) + data->state.scratch = malloc(2*BUFSIZE); + if(data->state.scratch == NULL) { + failf (data, "Failed to alloc scratch buffer!"); + return CURLE_OUT_OF_MEMORY; + } + /* This loop can be improved by some kind of Boyer-Moore style of + approach but that is saved for later... */ + for(i = 0, si = 0; i < nread; i++, si++) { + int left = nread - i; + + if(left>= (SMTP_EOB_LEN-smtpc->eob)) { + if(!memcmp(SMTP_EOB+smtpc->eob, &data->req.upload_fromhere[i], + SMTP_EOB_LEN-smtpc->eob)) { + /* It matched, copy the replacement data to the target buffer + instead. Note that the replacement does not contain the + trailing CRLF but we instead continue to match on that one + to deal with repeated sequences. Like CRLF.CRLF.CRLF etc + */ + memcpy(&data->state.scratch[si], SMTP_EOB_REPL, + SMTP_EOB_REPL_LEN); + si+=SMTP_EOB_REPL_LEN-1; /* minus one since the for() increments + it */ + i+=SMTP_EOB_LEN-smtpc->eob-1-2; + smtpc->eob = 0; /* start over */ + continue; + } + } + else if(!memcmp(SMTP_EOB+smtpc->eob, &data->req.upload_fromhere[i], + left)) { + /* the last piece of the data matches the EOB so we can't send that + until we know the rest of it */ + smtpc->eob += left; + break; + } + + data->state.scratch[si] = data->req.upload_fromhere[i]; + } /* for() */ + + if(si != nread) { + /* only use the new buffer if we replaced something */ + nread = si; + + /* upload from the new (replaced) buffer instead */ + data->req.upload_fromhere = data->state.scratch; + + /* set the new amount too */ + data->req.upload_present = nread; + } + + } + else +#endif /* CURL_DISABLE_SMTP */ + /* convert LF to CRLF if so asked */ if((!sending_http_headers) && #ifdef CURL_DO_LINEEND_CONV @@ -837,10 +899,10 @@ static CURLcode readwrite_upload(struct SessionHandle *data, /* write to socket (send away data) */ result = Curl_write(conn, - conn->writesockfd, /* socket to send to */ + conn->writesockfd, /* socket to send to */ data->req.upload_fromhere, /* buffer pointer */ data->req.upload_present, /* buffer size */ - &bytes_written); /* actually send away */ + &bytes_written); /* actually sent */ if(result) return result; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 3094d642a..b2c4cc9b5 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -63,7 +63,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46 \ test1089 test1090 test1091 test1092 test1093 test1094 test1095 test1096 \ test1097 test560 test561 test1098 test1099 test562 test563 test1100 \ test564 test1101 test1102 test1103 test1104 test299 test310 test311 \ - test312 test1105 test565 test800 test1106 test801 test566 test802 + test312 test1105 test565 test800 test1106 test801 test566 test802 test803 filecheck: @mkdir test-place; \ diff --git a/tests/data/test802 b/tests/data/test802 index 9c7013a5c..59d2e8c1f 100644 --- a/tests/data/test802 +++ b/tests/data/test802 @@ -38,11 +38,15 @@ EHLO user MAIL FROM:802@from RCPT TO:802@foo DATA +QUIT + + From: different To: another body -QUIT - + +. + diff --git a/tests/data/test803 b/tests/data/test803 new file mode 100644 index 000000000..e6e48b5d2 --- /dev/null +++ b/tests/data/test803 @@ -0,0 +1,64 @@ + + + +SMTP + + + +# +# Server-side + + + +# +# Client-side + + +smtp + + +SMTP with CRLF-dot-CRLF in data + + +From: different +To: another + + +. +. + +. + +body + + +smtp://%HOSTIP:%SMTPPORT -u user:secret --mail-rcpt 803@foo --mail-from 803@from -T - + + + +# +# Verify data after the test has been "shot" + + +EHLO user +MAIL FROM:803@from +RCPT TO:803@foo +DATA +QUIT + + +From: different +To: another + + +.. +.. + +.. + +body + +. + + + diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl index ccdc4cbb4..91f541235 100644 --- a/tests/ftpserver.pl +++ b/tests/ftpserver.pl @@ -473,12 +473,69 @@ sub DATA_smtp { if($testno eq "verifiedserver") { sendcontrol "554 WE ROOLZ: $$\r\n"; + return 0; # don't wait for data now } else { + $testno =~ s/^([0-9]*).*/$1/; sendcontrol "354 Show me the mail\r\n"; } logmsg "===> rcpt $testno was $smtp_rcpt\n"; + + my $filename = "log/upload.$testno"; + + logmsg "Store test number $testno in $filename\n"; + + open(FILE, ">$filename") || + return 0; # failed to open output + + my $line; + my $ulsize=0; + my $disc=0; + my $raw; + while (5 == (sysread \*SFREAD, $line, 5)) { + if($line eq "DATA\n") { + my $i; + my $eob; + sysread \*SFREAD, $i, 5; + + my $size = 0; + if($i =~ /^([0-9a-fA-F]{4})\n/) { + $size = hex($1); + } + + sysread \*SFREAD, $line, $size; + + $ulsize += $size; + print FILE $line if(!$nosave); + + $raw .= $line; + if($raw =~ /\x0d\x0a\x2e\x0d\x0a\z/) { + # end of data marker! + $eob = 1; + } + logmsg "> Appending $size bytes to file\n"; + if($eob) { + logmsg "Found SMTP EOB marker\n"; + last; + } + } + elsif($line eq "DISC\n") { + # disconnect! + $disc=1; + last; + } + else { + logmsg "No support for: $line"; + last; + } + } + if($nosave) { + print FILE "$ulsize bytes would've been stored here\n"; + } + close(FILE); + logmsg "received $ulsize bytes upload\n"; + } sub RCPT_smtp {