1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.IO;
  9 using System.Os;
 10 using System.Security;
 11 
 12 class EchoSetter
 13 {
 14     public EchoSetter()
 15     {
 16         SetEcho(0false);
 17     }
 18     public ~EchoSetter()
 19     {
 20         SetEcho(0true);
 21     }
 22 }
 23 
 24 void PrintHelp()
 25 {
 26     Console.Out() << "passwd [options]" << endl() << endl();
 27     Console.Out() << "Change the password of an account." << endl() << endl();
 28     Console.Out() << "Options:" << endl() << endl();
 29     Console.Out() << "--help | -h" << endl();
 30     Console.Out() << "  Print help and exit." << endl() << endl();
 31     Console.Out() << "--user USER | -u USER" << endl();
 32     Console.Out() << "  Set account name to USER." << endl() << endl();
 33     Console.Out() << "--delete | -d" << endl();
 34     Console.Out() << "  Delete account's password." << endl() << endl();
 35 }
 36 
 37 bool CheckPassword(int uidconst string& hash)
 38 {
 39     StreamReader reader = File.OpenRead("/etc/passwd_hash");
 40     int lineNumber = 0;
 41     while (!reader.EndOfStream())
 42     {
 43         string line = reader.ReadLine();
 44         if (!line.IsEmpty() && !line.StartsWith("#"))
 45         {
 46             List<string> fields = line.Split(':');
 47             if (fields.Count() != 2)
 48             {
 49                 throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/passwd_hash: wrong number of fields (" + ToString(fields.Count()) + "), should be 2.");
 50             }
 51             int puid = ParseInt(fields[0]);
 52             if (puid == uid)
 53             {
 54                 if (hash == fields[1])
 55                 {
 56                     return true;
 57                 }
 58                 else
 59                 {
 60                     return false;
 61                 }
 62             }
 63         }
 64         ++lineNumber;
 65     }
 66     throw SecurityException("no matching user account for UID " + ToString(uid) + " found from /etc/passwd_hash");
 67 }
 68 
 69 bool CheckExistingPassword(User* user)
 70 {
 71     if (!user->HasPassword())
 72     {
 73         return true;
 74     }
 75     int uid = GetUID();
 76     if (uid == 0)
 77     {
 78         return true;
 79     }
 80     if (uid != user->UID())
 81     {
 82         throw SecurityException("unauthorized");
 83     }
 84     int secs = 5;
 85     for (int i = 0; i < 3; ++i;)
 86     {
 87         Console.Out() << "current password for " << user->Name() << ": ";
 88         EchoSetter echoSetter;
 89         string password = Console.ReadLine();
 90         string hash = GetSha1MessageDigest(password);
 91         if (CheckPassword(user->UID()hash))
 92         {
 93             return true;
 94         }
 95         else
 96         {
 97             Sleep(Duration.FromSeconds(secs));
 98             secs = 2 * secs;
 99             if (i < 2)
100             {
101                 Console.Out() << "incorrect password, try again" << endl();
102             }
103         }
104     }
105     return false;
106 }
107 
108 string EnterNewPassword(const string& userNamebool retype)
109 {
110     if (retype)
111     {
112         Console.Out() << "retype new password for " << userName << ": ";
113     }
114     else
115     {
116         Console.Out() << "new password for " << userName << ": ";
117     }
118     EchoSetter echoSetter;
119     string password = Console.ReadLine();
120     return password;
121 }
122 
123 class PasswordHash
124 {
125     public PasswordHash() : uid()hash()
126     {
127     }
128     public PasswordHash(int uid_const string& hash_) : uid(uid_)hash(hash_)
129     {
130     }
131     public void Write(StreamWriter& writer)
132     {
133         writer << uid << ":" << hash << endl();
134     }
135     public int uid;
136     public string hash;
137 }
138 
139 List<PasswordHash> ReadPasswordHashFile()
140 {
141     List<PasswordHash> pwh;
142     List<string> lines = File.ReadAllLines("/etc/passwd_hash");
143     int lineNumber = 0;
144     for (const string& line : lines)
145     {
146         if (!line.IsEmpty() && !line.StartsWith("#"))
147         {
148             List<string> fields = line.Split(':');
149             if (fields.Count() != 2)
150             {
151                 throw SecurityException("invalid line " + ToString(lineNumber) + " in /etc/passwd_hash: wrong number of fields (" + ToString(fields.Count()) + "), should be 2.");
152             }
153             PasswordHash passwordHash(ParseInt(fields[0])fields[1]);
154             pwh.Add(passwordHash);
155         }
156     }
157     return pwh;
158 }
159 
160 void WritePasswordHashFile(const List<PasswordHash>& list)
161 {
162     StreamWriter writer = File.CreateText("/etc/passwd_hash");
163     for (const PasswordHash& pwh : list)
164     {
165         pwh.Write(writer);
166     }
167 }
168 
169 void RemovePasswordHash(List<PasswordHash>& listint uid)
170 {
171     int n = cast<int>(list.Count());
172     for (int i = 0; i < n; ++i;)
173     {
174         const PasswordHash& pwh = list[i];
175         if (pwh.uid == uid)
176         {
177             list.Remove(list.Begin() + i);
178             break;
179         }
180     }
181 }
182 
183 void AddPasswordHash(List<PasswordHash>& listconst PasswordHash& pwh)
184 {
185     list.Add(pwh);
186 }
187 
188 void SetPasswordHash(Users& usersUser* userconst string& hash)
189 {
190     user->SetHasPassword(true);
191     User clone(*user);
192     users.RemoveUser(user);
193     users.AddUser(clone);
194     WriteUsers(users);
195     List<PasswordHash> pwhList = ReadPasswordHashFile();
196     RemovePasswordHash(pwhListclone.UID());
197     PasswordHash pwh(clone.UID()hash);
198     AddPasswordHash(pwhListpwh);
199     WritePasswordHashFile(pwhList);
200 }
201 
202 void DeletePassword(Users& usersUser* user)
203 {
204     user->SetHasPassword(false);
205     User clone(*user);
206     users.RemoveUser(user);
207     users.AddUser(clone);
208     WriteUsers(users);
209     List<PasswordHash> pwhList = ReadPasswordHashFile();
210     RemovePasswordHash(pwhListclone.UID());
211     WritePasswordHashFile(pwhList);
212     Console.Out() << "password for user '" + clone.Name() + "' removed" << endl();
213 }
214 
215 int main(int argcconst char** argv)
216 {
217     try
218     {
219         Users users = GetUsers();
220         User* user = null;
221         string userName;
222         bool prevWasUser = false;
223         bool del = false;
224         for (int i = 1; i < argc; ++i;)
225         {
226             string arg = argv[i];
227             if (arg.StartsWith("--"))
228             {
229                 if (arg == "--help")
230                 {
231                     PrintHelp();
232                     return 1;
233                 }
234                 else if (arg == "--user")
235                 {
236                     prevWasUser = true;
237                 }
238                 else if (arg == "--delete")
239                 {
240                     del = true;
241                 }
242                 else
243                 {
244                     throw Exception("unknown option '" + arg + "'");
245                 }
246             }
247             else if (arg.StartsWith("-"))
248             {
249                 string options = arg.Substring(1);
250                 if (options.IsEmpty())
251                 {
252                     throw Exception("unknown argument '" + arg + "'");
253                 }
254                 else
255                 {
256                     bool unknown = false;
257                     string uo;
258                     for (char o : options)
259                     {
260                         switch (o)
261                         {
262                             case 'h':
263                             {
264                                 PrintHelp();
265                                 return 1;
266                             }
267                             case 'd':
268                             {
269                                 del = true;
270                                 break;
271                             }
272                             case 'u':
273                             {
274                                 prevWasUser = true;
275                                 break;
276                             }
277                             default:
278                             {
279                                 unknown = true;
280                                 uo.Append(o);
281                                 break;
282                             }
283                         }
284                         if (unknown)
285                         {
286                             throw Exception("unknown option '-" + uo + "'");
287                         }
288                     }
289                 }
290             }
291             else if (prevWasUser)
292             {
293                 prevWasUser = false;
294                 userName = arg;
295             }
296         }
297         if (userName.IsEmpty())
298         {
299             int uid = GetUID();
300             user = users.GetUser(uid);
301         }
302         else
303         {
304             user = users.GetUser(userName);
305         }
306         if (user == null)
307         {
308             throw SecurityException("user '" + userName + "' not found");
309         }
310         userName = user->Name();
311         if (CheckExistingPassword(user))
312         {
313             if (del)
314             {
315                 DeletePassword(usersuser);
316             }
317             else
318             {
319                 string password = EnterNewPassword(userNamefalse);
320                 string retypedPassword = EnterNewPassword(userNametrue);
321                 if (password != retypedPassword)
322                 {
323                     for (int i = 0; i < 2; ++i;)
324                     {
325                         Console.Out() << "retyped password differs, try again" << endl();
326                         retypedPassword = EnterNewPassword(userNametrue);
327                         if (retypedPassword == password)
328                         {
329                             break;
330                         }
331                     }
332                 }
333                 if (password != retypedPassword)
334                 {
335                     Console.Out() << "retyped password differs, password not changed" << endl();
336                     return 1;
337                 }
338                 string hash = GetSha1MessageDigest(password);
339                 SetPasswordHash(usersuserhash);
340                 Console.Out() << "password changed for user '" + userName + "'" << endl();
341             }
342         }
343         else
344         {
345             Console.Out() << "existing password incorrect, password not changed" << endl();
346             return 1;
347         }
348     }
349     catch (const Exception& ex)
350     {
351         Console.Error() << ex.ToString() << endl();
352         return 1;
353     }
354     return 0;
355 }