Changeset - 372ee69043f1
[Not reviewed]
0 11 0
Jan Kaluza - 13 years ago 2012-10-31 09:12:33
hanzz.k@gmail.com
Create directories with proper owner/group/permissions. Moved StorageBackend related methods from Util into StorageBackend
11 files changed with 164 insertions and 110 deletions:
0 comments (0 inline, 0 general)
include/transport/storagebackend.h
Show inline comments
 
@@ -86,6 +86,14 @@ class Config;
 
class StorageBackend
 
{
 
	public:
 
		static std::string encryptPassword(const std::string &password, const std::string &key);
 

	
 
		static std::string decryptPassword(std::string &encrypted, const std::string &key);
 

	
 
		static std::string serializeGroups(const std::vector<std::string> &groups);
 

	
 
		static std::vector<std::string> deserializeGroups(std::string &groups);
 

	
 
		/// Virtual desctructor.
 
		virtual ~StorageBackend() {}
 

	
include/transport/util.h
Show inline comments
 
@@ -26,19 +26,16 @@
 
#include <string>
 
#include "Swiften/StringCodecs/Base64.h"
 

	
 
#include <boost/filesystem.hpp>
 
#include "transport/config.h"
 

	
 
namespace Transport {
 

	
 
namespace Util {
 

	
 
void removeEverythingOlderThan(const std::vector<std::string> &dirs, time_t t);
 

	
 
std::string encryptPassword(const std::string &password, const std::string &key);
 
void createDirectories(Transport::Config *config, const boost::filesystem::path& ph);
 

	
 
std::string decryptPassword(std::string &encrypted, const std::string &key);
 

	
 
std::string serializeGroups(const std::vector<std::string> &groups);
 

	
 
std::vector<std::string> deserializeGroups(std::string &groups);
 
void removeEverythingOlderThan(const std::vector<std::string> &dirs, time_t t);
 

	
 
int getRandomPort(const std::string &s);
 

	
plugin/cpp/CMakeLists.txt
Show inline comments
 
@@ -5,6 +5,7 @@ FILE(GLOB HEADERS ../include/transport/*.h)
 
set(EXTRA_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../src/memoryusage.cpp)
 
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/logging.cpp)
 
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/config.cpp)
 
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/util.cpp)
 
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../include/transport/protocol.pb.cc)
 
 
if (NOT WIN32)
spectrum/src/main.cpp
Show inline comments
 
@@ -22,6 +22,7 @@
 
#include <boost/algorithm/string.hpp>
 
#ifndef WIN32
 
#include "sys/signal.h"
 
#include "sys/stat.h"
 
#include <pwd.h>
 
#include <grp.h>
 
#include <sys/resource.h>
 
@@ -248,9 +249,14 @@ int main(int argc, char **argv)
 
		return 1;
 
	}
 

	
 
#ifndef WIN32
 
	mode_t old_cmask = umask(0007);
 
#endif
 

	
 
	// create directories
 
	try {
 
		boost::filesystem::create_directories(CONFIG_STRING(&config, "service.working_dir"));
 
		
 
		Transport::Util::createDirectories(&config, CONFIG_STRING(&config, "service.working_dir"));
 
	}
 
	catch (...) {
 
		std::cerr << "Can't create service.working_dir directory " << CONFIG_STRING(&config, "service.working_dir") << ".\n";
 
@@ -284,20 +290,6 @@ int main(int argc, char **argv)
 
#endif
 

	
 
#ifndef WIN32
 
	if (!CONFIG_STRING(&config, "service.group").empty() ||!CONFIG_STRING(&config, "service.user").empty() ) {
 
		struct group *gr;
 
		if ((gr = getgrnam(CONFIG_STRING(&config, "service.group").c_str())) == NULL) {
 
			std::cerr << "Invalid service.group name " << CONFIG_STRING(&config, "service.group") << "\n";
 
			return 1;
 
		}
 
		struct passwd *pw;
 
		if ((pw = getpwnam(CONFIG_STRING(&config, "service.user").c_str())) == NULL) {
 
			std::cerr << "Invalid service.user name " << CONFIG_STRING(&config, "service.user") << "\n";
 
			return 1;
 
		}
 
		chown(CONFIG_STRING(&config, "service.working_dir").c_str(), pw->pw_uid, gr->gr_gid);
 
	}
 

	
 
	char backendport[20];
 
	FILE* port_file_f;
 
	port_file_f = fopen(CONFIG_STRING(&config, "service.portfile").c_str(), "w+");
 
@@ -423,6 +415,10 @@ int main(int argc, char **argv)
 

	
 
	eventLoop.run();
 

	
 
#ifndef WIN32
 
	umask(old_cmask);
 
#endif
 

	
 
	if (userRegistration) {
 
		userRegistration->stop();
 
		delete userRegistration;
src/logging.cpp
Show inline comments
 
@@ -20,6 +20,7 @@
 

	
 
#include "transport/logging.h"
 
#include "transport/config.h"
 
#include "transport/util.h"
 
#include <boost/foreach.hpp>
 
#include <iostream>
 
#include <iterator>
 
@@ -31,6 +32,7 @@
 

	
 
#ifndef WIN32
 
#include "sys/signal.h"
 
#include <sys/stat.h>
 
#include <pwd.h>
 
#include <grp.h>
 
#include <sys/resource.h>
 
@@ -136,35 +138,42 @@ static void initLogging(Config *config, std::string key) {
 
				break;
 
			}
 
		}
 

	
 
		mode_t old_cmask;
 
		if (!dir.empty()) {
 
			// create directories
 
#ifndef WIN32
 
			old_cmask = umask(0007);
 
#endif
 
			try {
 
				boost::filesystem::create_directories(
 
					boost::filesystem::path(dir).parent_path().string()
 
				);
 
				Transport::Util::createDirectories(config, boost::filesystem::path(dir).parent_path());
 
			}
 
			catch (...) {
 
				std::cerr << "Can't create logging directory directory " << boost::filesystem::path(dir).parent_path().string() << ".\n";
 
			catch (const boost::filesystem::filesystem_error &e) {
 
				std::cerr << "Can't create logging directory directory " << boost::filesystem::path(dir).parent_path().string() << ": " << e.what() << ".\n";
 
			}
 
		}
 

	
 
#ifndef WIN32
 
			if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) {
 
				struct group *gr;
 
				if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) {
 
					std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n";
 
				}
 
				struct passwd *pw;
 
				if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) {
 
					std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n";
 
				}
 
				chown(dir.c_str(), pw->pw_uid, gr->gr_gid);
 
			}
 
		log4cxx::PropertyConfigurator::configure(p);
 

	
 
#endif
 
		// Change owner of main log file
 
#ifndef WIN32
 
	if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) {
 
		struct group *gr;
 
		if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) {
 
			std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n";
 
		}
 
		struct passwd *pw;
 
		if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) {
 
			std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n";
 
		}
 
		chown(dir.c_str(), pw->pw_uid, gr->gr_gid);
 
	}
 
#endif
 

	
 
		log4cxx::PropertyConfigurator::configure(p);
 
#ifndef WIN32
 
		if (!dir.empty()) {
 
			umask(old_cmask);
 
		}
 
#endif
 
	}
 
}
 

	
src/mysqlbackend.cpp
Show inline comments
 
@@ -421,7 +421,7 @@ bool MySQLBackend::exec(const std::string &query) {
 
void MySQLBackend::setUser(const UserInfo &user) {
 
	std::string encrypted = user.password;
 
	if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) {
 
		encrypted = Util::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key"));
 
		encrypted = StorageBackend::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key"));
 
	}
 
	*m_setUser << user.jid << user.uin << encrypted << user.language << user.encoding << user.vip << user.uin << encrypted;
 
	EXEC(m_setUser, setUser(user));
 
@@ -439,7 +439,7 @@ bool MySQLBackend::getUser(const std::string &barejid, UserInfo &user) {
 
		*m_getUser >> user.id >> user.jid >> user.uin >> user.password >> user.encoding >> user.language >> user.vip;
 

	
 
		if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) {
 
			user.password = Util::decryptPassword(user.password, CONFIG_STRING(m_config, "database.encryption_key"));
 
			user.password = StorageBackend::decryptPassword(user.password, CONFIG_STRING(m_config, "database.encryption_key"));
 
		}
 
	}
 

	
 
@@ -467,7 +467,7 @@ bool MySQLBackend::getOnlineUsers(std::vector<std::string> &users) {
 

	
 
long MySQLBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
 
// 	"INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)"
 
	std::string groups = Util::serializeGroups(buddyInfo.groups);
 
	std::string groups = StorageBackend::serializeGroups(buddyInfo.groups);
 
	*m_addBuddy << userId << buddyInfo.legacyName << buddyInfo.subscription;
 
	*m_addBuddy << groups;
 
	*m_addBuddy << buddyInfo.alias << buddyInfo.flags;
 
@@ -517,7 +517,7 @@ void MySQLBackend::removeBuddy(long id) {
 

	
 
void MySQLBackend::updateBuddy(long userId, const BuddyInfo &buddyInfo) {
 
// 	"UPDATE " + m_prefix + "buddies SET groups=?, nickname=?, flags=?, subscription=? WHERE user_id=? AND uin=?"
 
	std::string groups = Util::serializeGroups(buddyInfo.groups);
 
	std::string groups = StorageBackend::serializeGroups(buddyInfo.groups);
 
	*m_updateBuddy << groups;
 
	*m_updateBuddy << buddyInfo.alias << buddyInfo.flags << buddyInfo.subscription;
 
	*m_updateBuddy << userId << buddyInfo.legacyName;
 
@@ -547,7 +547,7 @@ bool MySQLBackend::getBuddies(long id, std::list<BuddyInfo> &roster) {
 
		*m_getBuddies >> b.id >> b.legacyName >> b.subscription >> b.alias >> group >> b.flags;
 

	
 
		if (!group.empty()) {
 
			b.groups = Util::deserializeGroups(group);
 
			b.groups = StorageBackend::deserializeGroups(group);
 
		}
 

	
 
		roster.push_back(b);
src/pqxxbackend.cpp
Show inline comments
 
@@ -157,7 +157,7 @@ bool PQXXBackend::exec(pqxx::nontransaction &txn, const std::string &query, bool
 
void PQXXBackend::setUser(const UserInfo &user) {
 
	std::string encrypted = user.password;
 
	if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) {
 
		encrypted = Util::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key"));
 
		encrypted = StorageBackend::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key"));
 
	}
 
	try {
 
		pqxx::nontransaction txn(*m_conn);
 
@@ -236,7 +236,7 @@ long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
 
			+ "(" + pqxx::to_string(userId) + ","
 
			+ quote(txn, buddyInfo.legacyName) + ","
 
			+ quote(txn, buddyInfo.subscription) + ","
 
			+ quote(txn, Util::serializeGroups(buddyInfo.groups)) + ","
 
			+ quote(txn, StorageBackend::serializeGroups(buddyInfo.groups)) + ","
 
			+ quote(txn, buddyInfo.alias) + ","
 
			+ pqxx::to_string(buddyInfo.flags) + ") RETURNING id");
 

	
 
@@ -263,7 +263,7 @@ long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
 
void PQXXBackend::updateBuddy(long userId, const BuddyInfo &buddyInfo) {
 
	try {
 
		pqxx::nontransaction txn(*m_conn);
 
		txn.exec("UPDATE " + m_prefix + "buddies SET groups=" + quote(txn, Util::serializeGroups(buddyInfo.groups)) + ", nickname=" + quote(txn, buddyInfo.alias) + ", flags=" + pqxx::to_string(buddyInfo.flags) + ", subscription=" + quote(txn, buddyInfo.subscription) + " WHERE user_id=" + pqxx::to_string(userId) + " AND uin=" + quote(txn, buddyInfo.legacyName));
 
		txn.exec("UPDATE " + m_prefix + "buddies SET groups=" + quote(txn, StorageBackend::serializeGroups(buddyInfo.groups)) + ", nickname=" + quote(txn, buddyInfo.alias) + ", flags=" + pqxx::to_string(buddyInfo.flags) + ", subscription=" + quote(txn, buddyInfo.subscription) + " WHERE user_id=" + pqxx::to_string(userId) + " AND uin=" + quote(txn, buddyInfo.legacyName));
 
	}
 
	catch (std::exception& e) {
 
		LOG4CXX_ERROR(logger, e.what());
 
@@ -287,7 +287,7 @@ bool PQXXBackend::getBuddies(long id, std::list<BuddyInfo> &roster) {
 
			b.flags = r[0][5].as<long>();
 

	
 
			if (!group.empty()) {
 
				b.groups = Util::deserializeGroups(group);
 
				b.groups = StorageBackend::deserializeGroups(group);
 
			}
 

	
 
			roster.push_back(b);
src/sqlite3backend.cpp
Show inline comments
 
@@ -287,7 +287,7 @@ long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
 
	BIND_INT(m_addBuddy, userId);
 
	BIND_STR(m_addBuddy, buddyInfo.legacyName);
 
	BIND_STR(m_addBuddy, buddyInfo.subscription);
 
	BIND_STR(m_addBuddy, Util::serializeGroups(buddyInfo.groups));
 
	BIND_STR(m_addBuddy, StorageBackend::serializeGroups(buddyInfo.groups));
 
	BIND_STR(m_addBuddy, buddyInfo.alias);
 
	BIND_INT(m_addBuddy, buddyInfo.flags);
 

	
 
@@ -313,7 +313,7 @@ long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
 
void SQLite3Backend::updateBuddy(long userId, const BuddyInfo &buddyInfo) {
 
// 	UPDATE " + m_prefix + "buddies SET groups=?, nickname=?, flags=?, subscription=? WHERE user_id=? AND uin=?
 
	BEGIN(m_updateBuddy);
 
	BIND_STR(m_updateBuddy, Util::serializeGroups(buddyInfo.groups));
 
	BIND_STR(m_updateBuddy, StorageBackend::serializeGroups(buddyInfo.groups));
 
	BIND_STR(m_updateBuddy, buddyInfo.alias);
 
	BIND_INT(m_updateBuddy, buddyInfo.flags);
 
	BIND_STR(m_updateBuddy, buddyInfo.subscription);
 
@@ -355,7 +355,7 @@ bool SQLite3Backend::getBuddies(long id, std::list<BuddyInfo> &roster) {
 
		b.subscription = GET_STR(m_getBuddies);
 
		b.alias = GET_STR(m_getBuddies);
 
		std::string groups = GET_STR(m_getBuddies);
 
		b.groups = Util::deserializeGroups(groups);
 
		b.groups = StorageBackend::deserializeGroups(groups);
 
		b.flags = GET_INT(m_getBuddies);
 

	
 
		if (buddy_id == b.id) {
src/storagebackend.cpp
Show inline comments
 
@@ -5,6 +5,8 @@
 
#include "transport/mysqlbackend.h"
 
#include "transport/pqxxbackend.h"
 

	
 
#include "Swiften/Swiften.h"
 

	
 
namespace Transport {
 

	
 
StorageBackend *StorageBackend::createBackend(Config *config, std::string &error) {
 
@@ -47,4 +49,56 @@ StorageBackend *StorageBackend::createBackend(Config *config, std::string &error
 
	return storageBackend;
 
}
 

	
 
std::string StorageBackend::encryptPassword(const std::string &password, const std::string &key) {
 
	std::string encrypted;
 
	encrypted.resize(password.size());
 
	for (int i = 0; i < password.size(); i++) {
 
		char c = password[i];
 
		char keychar = key[i % key.size()];
 
		c += keychar;
 
		encrypted[i] = c;
 
	}
 

	
 
	encrypted = Swift::Base64::encode(Swift::createByteArray(encrypted));
 
	return encrypted;
 
}
 

	
 
std::string StorageBackend::decryptPassword(std::string &encrypted, const std::string &key) {
 
	encrypted = Swift::byteArrayToString(Swift::Base64::decode(encrypted));
 
	std::string password;
 
	password.resize(encrypted.size());
 
	for (int i = 0; i < encrypted.size(); i++) {
 
		char c = encrypted[i];
 
		char keychar = key[i % key.size()];
 
		c -= keychar;
 
		password[i] = c;
 
	}
 

	
 
	return password;
 
}
 

	
 
std::string StorageBackend::serializeGroups(const std::vector<std::string> &groups) {
 
	std::string ret;
 
	BOOST_FOREACH(const std::string &group, groups) {
 
		ret += group + "\n";
 
	}
 
	if (!ret.empty()) {
 
		ret.erase(ret.end() - 1);
 
	}
 
	return ret;
 
}
 

	
 
std::vector<std::string> StorageBackend::deserializeGroups(std::string &groups) {
 
	std::vector<std::string> ret;
 
	if (groups.empty()) {
 
		return ret;
 
	}
 

	
 
	boost::split(ret, groups, boost::is_any_of("\n"));
 
	if (ret.back().empty()) {
 
		ret.erase(ret.end() - 1);
 
	}
 
	return ret;
 
}
 

	
 
}
src/tests/util.cpp
Show inline comments
 
@@ -3,6 +3,7 @@
 
#include "transport/storagebackend.h"
 
#include "transport/user.h"
 
#include "transport/transport.h"
 
#include "transport/storagebackend.h"
 
#include "transport/conversation.h"
 
#include "transport/usermanager.h"
 
#include "transport/localbuddy.h"
 
@@ -36,8 +37,8 @@ class UtilTest : public CPPUNIT_NS :: TestFixture{
 
		}
 

	
 
	void encryptDecryptPassword() {
 
		std::string encrypted = Util::encryptPassword("password", "key");
 
		CPPUNIT_ASSERT_EQUAL(std::string("password"), Util::decryptPassword(encrypted, "key"));
 
		std::string encrypted = StorageBackend::encryptPassword("password", "key");
 
		CPPUNIT_ASSERT_EQUAL(std::string("password"), StorageBackend::decryptPassword(encrypted, "key"));
 
	}
 

	
 
};
src/util.cpp
Show inline comments
 
@@ -19,6 +19,7 @@
 
 */
 

	
 
#include "transport/util.h"
 
#include "transport/config.h"
 
#include <boost/foreach.hpp>
 
#include <iostream>
 
#include <iterator>
 
@@ -27,6 +28,19 @@
 
#include <boost/algorithm/string.hpp>
 
#include <boost/numeric/conversion/cast.hpp>
 

	
 
#ifndef WIN32
 
#include "sys/signal.h"
 
#include <sys/stat.h>
 
#include <pwd.h>
 
#include <grp.h>
 
#include <sys/resource.h>
 
#include "libgen.h"
 
#else
 
#include <windows.h>
 
#include <process.h>
 
#define getpid _getpid
 
#endif
 

	
 
using namespace boost::filesystem;
 

	
 
using namespace boost;
 
@@ -35,6 +49,32 @@ namespace Transport {
 

	
 
namespace Util {
 

	
 
void createDirectories(Transport::Config *config, const boost::filesystem::path& ph) {
 
	if (ph.empty() || exists(ph)) {
 
		return;
 
	}
 

	
 
	// First create branch, by calling ourself recursively
 
	createDirectories(config, ph.branch_path());
 
	
 
	// Now that parent's path exists, create the directory
 
	create_directory(ph);
 

	
 
#ifndef WIN32
 
	if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) {
 
		struct group *gr;
 
		if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) {
 
			std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n";
 
		}
 
		struct passwd *pw;
 
		if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) {
 
			std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n";
 
		}
 
		chown(ph.string().c_str(), pw->pw_uid, gr->gr_gid);
 
	}
 
#endif
 
}
 

	
 
void removeEverythingOlderThan(const std::vector<std::string> &dirs, time_t t) {
 
	BOOST_FOREACH(const std::string &dir, dirs) {
 
		path p(dir);
 
@@ -58,7 +98,7 @@ void removeEverythingOlderThan(const std::vector<std::string> &dirs, time_t t) {
 
							std::vector<std::string> nextDirs;
 
							nextDirs.push_back(itr->path().string());
 
							removeEverythingOlderThan(nextDirs, t);
 
							if (is_empty(itr->path())) {
 
							if (boost::filesystem::is_empty(itr->path())) {
 
								remove_all(itr->path());
 
							}
 
						}
 
@@ -77,58 +117,6 @@ void removeEverythingOlderThan(const std::vector<std::string> &dirs, time_t t) {
 
	}
 
}
 

	
 
std::string encryptPassword(const std::string &password, const std::string &key) {
 
	std::string encrypted;
 
	encrypted.resize(password.size());
 
	for (int i = 0; i < password.size(); i++) {
 
		char c = password[i];
 
		char keychar = key[i % key.size()];
 
		c += keychar;
 
		encrypted[i] = c;
 
	}
 

	
 
	encrypted = Swift::Base64::encode(Swift::createByteArray(encrypted));
 
	return encrypted;
 
}
 

	
 
std::string decryptPassword(std::string &encrypted, const std::string &key) {
 
	encrypted = Swift::byteArrayToString(Swift::Base64::decode(encrypted));
 
	std::string password;
 
	password.resize(encrypted.size());
 
	for (int i = 0; i < encrypted.size(); i++) {
 
		char c = encrypted[i];
 
		char keychar = key[i % key.size()];
 
		c -= keychar;
 
		password[i] = c;
 
	}
 

	
 
	return password;
 
}
 

	
 
std::string serializeGroups(const std::vector<std::string> &groups) {
 
	std::string ret;
 
	BOOST_FOREACH(const std::string &group, groups) {
 
		ret += group + "\n";
 
	}
 
	if (!ret.empty()) {
 
		ret.erase(ret.end() - 1);
 
	}
 
	return ret;
 
}
 

	
 
std::vector<std::string> deserializeGroups(std::string &groups) {
 
	std::vector<std::string> ret;
 
	if (groups.empty()) {
 
		return ret;
 
	}
 

	
 
	boost::split(ret, groups, boost::is_any_of("\n"));
 
	if (ret.back().empty()) {
 
		ret.erase(ret.end() - 1);
 
	}
 
	return ret;
 
}
 

	
 
int getRandomPort(const std::string &s) {
 
	unsigned long r = 0;
 
	BOOST_FOREACH(char c, s) {
0 comments (0 inline, 0 general)