1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2009 Michal Malek <michalm@jabster.pl>
4     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
5     SPDX-FileCopyrightText: 2009 Michal Malek <michalm@jabster.pl>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "k3bexternalbinpermissionmodel.h"
11 #include "k3bexternalbinmanager.h"
12 #include "k3bdefaultexternalprograms.h"
13 #include "k3bglobals.h"
14 
15 #include <KLocalizedString>
16 
17 #include <QDebug>
18 #include <QFile>
19 #include <QFileInfo>
20 #include <QList>
21 #include <QSet>
22 
23 #include <sys/stat.h>
24 
25 
26 namespace {
27 
shouldRunSuidRoot(const K3b::ExternalBin * bin)28 bool shouldRunSuidRoot( const K3b::ExternalBin* bin )
29 {
30     //
31     // Since kernel 2.6.8 older cdrecord versions are not able to use the SCSI subsystem when running suid root anymore
32     // So for we ignore the suid root issue with kernel >= 2.6.8 and cdrecord < 2.01.01a02
33     //
34     // Some kernel version 2.6.16.something again introduced a problem here. Since I do not know the exact version
35     // and a workaround was introduced in cdrecord 2.01.01a05 just use that version as the first for suid root.
36     //
37     // Seems as if cdrdao never had problems with suid root...
38     //
39 
40     if( bin->name() == "cdrecord" ) {
41         return ( K3b::simpleKernelVersion() < K3b::Version( 2, 6, 8 ) ||
42                  bin->version() >= K3b::Version( 2, 1, 1, "a05" ) ||
43                  bin->hasFeature( "wodim" ) );
44     }
45     else if( bin->name() == "cdrdao" ) {
46         return true;
47     }
48     else if( bin->name() == "growisofs" ) {
49         //
50         // starting with 6.0 growiofs raises it's priority using nice(-20)
51         // BUT: newer kernels have ridiculously low default memorylocked resource limit, which prevents privileged
52         // users from starting growisofs 6.0 with "unable to anonymously mmap 33554432: Resource temporarily unavailable"
53         // error message. Until Andy releases a version including a workaround we simply never configure growisofs suid root
54         return false; // bin->version >= K3b::Version( 6, 0 );
55     }
56     else
57         return false;
58 }
59 
60 } // namespace
61 
62 namespace K3b {
63 
64 class ExternalBinPermissionModel::Private
65 {
66 public:
Private(ExternalBinManager const & ebm)67     explicit Private(ExternalBinManager const& ebm) : externalBinManager(ebm) {}
68     ExternalBinManager const& externalBinManager;
69     QString burningGroup;
70     QList<const ExternalBin*> programs;
71     QSet<const ExternalBin*> selectedPrograms;
72 
73     void buildProgramList();
74     bool getProgramInfo( const ExternalBin* program,
75                          QString& owner, QString& group, QString& wantedGroup,
76                          int& perm, int& wantedPerm ) const;
77     bool needChangePermissions( const ExternalBin* program ) const;
78 };
79 
80 
buildProgramList()81 void ExternalBinPermissionModel::Private::buildProgramList()
82 {
83     programs.clear();
84     const QMap<QString, ExternalProgram*>& map = externalBinManager.programs();
85     for( QMap<QString, ExternalProgram*>::const_iterator it = map.constBegin(); it != map.constEnd(); ++it ) {
86         if (it.key() == "cdrecord" ||
87             it.key() == "cdrdao" ||
88             it.key() == "growisofs") {
89             programs += it.value()->bins();
90         }
91     }
92     selectedPrograms = programs.toSet();
93 }
94 
95 
getProgramInfo(const ExternalBin * program,QString & owner,QString & group,QString & wantedGroup,int & perm,int & wantedPerm) const96 bool ExternalBinPermissionModel::Private::getProgramInfo( const ExternalBin* program,
97                                                   QString& owner, QString& group, QString& wantedGroup,
98                                                   int& perm, int& wantedPerm ) const
99 {
100     // we need the uid bit which is not supported by QFileInfo
101     struct stat s;
102     if( ::stat( QFile::encodeName(program->path()), &s ) == 0 ) {
103 
104         QFileInfo fi( program->path() );
105         owner = fi.owner();
106         group = fi.group();
107         perm = s.st_mode & 0007777;
108 
109         if( !burningGroup.isEmpty() && burningGroup != "root" )
110             wantedGroup = burningGroup;
111         else
112             wantedGroup = "root";
113 
114         if( shouldRunSuidRoot( program ) ) {
115             if( wantedGroup != "root" )
116                 wantedPerm = 0004710;
117             else
118                 wantedPerm = 0004711;
119         }
120         else {
121             if( wantedGroup != "root" )
122                 wantedPerm = 0000750;
123             else
124                 wantedPerm = 0000755;
125         }
126 
127         return true;
128     }
129     else {
130         qDebug() << "(ExternalBinPermissionModel) unable to stat " << program->path();
131         return false;
132     }
133 }
134 
135 
needChangePermissions(const ExternalBin * program) const136 bool ExternalBinPermissionModel::Private::needChangePermissions( const ExternalBin* program ) const
137 {
138     QString owner, group, wantedGroup;
139     int perm, wantedPerm;
140 
141     if( getProgramInfo( program, owner, group, wantedGroup, perm, wantedPerm ) )
142         return( perm != wantedPerm || owner != "root" || group != wantedGroup );
143     else
144         return false;
145 }
146 
147 
ExternalBinPermissionModel(ExternalBinManager const & externalBinManager,QObject * parent)148 ExternalBinPermissionModel::ExternalBinPermissionModel(ExternalBinManager const& externalBinManager, QObject* parent)
149 :
150     QAbstractItemModel( parent ),
151     d( new Private( externalBinManager ) )
152 {
153     d->buildProgramList();
154 }
155 
156 
~ExternalBinPermissionModel()157 ExternalBinPermissionModel::~ExternalBinPermissionModel()
158 {
159     delete d;
160 }
161 
162 
selectedPrograms() const163 QList<HelperProgramItem> ExternalBinPermissionModel::selectedPrograms() const
164 {
165     QList<HelperProgramItem> selectedPrograms;
166     Q_FOREACH( const ExternalBin* program, d->selectedPrograms )
167     {
168         if( d->needChangePermissions( program ) )
169             selectedPrograms << HelperProgramItem( program->path(), shouldRunSuidRoot( program ) );
170     }
171     return selectedPrograms;
172 }
173 
174 
changesNeeded() const175 bool ExternalBinPermissionModel::changesNeeded() const
176 {
177     return !selectedPrograms().isEmpty();
178 }
179 
180 
searchPaths() const181 QStringList ExternalBinPermissionModel::searchPaths() const
182 {
183     return d->externalBinManager.searchPath();
184 }
185 
186 
burningGroup() const187 const QString& ExternalBinPermissionModel::burningGroup() const
188 {
189     return d->burningGroup;
190 }
191 
192 
programForIndex(const QModelIndex & index) const193 const ExternalBin* ExternalBinPermissionModel::programForIndex( const QModelIndex& index ) const
194 {
195     if( index.isValid() )
196         return static_cast<const ExternalBin*>( index.internalPointer() );
197     else
198         return 0;
199 }
200 
201 
indexForProgram(const ExternalBin * program) const202 QModelIndex ExternalBinPermissionModel::indexForProgram( const ExternalBin* program ) const
203 {
204     if( program != 0 && !d->programs.isEmpty() ) {
205         int row = d->programs.indexOf( program );
206         return createIndex( row, 0, const_cast<ExternalBin*>( program ) );
207     }
208     else
209         return QModelIndex();
210 }
211 
212 
data(const QModelIndex & index,int role) const213 QVariant ExternalBinPermissionModel::data( const QModelIndex& index, int role ) const
214 {
215     if( const ExternalBin* program = programForIndex( index ) ) {
216         if( role == Qt::DisplayRole ) {
217             if( index.column() == ProgramColumn ) {
218                 return program->path();
219             } else {
220                 QString owner, group, wantedGroup;
221                 int perm, wantedPerm;
222 
223                 if( d->getProgramInfo( program, owner, group, wantedGroup, perm, wantedPerm ) ) {
224 
225                     if( index.column() == PermissionsColumn ) {
226                         return QString(QString::number( perm, 8 ).rightJustified( 4, '0' ) + ' ' + owner + '.' + group);
227                     } else if ( index.column() == NewPermissionsColumn ) {
228                         if( perm != wantedPerm || owner != "root" || group != wantedGroup )
229                             return QString("%1 root.%2").arg(wantedPerm,0,8).arg(wantedGroup);
230                         else
231                             return i18n("no change");
232                     }
233                 }
234             }
235         }
236         else if( role == Qt::CheckStateRole && index.column() == ProgramColumn && d->needChangePermissions( program ) ) {
237             if( d->selectedPrograms.contains( program ) )
238                 return Qt::Checked;
239             else
240                 return Qt::Unchecked;
241         }
242     }
243     return QVariant();
244 }
245 
246 
setData(const QModelIndex & index,const QVariant & value,int role)247 bool ExternalBinPermissionModel::setData( const QModelIndex& index, const QVariant& value, int role )
248 {
249     if( role == Qt::CheckStateRole ) {
250         if( const ExternalBin* program = programForIndex( index ) ) {
251             if( value.toInt() == Qt::Unchecked && d->selectedPrograms.contains( program ) ) {
252                 d->selectedPrograms.remove( program );
253                 emit dataChanged( index, index );
254                 return true;
255             }
256             else if( value.toInt() == Qt::Checked && !d->selectedPrograms.contains( program ) ) {
257                 d->selectedPrograms.insert( program );
258                 emit dataChanged( index, index );
259                 return true;
260             }
261         }
262     }
263     return false;
264 }
265 
266 
flags(const QModelIndex & index) const267 Qt::ItemFlags ExternalBinPermissionModel::flags( const QModelIndex& index ) const
268 {
269     if( const ExternalBin* program = programForIndex( index ) )
270     {
271         if( index.column() == ProgramColumn && d->needChangePermissions( program ) )
272             return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
273         else
274             return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
275     }
276     else
277         return 0;
278 }
279 
280 
headerData(int section,Qt::Orientation orientation,int role) const281 QVariant ExternalBinPermissionModel::headerData( int section, Qt::Orientation orientation, int role ) const
282 {
283     if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) {
284         switch( section )
285         {
286             case ProgramColumn: return i18n( "Program" );
287             case PermissionsColumn: return i18n( "Permissions" );
288             case NewPermissionsColumn: return i18n( "New permissions" );
289             default: return QVariant();
290         }
291     }
292     else
293         return QVariant();
294 }
295 
296 
index(int row,int column,const QModelIndex & parent) const297 QModelIndex ExternalBinPermissionModel::index( int row, int column, const QModelIndex& parent ) const
298 {
299     if( hasIndex(row, column, parent) && !parent.isValid() ) {
300         const ExternalBin* program = d->programs.at( row );
301         if( program != 0 )
302             return createIndex( row, column, const_cast<ExternalBin*>( program ) );
303         else
304             return QModelIndex();
305     }
306     else
307         return QModelIndex();
308 }
309 
310 
parent(const QModelIndex & index) const311 QModelIndex ExternalBinPermissionModel::parent( const QModelIndex& index ) const
312 {
313     Q_UNUSED( index );
314     return QModelIndex();
315 }
316 
317 
rowCount(const QModelIndex & parent) const318 int ExternalBinPermissionModel::rowCount( const QModelIndex& parent ) const
319 {
320     if( !parent.isValid() )
321         return d->programs.size();
322     else
323         return 0;
324 }
325 
326 
columnCount(const QModelIndex & parent) const327 int ExternalBinPermissionModel::columnCount( const QModelIndex& parent ) const
328 {
329     Q_UNUSED( parent );
330     return NumColumns;
331 }
332 
333 
buddy(const QModelIndex & index) const334 QModelIndex ExternalBinPermissionModel::buddy( const QModelIndex& index ) const
335 {
336     if( programForIndex( index ) != 0 )
337         return ExternalBinPermissionModel::index( index.row(), ProgramColumn, index.parent() );
338     else
339         return index;
340 }
341 
setBurningGroup(const QString & burningGroup)342 void ExternalBinPermissionModel::setBurningGroup( const QString& burningGroup )
343 {
344     if( burningGroup != d->burningGroup ) {
345         beginResetModel();
346         d->burningGroup = burningGroup;
347 
348         // Remove from the selected list all programs
349         // whose permissions don't need to be changed anymore
350         for( QSet<const ExternalBin*>::iterator program = d->selectedPrograms.begin();
351              program != d->selectedPrograms.end(); )
352         {
353             if( !d->needChangePermissions( *program ) )
354                 program = d->selectedPrograms.erase( program );
355             else
356                 ++program;
357         }
358         endResetModel();
359     }
360 }
361 
update()362 void ExternalBinPermissionModel::update()
363 {
364     beginResetModel();
365     d->buildProgramList();
366     d->selectedPrograms.intersect( d->programs.toSet() );
367     endResetModel();
368 }
369 
370 } // namespace K3b
371 
372 
373