1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2001-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
6
7 This program is Free Software; you can redistribute it and/or
8 modify it under the terms of version three of the GNU Affero General Public
9 License as published by the Free Software Foundation and included
10 in the file LICENSE.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301, USA.
21 */
22 /*
23 * Challenge Response Authentication Method using MD5 (CRAM-MD5)
24 * cram-md5 is based on RFC2104.
25 *
26 * Kern E. Sibbald, May MMI.
27 */
28
29 #include "include/bareos.h"
30 #include "lib/cram_md5.h"
31 #include "lib/bsock.h"
32 #include "lib/util.h"
33
34
CramMd5Handshake(BareosSocket * bs,const char * password,TlsPolicy local_tls_policy,const std::string & own_qualified_name)35 CramMd5Handshake::CramMd5Handshake(BareosSocket* bs,
36 const char* password,
37 TlsPolicy local_tls_policy,
38 const std::string& own_qualified_name)
39 : bs_(bs)
40 , password_(password)
41 , local_tls_policy_(local_tls_policy)
42 , own_qualified_name_(own_qualified_name)
43 , own_qualified_name_bashed_spaces_(own_qualified_name_)
44 {
45 BashSpaces(own_qualified_name_bashed_spaces_);
46 }
47
48 CramMd5Handshake::ComparisonResult
CompareChallengeWithOwnQualifiedName(const char * challenge) const49 CramMd5Handshake::CompareChallengeWithOwnQualifiedName(
50 const char* challenge) const
51 {
52 uint32_t a, b;
53 char buffer[MAXHOSTNAMELEN]{"?"}; // at least one character
54
55 bool scan_success = sscanf(challenge, "<%u.%u@%s", &a, &b, buffer) == 3;
56
57 // string contains the closing ">" of the challenge
58 std::string challenge_qualified_name(buffer, strlen(buffer) - 1);
59
60 Dmsg1(debuglevel_, "my_name: <%s> - challenge_name: <%s>\n",
61 own_qualified_name_bashed_spaces_.c_str(),
62 challenge_qualified_name.c_str());
63
64 if (!scan_success) { return ComparisonResult::FAILURE; }
65
66 // check if the name in the challenge matches the daemons name
67 return own_qualified_name_bashed_spaces_ == challenge_qualified_name
68 ? ComparisonResult::IS_SAME
69 : ComparisonResult::IS_DIFFERENT;
70 }
71
72 /* Authorize other end
73 * Codes that tls_local_need and tls_remote_need can take:
74 *
75 * kBnetTlsNone I cannot do tls
76 * BNET_TLS_CERTIFICATE_ALLOWED I can do tls, but it is not required on
77 * my end BNET_TLS_CERTIFICATE_REQUIRED tls is required on my end
78 *
79 * Returns: false if authentication failed
80 * true if OK
81 */
CramMd5Challenge()82 bool CramMd5Handshake::CramMd5Challenge()
83 {
84 PoolMem chal(PM_NAME), host(PM_NAME);
85
86 InitRandom();
87
88
89 /* Send challenge -- no hashing yet */
90 Mmsg(chal, "<%u.%u@%s>", (uint32_t)random(), (uint32_t)time(NULL),
91 own_qualified_name_bashed_spaces_.c_str());
92
93 if (bs_->IsBnetDumpEnabled()) {
94 Dmsg2(debuglevel_, "send: auth cram-md5 %s ssl=%d qualified-name=%s\n",
95 chal.c_str(), local_tls_policy_, own_qualified_name_.c_str());
96
97 if (!bs_->fsend("auth cram-md5 %s ssl=%d qualified-name=%s\n", chal.c_str(),
98 local_tls_policy_, own_qualified_name_.c_str())) {
99 Dmsg1(debuglevel_, "Bnet send challenge comm error. ERR=%s\n",
100 bs_->bstrerror());
101 result = HandshakeResult::NETWORK_ERROR;
102 return false;
103 }
104 } else { // network dump disabled
105 Dmsg2(debuglevel_, "send: auth cram-md5 %s ssl=%d\n", chal.c_str(),
106 local_tls_policy_);
107
108 if (!bs_->fsend("auth cram-md5 %s ssl=%d\n", chal.c_str(),
109 local_tls_policy_)) {
110 Dmsg1(debuglevel_, "Bnet send challenge comm error. ERR=%s\n",
111 bs_->bstrerror());
112 result = HandshakeResult::NETWORK_ERROR;
113 return false;
114 }
115 }
116
117 /* Read hashed response to challenge */
118 if (bs_->WaitData(180) <= 0 || bs_->recv() <= 0) {
119 Dmsg1(debuglevel_, "Bnet receive challenge response comm error. ERR=%s\n",
120 bs_->bstrerror());
121 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
122 result = HandshakeResult::NETWORK_ERROR;
123 return false;
124 }
125
126 uint8_t hmac[20];
127 /* Attempt to duplicate hash with our password */
128 hmac_md5((uint8_t*)chal.c_str(), strlen(chal.c_str()), (uint8_t*)password_,
129 strlen(password_), hmac);
130 BinToBase64(host.c_str(), MAXHOSTNAMELEN, (char*)hmac, 16, compatible_);
131
132 bool ok = bstrcmp(bs_->msg, host.c_str());
133 if (ok) {
134 Dmsg1(debuglevel_, "Authenticate OK %s\n", host.c_str());
135 } else {
136 BinToBase64(host.c_str(), MAXHOSTNAMELEN, (char*)hmac, 16, false);
137 ok = bstrcmp(bs_->msg, host.c_str());
138 if (!ok) {
139 Dmsg2(debuglevel_, "Authenticate NOT OK: wanted %s, got %s\n",
140 host.c_str(), bs_->msg);
141 }
142 }
143 if (ok) {
144 result = HandshakeResult::SUCCESS;
145 bs_->fsend("1000 OK auth\n");
146 } else {
147 result = HandshakeResult::WRONG_HASH;
148 bs_->fsend(_("1999 Authorization failed.\n"));
149 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
150 }
151 return ok;
152 }
153
CramMd5Response()154 bool CramMd5Handshake::CramMd5Response()
155 {
156 PoolMem chal(PM_NAME);
157 uint8_t hmac[20];
158
159 compatible_ = false;
160 if (bs_->recv() <= 0) {
161 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
162 result = HandshakeResult::NETWORK_ERROR;
163 return false;
164 }
165
166 Dmsg1(100, "cram-get received: %s", bs_->msg);
167 chal.check_size(bs_->message_length);
168 if (bs_->IsBnetDumpEnabled()) {
169 std::vector<char> destination_qualified_name(256);
170 if (sscanf(bs_->msg, "auth cram-md5c %s ssl=%d qualified-name=%s",
171 chal.c_str(), &remote_tls_policy_,
172 destination_qualified_name.data())
173 >= 2) {
174 compatible_ = true;
175 } else if (sscanf(bs_->msg, "auth cram-md5 %s ssl=%d qualified-name=%s",
176 chal.c_str(), &remote_tls_policy_,
177 destination_qualified_name.data())
178 < 2) { // minimum 2
179 if (sscanf(bs_->msg, "auth cram-md5 %s\n", chal.c_str()) != 1) {
180 Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
181 bs_->fsend(_("1999 Authorization failed.\n"));
182 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
183 result = HandshakeResult::FORMAT_MISMATCH;
184 return false;
185 }
186 }
187 bs_->SetBnetDumpDestinationQualifiedName(destination_qualified_name.data());
188 } else { // network dump disabled
189 if (sscanf(bs_->msg, "auth cram-md5c %s ssl=%d", chal.c_str(),
190 &remote_tls_policy_)
191 == 2) {
192 compatible_ = true;
193 } else if (sscanf(bs_->msg, "auth cram-md5 %s ssl=%d", chal.c_str(),
194 &remote_tls_policy_)
195 != 2) {
196 if (sscanf(bs_->msg, "auth cram-md5 %s\n", chal.c_str()) != 1) {
197 Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
198 bs_->fsend(_("1999 Authorization failed.\n"));
199 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
200 result = HandshakeResult::FORMAT_MISMATCH;
201 return false;
202 }
203 }
204 }
205
206 auto comparison_result = CompareChallengeWithOwnQualifiedName(chal.c_str());
207
208 if (comparison_result == ComparisonResult::IS_SAME) {
209 std::string c(chal.c_str());
210 // same sd-sd connection should be possible i.e. for copy jobs
211 if (c.rfind("R_STORAGE") == std::string::npos) {
212 result = HandshakeResult::REPLAY_ATTACK;
213 return false;
214 }
215 }
216
217 if (comparison_result == ComparisonResult::FAILURE) {
218 result = HandshakeResult::FORMAT_MISMATCH;
219 return false;
220 }
221
222 hmac_md5((uint8_t*)chal.c_str(), strlen(chal.c_str()), (uint8_t*)password_,
223 strlen(password_), hmac);
224 bs_->message_length
225 = BinToBase64(bs_->msg, 50, (char*)hmac, 16, compatible_) + 1;
226 if (!bs_->send()) {
227 result = HandshakeResult::NETWORK_ERROR;
228 Dmsg1(debuglevel_, "Send challenge failed. ERR=%s\n", bs_->bstrerror());
229 return false;
230 }
231 Dmsg1(99, "sending resp to challenge: %s\n", bs_->msg);
232 if (bs_->WaitData(180) <= 0 || bs_->recv() <= 0) {
233 Dmsg1(debuglevel_, "Receive challenge response failed. ERR=%s\n",
234 bs_->bstrerror());
235 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
236 result = HandshakeResult::NETWORK_ERROR;
237 return false;
238 }
239 if (bstrcmp(bs_->msg, "1000 OK auth\n")) {
240 result = HandshakeResult::SUCCESS;
241 return true;
242 }
243 result = HandshakeResult::WRONG_HASH;
244 Dmsg1(debuglevel_, "Received bad response: %s\n", bs_->msg);
245 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
246 return false;
247 }
248
DoHandshake(bool initiated_by_remote)249 bool CramMd5Handshake::DoHandshake(bool initiated_by_remote)
250 {
251 if (initiated_by_remote) {
252 if (CramMd5Challenge()) {
253 if (CramMd5Response()) { return true; }
254 }
255 } else {
256 if (CramMd5Response()) {
257 if (CramMd5Challenge()) { return true; }
258 }
259 }
260
261 Dmsg1(debuglevel_, "cram-auth failed with %s\n", bs_->who());
262 return false;
263 }
264
InitRandom() const265 void CramMd5Handshake::InitRandom() const
266 {
267 struct timeval t1;
268 struct timeval t2;
269 struct timezone tz;
270
271 gettimeofday(&t1, &tz);
272 for (int i = 0; i < 4; i++) { gettimeofday(&t2, &tz); }
273 srandom((t1.tv_sec & 0xffff) * (t2.tv_usec & 0xff));
274 }
275