1 /*
2     Copyright (c) 2019, DUNEX Contributors
3     Use, modification and distribution are subject to the
4     Boost Software License, Version 1.0.  (See accompanying file
5     COPYING or copy at http://www.boost.org/LICENSE_1_0.txt)
6 
7     dunex-auth: D toolkit to manage UNIX authentication
8     
9     Author: Clipsey
10 */
11 module dunex.auth.passwd;
12 import std.array : split;
13 import std.conv;
14 import std.format;
15 import dunex.auth.fdb;
16 
17 /**
18     Password DB
19 */
20 class PasswdDB : UnixFDB!(PasswdEntry, PasswdDB, "/etc/passwd", "/etc/.pwd.lock") {
21 package(dunex.auth):
22     this(string data, bool readonly) { super(data, readonly); }
23 
24 public:
25     /**
26         Find entry with name
27     */
28     ref PasswdEntry find(string name) {
29         foreach(i, entry; entries) {
30             if (entry.username == name) return entries[i];
31         }
32         throw new Exception("could not find %s".format(name));
33     }
34 
35     /**
36         Find entry with uid
37     */
38     ref PasswdEntry find(ushort uid) {
39         foreach(i, entry; entries) {
40             if (entry.userId == uid) return entries[i];
41         }
42         throw new Exception("could not find %s".format(uid));
43     }
44 
45     /**
46         Gets wether a entry with specified name exists
47     */
48     bool has(string name) {
49         foreach(i, entry; entries) {
50             if (entry.username == name) return true;
51         }
52         return false;
53     }
54 }
55 
56 /**
57     Returns a field in the passwd database by name
58 */
59 PasswdEntry getpwnam(string name) {
60     auto db = PasswdDB.openro();
61     return db.find(name);
62 }
63 
64 /**
65     Returns a field in the passwd database by user id
66 */
67 PasswdEntry getpwuid(ushort uid) {
68     auto db = PasswdDB.openro();
69     return db.find(uid);
70 }
71 
72 /**
73     An entry in /etc/passwd
74 */
75 struct PasswdEntry {
76     /**
77         The user's username
78     */
79     string username;
80 
81     /**
82         The user's password
83     */
84     string password;
85 
86     /**
87         The user id
88     */
89     ushort userId;
90 
91     /**
92         The group id
93     */
94     ushort groupId;
95 
96     /**
97         Comment about entry
98     */
99     string comment;
100 
101     /**
102         Path to home directory
103     */
104     string homePath;
105 
106     /**
107         The user's preferred shell
108     */
109     string shell;
110 
111     /**
112         Gets wether the password is stored in /etc/shadow
113     */
114     bool isInShadow() {
115         return password == "x";
116     }
117 
118     /**
119         Converts this entry back to a /etc/passwd formatted entry
120     */
121     string toString() const {
122         return "%s:%s:%s:%s:%s:%s:%s".format(
123             username,
124             password,
125             userId.to!string,
126             groupId.to!string,
127             comment,
128             homePath,
129             shell
130         );
131     }
132 
133     /**
134         Parses an entry in the passwd file
135     */
136     static PasswdEntry parseEntry(string entry) {
137         string[] entities = entry.split(":");
138         if (entities.length != 7)
139             throw new Exception("Malformed passwd entry!");
140         return PasswdEntry(
141             entities[0],            // Username
142             entities[1],            // Password
143             entities[2].to!ushort,  // User ID
144             entities[3].to!ushort,  // Group ID
145             entities[4],            // Comment
146             entities[5],            // Home Path
147             entities[6]             // Shell
148         );
149     }
150 }
151 
152 unittest {
153     import std.stdio : writeln;
154     PasswdDB db = PasswdDB.openro();
155     
156     assert(db.has("root"), "Expected root to be present in file");
157     auto root = db.find("root");
158     assert(root.isInShadow(), "Root was expected to require a shadow file");
159     assert(root.username == "root", "root's name was expected to be root");
160     assert(root.userId == 0, "root's user id was expected to be 0");
161     assert(root.groupId == 0, "root's group id was expected to be 0");
162     assert(root.shell == "/bin/bash", "root's shell was expected to be /bin/bash");
163     destroy(db);
164 
165     // Try alternate ways to get root user
166     root = getpwnam("root");
167     root = getpwuid(0);
168 }