1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
{ config, lib, pkgs, ... }@args: with import <stockholm/lib>; let
cfg = config.lass.usershadow;
out = {
options.lass.usershadow = api;
config = lib.mkIf cfg.enable imp;
};
api = {
enable = mkEnableOption "usershadow";
pattern = mkOption {
type = types.str;
default = "/home/%/.shadow";
};
path = mkOption {
type = types.str;
};
};
imp = {
environment.systemPackages = [ usershadow ];
lass.usershadow.path = "${usershadow}";
security.pam.services.sshd.text = ''
account required pam_permit.so
auth required pam_env.so envfile=${config.system.build.pamEnvironment}
auth sufficient pam_exec.so quiet expose_authtok ${usershadow}/bin/verify_pam ${cfg.pattern}
auth sufficient pam_unix.so likeauth try_first_pass
session required pam_env.so envfile=${config.system.build.pamEnvironment}
session required pam_permit.so
session required pam_loginuid.so
'';
security.pam.services.dovecot2 = {
text = ''
auth required pam_exec.so debug expose_authtok log=/tmp/lol /run/wrappers/bin/shadow_verify_pam ${cfg.pattern}
auth required pam_permit.so
account required pam_permit.so
session required pam_permit.so
session required pam_env.so envfile=${config.system.build.pamEnvironment}
'';
};
security.wrappers.shadow_verify_pam = {
source = "${usershadow}/bin/verify_pam";
owner = "root";
};
};
usershadow = let {
deps = [
"pwstore-fast"
"bytestring"
];
body = pkgs.writeHaskellPackage "passwords" {
executables.verify_pam = {
extra-depends = deps;
text = ''
import Data.Monoid
import System.IO
import Data.Char (chr)
import System.Environment (getEnv, getArgs)
import Crypto.PasswordStore (verifyPasswordWith, pbkdf2)
import qualified Data.ByteString.Char8 as BS8
import System.Exit (exitFailure, exitSuccess)
main :: IO ()
main = do
user <- getEnv "PAM_USER"
shadowFilePattern <- head <$> getArgs
let shadowFile = lhs <> user <> tail rhs
(lhs, rhs) = span (/= '%') shadowFilePattern
hash <- readFile shadowFile
password <- takeWhile (/= (chr 0)) <$> hGetLine stdin
let res = verifyPasswordWith pbkdf2 (2^) (BS8.pack password) (BS8.pack hash)
if res then exitSuccess else exitFailure
'';
};
executables.verify_arg = {
extra-depends = deps;
text = ''
import Data.Monoid
import System.Environment (getArgs)
import Crypto.PasswordStore (verifyPasswordWith, pbkdf2)
import qualified Data.ByteString.Char8 as BS8
import System.Exit (exitFailure, exitSuccess)
main :: IO ()
main = do
argsList <- getArgs
let shadowFilePattern = argsList !! 0
let user = argsList !! 1
let password = argsList !! 2
let shadowFile = lhs <> user <> tail rhs
(lhs, rhs) = span (/= '%') shadowFilePattern
hash <- readFile shadowFile
let res = verifyPasswordWith pbkdf2 (2^) (BS8.pack password) (BS8.pack hash)
if res then do (putStr "yes") else exitFailure
'';
};
executables.passwd = {
extra-depends = deps;
text = ''
import System.Environment (getEnv)
import Crypto.PasswordStore (makePasswordWith, pbkdf2)
import qualified Data.ByteString.Char8 as BS8
import System.IO (stdin, stdout, hSetEcho, hFlush, putStr, putStrLn)
import Control.Exception (bracket_)
main :: IO ()
main = do
home <- getEnv "HOME"
mb_password <- bracket_ (hSetEcho stdin False) (hSetEcho stdin True) $ do
putStr "Enter new UNIX password: "
hFlush stdout
password <- BS8.hGetLine stdin
putStrLn ""
putStr "Retype new UNIX password: "
hFlush stdout
password2 <- BS8.hGetLine stdin
return $ if password == password2
then Just password
else Nothing
case mb_password of
Just password -> do
hash <- makePasswordWith pbkdf2 password 10
BS8.writeFile (home ++ "/.shadow") hash
putStrLn "passwd: all authentication tokens updated successfully."
Nothing -> putStrLn "Sorry, passwords do not match"
'';
};
};
};
in out
|