{-# LANGUAGE FlexibleContexts #-}

module Propellor.Property.Postfix where

import Propellor.Base
import qualified Propellor.Property.Apt as Apt
import qualified Propellor.Property.File as File
import qualified Propellor.Property.Service as Service
import qualified Propellor.Property.User as User

import qualified Data.Map as M
import Data.List
import Data.Char

installed :: Property DebianLike
installed :: Property DebianLike
installed = Package -> Property DebianLike
Apt.serviceInstalledRunning Package
"postfix"

restarted :: Property DebianLike
restarted :: Property DebianLike
restarted = Package -> Property DebianLike
Service.restarted Package
"postfix"

reloaded :: Property DebianLike
reloaded :: Property DebianLike
reloaded = Package -> Property DebianLike
Service.reloaded Package
"postfix"

-- | Configures postfix as a satellite system, which 
-- relays all mail through a relay host, which defaults to smtp.domain,
-- but can be changed by @mainCf "relayhost"@.
--
-- The smarthost may refuse to relay mail on to other domains, without
-- further configuration/keys. But this should be enough to get cron job
-- mail flowing to a place where it will be seen.
satellite :: Property DebianLike
satellite :: Property DebianLike
satellite = IO Bool -> Property DebianLike -> Property DebianLike
forall (p :: * -> *) i (m :: * -> *).
(Checkable p i, LiftPropellor m) =>
m Bool -> p i -> Property i
check (Bool -> Bool
not (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Package -> IO Bool
mainCfIsSet Package
"relayhost") Property DebianLike
setup
	Property DebianLike
-> Property DebianLike
-> CombinedType (Property DebianLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` Property DebianLike
installed
  where
	desc :: Package
desc = Package
"postfix satellite system"
	setup :: Property DebianLike
	setup :: Property DebianLike
setup = Package
-> (OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    -> Propellor Result)
-> Property DebianLike
forall k (metatypes :: k).
SingI metatypes =>
Package
-> (OuterMetaTypesWitness metatypes -> Propellor Result)
-> Property (MetaTypes metatypes)
property' Package
desc ((OuterMetaTypesWitness
    '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
  -> Propellor Result)
 -> Property DebianLike)
-> (OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    -> Propellor Result)
-> Property DebianLike
forall a b. (a -> b) -> a -> b
$ \OuterMetaTypesWitness
  '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
w -> do
		Package
hn <- (Host -> Package) -> Propellor Package
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Host -> Package
hostName
		let (Package
_, Package
domain) = (Char -> Bool) -> Package -> (Package, Package)
forall a. (a -> Bool) -> [a] -> ([a], [a])
separate (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') Package
hn
		OuterMetaTypesWitness
  '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
-> Property DebianLike -> Propellor Result
forall (inner :: [MetaType]) (outer :: [MetaType]).
EnsurePropertyAllowed inner outer =>
OuterMetaTypesWitness outer
-> Property (MetaTypes inner) -> Propellor Result
ensureProperty OuterMetaTypesWitness
  '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
w (Property DebianLike -> Propellor Result)
-> Property DebianLike -> Propellor Result
forall a b. (a -> b) -> a -> b
$ Package -> Props DebianLike -> Property DebianLike
forall k (metatypes :: k).
SingI metatypes =>
Package
-> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
combineProperties Package
desc (Props DebianLike -> Property DebianLike)
-> Props DebianLike -> Property DebianLike
forall a b. (a -> b) -> a -> b
$ Props UnixLike
props
			Props UnixLike
-> Property DebianLike
-> Props
     (MetaTypes
        (Combine
           '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
              'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
           '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
forall a p (y :: [a]) (x :: [a]).
(IsProp p, MetaTypes y ~ GetMetaTypes p,
 CheckCombinableNote x y (NoteFor ('Text "&"))) =>
Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
& Package -> [(Package, Package, Package)] -> Property DebianLike
Apt.reConfigure Package
"postfix"
				[ (Package
"postfix/main_mailer_type", Package
"select", Package
"Satellite system")
				, (Package
"postfix/root_address", Package
"string", Package
"root")
				, (Package
"postfix/destinations", Package
"string", Package
"localhost")
				, (Package
"postfix/mailname", Package
"string", Package
hn)
				]
			Props DebianLike
-> Property DebianLike
-> Props
     (MetaTypes
        (Combine
           '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
           '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
forall a p (y :: [a]) (x :: [a]).
(IsProp p, MetaTypes y ~ GetMetaTypes p,
 CheckCombinableNote x y (NoteFor ('Text "&"))) =>
Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
& (Package, Package) -> Property UnixLike
mainCf (Package
"relayhost", Package
"smtp." Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
domain)
				Property UnixLike
-> Property DebianLike
-> CombinedType (Property UnixLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` Property DebianLike
reloaded

-- | Sets up a file by running a property (which the filename is passed
-- to). If the setup property makes a change, postmap will be run on the
-- file, and postfix will be reloaded.
mappedFile
	:: Combines (Property x) (Property UnixLike)
	=> FilePath
	-> (FilePath -> Property x)
	-> CombinedType (Property x) (Property UnixLike)
mappedFile :: Package
-> (Package -> Property x)
-> CombinedType (Property x) (Property UnixLike)
mappedFile Package
f Package -> Property x
setup = Package -> Property x
setup Package
f
	Property x
-> Property UnixLike
-> CombinedType (Property x) (Property UnixLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` (Package -> [Package] -> UncheckedProperty UnixLike
cmdProperty Package
"postmap" [Package
f] UncheckedProperty UnixLike -> Result -> Property UnixLike
forall (p :: * -> *) i.
Checkable p i =>
p i -> Result -> Property i
`assume` Result
MadeChange)

-- | Run newaliases command, which should be done after changing
-- @/etc/aliases@.
newaliases :: Property UnixLike
newaliases :: Property UnixLike
newaliases = IO Bool -> UncheckedProperty UnixLike -> Property UnixLike
forall (p :: * -> *) i (m :: * -> *).
(Checkable p i, LiftPropellor m) =>
m Bool -> p i -> Property i
check (Package
"/etc/aliases" Package -> Package -> IO Bool
`isNewerThan` Package
"/etc/aliases.db")
	(Package -> [Package] -> UncheckedProperty UnixLike
cmdProperty Package
"newaliases" [])

-- | The main config file for postfix.
mainCfFile :: FilePath
mainCfFile :: Package
mainCfFile = Package
"/etc/postfix/main.cf"

-- | Sets a main.cf @name=value@ pair. Does not reload postfix immediately.
mainCf :: (String, String) -> Property UnixLike
mainCf :: (Package, Package) -> Property UnixLike
mainCf (Package
name, Package
value) = IO Bool -> UncheckedProperty UnixLike -> Property UnixLike
forall (p :: * -> *) i (m :: * -> *).
(Checkable p i, LiftPropellor m) =>
m Bool -> p i -> Property i
check IO Bool
notset UncheckedProperty UnixLike
set
	Property UnixLike -> Package -> Property UnixLike
forall p. IsProp p => p -> Package -> p
`describe` (Package
"postfix main.cf " Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
setting)
  where
	setting :: Package
setting = Package
name Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
"=" Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
value
	notset :: IO Bool
notset = (Maybe Package -> Maybe Package -> Bool
forall a. Eq a => a -> a -> Bool
/= Package -> Maybe Package
forall a. a -> Maybe a
Just Package
value) (Maybe Package -> Bool) -> IO (Maybe Package) -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Package -> IO (Maybe Package)
getMainCf Package
name
	set :: UncheckedProperty UnixLike
set = Package -> [Package] -> UncheckedProperty UnixLike
cmdProperty Package
"postconf" [Package
"-e", Package
setting]

-- | Gets a main.cf setting.
getMainCf :: String -> IO (Maybe String)
getMainCf :: Package -> IO (Maybe Package)
getMainCf Package
name = [Package] -> Maybe Package
parse ([Package] -> Maybe Package)
-> (Package -> [Package]) -> Package -> Maybe Package
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Package -> [Package]
lines (Package -> Maybe Package) -> IO Package -> IO (Maybe Package)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Package -> [Package] -> IO Package
readProcess Package
"postconf" [Package
name]
  where
	parse :: [Package] -> Maybe Package
parse (Package
l:[Package]
_) = Package -> Maybe Package
forall a. a -> Maybe a
Just (Package -> Maybe Package) -> Package -> Maybe Package
forall a b. (a -> b) -> a -> b
$ 
		case (Char -> Bool) -> Package -> (Package, Package)
forall a. (a -> Bool) -> [a] -> ([a], [a])
separate (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'=') Package
l of
			(Package
_, (Char
' ':Package
v)) -> Package
v
			(Package
_, Package
v) -> Package
v
	parse [] = Maybe Package
forall a. Maybe a
Nothing

-- | Checks if a main.cf field is set. A field that is set to
-- the empty string is considered not set.
mainCfIsSet :: String -> IO Bool
mainCfIsSet :: Package -> IO Bool
mainCfIsSet Package
name = do
	Maybe Package
v <- Package -> IO (Maybe Package)
getMainCf Package
name
	Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ Maybe Package
v Maybe Package -> Maybe Package -> Bool
forall a. Eq a => a -> a -> Bool
/= Maybe Package
forall a. Maybe a
Nothing Bool -> Bool -> Bool
&& Maybe Package
v Maybe Package -> Maybe Package -> Bool
forall a. Eq a => a -> a -> Bool
/= Package -> Maybe Package
forall a. a -> Maybe a
Just Package
""

-- | Parses main.cf, and removes any initial configuration lines that are
-- overridden to other values later in the file.
--
-- For example, to add some settings, removing any old settings:
--
-- > mainCf `File.containsLines`
-- >	[ "# I like bars."
-- >	, "foo = bar"
-- >	] `onChange` dedupMainCf
--
-- Note that multiline configurations that continue onto the next line
-- are not currently supported.
dedupMainCf :: Property UnixLike
dedupMainCf :: Property UnixLike
dedupMainCf = Package -> ([Package] -> [Package]) -> Package -> Property UnixLike
forall c.
(FileContent c, Eq c) =>
Package -> (c -> c) -> Package -> Property UnixLike
File.fileProperty Package
"postfix main.cf dedupped" [Package] -> [Package]
dedupCf Package
mainCfFile

dedupCf :: [String] -> [String]
dedupCf :: [Package] -> [Package]
dedupCf [Package]
ls =
	let parsed :: [Either Package (Package, Package)]
parsed = (Package -> Either Package (Package, Package))
-> [Package] -> [Either Package (Package, Package)]
forall a b. (a -> b) -> [a] -> [b]
map Package -> Either Package (Package, Package)
parse [Package]
ls
	in [Package]
-> Map Package Integer
-> [Either Package (Package, Package)]
-> [Package]
forall a.
(Ord a, Num a) =>
[Package]
-> Map Package a
-> [Either Package (Package, Package)]
-> [Package]
dedup [] ([(Package, Package)] -> Map Package Integer
forall b. [(Package, b)] -> Map Package Integer
keycounts ([(Package, Package)] -> Map Package Integer)
-> [(Package, Package)] -> Map Package Integer
forall a b. (a -> b) -> a -> b
$ [Either Package (Package, Package)] -> [(Package, Package)]
forall a b. [Either a b] -> [b]
rights [Either Package (Package, Package)]
parsed) [Either Package (Package, Package)]
parsed
  where	
	parse :: Package -> Either Package (Package, Package)
parse Package
l
		| Package
"#" Package -> Package -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Package
l = Package -> Either Package (Package, Package)
forall a b. a -> Either a b
Left Package
l
		| Package
"=" Package -> Package -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` Package
l = 
			let (Package
k, Package
v) = (Char -> Bool) -> Package -> (Package, Package)
forall a. (a -> Bool) -> [a] -> ([a], [a])
separate (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'=') Package
l
			in (Package, Package) -> Either Package (Package, Package)
forall a b. b -> Either a b
Right (((Char -> Bool) -> Package -> Package
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isSpace) Package
k), Package
v)
		| Bool
otherwise = Package -> Either Package (Package, Package)
forall a b. a -> Either a b
Left Package
l
	fmt :: Package -> Package -> Package
fmt Package
k Package
v = Package
k Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
" =" Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
v

	keycounts :: [(Package, b)] -> Map Package Integer
keycounts = (Integer -> Integer -> Integer)
-> [(Package, Integer)] -> Map Package Integer
forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
M.fromListWith Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
(+) ([(Package, Integer)] -> Map Package Integer)
-> ([(Package, b)] -> [(Package, Integer)])
-> [(Package, b)]
-> Map Package Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Package, b) -> (Package, Integer))
-> [(Package, b)] -> [(Package, Integer)]
forall a b. (a -> b) -> [a] -> [b]
map (\(Package
k, b
_v) -> (Package
k, (Integer
1 :: Integer)))

	dedup :: [Package]
-> Map Package a
-> [Either Package (Package, Package)]
-> [Package]
dedup [Package]
c Map Package a
_ [] = [Package] -> [Package]
forall a. [a] -> [a]
reverse [Package]
c
	dedup [Package]
c Map Package a
kc ((Left Package
v):[Either Package (Package, Package)]
rest) = [Package]
-> Map Package a
-> [Either Package (Package, Package)]
-> [Package]
dedup (Package
vPackage -> [Package] -> [Package]
forall a. a -> [a] -> [a]
:[Package]
c) Map Package a
kc [Either Package (Package, Package)]
rest
	dedup [Package]
c Map Package a
kc ((Right (Package
k, Package
v)):[Either Package (Package, Package)]
rest) = case Package -> Map Package a -> Maybe a
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Package
k Map Package a
kc of
		Just a
n | a
n a -> a -> Bool
forall a. Ord a => a -> a -> Bool
> a
1 -> [Package]
-> Map Package a
-> [Either Package (Package, Package)]
-> [Package]
dedup [Package]
c (Package -> a -> Map Package a -> Map Package a
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert Package
k (a
n a -> a -> a
forall a. Num a => a -> a -> a
- a
1) Map Package a
kc) [Either Package (Package, Package)]
rest
		Maybe a
_ -> [Package]
-> Map Package a
-> [Either Package (Package, Package)]
-> [Package]
dedup (Package -> Package -> Package
fmt Package
k Package
vPackage -> [Package] -> [Package]
forall a. a -> [a] -> [a]
:[Package]
c) Map Package a
kc [Either Package (Package, Package)]
rest

-- | The master config file for postfix.
masterCfFile :: FilePath
masterCfFile :: Package
masterCfFile = Package
"/etc/postfix/master.cf"

-- | A service that can be present in the master config file.
data Service = Service
	{ Service -> ServiceType
serviceType :: ServiceType
	, Service -> Package
serviceCommand :: String
	, Service -> ServiceOpts
serviceOpts :: ServiceOpts
	}
	deriving (Int -> Service -> Package -> Package
[Service] -> Package -> Package
Service -> Package
(Int -> Service -> Package -> Package)
-> (Service -> Package)
-> ([Service] -> Package -> Package)
-> Show Service
forall a.
(Int -> a -> Package -> Package)
-> (a -> Package) -> ([a] -> Package -> Package) -> Show a
showList :: [Service] -> Package -> Package
$cshowList :: [Service] -> Package -> Package
show :: Service -> Package
$cshow :: Service -> Package
showsPrec :: Int -> Service -> Package -> Package
$cshowsPrec :: Int -> Service -> Package -> Package
Show, Service -> Service -> Bool
(Service -> Service -> Bool)
-> (Service -> Service -> Bool) -> Eq Service
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Service -> Service -> Bool
$c/= :: Service -> Service -> Bool
== :: Service -> Service -> Bool
$c== :: Service -> Service -> Bool
Eq)

data ServiceType 
	= InetService (Maybe HostName) ServicePort
	| UnixService FilePath PrivateService
	| FifoService FilePath PrivateService
	| PassService FilePath PrivateService
	deriving (Int -> ServiceType -> Package -> Package
[ServiceType] -> Package -> Package
ServiceType -> Package
(Int -> ServiceType -> Package -> Package)
-> (ServiceType -> Package)
-> ([ServiceType] -> Package -> Package)
-> Show ServiceType
forall a.
(Int -> a -> Package -> Package)
-> (a -> Package) -> ([a] -> Package -> Package) -> Show a
showList :: [ServiceType] -> Package -> Package
$cshowList :: [ServiceType] -> Package -> Package
show :: ServiceType -> Package
$cshow :: ServiceType -> Package
showsPrec :: Int -> ServiceType -> Package -> Package
$cshowsPrec :: Int -> ServiceType -> Package -> Package
Show, ServiceType -> ServiceType -> Bool
(ServiceType -> ServiceType -> Bool)
-> (ServiceType -> ServiceType -> Bool) -> Eq ServiceType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ServiceType -> ServiceType -> Bool
$c/= :: ServiceType -> ServiceType -> Bool
== :: ServiceType -> ServiceType -> Bool
$c== :: ServiceType -> ServiceType -> Bool
Eq)

-- Can be a port number or service name such as "smtp".
type ServicePort = String

type PrivateService = Bool

-- | Options for a service.
data ServiceOpts = ServiceOpts
	{ ServiceOpts -> Maybe Bool
serviceUnprivileged :: Maybe Bool
	, ServiceOpts -> Maybe Bool
serviceChroot :: Maybe Bool
	, ServiceOpts -> Maybe Int
serviceWakeupTime :: Maybe Int
	, ServiceOpts -> Maybe Int
serviceProcessLimit :: Maybe Int
	}
	deriving (Int -> ServiceOpts -> Package -> Package
[ServiceOpts] -> Package -> Package
ServiceOpts -> Package
(Int -> ServiceOpts -> Package -> Package)
-> (ServiceOpts -> Package)
-> ([ServiceOpts] -> Package -> Package)
-> Show ServiceOpts
forall a.
(Int -> a -> Package -> Package)
-> (a -> Package) -> ([a] -> Package -> Package) -> Show a
showList :: [ServiceOpts] -> Package -> Package
$cshowList :: [ServiceOpts] -> Package -> Package
show :: ServiceOpts -> Package
$cshow :: ServiceOpts -> Package
showsPrec :: Int -> ServiceOpts -> Package -> Package
$cshowsPrec :: Int -> ServiceOpts -> Package -> Package
Show, ServiceOpts -> ServiceOpts -> Bool
(ServiceOpts -> ServiceOpts -> Bool)
-> (ServiceOpts -> ServiceOpts -> Bool) -> Eq ServiceOpts
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ServiceOpts -> ServiceOpts -> Bool
$c/= :: ServiceOpts -> ServiceOpts -> Bool
== :: ServiceOpts -> ServiceOpts -> Bool
$c== :: ServiceOpts -> ServiceOpts -> Bool
Eq)

defServiceOpts :: ServiceOpts
defServiceOpts :: ServiceOpts
defServiceOpts = ServiceOpts :: Maybe Bool -> Maybe Bool -> Maybe Int -> Maybe Int -> ServiceOpts
ServiceOpts
	{ serviceUnprivileged :: Maybe Bool
serviceUnprivileged = Maybe Bool
forall a. Maybe a
Nothing
	, serviceChroot :: Maybe Bool
serviceChroot = Maybe Bool
forall a. Maybe a
Nothing
	, serviceWakeupTime :: Maybe Int
serviceWakeupTime = Maybe Int
forall a. Maybe a
Nothing
	, serviceProcessLimit :: Maybe Int
serviceProcessLimit = Maybe Int
forall a. Maybe a
Nothing
	}

formatServiceLine :: Service -> File.Line
formatServiceLine :: Service -> Package
formatServiceLine Service
s = [Package] -> Package
unwords ([Package] -> Package) -> [Package] -> Package
forall a b. (a -> b) -> a -> b
$ ((Int, Package) -> Package) -> [(Int, Package)] -> [Package]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Package) -> Package
pad
	[ (Int
10, case Service -> ServiceType
serviceType Service
s of
		InetService (Just Package
h) Package
p -> Package
h Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
":" Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
p
		InetService Maybe Package
Nothing Package
p -> Package
p
		UnixService Package
f Bool
_ -> Package
f
		FifoService Package
f Bool
_ -> Package
f
		PassService Package
f Bool
_ -> Package
f)
	, (Int
6, case Service -> ServiceType
serviceType Service
s of
		InetService Maybe Package
_ Package
_ -> Package
"inet"
		UnixService Package
_ Bool
_ -> Package
"unix"
		FifoService Package
_ Bool
_ -> Package
"fifo"
		PassService Package
_ Bool
_ -> Package
"pass")
	, (Int
8, case Service -> ServiceType
serviceType Service
s of
		InetService Maybe Package
_ Package
_ -> Bool -> Package
bool Bool
False
		UnixService Package
_ Bool
b -> Bool -> Package
bool Bool
b
		FifoService Package
_ Bool
b -> Bool -> Package
bool Bool
b
		PassService Package
_ Bool
b -> Bool -> Package
bool Bool
b)
	, (Int
8, (Bool -> Package) -> (ServiceOpts -> Maybe Bool) -> Package
forall a. (a -> Package) -> (ServiceOpts -> Maybe a) -> Package
v Bool -> Package
bool ServiceOpts -> Maybe Bool
serviceUnprivileged)
	, (Int
8, (Bool -> Package) -> (ServiceOpts -> Maybe Bool) -> Package
forall a. (a -> Package) -> (ServiceOpts -> Maybe a) -> Package
v Bool -> Package
bool ServiceOpts -> Maybe Bool
serviceChroot)
	, (Int
8, (Int -> Package) -> (ServiceOpts -> Maybe Int) -> Package
forall a. (a -> Package) -> (ServiceOpts -> Maybe a) -> Package
v Int -> Package
forall a. Show a => a -> Package
show ServiceOpts -> Maybe Int
serviceWakeupTime)
	, (Int
8, (Int -> Package) -> (ServiceOpts -> Maybe Int) -> Package
forall a. (a -> Package) -> (ServiceOpts -> Maybe a) -> Package
v Int -> Package
forall a. Show a => a -> Package
show ServiceOpts -> Maybe Int
serviceProcessLimit)
	, (Int
0, Service -> Package
serviceCommand Service
s)
	]
  where
	v :: (a -> Package) -> (ServiceOpts -> Maybe a) -> Package
