diff --git a/src/library/library.pro b/src/library/library.pro index 769fca9..c08f0aa 100644 --- a/src/library/library.pro +++ b/src/library/library.pro @@ -14,17 +14,19 @@ HEADERS += sysadm-global.h \ sysadm-general.h \ sysadm-lifepreserver.h \ sysadm-network.h \ - sysadm-firewall.h + sysadm-firewall.h \ + sysadm-usermanager.h SOURCES += sysadm-general.cpp \ sysadm-lifepreserver.cpp \ sysadm-network.cpp \ NetDevice.cpp \ - sysadm-firewall.cpp + sysadm-firewall.cpp \ + sysadm-usermanager.cpp include.path=/usr/local/include/ include.files=sysadm-*.h -INSTALLS += target include +INSTALLS += target include QMAKE_LIBDIR = /usr/local/lib/qt5 /usr/local/lib diff --git a/src/library/sysadm-usermanager.cpp b/src/library/sysadm-usermanager.cpp new file mode 100644 index 0000000..9586bf6 --- /dev/null +++ b/src/library/sysadm-usermanager.cpp @@ -0,0 +1,365 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-usermanager.h" +#include "sysadm-general.h" +using namespace sysadm; + +UserManager::UserManager(QString chroot) +{ + this->chroot = chroot; + loadUsers(); + loadGroups(); + loadShells(); +} + +void UserManager::NewUser(QString fullName, QString userName, QString password, QString shell) +{ + User user; + user.UserName = userName; + user.FullName = fullName; + user.HomeFolder = "/usr/home/"+userName; + user.Shell = shell; + + //Add User + qDebug() << "Adding user " << userName; + // Create the zfs dataset associated with the home directory + if ( chroot.isEmpty() ) + { + QStringList args; + args.append(user.HomeFolder); + General::RunCommand("/usr/local/share/pcbsd/scripts/mkzfsdir.sh",args); + } + + QStringList args; + if ( ! chroot.isEmpty() ) //if chroot is not empty the command starts with chroot instead of pw + args << chroot << "pw"; //and thus we have to add it as an argument + args << "useradd"; //create a user + args << userName; //with this userName + args << "-c"; //sets the comment field + args << "\""+ fullName+"\""; //with the full name of the user + args << "-m"; //create the user's home directory + args << "-s"; //set the user's shell + args << shell; //to this + args << "-G"; //additionally add the user to + args << "operator"; //the operator's group + + if ( ! chroot.isEmpty() ) //if we're operating with a chroot call + General::RunCommand("chroot", args); + else //otherwise + General::RunCommand("pw", args); + + ChangeUserPassword(user,password); + + //enable flash for the user + if ( chroot.isEmpty() ) { //if we're not in a chroot + qDebug() << "Enabling Flash Plugin for " << userName; + args.clear(); + args << userName; //run command as this user + args << "-c"; //with the command + args << "\"flashpluginctl on\""; //turn on flashpluginctl + General::RunCommand("su",args); + } + + //reloads the groups and users so that the internal model is consistent + loadUsers(); + loadGroups(); +} + +void UserManager::DeleteUser(User user) +{ + //Delete User + qDebug() << "Deleting user " << user.UserName; + + //remove the dataset associated with the home folder + QStringList args; + args << user.HomeFolder; + General::RunCommand("/usr/local/share/pcbsd/scripts/rmzfsdir.sh",args); + + //delete the user and their home directory + args.clear(); + if ( ! chroot.isEmpty() ) //if we're in a chroot we need to use chroot before pw + args << chroot << "pw"; + args << "userdel"; //delete a user + args << user.UserName; //this user + args << "-r"; //remove the contents of the user's home directory + if ( ! chroot.isEmpty() ) + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadUsers(); + loadGroups(); +} + +const QVector UserManager::GetUsers() +{ + return users; +} + +const User UserManager::GetUser(int id) +{ + for(User user: users) + { + if(user.ID == id) + return user; + } + return User(); +} + +const User UserManager::GetUser(QString userName) +{ + for(User user: users) + { + if(user.UserName == userName) + return user; + } + return User(); +} + +void UserManager::ChangeUserPassword(User user, QString newPassword) +{ + + //Create a temporary file to store the password in + QTemporaryFile nfile("/tmp/.XXXXXXXX"); + if ( nfile.open() ) + { + QTextStream stream( &nfile ); + stream << newPassword; + nfile.close(); + } + + //set the user password + QStringList args; + args.append(nfile.fileName()); //the temp file holding the password + args.append("|"); //which we're going to pipe to the stdin of + if ( ! chroot.isEmpty() ) //if we're in a chroot + { + args << "chroot"; //a chroot + args << chroot; //located here + } + args << "pw"; //change users + args << "usermod"; //where we're going to modify a user + args << user.UserName;//this user + args << "-h"; //set the user's password + args << "0"; //using stdin + General::RunCommand("cat",args); + + //remove the temp file holding the password + nfile.remove(); + +} + +void UserManager::ChangeUserShell(User user, QString shell) +{ + if(shells.contains(shell)) + { + qDebug("Shell found"); + QStringList args; + args << "usermod"; // modify the user + args << "-n"; //specify a user name + args << user.UserName; //for this user + args << "-s"; //set the shell to + args << shell; //this shell + General::RunCommand("pw",args); + } + else + qDebug("Shell not found"); + + loadUsers(); +} + +void UserManager::ChangeUserFullName(User user, QString newName) +{ + QStringList args; + args << "usermod"; //modify the user + args << user.UserName; //for this user + args << "-c"; //change the gecos field to + args << newName; //this name + General::RunCommand("pw",args); + loadUsers(); +} + +void UserManager::AddUserToGroup(User user, Group group) +{ + QStringList args; + args << "groupmod"; //modify a group + args << "-n"; //modify for a group + args << group.Name;//this group + args << "-m";//by adding a member + args << user.UserName; //this user + General::RunCommand("pw",args); + + loadGroups(); +} + +void UserManager::RemoveUserFromGroup(User user, Group group) +{ + QStringList args; + args << "groupmod"; //modify a group + args << "-n"; //modify for a group + args << group.Name; //this group + args << "-d"; //by removing a user + args << user.UserName ; //this user + General::RunCommand("pw", args); + + loadGroups(); +} + +void UserManager::NewGroup(QString name, QStringList members) +{ + QStringList args; + qDebug() << "Adding group " << name; + if ( ! chroot.isEmpty() ) //if we're in a chroot need to add chroot before pw + args << chroot << "pw"; + args << "groupadd"; //create a new group + args << name; // with this name + args << "-M"; //with this list of users + args << members.join(","); //these guys + if ( ! chroot.isEmpty() ) //if we're in a chroot + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadGroups(); +} + +void UserManager::DeleteGroup(Group group) +{ + QStringList args; + qDebug() << "Deleting group " << group.Name; + if ( ! chroot.isEmpty() ) //if we're in a chroot need to add chroot before pw + args << chroot << "pw"; + args << "groupdel"; //delete a group + args << group.Name; //of this name + if ( ! chroot.isEmpty() ) //if we're in a chroot + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadGroups(); +} + +const QVector UserManager::GetGroups() +{ + return groups; +} + +const Group UserManager::getGroup(int id) +{ + for(Group group : groups) + { + if(group.ID == id) + return group; + } + return Group(); +} + +const Group UserManager::getGroup(QString name) +{ + for(Group group : groups) + { + if(group.Name == name) + return group; + } + return Group(); +} + +const QStringList UserManager::GetShells() +{ + return shells; +} + +void UserManager::loadUsers() +{ + users.clear(); + QFile userFile(chroot + "/etc/passwd"); + if ( userFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&userFile); + stream.setCodec("UTF-8"); + QString line; + + while ( !stream.atEnd() ) + { + line = stream.readLine(); + + if ((line.indexOf("#") != 0) && (! line.isEmpty())) //Make sure it isn't a comment or blank + { + User user; + user.UserName = line.section(":",0,0); + user.ID = line.section(":",2,2).toInt(); + user.GroupID = line.section(":",3,3).toInt(); + user.HomeFolder = line.section(":",5,5); + user.Shell = line.section(":",6,6); + user.FullName = line.section(":",4,4); + + users.append(user); + } + } + } else { + //Unable to open file error + qWarning("Error! Unable to open /etc/passwd"); + } +} + +void UserManager::loadGroups() +{ + groups.clear(); + QFile groupFile(chroot + "/etc/group"); + if ( groupFile.open(QIODevice::ReadOnly) ) + { + QTextStream stream(&groupFile); + stream.setCodec("UTF-8"); + + QString line; + + while ( !stream.atEnd() ) + { + line = stream.readLine(); + + if ((line.indexOf("#") != 0) && (! line.isEmpty())) //Make sure it isn't a comment or blank + { + Group group; + group.Name = line.section(":",0,0); + group.ID = line.section(":",2,2).toInt(); + QString memberString = line.section(":",3,3); + group.Members = memberString.split(","); + + groups.append(group); + } + } + } else { + //Unable to open file error + qWarning("Error! Unable to open /etc/group"); + } +} + +void UserManager::loadShells() +{ + shells.clear(); + QFile shellFile(chroot + "/etc/shells"); + if ( shellFile.open(QIODevice::ReadOnly) ) { + QTextStream stream(&shellFile); + stream.setCodec("UTF-8"); + + QString line; + + while ( !stream.atEnd() ) { + line = stream.readLine(); + + if ( !line.startsWith("#") && !line.isEmpty() ) { //Make sure it isn't a comment or blank + shells.append(line); + } + } + } else { + //Unable to open file error + qWarning("Error! Unable to open /etc/shells"); + } + + // Add /sbin/nologin as well + shells.append("/sbin/nologin"); +} diff --git a/src/library/sysadm-usermanager.h b/src/library/sysadm-usermanager.h new file mode 100644 index 0000000..cc908c8 --- /dev/null +++ b/src/library/sysadm-usermanager.h @@ -0,0 +1,194 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== + +#ifndef USERMANAGER_H +#define USERMANAGER_H +#include + +namespace sysadm{ +class User +{ +public: + User() + { + FullName = ""; + UserName = ""; + ID = -1; + HomeFolder = ""; + Shell = ""; + GroupID = -1; + } + + QString FullName; + QString UserName; + int ID; + QString HomeFolder; + QString Shell; + int GroupID; + friend bool operator<(const User lhs, const User rhs){ + return std::tie(lhs.ID,lhs.UserName) < std::tie(rhs.ID,rhs.UserName); + } + friend bool operator>(const User lhs, const User rhs) + { return rhs < lhs;} + friend bool operator==(const User lhs, const User rhs) + { + return lhs.ID == rhs.ID && lhs.UserName == rhs.UserName; + } + friend bool operator !=(const User lhs, const User rhs) + { return !(lhs == rhs);} +}; +class Group +{ +public: + Group() + { + ID = -1; + Name = ""; + Members = QStringList(); + } + + int ID; + QString Name; + //While the object model would be more "correct" if + //Users were to be a Vector of User pointers, it's + //expensive to wire up and we don't really gain anything + //from doing so + QStringList Members; +}; + +class UserManager +{ +public: + UserManager(QString chroot = ""); + + //#section user actions + /** + * @brief NewUser Create a new user + * @param fullName The full name of the user + * @param userName The username of the user + * @param password The user's password + * @param shell the user's shell, defaults to /bin/tcsh + */ + void NewUser(QString fullName, QString userName, QString password, QString shell = "/bin/tcsh"); + /** + * @brief DeleteUser Deletes a user + * @param user the user to delete + */ + void DeleteUser(User user); + + /** + * @brief GetUsers getter for the users vector + * @return a QVector that is a copy of the current state + * do not modify it, instead call functions on this class to change + * things and then get another copy of the vector + */ + const QVector GetUsers(); + /** + * @brief GetUser get a particular user by their UID + * @param id the UID of the user to get + * @return the user with the UID specified, if not found + * returns a blank User + */ + const User GetUser(int id); + + /** + * @brief GetUser get a particular user by their UID + * @param userName the username of the user to get + * @return the user with the user name specified, if not found + * returns a blank User + */ + const User GetUser(QString userName); + + /** + * @brief ChangeUserPassword changes the specified user's password + * @param user the user to change the password of + * @param newPassword the new password + */ + void ChangeUserPassword(User user, QString newPassword); + /** + * @brief ChangeUserShell change a specified user's shell + * @param user the user to change the shell for + * @param shell the shell to change to, note that if the shell + * is not in the shells list then it does nothing + */ + void ChangeUserShell(User user, QString shell); + /** + * @brief ChangeUserFullName change the gecos field of a user to a new name + * @param user the user to change the name of + * @param newName the name to change to + */ + void ChangeUserFullName(User user, QString newName); + //#endsection + + //#section group actions + /** + * @brief AddUserToGroup add the specified user to the specified group + * @param user the user to add to the group + * @param group the group to add the user to + */ + void AddUserToGroup(User user, Group group); + /** + * @brief RemoveUserFromGroup removes the specified user from the specified group + * @param user the user to remove from the group + * @param group the group to remove the user from + */ + void RemoveUserFromGroup(User user, Group group); + + /** + * @brief NewGroup creates a new group + * @param name the name of the new group + * @param Users a list of users to add to the group + */ + void NewGroup(QString name, QStringList Users = QStringList()); + /** + * @brief DeleteGroup delete a specified group + * @param group the group to delete + */ + void DeleteGroup(Group group); + + /** + * @brief GetGroups get the internal list of groups + * @return a QVector that is a copy of the current state + * do not modify it, instead call functions on this class to change + * things and then get another copy of the vector + */ + const QVector GetGroups(); + /** + * @brief getGroup get a specified group by their gid + * @param id the gid of the group to get + * @return the group with the specified gid + */ + const Group getGroup(int id); + /** + * @brief getGroup get a specified group by their name + * @param name the name of the group to get + * @return the group with the specified name + */ + const Group getGroup(QString name); + //#endsection + + /** + * @brief GetShells the list of shells that are currently installed on the system + * @return a QStringList of shells on the system + */ + const QStringList GetShells(); + +private: + QVector users; + QVector groups; + QStringList shells; + QString chroot; + + //loads the users from /etc/passwd + void loadUsers(); + //load the groups from /etc/group + void loadGroups(); + //load the shells from /etc/shells + void loadShells(); +}; +} +#endif // USERMANAGER_H