1
/***************************************************************************
2
smb4k_sudowriter - This program writes to the sudoers file. It
3
belongs to the utility programs of Smb4K.
6
copyright : (C) 2008 by Alexander Reinholdt
7
email : dustpuppy@users.berlios.de
8
***************************************************************************/
10
/***************************************************************************
11
* This program is free software; you can redistribute it and/or modify *
12
* it under the terms of the GNU General Public License as published by *
13
* the Free Software Foundation; either version 2 of the License, or *
14
* (at your option) any later version. *
16
* This program is distributed in the hope that it will be useful, but *
17
* WITHOUT ANY WARRANTY; without even the implied warranty of *
18
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
19
* General Public License for more details. *
21
* You should have received a copy of the GNU General Public License *
22
* along with this program; if not, write to the *
23
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
25
***************************************************************************/
33
#include <QStringList>
36
#include <kaboutdata.h>
37
#include <kcmdlineargs.h>
38
#include <kapplication.h>
42
#include <kstandarddirs.h>
46
#include <sys/types.h>
55
static QFile lock_file;
57
static const char description[] =
58
I18N_NOOP( "This program writes to the sudoers file." );
60
static const char authors[] =
61
I18N_NOOP( "(c) 2008, Alexander Reinholdt" );
65
// Determine the directory where to write the lock file. First, try
66
// /var/lock and than /var/tmp. If that does not work either, fall
68
QList<QByteArray> dirs;
75
for ( int i = 0; i < dirs.size(); ++i )
77
// First check if the directory is available and writable
78
if ( lstat( dirs.at( i ), &buf ) == -1 )
80
int error_number = errno;
82
if ( error_number != EACCES && error_number != ENOENT )
92
// Get the ids of the groups the user is in and check if
93
// on of them matches buf.st_gid.
94
KUser user( geteuid() );
95
QList<KUserGroup> gids = user.groups();
96
gid_t sup_gid = 65534; // set this to gid 'nobody' for initialization
97
bool found_gid = false;
99
for ( int j = 0; j < gids.size(); ++j )
101
if ( gids.at( j ).gid() == buf.st_gid )
103
sup_gid = gids.at( j ).gid();
114
// Check whether we are stat'ing a directory and that the
115
// user has read/write permissions.
116
if ( S_ISDIR( buf.st_mode ) /* is directory */ &&
117
((buf.st_uid == getuid() && (buf.st_mode & 00600) == (S_IWUSR | S_IRUSR)) /* user */ ||
118
(found_gid && buf.st_gid == sup_gid && (buf.st_mode & 00060) == (S_IWGRP | S_IRGRP)) /* group */ ||
119
((buf.st_mode & 00006) == (S_IWOTH | S_IROTH)) /* others */) )
121
lock_file.setFileName( dirs.at( i )+"/smb4k.lock" );
131
if ( !lock_file.exists() )
133
if ( lock_file.open( QIODevice::WriteOnly | QIODevice::Text ) )
135
QTextStream ts( &lock_file );
136
// Note: With Qt 4.3 this seems to be obsolete, we'll keep
138
ts.setCodec( QTextCodec::codecForLocale() );
140
#if QT_VERSION >= 0x040400
143
ts << kapp->applicationPid() << endl;
168
void removeLockFile()
170
// Just remove the lock file. No further checking is needed.
171
if ( lock_file.exists() )
181
const QByteArray findFile( const QString &filename )
185
paths << "/usr/local/etc";
187
QString canonical_path;
189
for ( int i = 0; i < paths.size(); ++i )
191
QDir::setCurrent( paths.at( i ) );
193
if ( QFile::exists( filename ) )
195
canonical_path = QDir::current().canonicalPath()+QDir::separator()+filename;
205
return canonical_path.toLocal8Bit();
208
bool checkUsers( const QStringList &list )
210
for ( int i = 0; i < list.size(); ++i )
212
if ( getpwnam( list.at( i ).toLocal8Bit() ) == NULL )
225
int checkFile( const QByteArray &path )
227
// Stat the file, so that we know that it is safe to
228
// read from and write to it and whether we need to
229
// ask for the super user's password:
232
if ( lstat( path, &buf ) == -1 )
241
// Get the ids of the groups the user is in and check if
242
// on of them matches buf.st_gid.
243
KUser user( geteuid() );
244
QList<KUserGroup> gids = user.groups();
245
gid_t sup_gid = 65534; // set this to gid 'nobody' for initialization
246
bool found_gid = false;
248
for ( int i = 0; i < gids.size(); ++i )
250
if ( gids.at( i ).gid() == buf.st_gid )
252
sup_gid = gids.at( i ).gid();
263
if ( !S_ISREG( buf.st_mode ) || S_ISFIFO( buf.st_mode ) || S_ISLNK( buf.st_mode ) )
272
// Check the access rights. We need to read the file.
273
if ( buf.st_uid != geteuid() && !found_gid &&
274
(buf.st_mode & 00004) != (S_IWOTH | S_IROTH) /* others */ )
286
bool findUtilityPrograms()
288
if ( KGlobal::dirs()->findResource( "exe", "smb4k_kill" ).isEmpty() ||
289
KGlobal::dirs()->findResource( "exe", "smb4k_umount" ).isEmpty() ||
290
KGlobal::dirs()->findResource( "exe", "smb4k_mount" ).isEmpty() )
302
int main ( int argc, char *argv[] )
304
KAboutData aboutData( "smb4k_sudowriter",
306
ki18n( "smb4k_sudowriter" ),
308
ki18n( description ),
309
KAboutData::License_GPL_V2,
312
"http://smb4k.berlios.de",
313
"smb4k-bugs@lists.berlios.de" );
315
KCmdLineArgs::init( argc, argv, &aboutData );
317
KCmdLineOptions options;
318
options.add( "adduser <user>",
319
ki18n( "Adds an user to the sudoers file" ),
321
options.add( "removeuser <user>",
322
ki18n( "Removes an user from the sudoers file" ),
325
KCmdLineArgs::addCmdLineOptions( options );
327
KApplication app( false /* no GUI */ );
329
// Before doing anything else, create the lock file.
330
int return_value = 0;
332
if ( (return_value = createLockFile()) != 0 )
334
switch ( return_value )
338
cerr << argv[0] << ": " << I18N_NOOP( "The lock file could not be created." ) << endl;
343
cerr << argv[0] << ": " << I18N_NOOP( "Another user is currently editing the sudoers file." ) << endl;
348
cerr << argv[0] << ": " << strerror( return_value ) << endl;
353
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
354
exit( EXIT_FAILURE );
361
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
363
// Check that everything is OK.
364
QStringList adduser = args->getOptionList( "adduser" );
365
QStringList removeuser = args->getOptionList( "removeuser" );
367
// Throw an error if no argument was provided.
368
if ( adduser.isEmpty() && removeuser.isEmpty() )
370
KCmdLineArgs::usageError( i18n( "No arguments given." ) );
372
exit( EXIT_FAILURE );
379
// Check that the given users are all valid.
380
if ( !checkUsers( adduser ) || !checkUsers( removeuser ) )
382
cerr << argv[0] << ": " << I18N_NOOP( "An invalid user name has been provided." ) << endl;
383
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
385
exit( EXIT_FAILURE );
392
// Find the sudoers file.
393
QByteArray path = findFile( "sudoers" );
395
if ( path.isEmpty() )
397
cerr << argv[0] << ": " << I18N_NOOP( "The sudoers file was not found." ) << endl;
398
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
400
exit( EXIT_FAILURE );
407
// Check that the file is regular.
408
if ( (return_value = checkFile( path )) != 0 )
410
switch ( return_value )
414
cerr << argv[0] << ": " << I18N_NOOP( "The sudoers file is irregular." ) << endl;
419
cerr << argv[0] << ": " << I18N_NOOP( "Cannot access sudoers file." ) << endl;
424
cerr << argv[0] << ": " << strerror( return_value ) << endl;
429
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
431
exit( EXIT_FAILURE );
438
// Check that the utility programs can actually be found.
439
if ( !findUtilityPrograms() )
441
cerr << argv[0] << ": " << I18N_NOOP( "One or more utility programs could not be found." ) << endl;
442
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
444
exit( EXIT_FAILURE );
451
// Now work with the sudoers file.
454
// Save the original permissions for later.
455
QFile::Permissions perms = file.permissions();
457
// Temporarily give the *owner* the permission to
458
// write to the file.
459
file.setPermissions( QFile::WriteOwner | QFile::ReadOwner );
461
QStringList contents;
463
if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
465
QTextStream ts( &file );
466
// Note: With Qt 4.3 this seems to be obsolete, we'll keep
468
ts.setCodec( QTextCodec::codecForLocale() );
470
while ( !ts.atEnd() )
472
contents.append( ts.readLine( 0 ) );
476
file.setPermissions( perms );
480
file.setPermissions( perms );
481
cerr << argv[0] << ": " << file.errorString().toLocal8Bit().data() << endl;
483
exit( EXIT_FAILURE );
486
// Find the beginning and the end of the entries in
488
int begin = contents.indexOf( "# Entries for Smb4K users.", 0 );
489
int end = contents.lastIndexOf( "# End of Smb4K user entries.", -1 );
494
if ( !adduser.isEmpty() )
496
if ( begin == -1 && end == -1 )
499
size_t hostnamelen = 255;
500
char *hn = new char[hostnamelen];
502
if ( gethostname( hn, hostnamelen ) == -1 )
504
int error_number = errno;
505
cerr << argv[0] << ": " << strerror( error_number ) << endl;
507
exit( EXIT_FAILURE );
514
QString hostname( hn );
517
// Add the new entries.
518
if ( !contents.last().trimmed().isEmpty() )
520
contents.append( "" );
524
// Do not add empty line to the end.
527
contents.append( "# Entries for Smb4K users." );
528
contents.append( "# Generated by Smb4K. Please do not modify!" );
529
contents.append( "User_Alias\tSMB4KUSERS = "+QString( "%1" ).arg( adduser.join( "," ) ) );
530
contents.append( "Defaults:SMB4KUSERS\tenv_keep += \"PASSWD USER\"" );
531
contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "
532
+KGlobal::dirs()->findResource( "exe", "smb4k_kill" ) );
533
contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "
534
+KGlobal::dirs()->findResource( "exe", "smb4k_umount" ) );
535
contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "
536
+KGlobal::dirs()->findResource( "exe", "smb4k_mount" ) );
537
contents.append( "# End of Smb4K user entries." );
541
else if ( begin != -1 && end != -1 )
543
for ( int i = begin; i != end; ++i )
545
if ( contents.at( i ).startsWith( "User_Alias\tSMB4KUSERS" ) )
547
for ( int j = 0; j < adduser.size(); ++j )
549
if ( !contents.at( i ).contains( adduser.at( j ) ) )
551
contents[i].append( ","+adduser.at( j ) );
571
cerr << argv[0] << ": " << I18N_NOOP( "The Smb4K section does not conform with the required format." ) << endl;
572
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
574
exit( EXIT_FAILURE );
583
if ( !removeuser.isEmpty() )
585
if ( begin != -1 && end != -1 )
587
for ( int i = begin; i != end; ++i )
589
if ( contents.at( i ).startsWith( "User_Alias\tSMB4KUSERS" ) )
591
QString users = contents.at( i ).section( "=", 1, 1 ).trimmed();
593
if ( !users.contains( "," ) )
595
// In this case, there is only one user in the list. Check if
596
// it is the user who requested the removal:
597
for ( int j = 0; j < removeuser.size(); ++j )
599
if ( QString::compare( users, removeuser.at( j ) ) == 0 )
601
// They are equal. Remove all entries:
604
while ( k != end + 1 ) // We want to remove line 'end' as well.
606
contents.removeAt( begin );
617
// They are not equal: Do nothing.
626
// In this case there is more than one user in the list.
627
// Remove the user who requested the removal:
628
QStringList list = users.split( ",", QString::SkipEmptyParts );
631
for ( int j = 0; j < removeuser.size(); ++j )
633
index = list.indexOf( removeuser.at( j ), 0 );
637
list.removeAt( index );
638
contents[i].replace( users, list.join( "," ) );
661
else if ( begin == -1 && end == -1 )
667
cerr << argv[0] << ": " << I18N_NOOP( "The Smb4K section does not conform with the required format." ) << endl;
668
cerr << argv[0] << ": " << I18N_NOOP( "Aborting." ) << endl;
670
exit( EXIT_FAILURE );
680
// Temporarily give the *owner* the permission to
681
// write to the file.
682
file.setPermissions( QFile::WriteOwner | QFile::ReadOwner );
684
if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) )
686
QTextStream ts( &file );
687
// Note: With Qt 4.3 this seems to be obsolete, we'll keep
689
ts.setCodec( QTextCodec::codecForLocale() );
691
ts << contents.join( "\n" ) << endl;
694
file.setPermissions( perms );
698
file.setPermissions( perms );
699
cerr << argv[0] << ": " << file.errorString().toLocal8Bit().data() << endl;
701
exit( EXIT_FAILURE );
706
// No modifications are needed.
709
// File permissions were fixed above.
715
app.exit( EXIT_SUCCESS );