v a -> Package
f ServiceOpts -> Maybe a
sel = Package -> (a -> Package) -> Maybe a -> Package
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Package
"-" a -> Package
f (ServiceOpts -> Maybe a
sel (Service -> ServiceOpts
serviceOpts Service
s))
	bool :: Bool -> Package
bool Bool
True = Package
"y"
	bool Bool
False = Package
"n"
	pad :: (Int, Package) -> Package
pad (Int
n, Package
t) = Package
t Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Int -> Char -> Package
forall a. Int -> a -> [a]
replicate (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Package -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length Package
t) Char
' '

-- | Note that this does not handle multi-line service entries,
-- in which subsequent lines are indented. `serviceLine` does not generate
-- such entries.
parseServiceLine :: File.Line -> Maybe Service
parseServiceLine :: Package -> Maybe Service
parseServiceLine (Char
'#':Package
_) = Maybe Service
forall a. Maybe a
Nothing
parseServiceLine (Char
' ':Package
_) = Maybe Service
forall a. Maybe a
Nothing -- continuation of multiline entry
parseServiceLine Package
l = ServiceType -> Package -> ServiceOpts -> Service
Service
	(ServiceType -> Package -> ServiceOpts -> Service)
-> Maybe ServiceType -> Maybe (Package -> ServiceOpts -> Service)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ServiceType
parsetype
	Maybe (Package -> ServiceOpts -> Service)
