1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.IO;
  8 using System.Collections;
  9 
 10 namespace System.Security
 11 {
 12     public class SecurityException : Exception
 13     {
 14         public nothrow SecurityException(const string& message_) : base(message_)
 15         {
 16         }
 17     }
 18 
 19     public class User
 20     {
 21         public nothrow User(const string& username_bool hasPassword_int uid_int gid_const string& info_const string& home_const string& shell_) : 
 22             username(username_)hasPassword(hasPassword_)uid(uid_)gid(gid_)info(info_)home(home_)shell(shell_)
 23         {
 24         }
 25         public inline nothrow const string& Name() const
 26         {
 27             return username;
 28         }
 29         public inline nothrow bool HasPassword() const
 30         {
 31             return hasPassword;
 32         }
 33         public nothrow void SetHasPassword(bool hasPassword_)
 34         {
 35             hasPassword = hasPassword_;
 36         }
 37         public inline nothrow int UID() const
 38         {
 39             return uid;
 40         }
 41         public inline nothrow int GID() const
 42         {
 43             return gid;
 44         }
 45         public inline nothrow const string& Info() const
 46         {
 47             return info;
 48         }
 49         public inline nothrow const string& Home() const
 50         {
 51             return home;
 52         }
 53         public inline nothrow const string& Shell() const
 54         {
 55             return shell;
 56         }
 57         public void Write(StreamWriter& writer)
 58         {
 59             string hpwd;
 60             if (hasPassword)
 61             {
 62                 hpwd = "x";
 63             }
 64             writer << username << ":" << hpwd << ":" << uid << ":" << gid << ":" << info << ":" << home << ":" << shell << endl();
 65         }
 66         private string username;
 67         private bool hasPassword;
 68         private int uid;
 69         private int gid;
 70         private string info;
 71         private string home;
 72         private string shell;
 73     }
 74     
 75     public class Users
 76     {
 77         public nothrow Users() : lastUserId(0)
 78         {
 79         }
 80         public void AddUser(const User& user)
 81         {
 82             users.Add(user);
 83             if (user.UID() > lastUserId)
 84             {
 85                 lastUserId = user.UID();
 86             }
 87         }
 88         public void RemoveUser(User* user)
 89         {
 90             int index = -1;
 91             int n = cast<int>(users.Count());
 92             for (int i = 0; i < n; ++i;)
 93             {
 94                 const User& usr = users[i];
 95                 if (usr.UID() == user->UID())
 96                 {
 97                     index = i;
 98                     break;
 99                 }
100             }
101             if (index != -1)
102             {
103                 users.Remove(users.Begin() + index);
104             }
105         }
106         public User* GetUser(int uid) const
107         {
108             Map<intUser*>.ConstIterator it = uidUserMap.CFind(uid);
109             if (it != uidUserMap.CEnd())
110             {
111                 return it->second;
112             }
113             else
114             {
115                 return null;
116             }
117         }
118         public User* GetUser(const string& userName) const
119         {
120             Map<stringUser*>.ConstIterator it = userNameUserMap.CFind(userName);
121             if (it != userNameUserMap.CEnd())
122             {
123                 return it->second;
124             }
125             else
126             {
127                 return null;
128             }
129         }
130         public void Finalize()
131         {
132             for (User& user : users)
133             {
134                 uidUserMap[user.UID()] = &user;
135                 userNameUserMap[user.Name()] = &user;
136             }
137         }
138         public void Write(StreamWriter& writer)
139         {
140             for (const User& user : users)
141             {
142                 user.Write(writer);
143             }
144         }
145         public nothrow int GetFreeUserId() const
146         {
147             return lastUserId + 1;
148         }
149         private List<User> users;
150         private Map<intUser*> uidUserMap;
151         private Map<stringUser*> userNameUserMap;
152         private int lastUserId;
153     }
154     
155     public class Group
156     {
157         public nothrow Group(const string& groupname_int gid_const List<string>& users_) : 
158             groupname(groupname_)gid(gid_)users(users_)
159         {
160         }
161         public inline nothrow const string& Name() const
162         {
163             return groupname;
164         }
165         public inline nothrow int GID() const
166         {
167             return gid;
168         }
169         public void Write(StreamWriter& writer)
170         {
171             writer << groupname << ":" << gid << ":" << ToCsv(users) << endl();
172         }
173         public inline nothrow const List<string>& Users() const
174         {
175             return users;
176         }
177         public void AddUser(const string& userName)
178         {
179             users.Add(userName);
180         }
181         private string groupname;
182         private int gid;
183         private List<string> users;
184     }
185     
186     public class Groups
187     {
188         public nothrow Groups() : lastGroupId(0)
189         {
190         }
191         public void AddGroup(const Group& group)
192         {
193             groups.Add(group);
194             if (group.GID() > lastGroupId)
195             {
196                 lastGroupId = group.GID();
197             }
198         }
199         public Group* GetGroup(int gid) const
200         {
201             Map<intGroup*>.ConstIterator it = gidGroupMap.CFind(gid);
202             if (it != gidGroupMap.CEnd())
203             {
204                 return it->second;
205             }
206             else
207             {
208                 return null;
209             }
210         }
211         public void Finalize()
212         {
213             for (Group& group : groups)
214             {
215                 gidGroupMap[group.GID()] = &group;
216             }
217         }
218         public void Write(StreamWriter& writer)
219         {
220             for (const Group& group : groups)
221             {
222                 group.Write(writer);
223             }
224         }
225         public nothrow int GetFreeGroupId() const
226         {
227             return lastGroupId + 1;
228         }
229         private List<Group> groups;
230         private Map<intGroup*> gidGroupMap;
231         private int lastGroupId;
232     }
233     
234     public User GetUser(int uid)
235     {
236         StreamReader reader = File.OpenRead("/etc/passwd");
237         int lineNumber = 0;
238         while (!reader.EndOfStream())
239         {
240             string line = reader.ReadLine();
241             if (!line.IsEmpty() && !line.StartsWith("#"))
242             {
243                 List<string> fields = line.Split(':');
244                 if (fields.Count() != 7)
245                 {
246                     throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/passwd: wrong number of fields (" + ToString(fields.Count()) + "), should be 7.");
247                 }
248                 User user(fields[0]fields[1] == "x"ParseInt(fields[2])ParseInt(fields[3])fields[4]fields[5]fields[6]);
249                 if (user.UID() == uid)
250                 {
251                     return user;
252                 }
253             }
254             ++lineNumber;
255         }
256         throw SecurityException("no matching user account for UID " + ToString(uid) + " found from /etc/passwd");
257     }
258     
259     public Users GetUsers()
260     {
261         Users users;
262         StreamReader reader = File.OpenRead("/etc/passwd");
263         int lineNumber = 0;
264         while (!reader.EndOfStream())
265         {
266             string line = reader.ReadLine();
267             if (!line.IsEmpty() && !line.StartsWith("#"))
268             {
269                 List<string> fields = line.Split(':');
270                 if (fields.Count() != 7)
271                 {
272                     throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/passwd: wrong number of fields (" + ToString(fields.Count()) + "), should be 7.");
273                 }
274                 User user(fields[0]fields[1] == "x"ParseInt(fields[2])ParseInt(fields[3])fields[4]fields[5]fields[6]);
275                 users.AddUser(user);
276             }
277             ++lineNumber;
278         }
279         users.Finalize();
280         return users;
281     }
282     
283     public void WriteUsers(const Users& users)
284     {
285         StreamWriter writer = File.CreateText("/etc/passwd");
286         users.Write(writer);
287     }
288     
289     public Group GetGroup(int gid)
290     {
291         StreamReader reader = File.OpenRead("/etc/group");
292         int lineNumber = 0;
293         while (!reader.EndOfStream())
294         {
295             string line = reader.ReadLine();
296             if (!line.IsEmpty() && !line.StartsWith("#"))
297             {
298                 List<string> fields = line.Split(':');
299                 if (fields.Count() != 3)
300                 {
301                     throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/group: wrong number of fields (" + ToString(fields.Count()) + "), should be 3.");
302                 }
303                 Group group(fields[0]ParseInt(fields[1])ParseCSV(fields[2]));
304                 if (group.GID() == gid)
305                 {
306                     return group;
307                 }
308             }
309             ++lineNumber;
310         }
311         throw SecurityException("no matching group for GID " + ToString(gid) + " found from /etc/group");
312     }
313     
314     public Groups GetGroups()
315     {
316         Groups groups;
317         StreamReader reader = File.OpenRead("/etc/group");
318         int lineNumber = 0;
319         while (!reader.EndOfStream())
320         {
321             string line = reader.ReadLine();
322             if (!line.IsEmpty() && !line.StartsWith("#"))
323             {
324                 List<string> fields = line.Split(':');
325                 if (fields.Count() != 3)
326                 {
327                     throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/group: wrong number of fields (" + ToString(fields.Count()) + "), should be 3.");
328                 }
329                 Group group(fields[0]ParseInt(fields[1])ParseCSV(fields[2]));
330                 groups.AddGroup(group);
331             }
332             ++lineNumber;
333         }
334         groups.Finalize();
335         return groups;
336     }
337 
338     public void WriteGroups(const Groups& groups)
339     {
340         StreamWriter writer = File.CreateText("/etc/group");
341         groups.Write(writer);
342     }
343 }