summaryrefslogtreecommitdiff
path: root/modules/extra/enc_bcrypt.cpp
blob: cd5e05c3519b34246cc1466dfc50669457eca8ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* RequiredLibraries: xcrypt */

#include "module.h"
#include "modules/encryption.h"
#include <xcrypt.h>

class EBCRYPT : public Module
{
	unsigned int rounds;

	Anope::string Salt()
	{
		char entropy[16];
		for (unsigned int i = 0; i < sizeof(entropy); i++)
			entropy[i] = static_cast<char>(rand() % 0xFF);

		char salt[32];
		if (!crypt_gensalt_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt)))
			return "";
		return salt;
	}

	Anope::string Generate(const Anope::string& data, const Anope::string& salt)
	{
		char hash[64];
		crypt_rn(data.c_str(), salt.c_str(), hash, sizeof(hash));
		return hash;
	}

	bool Compare(const Anope::string& string, const Anope::string& hash)
	{
		Anope::string ret = Generate(string, hash);
		if (ret.empty())
			return false;

		return (ret == hash);
	}

 public:
	EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR | EXTRA),
		rounds(10)
	{
		// Should obviously never happen, but if xcrypt somehow was compiled without support for $2a$ I guess it might
		if (Salt() == "")
			throw ModuleException("Could not salt! BCrypt will not be available!");
	}

	EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) anope_override
	{
		dest = "bcrypt:" + Generate(src, Salt());
		Log(LOG_DEBUG_2) << "(enc_bcrypt) hashed password from [" << src << "] to [" << dest << "]";
		return EVENT_ALLOW;
	}

	void OnCheckAuthentication(User *, IdentifyRequest *req) anope_override
	{
		const NickAlias *na = NickAlias::Find(req->GetAccount());
		if (na == NULL)
			return;
		NickCore *nc = na->nc;

		size_t pos = nc->pass.find(':');
		if (pos == Anope::string::npos)
			return;
		Anope::string hash_method(nc->pass.begin(), nc->pass.begin() + pos);
		if (hash_method != "bcrypt")
			return;

		if (Compare(req->GetPassword(), nc->pass.substr(7)))
		{
			/* if we are NOT the first module in the list,
			 * we want to re-encrypt the pass with the new encryption
			 */

			unsigned int hashrounds = 0;
			try
			{
				size_t roundspos = nc->pass.find('$', 11);
				if (roundspos == Anope::string::npos)
					throw ConvertException("Could not find hashrounds");

				hashrounds = convertTo<unsigned int>(nc->pass.substr(11, roundspos - 11));
			}
			catch (const ConvertException &)
			{
				Log(this) << "Could not get the round size of a hash. This is probably a bug. Hash: " << nc->pass;
			}

			if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (hashrounds && hashrounds != rounds))
				Anope::Encrypt(req->GetPassword(), nc->pass);
			req->Success(this);
		}
	}

	void OnReload(Configuration::Conf *conf) anope_override
	{
		Configuration::Block *block = conf->GetModule(this);
		rounds = block->Get<unsigned int>("rounds", "10");

		if (rounds == 0)
		{
			rounds = 10;
			Log(this) << "Rounds can't be 0! Setting ignored.";
		}
		else if (rounds < 10)
		{
			Log(this) << "10 to 12 rounds is recommended.";
		}
		else if (rounds >= 32)
		{
			rounds = 10;
			Log(this) << "The maximum number of rounds supported is 31. Ignoring setting and using 10.";
		}
		else if (rounds >= 14)
		{
			Log(this) << "Are you sure you want to use " << stringify(rounds) << " in your bcrypt settings? This is very CPU intensive! Recommended rounds is 10-12.";
		}
	}
};

MODULE_INIT(EBCRYPT)