-> Maybe Package -> Maybe (ServiceOpts -> Service)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe Package
parsecommand
	Maybe (ServiceOpts -> Service)
-> Maybe ServiceOpts -> Maybe Service
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe ServiceOpts
parseopts
  where
	parsetype :: Maybe ServiceType
parsetype = do
		Package
t <- Int -> Maybe Package
getword Int
2
		case Package
t of
			Package
"inet" -> do
				Package
v <- Int -> Maybe Package
getword Int
1
				let (Package
h,Package
p) = (Char -> Bool) -> Package -> (Package, Package)
forall a. (a -> Bool) -> [a] -> ([a], [a])
separate (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
':') Package
v
				if Package -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null Package
p
					then Maybe ServiceType
forall a. Maybe a
Nothing
					else ServiceType -> Maybe ServiceType
forall a. a -> Maybe a
Just (ServiceType -> Maybe ServiceType)
-> ServiceType -> Maybe ServiceType
forall a b. (a -> b) -> a -> b
$ Maybe Package -> Package -> ServiceType
InetService
						(if Package -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null Package
h then Maybe Package
forall a. Maybe a
Nothing else Package -> Maybe Package
forall a. a -> Maybe a
Just Package
h) Package
p
			Package
"unix" -> Package -> Bool -> ServiceType
UnixService (Package -> Bool -> ServiceType)
-> Maybe Package -> Maybe (Bool -> ServiceType)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> Maybe Package
getword Int
1 Maybe (Bool -> ServiceType) -> Maybe Bool -> Maybe ServiceType
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe Bool
parseprivate
			Package
