diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,16 @@
+propellor (2.7.2) unstable; urgency=medium
+
+  * Added Propellor.Property.ConfFile, with support for Windows-style .ini
+    files, and generic support for files containing some sort of sections.
+    Thanks, Sean Whitton for completing the implementation.
+  * Added Propellor.Property.LightDM
+    Thanks, Sean Whitton.
+  * Multiple Tor.hiddenService properties can now be defined for a host;
+    previously only one such property worked per host.
+    Thanks, Félix Sipma.
+
+ -- Joey Hess <id@joeyh.name>  Tue, 25 Aug 2015 12:00:25 -0700
+
 propellor (2.7.1) unstable; urgency=medium
 
   * Make sure that make is installed when bootstrapping propellor.
diff --git a/config-joey.hs b/config-joey.hs
--- a/config-joey.hs
+++ b/config-joey.hs
@@ -133,10 +133,10 @@
 
 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer
 		GitAnnexBuilder.standardAutoBuilder
-		(System (Debian Unstable) "amd64") fifteenpast "2h")
+		(System (Debian Testing) "amd64") fifteenpast "2h")
 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer
 		GitAnnexBuilder.standardAutoBuilder
-		(System (Debian Unstable) "i386") fifteenpast "2h")
+		(System (Debian Testing) "i386") fifteenpast "2h")
 	& Systemd.nspawned (GitAnnexBuilder.androidAutoBuilderContainer
 		(Cron.Times "1 1 * * *") "3h")
   where
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,16 @@
+propellor (2.7.2) unstable; urgency=medium
+
+  * Added Propellor.Property.ConfFile, with support for Windows-style .ini
+    files, and generic support for files containing some sort of sections.
+    Thanks, Sean Whitton for completing the implementation.
+  * Added Propellor.Property.LightDM
+    Thanks, Sean Whitton.
+  * Multiple Tor.hiddenService properties can now be defined for a host;
+    previously only one such property worked per host.
+    Thanks, Félix Sipma.
+
+ -- Joey Hess <id@joeyh.name>  Tue, 25 Aug 2015 12:00:25 -0700
+
 propellor (2.7.1) unstable; urgency=medium
 
   * Make sure that make is installed when bootstrapping propellor.
diff --git a/debian/lintian-overrides b/debian/lintian-overrides
new file mode 100644
--- /dev/null
+++ b/debian/lintian-overrides
@@ -0,0 +1,1 @@
+binary-or-shlib-defines-rpath
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 2.7.1
+Version: 2.7.2
 Cabal-Version: >= 1.8
 License: BSD3
 Maintainer: Joey Hess <id@joeyh.name>
@@ -24,6 +24,7 @@
   debian/control
   debian/copyright
   debian/rules
+  debian/lintian-overrides
 Synopsis: property-based host configuration management in haskell
 Description:
  Propellor enures that the system it's run in satisfies a list of
@@ -74,6 +75,7 @@
     Propellor.Property.Cmd
     Propellor.Property.Hostname
     Propellor.Property.Chroot
+    Propellor.Property.ConfFile
     Propellor.Property.Cron
     Propellor.Property.Debootstrap
     Propellor.Property.Dns
@@ -96,6 +98,7 @@
     Propellor.Property.Prosody
     Propellor.Property.Reboot
     Propellor.Property.List
+    Propellor.Property.LightDM
     Propellor.Property.Scheduled
     Propellor.Property.Service
     Propellor.Property.Ssh
