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()) >= 2) {
173 compatible_ = true;
174 } else if (sscanf(bs_->msg, "auth cram-md5 %s ssl=%d qualified-name=%s",
175 chal.c_str(), &remote_tls_policy_,
176 destination_qualified_name.data()) < 2) { // minimum 2
177 if (sscanf(bs_->msg, "auth cram-md5 %s\n", chal.c_str()) != 1) {
178 Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
179 bs_->fsend(_("1999 Authorization failed.\n"));
180 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
181 result = HandshakeResult::FORMAT_MISMATCH;
182 return false;
183 }
184 }
185 bs_->SetBnetDumpDestinationQualifiedName(destination_qualified_name.data());
186 } else { // network dump disabled
187 if (sscanf(bs_->msg, "auth cram-md5c %s ssl=%d", chal.c_str(),
188 &remote_tls_policy_) == 2) {
189 compatible_ = true;
190 } else if (sscanf(bs_->msg, "auth cram-md5 %s ssl=%d", chal.c_str(),
191 &remote_tls_policy_) != 2) {
192 if (sscanf(bs_->msg, "auth cram-md5 %s\n", chal.c_str()) != 1) {
193 Dmsg1(debuglevel_, "Cannot scan challenge: %s", bs_->msg);
194 bs_->fsend(_("1999 Authorization failed.\n"));
195 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
196 result = HandshakeResult::FORMAT_MISMATCH;
197 return false;
198 }
199 }
200 }
201
202 auto comparison_result = CompareChallengeWithOwnQualifiedName(chal.c_str());
203
204 if (comparison_result == ComparisonResult::IS_SAME) {
205 std::string c(chal.c_str());
206 // same sd-sd connection should be possible i.e. for copy jobs
207 if (c.rfind("R_STORAGE") == std::string::npos) {
208 result = HandshakeResult::REPLAY_ATTACK;
209 return false;
210 }
211 }
212
213 if (comparison_result == ComparisonResult::FAILURE) {
214 result = HandshakeResult::FORMAT_MISMATCH;
215 return false;
216 }
217
218 hmac_md5((uint8_t*)chal.c_str(), strlen(chal.c_str()), (uint8_t*)password_,
219 strlen(password_), hmac);
220 bs_->message_length =
221 BinToBase64(bs_->msg, 50, (char*)hmac, 16, compatible_) + 1;
222 if (!bs_->send()) {
223 result = HandshakeResult::NETWORK_ERROR;
224 Dmsg1(debuglevel_, "Send challenge failed. ERR=%s\n", bs_->bstrerror());
225 return false;
226 }
227 Dmsg1(99, "sending resp to challenge: %s\n", bs_->msg);
228 if (bs_->WaitData(180) <= 0 || bs_->recv() <= 0) {
229 Dmsg1(debuglevel_, "Receive challenge response failed. ERR=%s\n",
230 bs_->bstrerror());
231 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
232 result = HandshakeResult::NETWORK_ERROR;
233 return false;
234 }
235 if (bstrcmp(bs_->msg, "1000 OK auth\n")) {
236 result = HandshakeResult::SUCCESS;
237 return true;
238 }
239 result = HandshakeResult::WRONG_HASH;
240 Dmsg1(debuglevel_, "Received bad response: %s\n", bs_->msg);
241 Bmicrosleep(bs_->sleep_time_after_authentication_error, 0);
242 return false;
243 }
244
DoHandshake(bool initiated_by_remote)245 bool CramMd5Handshake::DoHandshake(bool initiated_by_remote)
246 {
247 if (initiated_by_remote) {
248 if (CramMd5Challenge()) {
249 if (CramMd5Response()) { return true; }
250 }
251 } else {
252 if (CramMd5Response()) {
253 if (CramMd5Challenge()) { return true; }
254 }
255 }
256
257 Dmsg1(debuglevel_, "cram-auth failed with %s\n", bs_->who());
258 return false;
259 }
260
InitRandom() const261 void CramMd5Handshake::InitRandom() const
262 {
263 struct timeval t1;
264 struct timeval t2;
265 struct timezone tz;
266
267 gettimeofday(&t1, &tz);
268 for (int i = 0; i < 4; i++) { gettimeofday(&t2, &tz); }
269 srandom((t1.tv_sec & 0xffff) * (t2.tv_usec & 0xff));
270 }
271