"fifo" -> Package -> Bool -> ServiceType
FifoService (Package -> Bool -> ServiceType)
-> Maybe Package -> Maybe (Bool -> ServiceType)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> Maybe Package
getword Int
1 Maybe (Bool -> ServiceType) -> Maybe Bool -> Maybe ServiceType
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe Bool
parseprivate
			Package
"pass" -> Package -> Bool -> ServiceType
PassService (Package -> Bool -> ServiceType)
-> Maybe Package -> Maybe (Bool -> ServiceType)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> Maybe Package
getword Int
1 Maybe (Bool -> ServiceType) -> Maybe Bool -> Maybe ServiceType
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe Bool
parseprivate
			Package
_ -> Maybe ServiceType
forall a. Maybe a
Nothing
	parseprivate :: Maybe Bool
parseprivate = Maybe (Maybe Bool) -> Maybe Bool
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (Maybe (Maybe Bool) -> Maybe Bool)
-> (Package -> Maybe (Maybe Bool)) -> Package -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Package -> Maybe (Maybe Bool)
bool (Package -> Maybe Bool) -> Maybe Package -> Maybe Bool
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Int -> Maybe Package
getword Int
3
	
	parsecommand :: Maybe Package
parsecommand = case [Package] -> Package
unwords (Int -> [Package] -> [Package]
forall a. Int -> [a] -> [a]
drop Int
7 [Package]
ws) of
		Package
