]>
Commit | Line | Data |
---|---|---|
cfe95ada RG |
1 | /* |
2 | * This file is part of PowerDNS or dnsdist. | |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
22 | #include "config.h" | |
23 | ||
8a6030b6 | 24 | #include <cmath> |
cfe95ada RG |
25 | #include <stdexcept> |
26 | ||
27 | #ifdef HAVE_LIBSODIUM | |
28 | #include <sodium.h> | |
29 | #endif | |
30 | ||
2f32819a | 31 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
32 | #include <openssl/evp.h> |
33 | #include <openssl/kdf.h> | |
8535f666 | 34 | #include <openssl/opensslv.h> |
8a6030b6 RG |
35 | #include <openssl/rand.h> |
36 | #endif | |
37 | ||
061bc5f0 RG |
38 | #include <fcntl.h> |
39 | #include <sys/stat.h> | |
40 | #include <unistd.h> | |
41 | ||
8a6030b6 | 42 | #include "base64.hh" |
3dc4644f | 43 | #include "dns_random.hh" |
cfe95ada | 44 | #include "credentials.hh" |
b2504b29 | 45 | #include "misc.hh" |
cfe95ada | 46 | |
2f32819a | 47 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
48 | static size_t const pwhash_max_size = 128U; /* maximum size of the output */ |
49 | static size_t const pwhash_output_size = 32U; /* size of the hashed output (before base64 encoding) */ | |
50 | static unsigned int const pwhash_salt_size = 16U; /* size of the salt (before base64 encoding */ | |
31ec1c9f | 51 | static uint64_t const pwhash_max_work_factor = 32768U; /* max N for interactive login purposes */ |
8a6030b6 RG |
52 | |
53 | /* PHC string format, storing N as log2(N) as done by passlib. | |
54 | for now we only support one algo but we might have to change that later */ | |
55 | static std::string const pwhash_prefix = "$scrypt$"; | |
56 | static size_t const pwhash_prefix_size = pwhash_prefix.size(); | |
57 | #endif | |
58 | ||
59 | uint64_t const CredentialsHolder::s_defaultWorkFactor{1024U}; /* N */ | |
60 | uint64_t const CredentialsHolder::s_defaultParallelFactor{1U}; /* p */ | |
61 | uint64_t const CredentialsHolder::s_defaultBlockSize{8U}; /* r */ | |
62 | ||
31ec1c9f RG |
63 | SensitiveData::SensitiveData(std::string&& data) : |
64 | d_data(std::move(data)) | |
8a6030b6 | 65 | { |
c13de8e8 | 66 | data.clear(); |
8a6030b6 RG |
67 | #ifdef HAVE_LIBSODIUM |
68 | sodium_mlock(d_data.data(), d_data.size()); | |
69 | #endif | |
70 | } | |
71 | ||
71633799 | 72 | SensitiveData& SensitiveData::operator=(SensitiveData&& rhs) noexcept |
0bc984f9 RG |
73 | { |
74 | d_data = std::move(rhs.d_data); | |
c13de8e8 | 75 | rhs.clear(); |
0bc984f9 RG |
76 | return *this; |
77 | } | |
78 | ||
8a6030b6 RG |
79 | SensitiveData::SensitiveData(size_t bytes) |
80 | { | |
81 | d_data.resize(bytes); | |
82 | #ifdef HAVE_LIBSODIUM | |
83 | sodium_mlock(d_data.data(), d_data.size()); | |
84 | #endif | |
85 | } | |
86 | ||
87 | SensitiveData::~SensitiveData() | |
88 | { | |
89 | clear(); | |
90 | } | |
91 | ||
92 | void SensitiveData::clear() | |
93 | { | |
94 | #ifdef HAVE_LIBSODIUM | |
95 | sodium_munlock(d_data.data(), d_data.size()); | |
96 | #endif | |
97 | d_data.clear(); | |
98 | } | |
99 | ||
aa87b287 | 100 | static std::string hashPasswordInternal([[maybe_unused]] const std::string& password, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize) |
8a6030b6 | 101 | { |
2f32819a | 102 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
31ec1c9f | 103 | auto pctx = std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, nullptr), EVP_PKEY_CTX_free); |
8a6030b6 RG |
104 | if (!pctx) { |
105 | throw std::runtime_error("Error getting a scrypt context to hash the supplied password"); | |
106 | } | |
107 | ||
108 | if (EVP_PKEY_derive_init(pctx.get()) <= 0) { | |
109 | throw std::runtime_error("Error intializing the scrypt context to hash the supplied password"); | |
110 | } | |
111 | ||
8535f666 RG |
112 | // OpenSSL 3.0 changed the string arg to const unsigned char*, other versions use const char * |
113 | #if OPENSSL_VERSION_MAJOR >= 3 | |
114 | auto passwordData = reinterpret_cast<const char*>(password.data()); | |
115 | #else | |
116 | auto passwordData = reinterpret_cast<const unsigned char*>(password.data()); | |
117 | #endif | |
118 | if (EVP_PKEY_CTX_set1_pbe_pass(pctx.get(), passwordData, password.size()) <= 0) { | |
8a6030b6 | 119 | throw std::runtime_error("Error adding the password to the scrypt context to hash the supplied password"); |
31ec1c9f | 120 | } |
8a6030b6 | 121 | |
810cb195 | 122 | if (EVP_PKEY_CTX_set1_scrypt_salt(pctx.get(), reinterpret_cast<const unsigned char*>(salt.data()), salt.size()) <= 0) { |
8a6030b6 RG |
123 | throw std::runtime_error("Error adding the salt to the scrypt context to hash the supplied password"); |
124 | } | |
125 | ||
126 | if (EVP_PKEY_CTX_set_scrypt_N(pctx.get(), workFactor) <= 0) { | |
127 | throw std::runtime_error("Error setting the work factor to the scrypt context to hash the supplied password"); | |
128 | } | |
129 | ||
130 | if (EVP_PKEY_CTX_set_scrypt_r(pctx.get(), blockSize) <= 0) { | |
131 | throw std::runtime_error("Error setting the block size to the scrypt context to hash the supplied password"); | |
132 | } | |
133 | ||
134 | if (EVP_PKEY_CTX_set_scrypt_p(pctx.get(), parallelFactor) <= 0) { | |
135 | throw std::runtime_error("Error setting the parallel factor to the scrypt context to hash the supplied password"); | |
136 | } | |
137 | ||
138 | std::string out; | |
139 | out.resize(pwhash_output_size); | |
140 | size_t outlen = out.size(); | |
141 | ||
142 | if (EVP_PKEY_derive(pctx.get(), reinterpret_cast<unsigned char*>(out.data()), &outlen) <= 0 || outlen != pwhash_output_size) { | |
143 | throw std::runtime_error("Error deriving the output from the scrypt context to hash the supplied password"); | |
144 | } | |
145 | ||
146 | return out; | |
b59e6712 RG |
147 | #else |
148 | throw std::runtime_error("Hashing support is not available"); | |
8a6030b6 | 149 | #endif |
b59e6712 | 150 | } |
8a6030b6 RG |
151 | |
152 | static std::string generateRandomSalt() | |
153 | { | |
2f32819a | 154 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
155 | /* generate a random salt */ |
156 | std::string salt; | |
157 | salt.resize(pwhash_salt_size); | |
158 | ||
159 | if (RAND_bytes(reinterpret_cast<unsigned char*>(salt.data()), salt.size()) != 1) { | |
160 | throw std::runtime_error("Error while generating a salt to hash the supplied password"); | |
161 | } | |
162 | ||
163 | return salt; | |
164 | #else | |
165 | throw std::runtime_error("Generating a salted password requires scrypt support in OpenSSL, and it is not available"); | |
166 | #endif | |
167 | } | |
168 | ||
aa87b287 | 169 | std::string hashPassword([[maybe_unused]] const std::string& password, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize) |
cfe95ada | 170 | { |
2f32819a RG |
171 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
172 | if (workFactor == 0) { | |
173 | throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " passed to hashPassword()"); | |
174 | } | |
175 | ||
cfe95ada | 176 | std::string result; |
8a6030b6 | 177 | result.reserve(pwhash_max_size); |
cfe95ada | 178 | |
8a6030b6 RG |
179 | result.append(pwhash_prefix); |
180 | result.append("ln="); | |
4ec3ff03 | 181 | result.append(std::to_string(static_cast<uint64_t>(std::log2(workFactor)))); |
8a6030b6 | 182 | result.append(",p="); |
4ec3ff03 | 183 | result.append(std::to_string(parallelFactor)); |
8a6030b6 | 184 | result.append(",r="); |
4ec3ff03 | 185 | result.append(std::to_string(blockSize)); |
8a6030b6 RG |
186 | result.append("$"); |
187 | auto salt = generateRandomSalt(); | |
188 | result.append(Base64Encode(salt)); | |
189 | result.append("$"); | |
190 | ||
4ec3ff03 | 191 | auto out = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize); |
8a6030b6 RG |
192 | |
193 | result.append(Base64Encode(out)); | |
cfe95ada RG |
194 | |
195 | return result; | |
196 | #else | |
8a6030b6 RG |
197 | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); |
198 | #endif | |
199 | } | |
200 | ||
aa87b287 | 201 | std::string hashPassword([[maybe_unused]] const std::string& password) |
4ec3ff03 | 202 | { |
2f32819a | 203 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
4ec3ff03 RG |
204 | return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); |
205 | #else | |
206 | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); | |
207 | #endif | |
208 | } | |
209 | ||
aa87b287 | 210 | bool verifyPassword([[maybe_unused]] const std::string& binaryHash, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize, [[maybe_unused]] const std::string& binaryPassword) |
8a6030b6 | 211 | { |
2f32819a | 212 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
213 | auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize); |
214 | return constantTimeStringEquals(expected, binaryHash); | |
215 | #else | |
216 | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); | |
217 | #endif | |
218 | } | |
219 | ||
220 | /* parse a hashed password in PHC string format */ | |
aa87b287 | 221 | static void parseHashed([[maybe_unused]] const std::string& hash, [[maybe_unused]] std::string& salt, [[maybe_unused]] std::string& hashedPassword, [[maybe_unused]] uint64_t& workFactor, [[maybe_unused]] uint64_t& parallelFactor, [[maybe_unused]] uint64_t& blockSize) |
8a6030b6 | 222 | { |
2f32819a | 223 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
224 | auto parametersEnd = hash.find('$', pwhash_prefix.size()); |
225 | if (parametersEnd == std::string::npos || parametersEnd == hash.size()) { | |
8a6030b6 RG |
226 | throw std::runtime_error("Invalid hashed password format, no parameters"); |
227 | } | |
228 | ||
229 | auto parametersStr = hash.substr(pwhash_prefix.size(), parametersEnd); | |
230 | std::vector<std::string> parameters; | |
231 | parameters.reserve(3); | |
232 | stringtok(parameters, parametersStr, ","); | |
233 | if (parameters.size() != 3) { | |
234 | throw std::runtime_error("Invalid hashed password format, expecting 3 parameters, got " + std::to_string(parameters.size())); | |
235 | } | |
236 | ||
237 | if (!boost::starts_with(parameters.at(0), "ln=")) { | |
238 | throw std::runtime_error("Invalid hashed password format, ln= parameter not found"); | |
239 | } | |
240 | ||
241 | if (!boost::starts_with(parameters.at(1), "p=")) { | |
242 | throw std::runtime_error("Invalid hashed password format, p= parameter not found"); | |
243 | } | |
244 | ||
245 | if (!boost::starts_with(parameters.at(2), "r=")) { | |
246 | throw std::runtime_error("Invalid hashed password format, r= parameter not found"); | |
247 | } | |
248 | ||
249 | auto saltPos = parametersEnd + 1; | |
250 | auto saltEnd = hash.find('$', saltPos); | |
251 | if (saltEnd == std::string::npos || saltEnd == hash.size()) { | |
252 | throw std::runtime_error("Invalid hashed password format"); | |
253 | } | |
254 | ||
255 | try { | |
a0383aad | 256 | workFactor = pdns::checked_stoi<uint64_t>(parameters.at(0).substr(3)); |
0ef92c6c | 257 | workFactor = static_cast<uint64_t>(1) << workFactor; |
8a6030b6 RG |
258 | if (workFactor > pwhash_max_work_factor) { |
259 | throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " in hashed password string, maximum is " + std::to_string(pwhash_max_work_factor)); | |
260 | } | |
261 | ||
a0383aad FM |
262 | parallelFactor = pdns::checked_stoi<uint64_t>(parameters.at(1).substr(2)); |
263 | blockSize = pdns::checked_stoi<uint64_t>(parameters.at(2).substr(2)); | |
8a6030b6 RG |
264 | |
265 | auto b64Salt = hash.substr(saltPos, saltEnd - saltPos); | |
266 | salt.reserve(pwhash_salt_size); | |
267 | B64Decode(b64Salt, salt); | |
268 | ||
269 | if (salt.size() != pwhash_salt_size) { | |
270 | throw std::runtime_error("Invalid salt in hashed password string"); | |
271 | } | |
272 | ||
273 | hashedPassword.reserve(pwhash_output_size); | |
274 | B64Decode(hash.substr(saltEnd + 1), hashedPassword); | |
275 | ||
276 | if (hashedPassword.size() != pwhash_output_size) { | |
277 | throw std::runtime_error("Invalid hash in hashed password string"); | |
278 | } | |
279 | } | |
280 | catch (const std::exception& e) { | |
281 | throw std::runtime_error("Invalid hashed password format, unable to parse parameters"); | |
282 | } | |
cfe95ada RG |
283 | #endif |
284 | } | |
285 | ||
aa87b287 | 286 | bool verifyPassword(const std::string& hash, [[maybe_unused]] const std::string& password) |
cfe95ada | 287 | { |
8a6030b6 RG |
288 | if (!isPasswordHashed(hash)) { |
289 | return false; | |
cfe95ada RG |
290 | } |
291 | ||
2f32819a | 292 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
293 | std::string salt; |
294 | std::string hashedPassword; | |
295 | uint64_t workFactor = 0; | |
296 | uint64_t parallelFactor = 0; | |
297 | uint64_t blockSize = 0; | |
298 | parseHashed(hash, salt, hashedPassword, workFactor, parallelFactor, blockSize); | |
299 | ||
300 | auto expected = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize); | |
301 | ||
302 | return constantTimeStringEquals(expected, hashedPassword); | |
cfe95ada | 303 | #else |
8a6030b6 | 304 | throw std::runtime_error("Verifying a hashed password requires scrypt support in OpenSSL, and it is not available"); |
cfe95ada RG |
305 | #endif |
306 | } | |
307 | ||
aa87b287 | 308 | bool isPasswordHashed([[maybe_unused]] const std::string& password) |
cfe95ada | 309 | { |
2f32819a | 310 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
8a6030b6 RG |
311 | if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) { |
312 | return false; | |
313 | } | |
314 | ||
315 | if (!boost::starts_with(password, pwhash_prefix)) { | |
cfe95ada RG |
316 | return false; |
317 | } | |
318 | ||
8a6030b6 RG |
319 | auto parametersEnd = password.find('$', pwhash_prefix.size()); |
320 | if (parametersEnd == std::string::npos || parametersEnd == password.size()) { | |
321 | return false; | |
322 | } | |
cfe95ada | 323 | |
4ec3ff03 RG |
324 | size_t parametersSize = parametersEnd - pwhash_prefix.size(); |
325 | /* ln=X,p=Y,r=Z */ | |
326 | if (parametersSize < 12) { | |
327 | return false; | |
328 | } | |
329 | ||
8a6030b6 RG |
330 | auto saltEnd = password.find('$', parametersEnd + 1); |
331 | if (saltEnd == std::string::npos || saltEnd == password.size()) { | |
cfe95ada RG |
332 | return false; |
333 | } | |
8a6030b6 RG |
334 | |
335 | /* the salt is base64 encoded so it has to be larger than that */ | |
336 | if ((saltEnd - parametersEnd - 1) < pwhash_salt_size) { | |
337 | return false; | |
338 | } | |
339 | ||
340 | /* the hash base64 encoded so it has to be larger than that */ | |
31ec1c9f | 341 | if ((password.size() - saltEnd - 1) < pwhash_output_size) { |
8a6030b6 RG |
342 | return false; |
343 | } | |
344 | ||
cfe95ada RG |
345 | return true; |
346 | #else | |
347 | return false; | |
348 | #endif | |
349 | } | |
350 | ||
351 | /* if the password is in cleartext and hashing is available, | |
352 | the hashed form will be kept in memory */ | |
31ec1c9f RG |
353 | CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext) : |
354 | d_credentials(std::move(password)) | |
cfe95ada | 355 | { |
cfe95ada | 356 | if (isHashingAvailable()) { |
8a6030b6 | 357 | if (!isPasswordHashed(d_credentials.getString())) { |
64c4f83c | 358 | if (hashPlaintext) { |
8a6030b6 RG |
359 | d_salt = generateRandomSalt(); |
360 | d_workFactor = s_defaultWorkFactor; | |
361 | d_parallelFactor = s_defaultParallelFactor; | |
362 | d_blockSize = s_defaultBlockSize; | |
363 | d_credentials = SensitiveData(hashPasswordInternal(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize)); | |
64c4f83c RG |
364 | d_isHashed = true; |
365 | } | |
cfe95ada RG |
366 | } |
367 | else { | |
32e8669a | 368 | d_wasHashed = true; |
64c4f83c | 369 | d_isHashed = true; |
8a6030b6 RG |
370 | std::string hashedPassword; |
371 | parseHashed(d_credentials.getString(), d_salt, hashedPassword, d_workFactor, d_parallelFactor, d_blockSize); | |
372 | d_credentials = SensitiveData(std::move(hashedPassword)); | |
cfe95ada RG |
373 | } |
374 | } | |
64c4f83c RG |
375 | |
376 | if (!d_isHashed) { | |
3dc4644f | 377 | d_fallbackHashPerturb = dns_random_uint32(); |
8a6030b6 | 378 | d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb); |
cfe95ada | 379 | } |
cfe95ada RG |
380 | } |
381 | ||
382 | CredentialsHolder::~CredentialsHolder() | |
383 | { | |
b2504b29 RG |
384 | d_fallbackHashPerturb = 0; |
385 | d_fallbackHash = 0; | |
cfe95ada RG |
386 | } |
387 | ||
388 | bool CredentialsHolder::matches(const std::string& password) const | |
389 | { | |
64c4f83c | 390 | if (d_isHashed) { |
8a6030b6 | 391 | return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password); |
cfe95ada RG |
392 | } |
393 | else { | |
b2504b29 RG |
394 | uint32_t fallback = burtle(reinterpret_cast<const unsigned char*>(password.data()), password.size(), d_fallbackHashPerturb); |
395 | if (fallback != d_fallbackHash) { | |
396 | return false; | |
397 | } | |
398 | ||
8a6030b6 | 399 | return constantTimeStringEquals(password, d_credentials.getString()); |
cfe95ada RG |
400 | } |
401 | } | |
402 | ||
403 | bool CredentialsHolder::isHashingAvailable() | |
404 | { | |
2f32819a | 405 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
cfe95ada RG |
406 | return true; |
407 | #else | |
408 | return false; | |
409 | #endif | |
410 | } | |
061bc5f0 | 411 | |
dc6aa7f5 | 412 | #include <csignal> |
061bc5f0 RG |
413 | #include <termios.h> |
414 | ||
8a6030b6 | 415 | SensitiveData CredentialsHolder::readFromTerminal() |
061bc5f0 RG |
416 | { |
417 | struct termios term; | |
418 | struct termios oterm; | |
061bc5f0 RG |
419 | bool restoreTermSettings = false; |
420 | int termAction = TCSAFLUSH; | |
421 | #ifdef TCSASOFT | |
9437be08 | 422 | termAction |= TCSASOFT; |
061bc5f0 RG |
423 | #endif |
424 | ||
425 | FDWrapper input(open("/dev/tty", O_RDONLY)); | |
426 | if (int(input) != -1) { | |
427 | if (tcgetattr(input, &oterm) == 0) { | |
428 | memcpy(&term, &oterm, sizeof(term)); | |
429 | term.c_lflag &= ~(ECHO | ECHONL); | |
430 | tcsetattr(input, termAction, &term); | |
431 | restoreTermSettings = true; | |
432 | } | |
433 | } | |
434 | else { | |
435 | input = FDWrapper(dup(STDIN_FILENO)); | |
d78e47e3 | 436 | restoreTermSettings = false; |
061bc5f0 | 437 | } |
d78e47e3 | 438 | |
061bc5f0 RG |
439 | FDWrapper output(open("/dev/tty", O_WRONLY)); |
440 | if (int(output) == -1) { | |
441 | output = FDWrapper(dup(STDERR_FILENO)); | |
442 | } | |
443 | ||
444 | struct std::map<int, struct sigaction> signals; | |
445 | struct sigaction sa; | |
446 | sigemptyset(&sa.sa_mask); | |
447 | sa.sa_flags = 0; | |
d73de874 | 448 | sa.sa_handler = [](int /* s */) {}; |
061bc5f0 RG |
449 | sigaction(SIGALRM, &sa, &signals[SIGALRM]); |
450 | sigaction(SIGHUP, &sa, &signals[SIGHUP]); | |
451 | sigaction(SIGINT, &sa, &signals[SIGINT]); | |
452 | sigaction(SIGPIPE, &sa, &signals[SIGPIPE]); | |
453 | sigaction(SIGQUIT, &sa, &signals[SIGQUIT]); | |
454 | sigaction(SIGTERM, &sa, &signals[SIGTERM]); | |
455 | sigaction(SIGTSTP, &sa, &signals[SIGTSTP]); | |
456 | sigaction(SIGTTIN, &sa, &signals[SIGTTIN]); | |
457 | sigaction(SIGTTOU, &sa, &signals[SIGTTOU]); | |
458 | ||
459 | std::string buffer; | |
460 | /* let's allocate a huge buffer now to prevent reallocation, | |
461 | which would leave parts of the buffer around */ | |
462 | buffer.reserve(512); | |
463 | ||
464 | for (;;) { | |
465 | char ch = '\0'; | |
466 | auto got = read(input, &ch, 1); | |
467 | if (got == 1 && ch != '\n' && ch != '\r') { | |
468 | buffer.push_back(ch); | |
469 | } | |
470 | else { | |
471 | break; | |
472 | } | |
473 | } | |
474 | ||
061bc5f0 RG |
475 | if (restoreTermSettings) { |
476 | tcsetattr(input, termAction, &oterm); | |
477 | } | |
478 | ||
479 | for (const auto& sig : signals) { | |
480 | sigaction(sig.first, &sig.second, nullptr); | |
481 | } | |
482 | ||
8a6030b6 | 483 | return SensitiveData(std::move(buffer)); |
061bc5f0 | 484 | } |