PageRenderTime 57ms CodeModel.GetById 27ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llmessage/llmail.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 401 lines | 300 code | 38 blank | 63 comment | 36 complexity | 66e31ba739f2254912f003fac9a301ee MD5 | raw file
  1/** 
  2 * @file llmail.cpp
  3 * @brief smtp helper functions.
  4 *
  5 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "linden_common.h"
 28
 29#include "llmail.h"
 30
 31// APR on Windows needs full windows headers
 32#ifdef LL_WINDOWS
 33#	undef WIN32_LEAN_AND_MEAN
 34#	include <winsock2.h>
 35#	include <windows.h>
 36#endif
 37
 38#include <string>
 39#include <sstream>
 40
 41#include "apr_pools.h"
 42#include "apr_network_io.h"
 43
 44#include "llapr.h"
 45#include "llbase32.h"	// IM-to-email address
 46#include "llblowfishcipher.h"
 47#include "llerror.h"
 48#include "llhost.h"
 49#include "llsd.h"
 50#include "llstring.h"
 51#include "lluuid.h"
 52#include "net.h"
 53
 54//
 55// constants
 56//
 57const size_t LL_MAX_KNOWN_GOOD_MAIL_SIZE = 4096;
 58
 59static bool gMailEnabled = true;
 60static apr_pool_t* gMailPool;
 61static apr_sockaddr_t* gSockAddr;
 62static apr_socket_t* gMailSocket;
 63
 64bool connect_smtp();
 65void disconnect_smtp();
 66 
 67//#if LL_WINDOWS
 68//SOCKADDR_IN gMailDstAddr, gMailSrcAddr, gMailLclAddr;
 69//#else
 70//struct sockaddr_in gMailDstAddr, gMailSrcAddr, gMailLclAddr;
 71//#endif
 72
 73// Define this for a super-spammy mail mode.
 74//#define LL_LOG_ENTIRE_MAIL_MESSAGE_ON_SEND 1
 75
 76bool connect_smtp()
 77{
 78	// Prepare an soket to talk smtp
 79	apr_status_t status;
 80	status = apr_socket_create(
 81		&gMailSocket,
 82		gSockAddr->sa.sin.sin_family,
 83		SOCK_STREAM,
 84		APR_PROTO_TCP,
 85		gMailPool);
 86	if(ll_apr_warn_status(status)) return false;
 87	status = apr_socket_connect(gMailSocket, gSockAddr);
 88	if(ll_apr_warn_status(status))
 89	{
 90		status = apr_socket_close(gMailSocket);
 91		ll_apr_warn_status(status);
 92		return false;
 93	}
 94	return true;
 95}
 96
 97void disconnect_smtp()
 98{
 99	if(gMailSocket)
100	{
101		apr_status_t status = apr_socket_close(gMailSocket);
102		ll_apr_warn_status(status);
103		gMailSocket = NULL;
104	}
105}
106
107// Returns TRUE on success.
108// message should NOT be SMTP escaped.
109// static
110BOOL LLMail::send(
111	const char* from_name,
112	const char* from_address,
113	const char* to_name,
114	const char* to_address,
115	const char* subject,
116	const char* message,
117	const LLSD& headers)
118{
119	std::string header = buildSMTPTransaction(
120		from_name,
121		from_address,
122		to_name,
123		to_address,
124		subject,
125		headers);
126	if(header.empty())
127	{
128		return FALSE;
129	}
130
131	std::string message_str;
132	if(message)
133	{
134		message_str = message;
135	}
136	bool rv = send(header, message_str, to_address, from_address);
137	if(rv) return TRUE;
138	return FALSE;
139}
140
141// static
142void LLMail::init(const std::string& hostname, apr_pool_t* pool)
143{
144	gMailSocket = NULL;
145	if(hostname.empty() || !pool)
146	{
147		gMailPool = NULL;
148		gSockAddr = NULL;
149	}
150	else
151	{
152		gMailPool = pool;
153
154		// collect all the information into a socaddr sturcture. the
155		// documentation is a bit unclear, but I either have to
156		// specify APR_UNSPEC or not specify any flags. I am not sure
157		// which option is better.
158		apr_status_t status = apr_sockaddr_info_get(
159			&gSockAddr,
160			hostname.c_str(),
161			APR_UNSPEC,
162			25,
163			APR_IPV4_ADDR_OK,
164			gMailPool);
165		ll_apr_warn_status(status);
166	}
167}
168
169// static
170void LLMail::enable(bool mail_enabled)
171{
172	gMailEnabled = mail_enabled;
173}
174
175// Test a subject line for RFC2822 compliance.
176static bool valid_subject_chars(const char *subject)
177{
178	for (; *subject != '\0'; subject++)
179	{
180		unsigned char c = *subject;
181
182		if (c == '\xa' || c == '\xd' || c > '\x7f')
183		{
184			return false;
185		}
186	}
187
188	return true;
189}
190
191// static
192std::string LLMail::buildSMTPTransaction(
193	const char* from_name,
194	const char* from_address,
195	const char* to_name,
196	const char* to_address,
197	const char* subject,
198	const LLSD& headers)
199{
200	if(!from_address || !to_address)
201	{
202		llinfos << "send_mail build_smtp_transaction reject: missing to and/or"
203			<< " from address." << llendl;
204		return std::string();
205	}
206	if(!valid_subject_chars(subject))
207	{
208		llinfos << "send_mail build_smtp_transaction reject: bad subject header: "
209			<< "to=<" << to_address
210			<< ">, from=<" << from_address << ">"
211			<< llendl;
212		return std::string();
213	}
214	std::ostringstream from_fmt;
215	if(from_name && from_name[0])
216	{
217		// "My Name" <myaddress@example.com>
218		from_fmt << "\"" << from_name << "\" <" << from_address << ">";
219	}
220	else
221	{
222		// <myaddress@example.com>
223		from_fmt << "<" << from_address << ">";
224	}
225	std::ostringstream to_fmt;
226	if(to_name && to_name[0])
227	{
228		to_fmt << "\"" << to_name << "\" <" << to_address << ">";
229	}
230	else
231	{
232		to_fmt << "<" << to_address << ">";
233	}
234	std::ostringstream header;
235	header
236		<< "HELO lindenlab.com\r\n"
237		<< "MAIL FROM:<" << from_address << ">\r\n"
238		<< "RCPT TO:<" << to_address << ">\r\n"
239		<< "DATA\r\n"
240		<< "From: " << from_fmt.str() << "\r\n"
241		<< "To: " << to_fmt.str() << "\r\n"
242		<< "Subject: " << subject << "\r\n";
243	
244	if(headers.isMap())
245	{
246		LLSD::map_const_iterator iter = headers.beginMap();
247		LLSD::map_const_iterator end = headers.endMap();
248		for(; iter != end; ++iter)
249		{
250			header << (*iter).first << ": " << ((*iter).second).asString()
251				<< "\r\n";
252		}
253	}
254
255	header << "\r\n";
256	return header.str();
257}
258
259// static
260bool LLMail::send(
261	const std::string& header,
262	const std::string& raw_message,
263	const char* from_address,
264	const char* to_address)
265{
266	if(!from_address || !to_address)
267	{
268		llinfos << "send_mail reject: missing to and/or from address."
269			<< llendl;
270		return false;
271	}
272
273	// remove any "." SMTP commands to prevent injection (DEV-35777)
274	// we don't need to worry about "\r\n.\r\n" because of the 
275	// "\n" --> "\n\n" conversion going into rfc2822_msg below
276	std::string message = raw_message;
277	std::string bad_string = "\n.\n";
278	std::string good_string = "\n..\n";
279	while (1)
280	{
281		int index = message.find(bad_string);
282		if (index == std::string::npos) break;
283		message.replace(index, bad_string.size(), good_string);
284	}
285
286	// convert all "\n" into "\r\n"
287	std::ostringstream rfc2822_msg;
288	for(U32 i = 0; i < message.size(); ++i)
289	{
290		switch(message[i])
291		{
292		case '\0':
293			break;
294		case '\n':
295			// *NOTE: this is kinda busted if we're fed \r\n
296			rfc2822_msg << "\r\n";
297			break;
298		default:
299			rfc2822_msg << message[i];
300			break;
301		}
302	}
303
304	if(!gMailEnabled)
305	{
306		llinfos << "send_mail reject: mail system is disabled: to=<"
307			<< to_address << ">, from=<" << from_address
308			<< ">" << llendl;
309		// Any future interface to SMTP should return this as an
310		// error.  --mark
311		return true;
312	}
313	if(!gSockAddr)
314	{
315		llwarns << "send_mail reject: mail system not initialized: to=<"
316			<< to_address << ">, from=<" << from_address
317			<< ">" << llendl;
318		return false;
319	}
320
321	if(!connect_smtp())
322	{
323		llwarns << "send_mail reject: SMTP connect failure: to=<"
324			<< to_address << ">, from=<" << from_address
325			<< ">" << llendl;
326		return false;
327	}
328
329	std::ostringstream smtp_fmt;
330	smtp_fmt << header << rfc2822_msg.str() << "\r\n" << ".\r\n" << "QUIT\r\n";
331	std::string smtp_transaction = smtp_fmt.str();
332	size_t original_size = smtp_transaction.size();
333	apr_size_t send_size = original_size;
334	apr_status_t status = apr_socket_send(
335		gMailSocket,
336		smtp_transaction.c_str(),
337		(apr_size_t*)&send_size);
338	disconnect_smtp();
339	if(ll_apr_warn_status(status))
340	{
341		llwarns << "send_mail socket failure: unable to write "
342			<< "to=<" << to_address
343			<< ">, from=<" << from_address << ">"
344			<< ", bytes=" << original_size
345			<< ", sent=" << send_size << llendl;
346		return false;
347	}
348	if(send_size >= LL_MAX_KNOWN_GOOD_MAIL_SIZE)
349	{
350		llwarns << "send_mail message has been shown to fail in testing "
351			<< "when sending messages larger than " << LL_MAX_KNOWN_GOOD_MAIL_SIZE
352			<< " bytes. The next log about success is potentially a lie." << llendl;
353	}
354	lldebugs << "send_mail success: "
355		<< "to=<" << to_address
356		<< ">, from=<" << from_address << ">"
357		<< ", bytes=" << original_size
358		<< ", sent=" << send_size << llendl;
359
360#if LL_LOG_ENTIRE_MAIL_MESSAGE_ON_SEND
361	llinfos << rfc2822_msg.str() << llendl;
362#endif
363	return true;
364}
365
366
367// static
368std::string LLMail::encryptIMEmailAddress(const LLUUID& from_agent_id,
369											const LLUUID& to_agent_id,
370											U32 time,
371											const U8* secret,
372											size_t secret_size)
373{
374#if LL_WINDOWS
375	return "blowfish-not-supported-on-windows";
376#else
377	size_t data_size = 4 + UUID_BYTES + UUID_BYTES;
378	// Convert input data into a binary blob
379	std::vector<U8> data;
380	data.resize(data_size);
381	// *NOTE: This may suffer from endian issues.  Could be htonmemcpy.
382	memcpy(&data[0], &time, 4);
383	memcpy(&data[4], &from_agent_id.mData[0], UUID_BYTES);
384	memcpy(&data[4 + UUID_BYTES], &to_agent_id.mData[0], UUID_BYTES);
385	
386	// Encrypt the blob
387	LLBlowfishCipher cipher(secret, secret_size);
388	size_t encrypted_size = cipher.requiredEncryptionSpace(data.size());
389	U8* encrypted = new U8[encrypted_size];
390	cipher.encrypt(&data[0], data_size, encrypted, encrypted_size);
391
392	std::string address = LLBase32::encode(encrypted, encrypted_size);
393
394	// Make it more pretty for humans.
395	LLStringUtil::toLower(address);
396
397	delete [] encrypted;
398
399	return address;
400#endif
401}