SLikeNet  0.1.3
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
EmailSender.cpp
Go to the documentation of this file.
1 /*
2  * Original work: Copyright (c) 2014, Oculus VR, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * RakNet License.txt file in the licenses directory of this source tree. An additional grant
7  * of patent rights can be found in the RakNet Patents.txt file in the same directory.
8  *
9  *
10  * Modified work: Copyright (c) 2016-2017, SLikeSoft UG (haftungsbeschränkt)
11  *
12  * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
13  * license found in the license.txt file in the root directory of this source tree.
14  */
15 
17 #if _RAKNET_SUPPORT_EmailSender==1 && _RAKNET_SUPPORT_TCPInterface==1 && _RAKNET_SUPPORT_FileOperations==1
18 
19 // Useful sites
20 // http://www.faqs.org\rfcs\rfc2821.html
21 // http://www2.rad.com\networks/1995/mime/examples.htm
22 
23 #include "slikenet/EmailSender.h"
24 #include "slikenet/TCPInterface.h"
25 #include "slikenet/GetTime.h"
26 #include "slikenet/Rand.h"
27 #include "slikenet/FileList.h"
28 #include "slikenet/BitStream.h"
29 #include "slikenet/Base64Encoder.h"
30 #include <stdio.h>
31 #include "slikenet/linux_adapter.h"
32 #include "slikenet/osx_adapter.h"
33 
34 
35 
36 
37 
38 #include "slikenet/sleep.h"
39 
40 using namespace SLNet;
41 
42 
44 
45 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)
46 {
47  SLNet::Packet *packet;
48  char query[1024];
49  TCPInterface tcpInterface;
50  SystemAddress emailServer;
51  if (tcpInterface.Start(0, 0)==false)
52  return "Unknown error starting TCP";
53  emailServer=tcpInterface.Connect(hostAddress, hostPort,true);
54  if (emailServer==UNASSIGNED_SYSTEM_ADDRESS)
55  return "Failed to connect to host";
56 #if OPEN_SSL_CLIENT_SUPPORT==1
57  tcpInterface.StartSSLClient(emailServer);
58 #endif
59  SLNet::TimeMS timeoutTime = SLNet::GetTimeMS()+3000;
60  packet=0;
61  while (SLNet::GetTimeMS() < timeoutTime)
62  {
63  packet = tcpInterface.Receive();
64  if (packet)
65  {
66  if (doPrintf)
67  {
68  RAKNET_DEBUG_PRINTF("%s", packet->data);
69  tcpInterface.DeallocatePacket(packet);
70  }
71  break;
72  }
73  RakSleep(250);
74  }
75 
76  if (packet==0)
77  return "Timeout while waiting for initial data from server.";
78 
79  tcpInterface.Send("EHLO\r\n", 6, emailServer,false);
80  const char *response;
81  bool authenticate=false;
82  for(;;)
83  {
84  response=GetResponse(&tcpInterface, emailServer, doPrintf);
85 
86  if (response!=0 && strcmp(response, "AUTHENTICATE")==0)
87  {
88  authenticate=true;
89  break;
90  }
91 
92  // Something other than continue?
93  if (response!=0 && strcmp(response, "CONTINUE")!=0)
94  return response;
95 
96  // Success?
97  if (response==0)
98  break;
99  }
100 
101  if (authenticate)
102  {
103  sprintf_s(query, "EHLO %s\r\n", sender);
104  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
105  response=GetResponse(&tcpInterface, emailServer, doPrintf);
106  if (response!=0)
107  return response;
108  if (password==0)
109  return "Password needed";
110  char *outputData = SLNet::OP_NEW_ARRAY<char >((const int) (strlen(sender)+strlen(password)+2)*3, _FILE_AND_LINE_ );
111  SLNet::BitStream bs;
112  char zero=0;
113  bs.Write(&zero,1);
114  bs.Write(sender,(const unsigned int)strlen(sender));
115  //bs.Write("jms1@jms1.net",(const unsigned int)strlen("jms1@jms1.net"));
116  bs.Write(&zero,1);
117  bs.Write(password,(const unsigned int)strlen(password));
118  bs.Write(&zero,1);
119  //bs.Write("not.my.real.password",(const unsigned int)strlen("not.my.real.password"));
120  Base64Encoding((const unsigned char*)bs.GetData(), bs.GetNumberOfBytesUsed(), outputData);
121  sprintf_s(query, "AUTH PLAIN %s", outputData);
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 
128 
129  if (sender)
130  sprintf_s(query, "MAIL From: <%s>\r\n", sender);
131  else
132  sprintf_s(query, "MAIL From: <>\r\n");
133  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
134  response=GetResponse(&tcpInterface, emailServer, doPrintf);
135  if (response!=0)
136  return response;
137 
138  if (recipient)
139  sprintf_s(query, "RCPT TO: <%s>\r\n", recipient);
140  else
141  sprintf_s(query, "RCPT TO: <>\r\n");
142  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
143  response=GetResponse(&tcpInterface, emailServer, doPrintf);
144  if (response!=0)
145  return response;
146 
147  tcpInterface.Send("DATA\r\n", (unsigned int)strlen("DATA\r\n"), emailServer,false);
148 
149  // Wait for 354...
150 
151  response=GetResponse(&tcpInterface, emailServer, doPrintf);
152  if (response!=0)
153  return response;
154 
155  if (subject)
156  {
157  sprintf_s(query, "Subject: %s\r\n", subject);
158  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
159  }
160  if (senderName)
161  {
162  sprintf_s(query, "From: %s\r\n", senderName);
163  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
164  }
165  if (recipientName)
166  {
167  sprintf_s(query, "To: %s\r\n", recipientName);
168  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
169  }
170 
171  const int boundarySize=60;
172  char boundary[boundarySize+1];
173  int i,j;
174  if (attachedFiles && attachedFiles->fileList.Size())
175  {
176  rakNetRandom.SeedMT((unsigned int)SLNet::GetTimeMS());
177  // Random multipart message boundary
178  for (i=0; i < boundarySize; i++)
179  boundary[i]=Base64Map()[rakNetRandom.RandomMT()%64];
180  boundary[boundarySize]=0;
181  }
182 
183  sprintf_s(query, "MIME-version: 1.0\r\n");
184  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
185 
186  if (attachedFiles && attachedFiles->fileList.Size())
187  {
188  sprintf_s(query, "Content-type: multipart/mixed; BOUNDARY=\"%s\"\r\n\r\n", boundary);
189  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
190 
191  sprintf_s(query, "This is a multi-part message in MIME format.\r\n\r\n--%s\r\n", boundary);
192  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
193  }
194 
195  sprintf_s(query, "Content-Type: text/plain; charset=\"US-ASCII\"\r\n\r\n");
196  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
197 
198  // 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.
199  char *newBody;
200  int bodyLength;
201  bodyLength=(int)strlen(body);
202  newBody = (char*) rakMalloc_Ex( bodyLength*3, _FILE_AND_LINE_ );
203  if (bodyLength>=0)
204  newBody[0]=body[0];
205  for (i=1, j=1; i < bodyLength; i++)
206  {
207  // Transform \n . \r \n into \n . . \r \n
208  if (i < bodyLength-2 &&
209  body[i-1]=='\n' &&
210  body[i+0]=='.' &&
211  body[i+1]=='\r' &&
212  body[i+2]=='\n')
213  {
214  newBody[j++]='.';
215  newBody[j++]='.';
216  newBody[j++]='\r';
217  newBody[j++]='\n';
218  i+=2;
219  }
220  // Transform \n . . \r \n into \n . . . \r \n
221  // Having to process .. is a bug in the mail server - the spec says ONLY \r\n.\r\n should be transformed
222  else if (i <= bodyLength-3 &&
223  body[i-1]=='\n' &&
224  body[i+0]=='.' &&
225  body[i+1]=='.' &&
226  body[i+2]=='\r' &&
227  body[i+3]=='\n')
228  {
229  newBody[j++]='.';
230  newBody[j++]='.';
231  newBody[j++]='.';
232  newBody[j++]='\r';
233  newBody[j++]='\n';
234  i+=3;
235  }
236  // 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)
237  else if (i < bodyLength-1 &&
238  body[i-1]=='\n' &&
239  body[i+0]=='.' &&
240  body[i+1]=='\n')
241  {
242  newBody[j++]='.';
243  newBody[j++]='.';
244  newBody[j++]='\r';
245  newBody[j++]='\n';
246  i+=1;
247  }
248  // 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)
249  // In fact having to process .. is a bug too - because the spec says ONLY \r\n.\r\n should be transformed
250  else if (i <= bodyLength-2 &&
251  body[i-1]=='\n' &&
252  body[i+0]=='.' &&
253  body[i+1]=='.' &&
254  body[i+2]=='\n')
255  {
256  newBody[j++]='.';
257  newBody[j++]='.';
258  newBody[j++]='.';
259  newBody[j++]='\r';
260  newBody[j++]='\n';
261  i+=2;
262  }
263  else
264  newBody[j++]=body[i];
265  }
266 
267  newBody[j++]='\r';
268  newBody[j++]='\n';
269  tcpInterface.Send(newBody, j, emailServer,false);
270 
271  rakFree_Ex(newBody, _FILE_AND_LINE_ );
272  int outputOffset;
273 
274  // What a pain in the rear. I have to map the binary to printable characters using 6 bits per character.
275  if (attachedFiles && attachedFiles->fileList.Size())
276  {
277  for (i=0; i < (int) attachedFiles->fileList.Size(); i++)
278  {
279  // Write boundary
280  sprintf_s(query, "\r\n--%s\r\n", boundary);
281  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
282 
283  sprintf_s(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());
284  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
285 
286  newBody = (char*) rakMalloc_Ex( (size_t) (attachedFiles->fileList[i].dataLengthBytes*3)/2, _FILE_AND_LINE_ );
287 
288  outputOffset=Base64Encoding((const unsigned char*) attachedFiles->fileList[i].data, (int) attachedFiles->fileList[i].dataLengthBytes, newBody);
289 
290  // Send the base64 mapped file.
291  tcpInterface.Send(newBody, outputOffset, emailServer,false);
292  rakFree_Ex(newBody, _FILE_AND_LINE_ );
293 
294  }
295 
296  // Write last boundary
297  sprintf_s(query, "\r\n--%s--\r\n", boundary);
298  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
299  }
300 
301 
302  sprintf_s(query, "\r\n.\r\n");
303  tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
304  response=GetResponse(&tcpInterface, emailServer, doPrintf);
305  if (response!=0)
306  return response;
307 
308  tcpInterface.Send("QUIT\r\n", (unsigned int)strlen("QUIT\r\n"), emailServer,false);
309 
310  RakSleep(30);
311  if (doPrintf)
312  {
313  packet = tcpInterface.Receive();
314  while (packet)
315  {
316  RAKNET_DEBUG_PRINTF("%s", packet->data);
317  tcpInterface.DeallocatePacket(packet);
318  packet = tcpInterface.Receive();
319  }
320  }
321  tcpInterface.Stop();
322  return 0; // Success
323 }
324 
325 const char *EmailSender::GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf)
326 {
327  SLNet::Packet *packet;
328  SLNet::TimeMS timeout;
329  timeout= SLNet::GetTimeMS()+5000;
330  for(;;)
331  {
332  if (tcpInterface->HasLostConnection()==emailServer)
333  return "Connection to server lost.";
334  packet = tcpInterface->Receive();
335  if (packet)
336  {
337  if (doPrintf)
338  {
339  RAKNET_DEBUG_PRINTF("%s", packet->data);
340  }
341 #if OPEN_SSL_CLIENT_SUPPORT==1
342  if (strstr((const char*)packet->data, "220"))
343  {
344  tcpInterface->StartSSLClient(packet->systemAddress);
345  return "AUTHENTICATE"; // OK
346  }
347 // if (strstr((const char*)packet->data, "250-AUTH LOGIN PLAIN"))
348 // {
349 // tcpInterface->StartSSLClient(packet->systemAddress);
350 // return "AUTHENTICATE"; // OK
351 // }
352 #endif
353  if (strstr((const char*)packet->data, "235"))
354  return 0; // Authentication accepted
355  if (strstr((const char*)packet->data, "354"))
356  return 0; // Go ahead
357 #if OPEN_SSL_CLIENT_SUPPORT==1
358  if (strstr((const char*)packet->data, "250-STARTTLS"))
359  {
360  tcpInterface->Send("STARTTLS\r\n", (unsigned int) strlen("STARTTLS\r\n"), packet->systemAddress, false);
361  return "CONTINUE";
362  }
363 #endif
364  if (strstr((const char*)packet->data, "250"))
365  return 0; // OK
366  if (strstr((const char*)packet->data, "550"))
367  return "Failed on error code 550";
368  if (strstr((const char*)packet->data, "553"))
369  return "Failed on error code 553";
370  }
371  if (SLNet::GetTimeMS() > timeout)
372  return "Timed out";
373  RakSleep(100);
374  }
375 }
376 
377 
378 #endif // _RAKNET_SUPPORT_*