"" -> Maybe Package
forall a. Maybe a
Nothing
		Package
s -> Package -> Maybe Package
forall a. a -> Maybe a
Just Package
s

	parseopts :: Maybe ServiceOpts
parseopts = Maybe Bool -> Maybe Bool -> Maybe Int -> Maybe Int -> ServiceOpts
ServiceOpts
		(Maybe Bool -> Maybe Bool -> Maybe Int -> Maybe Int -> ServiceOpts)
-> Maybe (Maybe Bool)
-> Maybe (Maybe Bool -> Maybe Int -> Maybe Int -> ServiceOpts)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Package -> Maybe (Maybe Bool)
bool (Package -> Maybe (Maybe Bool))
-> Maybe Package -> Maybe (Maybe Bool)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Int -> Maybe Package
getword Int
4)
		Maybe (Maybe Bool -> Maybe Int -> Maybe Int -> ServiceOpts)
-> Maybe (Maybe Bool)
-> Maybe (Maybe Int -> Maybe Int -> ServiceOpts)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Package -> Maybe (Maybe Bool)
bool (Package -> Maybe (Maybe Bool))
-> Maybe Package -> Maybe (Maybe Bool)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Int -> Maybe Package
getword Int
5)
		Maybe (Maybe Int -> Maybe Int -> ServiceOpts)
