summaryrefslogtreecommitdiffstats
path: root/lass/3modules/usershadow.nix
blob: c3d4de84d95d4076a004461c35e4381026224c2a (plain)
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
136
137
138
{ 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 = ''
      auth required pam_exec.so expose_authtok /run/wrappers/bin/shadow_verify_pam ${cfg.pattern}
      auth required pam_permit.so
      account required pam_permit.so
      session required pam_permit.so
    '';

    security.pam.services.dovecot2 = {
      text = ''
        auth required pam_exec.so expose_authtok /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";
    };
    security.wrappers.shadow_verify_arg = {
      source = "${usershadow}/bin/verify_arg";
      owner = "root";
    };
  };

  usershadow = let {
    deps = [
      "pwstore-fast"
      "bytestring"
    ];
    body = pkgs.writeHaskellPackage "passwords" {
      ghc-options = [
        "-rtsopts"
        "-Wall"
      ];
      executables.verify_pam = {
        extra-depends = deps;
        text = ''
          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 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