1
2
3
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(0, false);
17 }
18 public ~EchoSetter()
19 {
20 SetEcho(0, true);
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 uid, const 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& userName, bool 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>& list, int 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>& list, const PasswordHash& pwh)
184 {
185 list.Add(pwh);
186 }
187
188 void SetPasswordHash(Users& users, User* user, const 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(pwhList, clone.UID());
197 PasswordHash pwh(clone.UID(), hash);
198 AddPasswordHash(pwhList, pwh);
199 WritePasswordHashFile(pwhList);
200 }
201
202 void DeletePassword(Users& users, User* 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(pwhList, clone.UID());
211 WritePasswordHashFile(pwhList);
212 Console.Out() << "password for user '" + clone.Name() + "' removed" << endl();
213 }
214
215 int main(int argc, const 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(users, user);
316 }
317 else
318 {
319 string password = EnterNewPassword(userName, false);
320 string retypedPassword = EnterNewPassword(userName, true);
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(userName, true);
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(users, user, hash);
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 }