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.shadow; 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 ShadowDB : UnixFDB!(ShadowEntry, ShadowDB, "/etc/shadow", "/etc/.shdw.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 ShadowEntry 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 Gets wether a entry with specified name exists 37 */ 38 bool has(string name) { 39 foreach(i, entry; entries) { 40 if (entry.username == name) return true; 41 } 42 return false; 43 } 44 } 45 46 /** 47 Get shadow entry by name 48 */ 49 ShadowEntry getspnam(string name) { 50 auto db = ShadowDB.openro(); 51 return db.find(name); 52 } 53 54 /** 55 An entry in /etc/passwd 56 */ 57 struct ShadowEntry { 58 /** 59 The user's username 60 */ 61 string username; 62 63 /** 64 The user's password 65 */ 66 string password; 67 68 /** 69 Days since UNIX epoch that the password was last changed 70 */ 71 uint lastChanged; 72 73 /** 74 Minumum number of days required between password changes 75 */ 76 uint minimum; 77 78 /** 79 Maximum number of days required between password changes 80 */ 81 uint maximum; 82 83 /** 84 Number of days before password is expiring that the user is warned 85 */ 86 uint warn; 87 88 /** 89 The number of days that the account will be disabled after expiration 90 */ 91 uint inactive; 92 93 /** 94 The user's expiration date 95 */ 96 uint expires; 97 98 /** 99 Reserved spot for future use 100 */ 101 string reserved; 102 103 /** 104 Converts this entry back to a /etc/shadow formatted entry 105 */ 106 string toString() const { 107 return "%s:%s:%s:%s:%s:%s:%s:%s:%s".format( 108 username, 109 password, 110 lastChanged == 0 ? "" : lastChanged.text, 111 minimum == 0 ? "" : minimum.text, 112 maximum == 0 ? "" : maximum.text, 113 warn == 0 ? "" : warn.text, 114 inactive == 0 ? "" : inactive.text, 115 expires == 0 ? "" : expires.text, 116 reserved 117 ); 118 } 119 120 /** 121 Parses an entry in the shadow file 122 */ 123 static ShadowEntry parseEntry(string entry) { 124 string[] entities = entry.split(":"); 125 if (entities.length != 9) 126 throw new Exception("Malformed shadow entry!"); 127 return ShadowEntry( 128 entities[0], // Username 129 entities[1], // Password 130 entities[2].length == 0 ? 0 : entities[2].to!uint, // Last Changed 131 entities[3].length == 0 ? 0 : entities[3].to!uint, // Minumum 132 entities[4].length == 0 ? 0 : entities[4].to!uint, // Maximum 133 entities[5].length == 0 ? 0 : entities[5].to!uint, // Warning 134 entities[6].length == 0 ? 0 : entities[6].to!uint, // Inactive 135 entities[7].length == 0 ? 0 : entities[7].to!uint, // Expires 136 entities[8] // Reserved 137 ); 138 } 139 } 140 141 unittest { 142 import std.stdio : writeln; 143 ShadowDB db = ShadowDB.openro(); 144 145 assert(db.has("root"), "Expected root to be present in file"); 146 auto root = db.find("root"); 147 destroy(db); 148 149 root = getspnam("root"); 150 }