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 }