-> Maybe (Maybe Int) -> Maybe (Maybe Int -> ServiceOpts)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Package -> Maybe (Maybe Int)
forall a. Read a => Package -> Maybe (Maybe a)
int (Package -> Maybe (Maybe Int))
-> Maybe Package -> Maybe (Maybe Int)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Int -> Maybe Package
getword Int
6)
		Maybe (Maybe Int -> ServiceOpts)
-> Maybe (Maybe Int) -> Maybe ServiceOpts
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Package -> Maybe (Maybe Int)
forall a. Read a => Package -> Maybe (Maybe a)
int (Package -> Maybe (Maybe Int))
-> Maybe Package -> Maybe (Maybe Int)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Int -> Maybe Package
getword Int
7)

	bool :: Package -> Maybe (Maybe Bool)
bool Package
"-" = Maybe Bool -> Maybe (Maybe Bool)
forall a. a -> Maybe a
Just Maybe Bool
forall a. Maybe a
Nothing
	bool Package
"y" = Maybe Bool -> Maybe (Maybe Bool)
forall a. a -> Maybe a
Just (Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True)
	bool Package
"n" = Maybe Bool -> Maybe (Maybe Bool)
forall a. a -> Maybe a
Just (Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False)
	bool Package
_ = Maybe (Maybe Bool)
forall a. Maybe a
Nothing

	int :: Package -> Maybe (Maybe a)
int Package
"-" = Maybe a -> Maybe (Maybe a)
forall a. a -> Maybe a
Just Maybe a
forall a. Maybe a
Nothing
	int Package
