1 /***************************************************************************
2 * Copyright (c) 2013 Abdurrahman AVCI <abdurrahmanavci@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 ***************************************************************************/
19 
20 #include "UserModel.h"
21 
22 #include "Constants.h"
23 #include "Configuration.h"
24 
25 #include <QFile>
26 #include <QList>
27 #include <QTextStream>
28 #include <QStringList>
29 
30 #include <memory>
31 #include <pwd.h>
32 
33 namespace SDDM {
34     class User {
35     public:
User(const struct passwd * data,const QString icon)36         User(const struct passwd *data, const QString icon) :
37             name(QString::fromLocal8Bit(data->pw_name)),
38             realName(QString::fromLocal8Bit(data->pw_gecos).split(QLatin1Char(',')).first()),
39             homeDir(QString::fromLocal8Bit(data->pw_dir)),
40             uid(data->pw_uid),
41             gid(data->pw_gid),
42             // if shadow is used pw_passwd will be 'x' nevertheless, so this
43             // will always be true
44             needsPassword(strcmp(data->pw_passwd, "") != 0),
45             icon(icon)
46         {}
47 
48         QString name;
49         QString realName;
50         QString homeDir;
51         int uid { 0 };
52         int gid { 0 };
53         bool needsPassword { false };
54         QString icon;
55     };
56 
57     typedef std::shared_ptr<User> UserPtr;
58 
59     class UserModelPrivate {
60     public:
61         int lastIndex { 0 };
62         QList<UserPtr> users;
63         bool containsAllUsers { true };
64     };
65 
UserModel(bool needAllUsers,QObject * parent)66     UserModel::UserModel(bool needAllUsers, QObject *parent) : QAbstractListModel(parent), d(new UserModelPrivate()) {
67         const QString facesDir = mainConfig.Theme.FacesDir.get();
68         const QString themeDir = mainConfig.Theme.ThemeDir.get();
69         const QString currentTheme = mainConfig.Theme.Current.get();
70         const QString themeDefaultFace = QStringLiteral("%1/%2/faces/.face.icon").arg(themeDir).arg(currentTheme);
71         const QString defaultFace = QStringLiteral("%1/.face.icon").arg(facesDir);
72         const QString iconURI = QStringLiteral("file://%1").arg(
73                 QFile::exists(themeDefaultFace) ? themeDefaultFace : defaultFace);
74 
75         bool lastUserFound = false;
76 
77         struct passwd *current_pw;
78         setpwent();
79         while ((current_pw = getpwent()) != nullptr) {
80 
81             // skip entries with uids smaller than minimum uid
82             if (int(current_pw->pw_uid) < mainConfig.Users.MinimumUid.get())
83                 continue;
84 
85             // skip entries with uids greater than maximum uid
86             if (int(current_pw->pw_uid) > mainConfig.Users.MaximumUid.get())
87                 continue;
88             // skip entries with user names in the hide users list
89             if (mainConfig.Users.HideUsers.get().contains(QString::fromLocal8Bit(current_pw->pw_name)))
90                 continue;
91 
92             // skip entries with shells in the hide shells list
93             if (mainConfig.Users.HideShells.get().contains(QString::fromLocal8Bit(current_pw->pw_shell)))
94                 continue;
95 
96             // create user
97             UserPtr user { new User(current_pw, iconURI) };
98 
99             // add user
100             d->users << user;
101 
102             if (user->name == lastUser())
103                 lastUserFound = true;
104 
105             if (!needAllUsers && d->users.count() > mainConfig.Theme.DisableAvatarsThreshold.get()) {
106                 struct passwd *lastUserData;
107                 // If the theme doesn't require that all users are present, try to add the data for lastUser at least
108                 if(!lastUserFound && (lastUserData = getpwnam(qPrintable(lastUser()))))
109                     d->users << UserPtr(new User(lastUserData, themeDefaultFace));
110 
111                 d->containsAllUsers = false;
112                 break;
113             }
114         }
115 
116         endpwent();
117 
118         // sort users by username
119         std::sort(d->users.begin(), d->users.end(), [&](const UserPtr &u1, const UserPtr &u2) { return u1->name < u2->name; });
120         // Remove duplicates in case we have several sources specified
121         // in nsswitch.conf(5).
122         auto newEnd = std::unique(d->users.begin(), d->users.end(), [&](const UserPtr &u1, const UserPtr &u2) { return u1->name == u2->name; });
123         d->users.erase(newEnd, d->users.end());
124 
125         bool avatarsEnabled = mainConfig.Theme.EnableAvatars.get();
126         if (avatarsEnabled && mainConfig.Theme.EnableAvatars.isDefault()) {
127             if (d->users.count() > mainConfig.Theme.DisableAvatarsThreshold.get()) avatarsEnabled=false;
128         }
129 
130         // find out index of the last user
131         for (int i = 0; i < d->users.size(); ++i) {
132             UserPtr user { d->users.at(i) };
133             if (user->name == stateConfig.Last.User.get())
134                 d->lastIndex = i;
135 
136             if (avatarsEnabled) {
137                 const QString userFace = QStringLiteral("%1/.face.icon").arg(user->homeDir);
138                 const QString systemFace = QStringLiteral("%1/%2.face.icon").arg(facesDir).arg(user->name);
139                 QString accountsServiceFace = QStringLiteral("/var/lib/AccountsService/icons/%1").arg(user->name);
140 
141                 if (QFile::exists(userFace))
142                     user->icon = QStringLiteral("file://%1").arg(userFace);
143                 else if (QFile::exists(accountsServiceFace))
144                     user->icon = accountsServiceFace;
145                 else if (QFile::exists(systemFace))
146                     user->icon = QStringLiteral("file://%1").arg(systemFace);
147             }
148         }
149     }
150 
~UserModel()151     UserModel::~UserModel() {
152         delete d;
153     }
154 
roleNames() const155     QHash<int, QByteArray> UserModel::roleNames() const {
156         // set role names
157         QHash<int, QByteArray> roleNames;
158         roleNames[NameRole] = QByteArrayLiteral("name");
159         roleNames[RealNameRole] = QByteArrayLiteral("realName");
160         roleNames[HomeDirRole] = QByteArrayLiteral("homeDir");
161         roleNames[IconRole] = QByteArrayLiteral("icon");
162         roleNames[NeedsPasswordRole] = QByteArrayLiteral("needsPassword");
163 
164         return roleNames;
165     }
166 
lastIndex() const167     const int UserModel::lastIndex() const {
168         return d->lastIndex;
169     }
170 
lastUser() const171     QString UserModel::lastUser() const {
172         return stateConfig.Last.User.get();
173     }
174 
rowCount(const QModelIndex & parent) const175     int UserModel::rowCount(const QModelIndex &parent) const {
176         return d->users.length();
177     }
178 
data(const QModelIndex & index,int role) const179     QVariant UserModel::data(const QModelIndex &index, int role) const {
180         if (index.row() < 0 || index.row() > d->users.count())
181             return QVariant();
182 
183         // get user
184         UserPtr user = d->users[index.row()];
185 
186         // return correct value
187         if (role == NameRole)
188             return user->name;
189         else if (role == RealNameRole)
190             return user->realName;
191         else if (role == HomeDirRole)
192             return user->homeDir;
193         else if (role == IconRole)
194             return user->icon;
195         else if (role == NeedsPasswordRole)
196             return user->needsPassword;
197 
198         // return empty value
199         return QVariant();
200     }
201 
disableAvatarsThreshold() const202     int UserModel::disableAvatarsThreshold() const {
203         return mainConfig.Theme.DisableAvatarsThreshold.get();
204     }
205 
containsAllUsers() const206     bool UserModel::containsAllUsers() const {
207         return d->containsAllUsers;
208     }
209 }
210