1 #include "NativeFeatureIncludes.h"
2 #if _RAKNET_SUPPORT_EmailSender==1
3 
4 // Useful sites
5 // http://www.faqs.org\rfcs\rfc2821.html
6 // http://en.wikipedia.org/wiki/Base64
7 // http://www2.rad.com\networks/1995/mime/examples.htm
8 
9 #include "EmailSender.h"
10 #include "TCPInterface.h"
11 #include "GetTime.h"
12 #include "Rand.h"
13 #include "FileList.h"
14 #include "BitStream.h"
15 #include <stdio.h>
16 
17 
18 
19 
20 
21 #include "RakSleep.h"
22 
23 static const char base64Map[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
24 
Send(const char * hostAddress,unsigned short hostPort,const char * sender,const char * recipient,const char * senderName,const char * recipientName,const char * subject,const char * body,FileList * attachedFiles,bool doPrintf,const char * password)25 const char *EmailSender::Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf, const char *password)
26 {
27 	Packet *packet;
28 	char query[1024];
29 	TCPInterface tcpInterface;
30 	SystemAddress emailServer;
31 	if (tcpInterface.Start(0, 0)==false)
32 		return "Unknown error starting TCP";
33 	emailServer=tcpInterface.Connect(hostAddress, hostPort,true);
34 	if (emailServer==UNASSIGNED_SYSTEM_ADDRESS)
35 		return "Failed to connect to host";
36 #if  OPEN_SSL_CLIENT_SUPPORT==1
37 	tcpInterface.StartSSLClient(emailServer);
38 #endif
39 	RakNetTime timeoutTime = RakNet::GetTime()+3000;
40 	packet=0;
41 	while (RakNet::GetTime() < timeoutTime)
42 	{
43 		packet = tcpInterface.Receive();
44 		if (packet)
45 		{
46 			if (doPrintf)
47 				RAKNET_DEBUG_PRINTF("%s", packet->data);
48 			break;
49 		}
50 		RakSleep(250);
51 	}
52 
53 	if (packet==0)
54 		return "Timeout while waiting for initial data from server.";
55 
56 	tcpInterface.Send("EHLO\r\n", 6, emailServer,false);
57 	const char *response;
58 	bool authenticate=false;
59 #ifdef _MSC_VER
60 #pragma warning(disable:4127)   // conditional expression is constant
61 #endif
62 	while (1)
63 	{
64 		response=GetResponse(&tcpInterface, emailServer, doPrintf);
65 
66 		if (response!=0 && strcmp(response, "AUTHENTICATE")==0)
67 		{
68 			authenticate=true;
69 			break;
70 		}
71 
72 		// Something other than continue?
73 		if (response!=0 && strcmp(response, "CONTINUE")!=0)
74 			return response;
75 
76 		// Success?
77 		if (response==0)
78 			break;
79 	}
80 
81 	if (authenticate)
82 	{
83 		sprintf(query, "EHLO %s\r\n", sender);
84 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
85 		response=GetResponse(&tcpInterface, emailServer, doPrintf);
86 		if (response!=0)
87 			return response;
88 		if (password==0)
89 			return "Password needed";
90 		char *outputData = RakNet::OP_NEW_ARRAY<char >((const int) (strlen(sender)+strlen(password)+2)*3, __FILE__, __LINE__ );
91 		RakNet::BitStream bs;
92 		char zero=0;
93 		bs.Write(&zero,1);
94 		bs.Write(sender,(const unsigned int)strlen(sender));
95 		//bs.Write("jms1@jms1.net",(const unsigned int)strlen("jms1@jms1.net"));
96 		bs.Write(&zero,1);
97 		bs.Write(password,(const unsigned int)strlen(password));
98 		bs.Write(&zero,1);
99 		//bs.Write("not.my.real.password",(const unsigned int)strlen("not.my.real.password"));
100 		Base64Encoding((const char*)bs.GetData(), bs.GetNumberOfBytesUsed(), outputData, base64Map);
101 		sprintf(query, "AUTH PLAIN %s", outputData);
102 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
103 		response=GetResponse(&tcpInterface, emailServer, doPrintf);
104 		if (response!=0)
105 			return response;
106 	}
107 
108 
109 	if (sender)
110 		sprintf(query, "MAIL From: <%s>\r\n", sender);
111 	else
112 		sprintf(query, "MAIL From: <>\r\n");
113 	tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
114 	response=GetResponse(&tcpInterface, emailServer, doPrintf);
115 	if (response!=0)
116 		return response;
117 
118 	if (recipient)
119 		sprintf(query, "RCPT TO: <%s>\r\n", recipient);
120 	else
121 		sprintf(query, "RCPT TO: <>\r\n");
122 	tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
123 	response=GetResponse(&tcpInterface, emailServer, doPrintf);
124 	if (response!=0)
125 		return response;
126 
127 	tcpInterface.Send("DATA\r\n", (unsigned int)strlen("DATA\r\n"), emailServer,false);
128 
129 	// Wait for 354...
130 
131 	response=GetResponse(&tcpInterface, emailServer, doPrintf);
132 	if (response!=0)
133 		return response;
134 
135 	if (subject)
136 	{
137 		sprintf(query, "Subject: %s\r\n", subject);
138 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
139 	}
140 	if (senderName)
141 	{
142 		sprintf(query, "From: %s\r\n", senderName);
143 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
144 	}
145 	if (recipientName)
146 	{
147 		sprintf(query, "To: %s\r\n", recipientName);
148 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
149 	}
150 
151 	const int boundarySize=60;
152 	char boundary[boundarySize+1];
153 	int i,j;
154 	if (attachedFiles && attachedFiles->fileList.Size())
155 	{
156 		seedMT((unsigned int) RakNet::GetTime());
157 		// Random multipart message boundary
158 		for (i=0; i < boundarySize; i++)
159 			boundary[i]=base64Map[randomMT()%64];
160 		boundary[boundarySize]=0;
161 	}
162 
163 	sprintf(query, "MIME-version: 1.0\r\n");
164 	tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
165 
166 	if (attachedFiles && attachedFiles->fileList.Size())
167 	{
168 		sprintf(query, "Content-type: multipart/mixed; BOUNDARY=\"%s\"\r\n\r\n", boundary);
169 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
170 
171 		sprintf(query, "This is a multi-part message in MIME format.\r\n\r\n--%s\r\n", boundary);
172 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
173 	}
174 
175 	sprintf(query, "Content-Type: text/plain; charset=\"US-ASCII\"\r\n\r\n");
176 	tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
177 
178 	// Write the body of the email, doing some lame shitty shit where I have to make periods at the start of a newline have a second period.
179 	char *newBody;
180 	int bodyLength;
181 	bodyLength=(int)strlen(body);
182 	newBody = (char*) rakMalloc_Ex( bodyLength*3, __FILE__, __LINE__ );
183 	if (bodyLength>0)
184 		newBody[0]=body[0];
185 	for (i=1, j=1; i < bodyLength; i++)
186 	{
187 		// Transform \n . \r \n into \n . . \r \n
188 		if (i < bodyLength-2 &&
189 			body[i-1]=='\n' &&
190 			body[i+0]=='.' &&
191 			body[i+1]=='\r' &&
192 			body[i+2]=='\n')
193 		{
194 			newBody[j++]='.';
195 			newBody[j++]='.';
196 			newBody[j++]='\r';
197 			newBody[j++]='\n';
198 			i+=2;
199 		}
200 		// Transform \n . . \r \n into \n . . . \r \n
201 		// Having to process .. is a bug in the mail server - the spec says ONLY \r\n.\r\n should be transformed
202 		else if (i <= bodyLength-3 &&
203 			body[i-1]=='\n' &&
204 			body[i+0]=='.' &&
205 			body[i+1]=='.' &&
206 			body[i+2]=='\r' &&
207 			body[i+3]=='\n')
208 		{
209 			newBody[j++]='.';
210 			newBody[j++]='.';
211 			newBody[j++]='.';
212 			newBody[j++]='\r';
213 			newBody[j++]='\n';
214 			i+=3;
215 		}
216 		// Transform \n . \n into \n . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
217 		else if (i < bodyLength-1 &&
218 			body[i-1]=='\n' &&
219 			body[i+0]=='.' &&
220 			body[i+1]=='\n')
221 		{
222 			newBody[j++]='.';
223 			newBody[j++]='.';
224 			newBody[j++]='\r';
225 			newBody[j++]='\n';
226 			i+=1;
227 		}
228 		// Transform \n . . \n into \n . . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
229 		// In fact having to process .. is a bug too - because the spec says ONLY \r\n.\r\n should be transformed
230 		else if (i <= bodyLength-2 &&
231 			body[i-1]=='\n' &&
232 			body[i+0]=='.' &&
233 			body[i+1]=='.' &&
234 			body[i+2]=='\n')
235 		{
236 			newBody[j++]='.';
237 			newBody[j++]='.';
238 			newBody[j++]='.';
239 			newBody[j++]='\r';
240 			newBody[j++]='\n';
241 			i+=2;
242 		}
243 		else
244 			newBody[j++]=body[i];
245 	}
246 
247 	newBody[j++]='\r';
248 	newBody[j++]='\n';
249 	tcpInterface.Send(newBody, j, emailServer,false);
250 
251 	rakFree_Ex(newBody, __FILE__, __LINE__ );
252 	int outputOffset;
253 
254 	// What a pain in the rear.  I have to map the binary to printable characters using 6 bits per character.
255 	if (attachedFiles && attachedFiles->fileList.Size())
256 	{
257 		for (i=0; i < (int) attachedFiles->fileList.Size(); i++)
258 		{
259 			// Write boundary
260 			sprintf(query, "\r\n--%s\r\n", boundary);
261 			tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
262 
263 			sprintf(query, "Content-Type: APPLICATION/Octet-Stream; SizeOnDisk=%i; name=\"%s\"\r\nContent-Transfer-Encoding: BASE64\r\nContent-Description: %s\r\n\r\n", attachedFiles->fileList[i].dataLengthBytes, attachedFiles->fileList[i].filename.C_String(), attachedFiles->fileList[i].filename.C_String());
264 			tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
265 
266 			newBody = (char*) rakMalloc_Ex( (size_t) (attachedFiles->fileList[i].dataLengthBytes*3)/2, __FILE__, __LINE__ );
267 
268 			outputOffset=Base64Encoding(attachedFiles->fileList[i].data, (int) attachedFiles->fileList[i].dataLengthBytes, newBody, base64Map);
269 
270 			// Send the base64 mapped file.
271 			tcpInterface.Send(newBody, outputOffset, emailServer,false);
272 			rakFree_Ex(newBody, __FILE__, __LINE__ );
273 
274 		}
275 
276 		// Write last boundary
277 		sprintf(query, "\r\n--%s--\r\n", boundary);
278 		tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
279 	}
280 
281 
282 	sprintf(query, "\r\n.\r\n");
283 	tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
284 	response=GetResponse(&tcpInterface, emailServer, doPrintf);
285 	if (response!=0)
286 		return response;
287 
288 	tcpInterface.Send("QUIT\r\n", (unsigned int)strlen("QUIT\r\n"), emailServer,false);
289 
290 	RakSleep(30);
291 	if (doPrintf)
292 	{
293 		packet = tcpInterface.Receive();
294 		while (packet)
295 		{
296 			RAKNET_DEBUG_PRINTF("%s", packet->data);
297 			packet = tcpInterface.Receive();
298 		}
299 	}
300 	tcpInterface.Stop();
301 	return 0; // Success
302 }
303 
GetResponse(TCPInterface * tcpInterface,const SystemAddress & emailServer,bool doPrintf)304 const char *EmailSender::GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf)
305 {
306 	Packet *packet;
307 	RakNetTime timeout;
308 	timeout=RakNet::GetTime()+5000;
309 #ifdef _MSC_VER
310 	#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
311 #endif
312 	while (1)
313 	{
314 		if (tcpInterface->HasLostConnection()==emailServer)
315 			return "Connection to server lost.";
316 		packet = tcpInterface->Receive();
317 		if (packet)
318 		{
319 			if (doPrintf)
320 			{
321 				RAKNET_DEBUG_PRINTF("%s", packet->data);
322 			}
323 #if OPEN_SSL_CLIENT_SUPPORT==1
324 			if (strstr((const char*)packet->data, "220"))
325 			{
326 				tcpInterface->StartSSLClient(packet->systemAddress);
327 				return "AUTHENTICATE"; // OK
328 			}
329 // 			if (strstr((const char*)packet->data, "250-AUTH LOGIN PLAIN"))
330 // 			{
331 // 				tcpInterface->StartSSLClient(packet->systemAddress);
332 // 				return "AUTHENTICATE"; // OK
333 // 			}
334 #endif
335 			if (strstr((const char*)packet->data, "235"))
336 				return 0; // Authentication accepted
337 			if (strstr((const char*)packet->data, "354"))
338 				return 0; // Go ahead
339 #if OPEN_SSL_CLIENT_SUPPORT==1
340 			if (strstr((const char*)packet->data, "250-STARTTLS"))
341 			{
342 				tcpInterface->Send("STARTTLS\r\n", (unsigned int) strlen("STARTTLS\r\n"), packet->systemAddress, false);
343 				return "CONTINUE";
344 			}
345 #endif
346 			if (strstr((const char*)packet->data, "250"))
347 				return 0; // OK
348 			if (strstr((const char*)packet->data, "550"))
349 				return "Failed on error code 550";
350 			if (strstr((const char*)packet->data, "553"))
351 				return "Failed on error code 553";
352 		}
353 		if (RakNet::GetTime() > timeout)
354 			return "Timed out";
355 		RakSleep(100);
356 	}
357 }
358 
Base64Encoding(const char * inputData,int dataLength,char * outputData,const char * base64Map)359 int EmailSender::Base64Encoding(const char *inputData, int dataLength, char *outputData, const char *base64Map)
360 {
361 	int outputOffset, charCount;
362 	int write3Count;
363 	outputOffset=0;
364 	charCount=0;
365 	int j;
366 
367 	write3Count=dataLength/3;
368 	for (j=0; j < write3Count; j++)
369 	{
370 		// 6 leftmost bits from first byte, shifted to bits 7,8 are 0
371 		outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2];
372 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
373 
374 		// Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8
375 		outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63];
376 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
377 
378 		// 4 low bits from the second byte and the two high bits from the third byte, masked to ignore bits 7,8
379 		outputData[outputOffset++]=base64Map[((inputData[j*3+1] << 2) | (inputData[j*3+2] >> 6)) & 63]; // Third 6 bits
380 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
381 
382 		// Last 6 bits from the third byte, masked to ignore bits 7,8
383 		outputData[outputOffset++]=base64Map[inputData[j*3+2] & 63];
384 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
385 	}
386 
387 	if (dataLength % 3==1)
388 	{
389 		// One input byte remaining
390 		outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2];
391 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
392 
393 		// Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8
394 		outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63];
395 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
396 
397 		// Pad with two equals
398 		outputData[outputOffset++]='=';
399 		outputData[outputOffset++]='=';
400 	}
401 	else if (dataLength % 3==2)
402 	{
403 		// Two input bytes remaining
404 
405 		// 6 leftmost bits from first byte, shifted to bits 7,8 are 0
406 		outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2];
407 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
408 
409 		// Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8
410 		outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63];
411 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
412 
413 		// 4 low bits from the second byte, followed by 00
414 		outputData[outputOffset++]=base64Map[(inputData[j*3+1] << 2) & 63]; // Third 6 bits
415 		if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;}
416 
417 		// Pad with one equal
418 		outputData[outputOffset++]='=';
419 		//outputData[outputOffset++]='=';
420 	}
421 
422 	// Append \r\n
423 	outputData[outputOffset++]='\r';
424 	outputData[outputOffset++]='\n';
425 	outputData[outputOffset]=0;
426 
427 	return outputOffset;
428 }
429 
430 #endif // _RAKNET_SUPPORT_*
431