diff --git a/src/Propellor/Property/ConfFile.hs b/src/Propellor/Property/ConfFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Propellor/Property/ConfFile.hs
@@ -0,0 +1,104 @@
+module Propellor.Property.ConfFile (
+	-- * Generic conffiles with sections
+	SectionStart,
+	SectionPast,
+	AdjustSection,
+	InsertSection,
+	adjustSection,
+	-- * Windows .ini files
+	IniSection,
+	IniKey,
+	containsIniSetting,
+	lacksIniSection,
+) where
+
+import Propellor
+import Propellor.Property.File
+
+import Data.List (isPrefixOf, foldl')
+
+-- | find the line that is the start of the wanted section (eg, == "<Foo>")
+type SectionStart  = Line -> Bool
+-- | find a line that indicates we are past the section
+-- (eg, a new section header)
+type SectionPast   = Line -> Bool
+-- | run on all lines in the section, including the SectionStart line;
+-- can add, delete, and modify lines, or even delete entire section
+type AdjustSection = [Line] -> [Line] 
+-- | if SectionStart does not find the section in the file, this is used to
+-- insert the section somewhere within it
+type InsertSection = [Line] -> [Line]
+
+-- | Adjusts a section of conffile.
+adjustSection
+	:: Desc
+	-> SectionStart
+	-> SectionPast
+	-> AdjustSection
+	-> InsertSection
+	-> FilePath
+	-> Property NoInfo
+adjustSection desc start past adjust insert f =
+	fileProperty desc go f
+  where
+	go ls = let (pre, wanted, post) = foldl' find ([], [], []) ls
+		in if null wanted
+			then insert ls
+			else pre ++ (adjust wanted) ++ post
+	find (pre, wanted, post) l
+		| null wanted && null post && (not . start) l =
+			(pre ++ [l], wanted, post)
+		| (start l && null wanted && null post)
+		  || ((not . null) wanted && null post && (not . past) l) =
+			  (pre, wanted ++ [l], post)
+		| otherwise = (pre, wanted, post ++ [l])
+
+-- | Name of a section of an .ini file. This value is put
+-- in square braces to generate the section header.
+type IniSection = String
+
+-- | Name of a configuration setting within a .ini file.
+type IniKey = String
+
+iniHeader :: IniSection -> String
+iniHeader header = '[' : header ++ "]"
+
+adjustIniSection
+	:: Desc
+	-> IniSection
+	-> AdjustSection
+	-> InsertSection
+	-> FilePath
+	-> Property NoInfo
+adjustIniSection desc header =
+	adjustSection
+	desc
+	(== iniHeader header)
+	("[" `isPrefixOf`)
+
+-- | Ensures that a .ini file exists and contains a section
+-- with a key=value setting.
+containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property NoInfo
+containsIniSetting f (header, key, value) =
+	adjustIniSection
+	(f ++ " section [" ++ header ++ "] contains " ++ key ++ "=" ++ value)
+	header
+	go
+	(++ [confheader, confline]) -- add missing section at end
+	f
+  where
+	confheader = iniHeader header
+	confline   = key ++ "=" ++ value
+	go []      = [confline]
+	go (l:ls)  = if isKeyVal l then confline : ls else l : (go ls)
+	isKeyVal x = (filter (/= ' ') . takeWhile (/= '=')) x `elem` [key, '#':key]
+
+-- | Ensures that a .ini file does not contain the specified section.
+lacksIniSection :: FilePath -> IniSection -> Property NoInfo
+lacksIniSection f header =
+	adjustIniSection
+	(f ++ " lacks section [" ++ header ++ "]")
+	header
+	(const []) -- remove all lines of section
+	id -- add no lines if section is missing
+	f
diff --git a/src/Propellor/Property/LightDM.hs b/src/Propellor/Property/LightDM.hs
new file mode 100644
--- /dev/null
+++ b/src/Propellor/Property/LightDM.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+module Propellor.Property.LightDM where
+
+import Propellor
+import qualified Propellor.Property.ConfFile as ConfFile
+
+-- | Configures LightDM to skip the login screen and autologin as a user.
+autoLogin :: User -> Property NoInfo
+autoLogin (User u) = "/etc/lightdm/lightdm.conf" `ConfFile.containsIniSetting`
+	("SeatDefaults", "autologin-user", u)
+	`describe` "lightdm autologin"
diff --git a/src/Propellor/Property/Tor.hs b/src/Propellor/Property/Tor.hs
--- a/src/Propellor/Property/Tor.hs
+++ b/src/Propellor/Property/Tor.hs
@@ -4,6 +4,7 @@
 import qualified Propellor.Property.File as File
 import qualified Propellor.Property.Apt as Apt
 import qualified Propellor.Property.Service as Service
+import qualified Propellor.Property.ConfFile as ConfFile
 import Utility.FileMode
 import Utility.DataUnits
 
@@ -112,11 +113,17 @@
 		return r
 
 hiddenService :: HiddenServiceName -> Int -> Property NoInfo
-hiddenService hn port = configured
-	[ ("HiddenServiceDir", varLib </> hn)
-	, ("HiddenServicePort", unwords [show port, "127.0.0.1:" ++ show port])
-	]
-	`describe` unwords ["hidden service available:", hn, show port]
+hiddenService hn port = ConfFile.adjustSection
+	(unwords ["hidden service", hn, "available on port", show port])
+	(== oniondir)
+	(not . isPrefixOf "HiddenServicePort")
+	(const [oniondir, onionport])
+	(++ [oniondir, onionport])
+	mainConfig
+	`onChange` restarted
+  where
+	oniondir = unwords ["HiddenServiceDir", varLib </> hn]
+	onionport = unwords ["HiddenServicePort", show port, "127.0.0.1:" ++ show port]
 
 hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property HasInfo
 hiddenServiceData hn context = combineProperties desc
