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