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
|
{ 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 expose_authtok ${usershadow}/bin/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}
'';
};
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
|