1 /* === This file is part of Calamares - <https://calamares.io> ===
2 *
3 * SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 * Contains strings from libpwquality under the terms of the
7 * GPL-3.0-or-later (libpwquality is BSD-3-clause or GPL-2.0-or-later,
8 * so we pick GPL-3.0-or-later).
9 *
10 * Calamares is Free Software: see the License-Identifier above.
11 *
12 */
13
14 #include "CheckPWQuality.h"
15
16 #include "utils/Logger.h"
17
18 #include <QCoreApplication>
19 #include <QString>
20
21 #ifdef HAVE_LIBPWQUALITY
22 #include <pwquality.h>
23 #endif
24
25 #include <memory>
26
PasswordCheck()27 PasswordCheck::PasswordCheck()
28 : m_weight( 0 )
29 , m_message()
30 , m_accept( []( const QString& ) { return true; } )
31 {
32 }
33
PasswordCheck(MessageFunc m,AcceptFunc a,Weight weight)34 PasswordCheck::PasswordCheck( MessageFunc m, AcceptFunc a, Weight weight )
35 : m_weight( weight )
36 , m_message( m )
37 , m_accept( a )
38 {
39 }
40
DEFINE_CHECK_FUNC(minLength)41 DEFINE_CHECK_FUNC( minLength )
42 {
43 int minLength = -1;
44 if ( value.canConvert( QVariant::Int ) )
45 {
46 minLength = value.toInt();
47 }
48 if ( minLength > 0 )
49 {
50 cDebug() << Logger::SubEntry << "minLength set to" << minLength;
51 checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too short" ); },
52 [minLength]( const QString& s ) { return s.length() >= minLength; },
53 PasswordCheck::Weight( 10 ) ) );
54 }
55 }
56
DEFINE_CHECK_FUNC(maxLength)57 DEFINE_CHECK_FUNC( maxLength )
58 {
59 int maxLength = -1;
60 if ( value.canConvert( QVariant::Int ) )
61 {
62 maxLength = value.toInt();
63 }
64 if ( maxLength > 0 )
65 {
66 cDebug() << Logger::SubEntry << "maxLength set to" << maxLength;
67 checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too long" ); },
68 [maxLength]( const QString& s ) { return s.length() <= maxLength; },
69 PasswordCheck::Weight( 10 ) ) );
70 }
71 }
72
73 #ifdef HAVE_LIBPWQUALITY
74 /* NOTE:
75 *
76 * The munge*() functions are here because libpwquality uses void* to
77 * represent user-data in callbacks and as a general "pass some parameter"
78 * type. These need to be munged to the right C++ type.
79 */
80
81 /// @brief Handle libpwquality using void* to represent a long
82 static inline long
mungeLong(void * p)83 mungeLong( void* p )
84 {
85 return static_cast< long >( reinterpret_cast< intptr_t >( p ) );
86 }
87
88 /// @brief Handle libpwquality using void* to represent a char*
89 static inline const char*
mungeString(void * p)90 mungeString( void* p )
91 {
92 return reinterpret_cast< const char* >( p );
93 }
94
95 /**
96 * Class that acts as a RAII placeholder for pwquality_settings_t pointers.
97 * Gets a new pointer and ensures it is deleted only once; provides
98 * convenience functions for setting options and checking passwords.
99 */
100 class PWSettingsHolder
101 {
102 public:
103 static constexpr int arbitrary_minimum_strength = 40;
104
PWSettingsHolder()105 PWSettingsHolder()
106 : m_settings( pwquality_default_settings() )
107 {
108 }
109
~PWSettingsHolder()110 ~PWSettingsHolder() { pwquality_free_settings( m_settings ); }
111
112 /// Sets an option via the configuration string @p v, <key>=<value> style.
set(const QString & v)113 int set( const QString& v ) { return pwquality_set_option( m_settings, v.toUtf8().constData() ); }
114
115 /** @brief Checks the given password @p pwd against the current configuration
116 *
117 * Resets m_errorString and m_errorCount and then sets them appropriately
118 * so that explanation() can be called afterwards. Sets m_rv as well.
119 */
120
check(const QString & pwd)121 int check( const QString& pwd )
122 {
123 void* auxerror = nullptr;
124 m_rv = pwquality_check( m_settings, pwd.toUtf8().constData(), nullptr, nullptr, &auxerror );
125
126 // Positive return values could be ignored; some negative ones
127 // place extra information in auxerror, which is a void* and
128 // which needs interpretation to long- or string-values.
129 m_errorCount = 0;
130 m_errorString = QString();
131
132 switch ( m_rv )
133 {
134 case PWQ_ERROR_CRACKLIB_CHECK:
135 if ( auxerror )
136 {
137 /* Here the string comes from cracklib, don't free? */
138 m_errorString = mungeString( auxerror );
139 }
140 break;
141 case PWQ_ERROR_MEM_ALLOC:
142 case PWQ_ERROR_UNKNOWN_SETTING:
143 case PWQ_ERROR_INTEGER:
144 case PWQ_ERROR_NON_INT_SETTING:
145 case PWQ_ERROR_NON_STR_SETTING:
146 if ( auxerror )
147 {
148 m_errorString = mungeString( auxerror );
149 free( auxerror );
150 }
151 break;
152 case PWQ_ERROR_MIN_DIGITS:
153 case PWQ_ERROR_MIN_UPPERS:
154 case PWQ_ERROR_MIN_LOWERS:
155 case PWQ_ERROR_MIN_OTHERS:
156 case PWQ_ERROR_MIN_LENGTH:
157 case PWQ_ERROR_MIN_CLASSES:
158 case PWQ_ERROR_MAX_CONSECUTIVE:
159 case PWQ_ERROR_MAX_CLASS_REPEAT:
160 case PWQ_ERROR_MAX_SEQUENCE:
161 if ( auxerror )
162 {
163 m_errorCount = mungeLong( auxerror );
164 }
165 break;
166 default:
167 break;
168 }
169
170 return m_rv;
171 }
172
173 /** @brief Explain the results of the last call to check()
174 *
175 * This is roughly the same as the function pwquality_strerror,
176 * only with QStrings instead, and using the Qt translation scheme.
177 * It is used under the terms of the GNU GPL v3 or later, as
178 * allowed by the libpwquality license (LICENSES/GPLv2+-libpwquality)
179 */
explanation()180 QString explanation()
181 {
182 if ( m_rv >= arbitrary_minimum_strength )
183 {
184 return QString();
185 }
186 if ( m_rv >= 0 )
187 {
188 return QCoreApplication::translate( "PWQ", "Password is too weak" );
189 }
190
191 switch ( m_rv )
192 {
193 case PWQ_ERROR_MEM_ALLOC:
194 if ( !m_errorString.isEmpty() )
195 {
196 return QCoreApplication::translate( "PWQ", "Memory allocation error when setting '%1'" )
197 .arg( m_errorString );
198 }
199 return QCoreApplication::translate( "PWQ", "Memory allocation error" );
200 case PWQ_ERROR_SAME_PASSWORD:
201 return QCoreApplication::translate( "PWQ", "The password is the same as the old one" );
202 case PWQ_ERROR_PALINDROME:
203 return QCoreApplication::translate( "PWQ", "The password is a palindrome" );
204 case PWQ_ERROR_CASE_CHANGES_ONLY:
205 return QCoreApplication::translate( "PWQ", "The password differs with case changes only" );
206 case PWQ_ERROR_TOO_SIMILAR:
207 return QCoreApplication::translate( "PWQ", "The password is too similar to the old one" );
208 case PWQ_ERROR_USER_CHECK:
209 return QCoreApplication::translate( "PWQ", "The password contains the user name in some form" );
210 case PWQ_ERROR_GECOS_CHECK:
211 return QCoreApplication::translate(
212 "PWQ", "The password contains words from the real name of the user in some form" );
213 case PWQ_ERROR_BAD_WORDS:
214 return QCoreApplication::translate( "PWQ", "The password contains forbidden words in some form" );
215 case PWQ_ERROR_MIN_DIGITS:
216 if ( m_errorCount )
217 {
218 return QCoreApplication::translate(
219 "PWQ", "The password contains fewer than %n digits", nullptr, m_errorCount );
220 }
221 return QCoreApplication::translate( "PWQ", "The password contains too few digits" );
222 case PWQ_ERROR_MIN_UPPERS:
223 if ( m_errorCount )
224 {
225 return QCoreApplication::translate(
226 "PWQ", "The password contains fewer than %n uppercase letters", nullptr, m_errorCount );
227 }
228 return QCoreApplication::translate( "PWQ", "The password contains too few uppercase letters" );
229 case PWQ_ERROR_MIN_LOWERS:
230 if ( m_errorCount )
231 {
232 return QCoreApplication::translate(
233 "PWQ", "The password contains fewer than %n lowercase letters", nullptr, m_errorCount );
234 }
235 return QCoreApplication::translate( "PWQ", "The password contains too few lowercase letters" );
236 case PWQ_ERROR_MIN_OTHERS:
237 if ( m_errorCount )
238 {
239 return QCoreApplication::translate(
240 "PWQ", "The password contains fewer than %n non-alphanumeric characters", nullptr, m_errorCount );
241 }
242 return QCoreApplication::translate( "PWQ", "The password contains too few non-alphanumeric characters" );
243 case PWQ_ERROR_MIN_LENGTH:
244 if ( m_errorCount )
245 {
246 return QCoreApplication::translate(
247 "PWQ", "The password is shorter than %n characters", nullptr, m_errorCount );
248 }
249 return QCoreApplication::translate( "PWQ", "The password is too short" );
250 case PWQ_ERROR_ROTATED:
251 return QCoreApplication::translate( "PWQ", "The password is a rotated version of the previous one" );
252 case PWQ_ERROR_MIN_CLASSES:
253 if ( m_errorCount )
254 {
255 return QCoreApplication::translate(
256 "PWQ", "The password contains fewer than %n character classes", nullptr, m_errorCount );
257 }
258 return QCoreApplication::translate( "PWQ", "The password does not contain enough character classes" );
259 case PWQ_ERROR_MAX_CONSECUTIVE:
260 if ( m_errorCount )
261 {
262 return QCoreApplication::translate(
263 "PWQ", "The password contains more than %n same characters consecutively", nullptr, m_errorCount );
264 }
265 return QCoreApplication::translate( "PWQ", "The password contains too many same characters consecutively" );
266 case PWQ_ERROR_MAX_CLASS_REPEAT:
267 if ( m_errorCount )
268 {
269 return QCoreApplication::translate(
270 "PWQ",
271 "The password contains more than %n characters of the same class consecutively",
272 nullptr,
273 m_errorCount );
274 }
275 return QCoreApplication::translate(
276 "PWQ", "The password contains too many characters of the same class consecutively" );
277 case PWQ_ERROR_MAX_SEQUENCE:
278 if ( m_errorCount )
279 {
280 return QCoreApplication::translate(
281 "PWQ",
282 "The password contains monotonic sequence longer than %n characters",
283 nullptr,
284 m_errorCount );
285 }
286 return QCoreApplication::translate( "PWQ",
287 "The password contains too long of a monotonic character sequence" );
288 case PWQ_ERROR_EMPTY_PASSWORD:
289 return QCoreApplication::translate( "PWQ", "No password supplied" );
290 case PWQ_ERROR_RNG:
291 return QCoreApplication::translate( "PWQ", "Cannot obtain random numbers from the RNG device" );
292 case PWQ_ERROR_GENERATION_FAILED:
293 return QCoreApplication::translate( "PWQ",
294 "Password generation failed - required entropy too low for settings" );
295 case PWQ_ERROR_CRACKLIB_CHECK:
296 if ( !m_errorString.isEmpty() )
297 {
298 return QCoreApplication::translate( "PWQ", "The password fails the dictionary check - %1" )
299 .arg( m_errorString );
300 }
301 return QCoreApplication::translate( "PWQ", "The password fails the dictionary check" );
302 case PWQ_ERROR_UNKNOWN_SETTING:
303 if ( !m_errorString.isEmpty() )
304 {
305 return QCoreApplication::translate( "PWQ", "Unknown setting - %1" ).arg( m_errorString );
306 }
307 return QCoreApplication::translate( "PWQ", "Unknown setting" );
308 case PWQ_ERROR_INTEGER:
309 if ( !m_errorString.isEmpty() )
310 {
311 return QCoreApplication::translate( "PWQ", "Bad integer value of setting - %1" ).arg( m_errorString );
312 }
313 return QCoreApplication::translate( "PWQ", "Bad integer value" );
314 case PWQ_ERROR_NON_INT_SETTING:
315 if ( !m_errorString.isEmpty() )
316 {
317 return QCoreApplication::translate( "PWQ", "Setting %1 is not of integer type" ).arg( m_errorString );
318 }
319 return QCoreApplication::translate( "PWQ", "Setting is not of integer type" );
320 case PWQ_ERROR_NON_STR_SETTING:
321 if ( !m_errorString.isEmpty() )
322 {
323 return QCoreApplication::translate( "PWQ", "Setting %1 is not of string type" ).arg( m_errorString );
324 }
325 return QCoreApplication::translate( "PWQ", "Setting is not of string type" );
326 case PWQ_ERROR_CFGFILE_OPEN:
327 return QCoreApplication::translate( "PWQ", "Opening the configuration file failed" );
328 case PWQ_ERROR_CFGFILE_MALFORMED:
329 return QCoreApplication::translate( "PWQ", "The configuration file is malformed" );
330 case PWQ_ERROR_FATAL_FAILURE:
331 return QCoreApplication::translate( "PWQ", "Fatal failure" );
332 default:
333 return QCoreApplication::translate( "PWQ", "Unknown error" );
334 }
335 }
336
337 private:
338 QString m_errorString; ///< Textual error from last call to check()
339 int m_errorCount = 0; ///< Count (used in %n) error from last call to check()
340 int m_rv = 0; ///< Return value from libpwquality
341
342 pwquality_settings_t* m_settings = nullptr;
343 };
344
DEFINE_CHECK_FUNC(libpwquality)345 DEFINE_CHECK_FUNC( libpwquality )
346 {
347 if ( !value.canConvert( QVariant::List ) )
348 {
349 cWarning() << "libpwquality settings is not a list";
350 return;
351 }
352
353 QVariantList l = value.toList();
354 unsigned int requirement_count = 0;
355 auto settings = std::make_shared< PWSettingsHolder >();
356 for ( const auto& v : l )
357 {
358 if ( v.type() == QVariant::String )
359 {
360 QString option = v.toString();
361 int r = settings->set( option );
362 if ( r )
363 {
364 cWarning() << "unrecognized libpwquality setting" << option;
365 }
366 else
367 {
368 cDebug() << Logger::SubEntry << "libpwquality setting" << option;
369 ++requirement_count;
370 }
371 }
372 else
373 {
374 cWarning() << "unrecognized libpwquality setting" << v;
375 }
376 }
377
378 /* Something actually added? */
379 if ( requirement_count )
380 {
381 checks.push_back( PasswordCheck( [settings]() { return settings->explanation(); },
382 [settings]( const QString& s ) {
383 int r = settings->check( s );
384 if ( r < 0 )
385 {
386 cWarning() << "libpwquality error" << r
387 << pwquality_strerror( nullptr, 256, r, nullptr );
388 }
389 else if ( r < settings->arbitrary_minimum_strength )
390 {
391 cDebug() << "Password strength" << r << "too low";
392 }
393 return r >= settings->arbitrary_minimum_strength;
394 },
395 PasswordCheck::Weight( 100 ) ) );
396 }
397 }
398 #endif
399