n = Maybe (Maybe a)
-> (a -> Maybe (Maybe a)) -> Maybe a -> Maybe (Maybe a)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Maybe (Maybe a)
forall a. Maybe a
Nothing (Maybe a -> Maybe (Maybe a)
forall a. a -> Maybe a
Just (Maybe a -> Maybe (Maybe a))
-> (a -> Maybe a) -> a -> Maybe (Maybe a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Maybe a
forall a. a -> Maybe a
Just) (Package -> Maybe a
forall a. Read a => Package -> Maybe a
readish Package
n)

	getword :: Int -> Maybe Package
getword Int
n
		| Int
nws Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
n = Package -> Maybe Package
forall a. a -> Maybe a
Just ([Package]
ws [Package] -> Int -> Package
forall a. [a] -> Int -> a
!! (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1))
		| Bool
otherwise = Maybe Package
forall a. Maybe a
Nothing
	ws :: [Package]
ws = Package -> [Package]
words Package
l
	nws :: Int
nws = [Package] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Package]
ws

-- | Enables a `Service` in postfix's `masterCfFile`.
service :: Service -> RevertableProperty DebianLike DebianLike
service :: Service -> RevertableProperty DebianLike DebianLike
service Service
s = (Property DebianLike
enable Property DebianLike
-> Property DebianLike -> RevertableProperty DebianLike DebianLike
forall setupmetatypes undometatypes.
Property setupmetatypes
-> Property undometatypes
-> RevertableProperty setupmetatypes undometatypes
<!> Property DebianLike
disable)
	RevertableProperty DebianLike DebianLike
-> Package -> RevertableProperty DebianLike DebianLike
forall p. IsProp p => p -> Package -> p
`describe` Package
desc
  where
	desc :: Package
desc = Package
"enabled postfix service " Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ ServiceType -> Package
forall a. Show a => a -> Package
show (Service -> ServiceType
serviceType Service
s)
	enable :: CombinedType (Property UnixLike) (Property DebianLike)
enable = Package
masterCfFile Package -> Package -> Property UnixLike
`File.containsLine` (Service -> Package
formatServiceLine Service
s)
		Property UnixLike
