#include "RaveUsersJobs.h" #include "RaveUsersBackend.h" #include "GlobalStorage.h" #include "JobQueue.h" #include "utils/Logger.h" #include "utils/Permissions.h" #include "utils/System.h" #include #include #include #include #include using WriteMode = Calamares::System::WriteMode; static QDir targetRootDir() { Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); return QDir( gs->value( "rootMountPoint" ).toString() ); } RaveUsersCreateUserJob::RaveUsersCreateUserJob( const RaveUsersBackend* backend ) : m_backend( backend ) { } QString RaveUsersCreateUserJob::prettyName() const { return tr( "Create user %1" ).arg( m_backend->username() ); } QString RaveUsersCreateUserJob::prettyStatusMessage() const { return tr( "Creating user %1…", "@status" ).arg( m_backend->username() ); } Calamares::JobResult RaveUsersCreateUserJob::exec() { const QString login = m_backend->username(); QStringList useraddCommand { "useradd", "-m", "-U" }; const QString shell = m_backend->userShell(); if ( !shell.isEmpty() ) { useraddCommand << "-s" << shell; } useraddCommand << "-c" << login; if ( m_backend->homeUMask() >= 0 ) { useraddCommand << "-K" << QStringLiteral( "UMASK=%1" ).arg( m_backend->homeUMask(), 3, 8, QChar( '0' ) ); } useraddCommand << login; auto result = Calamares::System::instance()->targetEnvCommand( useraddCommand ); if ( result.getExitCode() ) { cError() << "useradd failed" << result.getExitCode(); return result.explainProcess( useraddCommand, std::chrono::seconds( 10 ) ); } const QStringList groups = m_backend->groupsForThisUser(); if ( !groups.isEmpty() ) { QStringList usermodCommand { "usermod", "-aG", groups.join( ',' ), login }; result = Calamares::System::instance()->targetEnvCommand( usermodCommand ); if ( result.getExitCode() ) { cError() << "usermod failed" << result.getExitCode(); return result.explainProcess( usermodCommand, std::chrono::seconds( 10 ) ); } } const QString userGroup = QStringLiteral( "%1:%2" ).arg( login, login ); const QString homeDir = QStringLiteral( "/home/%1" ).arg( login ); result = Calamares::System::instance()->targetEnvCommand( { "chown", "-R", userGroup, homeDir } ); if ( result.getExitCode() ) { cError() << "chown failed" << result.getExitCode(); return result.explainProcess( "chown", std::chrono::seconds( 10 ) ); } return Calamares::JobResult::ok(); } RaveUsersSetPasswordJob::RaveUsersSetPasswordJob( const QString& userName, const QString& password ) : m_userName( userName ) , m_password( password ) { } QString RaveUsersSetPasswordJob::prettyName() const { return tr( "Set password for user %1" ).arg( m_userName ); } QString RaveUsersSetPasswordJob::prettyStatusMessage() const { return tr( "Setting password for user %1…", "@status" ).arg( m_userName ); } static QString makeSha512Salt( int length = 16 ) { static const QString chars = QStringLiteral( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" ); QString salt; salt.reserve( length ); for ( int i = 0; i < length; ++i ) { salt += chars.at( QRandomGenerator::global()->bounded( chars.size() ) ); } return QStringLiteral( "$6$" ) + salt + QLatin1Char( '$' ); } Calamares::JobResult RaveUsersSetPasswordJob::exec() { QDir destDir = targetRootDir(); if ( !destDir.exists() ) { return Calamares::JobResult::error( tr( "Bad destination system path." ), tr( "rootMountPoint is %1" ).arg( destDir.absolutePath() ) ); } if ( m_userName == QLatin1String( "root" ) && m_password.isEmpty() ) { int ec = Calamares::System::instance()->targetEnvCall( { "usermod", "-p", "!", m_userName } ); if ( ec ) { return Calamares::JobResult::error( tr( "Cannot disable root account." ), tr( "usermod terminated with error code %1." ).arg( ec ) ); } return Calamares::JobResult::ok(); } const QString salt = makeSha512Salt(); const QByteArray hash = crypt( m_password.toUtf8().constData(), salt.toUtf8().constData() ); if ( hash.isEmpty() ) { return Calamares::JobResult::error( tr( "Cannot hash password." ) ); } int ec = Calamares::System::instance()->targetEnvCall( { "usermod", "-p", QString::fromLatin1( hash ), m_userName } ); if ( ec ) { return Calamares::JobResult::error( tr( "Cannot set password for user %1." ).arg( m_userName ), tr( "usermod terminated with error code %1." ).arg( ec ) ); } return Calamares::JobResult::ok(); } RaveUsersSetHostnameJob::RaveUsersSetHostnameJob( const QString& hostname ) : m_hostname( hostname ) { } QString RaveUsersSetHostnameJob::prettyName() const { return tr( "Set hostname %1" ).arg( m_hostname ); } Calamares::JobResult RaveUsersSetHostnameJob::exec() { if ( !Calamares::System::instance()->createTargetFile( QStringLiteral( "/etc/hostname" ), ( m_hostname + '\n' ).toUtf8(), WriteMode::Overwrite ) ) { return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) ); } const QString hosts = QStringLiteral( R"(# Standard host addresses 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters # This host address 127.0.1.1 %1 )" ) .arg( m_hostname ); if ( !Calamares::System::instance()->createTargetFile( QStringLiteral( "/etc/hosts" ), hosts.toUtf8(), WriteMode::Overwrite ) ) { return Calamares::JobResult::error( tr( "Cannot write hosts file to target system" ) ); } return Calamares::JobResult::ok(); } RaveUsersSetupSudoJob::RaveUsersSetupSudoJob( const QString& group, bool configureWithGroup ) : m_group( group ) , m_configureWithGroup( configureWithGroup ) { } QString RaveUsersSetupSudoJob::prettyName() const { return tr( "Configuring sudo users…", "@status" ); } Calamares::JobResult RaveUsersSetupSudoJob::exec() { if ( m_group.isEmpty() ) { return Calamares::JobResult::ok(); } const QString designator = m_configureWithGroup ? QStringLiteral( "(ALL:ALL)" ) : QStringLiteral( "(ALL)" ); const QString line = QChar( '%' ) + QStringLiteral( "%1 ALL=%2 ALL\n" ).arg( m_group, designator ); auto fileResult = Calamares::System::instance()->createTargetFile( QStringLiteral( "/etc/sudoers.d/10-installer" ), line.toUtf8().constData(), WriteMode::Overwrite ); if ( fileResult ) { if ( !Calamares::Permissions::apply( fileResult.path(), 0440 ) ) { return Calamares::JobResult::error( tr( "Cannot chmod sudoers file." ) ); } } else { return Calamares::JobResult::error( tr( "Cannot create sudoers file for writing." ) ); } return Calamares::JobResult::ok(); } RaveUsersSetupGroupsJob::RaveUsersSetupGroupsJob( const QStringList& groups ) : m_groups( groups ) { } QString RaveUsersSetupGroupsJob::prettyName() const { return tr( "Preparing groups…", "@status" ); } Calamares::JobResult RaveUsersSetupGroupsJob::exec() { QDir targetRoot = targetRootDir(); QSet< QString > existing; QFile groupFile( targetRoot.absoluteFilePath( "etc/group" ) ); if ( groupFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) { while ( !groupFile.atEnd() ) { const QString line = QString::fromLocal8Bit( groupFile.readLine() ); if ( line.startsWith( '#' ) ) { continue; } const int idx = line.indexOf( ':' ); if ( idx > 0 ) { existing.insert( line.left( idx ) ); } } } for ( const QString& group : m_groups ) { if ( group.isEmpty() || existing.contains( group ) ) { continue; } QStringList cmd { "groupadd", "--system", group }; if ( Calamares::System::instance()->targetEnvCall( cmd ) ) { cWarning() << "groupadd failed for" << group; } } return Calamares::JobResult::ok(); }