-> Property DebianLike
-> CombinedType (Property UnixLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` Property DebianLike
reloaded
	disable :: CombinedType (Property UnixLike) (Property DebianLike)
disable = Package -> ([Package] -> [Package]) -> Package -> Property UnixLike
forall c.
(FileContent c, Eq c) =>
Package -> (c -> c) -> Package -> Property UnixLike
File.fileProperty Package
desc ((Package -> Bool) -> [Package] -> [Package]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Package -> Bool) -> Package -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Package -> Bool
matches)) Package
masterCfFile
		Property UnixLike
-> Property DebianLike
-> CombinedType (Property UnixLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` Property DebianLike
reloaded
	matches :: Package -> Bool
matches Package
l = case Package -> Maybe Service
parseServiceLine Package
l of
		Just Service
s' | Service
s' Service -> Service -> Bool
forall a. Eq a => a -> a -> Bool
== Service
s -> Bool
True
		Maybe Service
_ -> Bool
False

-- | Installs saslauthd and configures it for postfix, authenticating
-- against PAM.
--
-- Does not configure postfix to use it; eg @smtpd_sasl_auth_enable = yes@
-- needs to be set to enable use. See
-- <https://wiki.debian.org/PostfixAndSASL>.
--
-- Password brute force attacks are possible when SASL auth is enabled.
-- It would be wise to enable fail2ban, for example:
--
-- > Fail2Ban.jailEnabled "postfix-sasl"
saslAuthdInstalled :: Property DebianLike
saslAuthdInstalled :: Property DebianLike
saslAuthdInstalled = Property DebianLike
setupdaemon
	Property DebianLike
-> Property DebianLike
-> CombinedType (Property DebianLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` Package -> Property DebianLike
Service.running Package
"saslauthd"
	Property DebianLike
-> Property DebianLike
-> CombinedType (Property DebianLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` Property DebianLike
postfixgroup
	Property DebianLike
-> Property UnixLike
-> CombinedType (Property DebianLike) (Property UnixLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` Property UnixLike
dirperm
	Property DebianLike
-> Property DebianLike
-> CombinedType (Property DebianLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` [Package] -> Property DebianLike
Apt.installed [Package
"sasl2-bin"]
	Property DebianLike
-> Property UnixLike
-> CombinedType (Property DebianLike) (Property UnixLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`requires` Property UnixLike
smtpdconf
  where
	setupdaemon :: CombinedType (Property UnixLike) (Property DebianLike)
setupdaemon = Package
"/etc/default/saslauthd" Package -> [Package] -> Property UnixLike
`File.containsLines`
		[ Package
"START=yes" 
		, Package
"OPTIONS=\"-c -m " Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
dir Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
"\""
		]
		Property UnixLike
-> Property DebianLike
-> CombinedType (Property UnixLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` Package -> Property DebianLike
Service.restarted Package
"saslauthd"
	smtpdconf :: Property UnixLike
smtpdconf = Package
"/etc/postfix/sasl/smtpd.conf" Package -> [Package] -> Property UnixLike
`File.containsLines`
		[ Package
"pwcheck_method: saslauthd"
		, Package
"mech_list: PLAIN LOGIN"
		]
	dirperm :: Property UnixLike
dirperm = IO Bool -> UncheckedProperty UnixLike -> Property UnixLike
forall (p :: * -> *) i (m :: * -> *).
(Checkable p i, LiftPropellor m) =>
m Bool -> p i -> Property i
check (Bool -> Bool
not (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Package -> IO Bool
doesDirectoryExist Package
dir) (UncheckedProperty UnixLike -> Property UnixLike)
-> UncheckedProperty UnixLike -> Property UnixLike
forall a b. (a -> b) -> a -> b
$ 
		Package -> [Package] -> UncheckedProperty UnixLike
cmdProperty Package
"dpkg-statoverride"
			[ Package
"--add", Package
"root", Package
"sasl", Package
"710", Package
dir ]
	postfixgroup :: CombinedType (Property DebianLike) (Property DebianLike)
postfixgroup = (Package -> User
User Package
"postfix") User -> Group -> Property DebianLike
`User.hasGroup` (Package -> Group
Group Package
"sasl")
		Property DebianLike
-> Property DebianLike
-> CombinedType (Property DebianLike) (Property DebianLike)
forall x y. Combines x y => x -> y -> CombinedType x y
`onChange` Property DebianLike
restarted
	dir :: Package
dir = Package
"/var/spool/postfix/var/run/saslauthd"

-- | Uses `saslpasswd2` to set the password for a user in the sasldb2 file.
--
-- The password is taken from the privdata.
saslPasswdSet :: Domain -> User -> Property (HasInfo + UnixLike)
saslPasswdSet :: Package -> User -> Property (HasInfo + UnixLike)
saslPasswdSet Package
domain (User Package
user) = Property
  (MetaTypes
     '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
        'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
go Property
  (MetaTypes
     '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
        'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
-> Package
-> Property
     (MetaTypes
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
forall (p :: * -> *) i.
Checkable p i =>
p i -> Package -> Property i
`changesFileContent` Package
"/etc/sasldb2"
  where
	go :: Property
  (MetaTypes
     '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
        'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
go = PrivDataSource
-> Context
-> (((PrivData -> Propellor Result) -> Propellor Result)
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
-> Property
     (MetaTypes
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
forall c s metatypes.
(IsContext c, IsPrivDataSource s,
 IncludesInfo metatypes ~ 'True) =>
s
-> c
-> (((PrivData -> Propellor Result) -> Propellor Result)
    -> Property metatypes)
-> Property metatypes
withPrivData PrivDataSource
src Context
ctx ((((PrivData -> Propellor Result) -> Propellor Result)
  -> Property
       (MetaTypes
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
 -> Property
      (MetaTypes
         '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
            'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
-> (((PrivData -> Propellor Result) -> Propellor Result)
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
-> Property
     (MetaTypes
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
forall a b. (a -> b) -> a -> b
$ \(PrivData -> Propellor Result) -> Propellor Result
getpw ->
		Package
-> Propellor Result
-> Property
     (MetaTypes
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
forall k (metatypes :: k).
SingI metatypes =>
Package -> Propellor Result -> Property (MetaTypes metatypes)
property Package
desc (Propellor Result
 -> Property
      (MetaTypes
         '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
            'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
-> Propellor Result
-> Property
     (MetaTypes
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
forall a b. (a -> b) -> a -> b
$ (PrivData -> Propellor Result) -> Propellor Result
getpw ((PrivData -> Propellor Result) -> Propellor Result)
-> (PrivData -> Propellor Result) -> Propellor Result
forall a b. (a -> b) -> a -> b
$ \PrivData
pw -> IO Result -> Propellor Result
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Result -> Propellor Result) -> IO Result -> Propellor Result
forall a b. (a -> b) -> a -> b
$
			StdHandle
-> CreateProcessRunner
-> CreateProcess
-> (Handle -> IO Result)
-> IO Result
forall a.
StdHandle
-> CreateProcessRunner -> CreateProcess -> (Handle -> IO a) -> IO a
withHandle StdHandle
StdinHandle CreateProcessRunner
createProcessSuccess CreateProcess
p ((Handle -> IO Result) -> IO Result)
-> (Handle -> IO Result) -> IO Result
forall a b. (a -> b) -> a -> b
$ \Handle
h -> do
				Handle -> Package -> IO ()
hPutStrLn Handle
h (PrivData -> Package
privDataVal PrivData
pw)
				Handle -> IO ()
hClose Handle
h
				Result -> IO Result
forall (m :: * -> *) a. Monad m => a -> m a
return Result
NoChange
	desc :: Package
desc = Package
"sasl password for " Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
uatd
	uatd :: Package
uatd = Package
user Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
"@" Package -> Package -> Package
forall a. [a] -> [a] -> [a]
++ Package
domain
	ps :: [Package]
ps = [Package
"-p", Package
"-c", Package
"-u", Package
domain, Package
user]
	p :: CreateProcess
p = Package -> [Package] -> CreateProcess
proc Package
"saslpasswd2" [Package]
ps
	ctx :: Context
ctx = Package -> Context
Context Package
"sasl"
	src :: PrivDataSource
src = PrivDataField -> Package -> PrivDataSource
PrivDataSource (Package -> PrivDataField
Password Package
uatd) Package
"enter password"