Category: Technology

Amazon Prime Household and Security (or lack thereof)

There are a lot of odd security lapses in Amazon’s implementation of Prime Household. Anything that uses Alexa creates shared cards that are visible in the Alexa app. There’s some implicit trust between family members, but even married people may not want to share EVERYTHING. “Echo, add this present for my wife to the list” shows up as a card in your wife’s Alexa app.

We share an Amazon Prime Household with Scott’s dad. We both have Alexa-enabled devices (FireTVs and Dots). Our Dots interact with our home automation system through a Phillips bridge emulator. Scott’s dad has some smart devices, but his FireTV could not find any smart devices when it would search. It would, however, turn the ‘outside lights’ on and off. Except the lights didn’t turn on or off in his house.

Our outside lights started turning on and off one night. We have a lot of home automation, but nothing in the logs indicated why this was occurring. I’m not sure if Scott called him or he called Scott, but we were lucky not to spend a day trying to track down some crazy issue. Evidently your Alexa can switch between profiles on the Amazon Prime Household just by saying “switch accounts”. You can ask it “which account is this?”.

The thing I find odd — our HA Bridge is not publicly addressable. Evidently any device on your account can have another device on your account initiate communication. Otherwise there’s no way a device that is connected to our profile but NOT on our network would be able to communicate with the HA Bridge.

Web-Accessible History From OpenHAB MySQL Persistence Database

My husband has wanted a quick/easy way to see the data stored in OpenHAB’s MySQL persistence database, so I put together a very quick (and ugly) PHP page that provides a list of all Items. If you click on an item, you can page through the item’s records. The index.php from the page is available here. You need a web server (I am using Apache on Fedora), PHP (I am using 5.6) and MySQLi (php-mysqlnd package).

This is a bit of paranoia on my part, but even on a page that is ONLY available internally … I don’t like to use an account with read/write access to display data. I create a new user and assign read access:

CREATE USER 'YourUserName'@'localhost' IDENTIFIED BY 'P#ssw0rdH3r3';
GRANT SELECT ON openhabdb.* to 'YourUserName'@'localhost';
FLUSH PRIVILEGES;

Then use *that* user in the php code. This example has a web server running on the database server – and you connect to the MySQL server via localhost. If your web server is located on a different host, you’ll need to create and grant ‘YourUserName’@ the web server hostname.

Custom Password Filter Update (unable to log on after changing password with custom filter in place)

I had written and tested a custom Active Directory password filter – my test included verifying the password actually worked. The automated testing was to select a UID from a pool, select a test category (good password, re-used password, password from dictionary, password that doesn’t meet character requirements, password containing surname, password containing givenName), set the password on the user id. Record the result from the password set, then attempt to use that password and record the result from the bind attempt. Each test category has an expected result, and any operation where the password set or bind didn’t match the expected results were highlighted. I also included a high precision timer to record the time to complete the password set operation (wanted to verify we weren’t adversely impacting the user experience). Published results, documented the installation and configuration of my password filter, and was done.

Until the chap who was installing it in production rang me to say he couldn’t actually log in using the password he set on the account. Which was odd – I set one and then did an LDAP bind and verified the password. But he couldn’t use the same password to log into a workstation in the test domain. Huh?? I actually knew people who wanted *some* users to be able to log in anywhere and others to be restricted to LDAP-only logons (i.e. web portal stuff) and ended up using the userWorkstations attribute to allow logon to DCs only.

We opened a case with Microsoft and it turns out that their Password Filter Programming Considerations didn’t actually mean “Erase all memory used to store passwords by calling the SecureZeroMemory function before freeing memory.” What they meant was “If you have created copies of the password anywhere within your code, make sure you erase memory used to store those copies by calling SecureZeroMemory …”

Which makes SO much more sense … as the comments in the code I used as our base says, why wouldn’t MS handle wiping the memory? Does it not get cleaned well if you don’t have a custom password filter?? Remarked out the call to SecureZeroMemory and you could use the password on NTLM authentications as well as kerberos!

// MS documentation suggests doing this. I honestly don’t know why LSA
// doesn’t just do this for you after we return. But, I’ll do what the
// docs say…
// LJR – 2016-12-15 Per MS, they actually mean to wipe any COPIES you make
// SecureZeroMemory(Password->Buffer, Password->Length);

 

I’ve updated my version of the filter and opened an issue on the source GitHub project … but if anyone else is working a custom password filter, following MS’s published programming considerations, and finds themselves unable to use the password they set … see if you are zapping your copies of the password or the PUNICODE_STRING that comes in.

Active Directory: Custom Password Filtering

At work, we’ve never used the “normal” way of changing Windows passwords. Historically, this is because computers were not members of the domain … so you couldn’t use Ctrl-Alt-Del to change your domain password. Now that computers are members of the domain, changing Active Directory passwords using an external method creates a lot of account lockouts. The Windows workstation is logged in using the old credentials, the password gets changed without it knowing (although you can use ctrl-alt-del, lock the workstation unlock with the new password and update the local workstation creds), and the workstation continues using the old credentials and locks the account.

This is incredibly disruptive to business, and quite a burden on the help desk … so we are going to hook the AD-initiated password changes and feed them into the Identity Management platform. Except … the password policies don’t match. But AD doesn’t know the policy on the other end … so the AD password gets changed and then the new password fails to be committed into the IDM system. And then the user gets locked out of something else because they keep trying to use their new password (and it isn’t like a user knows which directory is the back-end authentication source for a web app to use password n in AD and n-1 in DSEE).

long time ago, back when I knew some military IT folks who were migrating to Windows 2000 and needed to implement Rainbow series compliant passwords in AD – which was possible using a custom password filter. This meant a custom coded DLL that accepted or rejected the proposed password based on custom-coded rules. Never got into the code behind it – I just knew they would grab the DLL & how to register it on the domain controller.

This functionality was exactly what we needed — and Microsoft still has a provision to use a custom password filter. Now all we needed was, well, a custom password filter. The password rules prohibit the use of your user ID, your name, and a small set of words that are globally applied to all users. Microsoft’s passfilt.dll takes care of the first two — although with subtle differences from the IDM system’s rules. So my requirement became a custom password filter that prohibits passwords containing case insensitive substrings from a list of words.

I based my project on OpenPasswordFilter on GitHub — the source code prohibits exact string matches. Close, but not quite 🙂 I modified the program to check the proposed password for case insensitive substrings. I also changed the application binding to localhost from all IP address since there’s no need for the program to be accessed from outside the box. For troubleshooting purposes, I removed the requirement that the binary be run as a service and instead allowed it to be run from a command prompt or as a service.  I’m still adding some more robust error handling, but we’re ready to test! I’ve asked them to baseline changing passwords without the custom filter, using a custom filter that has the banned word list hard coded into the binary, and using a custom filter that sources its banned words list from a text file. Hopefully we’ll find there isn’t a significant increase in the time it takes a user to change their password.

My updated code is available at http://lisa.rushworth.us/OpenPasswordFilter-Edited.zip

 

Change

There are a lot of areas where the trend oscillates between two states. “Cloud computing” is somewhat new and somewhat trendy, but the “computers are expensive, bandwidth is cheap … centralize everything” and “computers are cheap, bandwidth is expensive … distribute everything” states haven’t changed. A VAX with its terminals or a Citrix farm with its thin clients. Better graphics today, but conceptually the same thing. The difference is marketing — I don’t believe they pushed VT100’s for the home market. SaaS has personal targets as well as commercial.

With these changes, there were winners and losers. Mainframes lost market-share as companies deployed desktop PCs. And now desktop PC vendors are losing market-share as “cloud” services become prevalent. Sucks on an individual level — for, say, the people IBM laid off as their mainframe business contracted — but it wasn’t the driving force behind a political movement.

Manufacturing moved overseas. Energy production moved overseas. Some manufacturing (electronics, for instance) are harder to bring back — we simply lack the knowledge and equipment to pick up manufacturing electronics. Beyond that, though, it is hard to compete with someone who can continually undercut you on cost. You can slap import tariffs on everything you see, but the Chinese government can force employees to work for less. And who wants to start paying MORE for the same stuff? That’s the other side of import duties that people fail to talk about — sure we can jack up the price of ‘stuff’ that comes in from overseas so domestically produced items are competitive … but unless you’re getting a serious raise to go with it, that means items become less affordable. Apart from political change, some manufacturing is apt to move back to personal production (i.e. I’ll 3D print any cheap plastic junk we used to ship in from overseas). And that will negatively impact some of the BRIC economies. The move to personal production may benefit American companies — they design the products, license out the print file, and you make it (or use the 3D printer at your public library to print it, or go to the 3D document centre at Staples).

Until about 3AM today, I thought we’d be seeing a similar apolitical shift in energy markets. The renewable energy tax credits got extended in a Congress where even the trivial faced unimaginable opposition. Personal energy production makes electric vehicles a lot more enticing — paying 20$ a week to the petrol station or 20$ a week to the electric company … not much immediate, tangible benefit. But using the electricity that I’m producing v/s paying 20$ a week to the petrol station – that’s a whole different story.

That shift had worrying geo-political implications — the Middle East isn’t stable, but the area is useful and there’s incentive on both sides to maintain some semblance of order. As demand for oil shifts, incentives change. Odd, to me, that someone who wants to levy tariffs to make American products competitive doesn’t agree with leaving tax incentives in place to promote domestically manufactured clean energy solutions. “Clean” coal, sure … but don’t give ’em a tax incentive to buy solar panels manufactured out on the West coast.

 

 

Really Wacky Exchange (ActiveSync) Error

My husband changed his Active Directory password. Routine enough – we’ve got 15k accounts at the office and require a password change every 90 days. That’s 150-200 people changing their password every day. They get themselves locked out a lot (mobile devices, cached workstation credentials, and a host of other unique places people manage to store their creds), but it’s trivial to unlock an individual user.

*Except* — after the account was unlocked, his Windows 10 mail client updated properly and was interacting with the Exchange server. Android, however, still wouldn’t accept his new password. If he typed the wrong thing, it would say invalid password. But whenever he typed the right thing, he got an error indicating the phone and tablet were unable to communicate with the server. Which was bogus — I could see the communication coming across the reverse proxy server. With 200 codes — although you can have a very successful HTTP call deliver an application error message. But it wasn’t like he couldn’t COMMUNICATE with the server. He turned sync off on the phones to avoid getting locked out again, and in the process of troubleshooting ended up deleting all of his accounts hosted on our Exchange 2013 server.

I looked through all of the event logs, Exchange logs … nothing interesting. In desperation, I enabled the individual user ActiveSync logging:

Set-CASMailbox mailNickName -ActiveSyncDebugLogging:$true

Had him attempt to add the mailbox profile again, and dropped the log myself:

Get-ActiveSyncDeviceStatistics -Mailbox mailNickName -GetMailboxLog:$true -NotificationEmailAddress mysmtp@mydomain.ccTLD

Bingo! An exception in the provisioning (Microsoft-Server-ActiveSync?Cmd=Provision) call — I see the phone information come across, the mobile device gets partially added to his account (no OS, phone number, carrier type information … but if you go into OWA and remove the mobile device, an Android device gets added). Error:

Command_WorkerThread_Exception :
— Exception start —
Exception type: System.IO.FileLoadException
Exception message: Could not load file or assembly ‘Microsoft.Exchange.Configuration.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
Exception level: 0
Exception stack trace: at Microsoft.Exchange.AirSync.DeviceInformationSetting.ProcessSet(XmlNode setNode)
at Microsoft.Exchange.AirSync.DeviceInformationSetting.Execute()
at Microsoft.Exchange.AirSync.ProvisionCommand.Microsoft.Exchange.AirSync.IProvisionCommandHost.ProcessDeviceInformationSettings(XmlNode inboundDeviceInformationNode, XmlNode provisionResponseNode)
at Microsoft.Exchange.AirSync.ProvisionCommandPhaseOne.Process(XmlNode provisionResponseNode)
at Microsoft.Exchange.AirSync.ProvisionCommand.ExecuteCommand()
at Microsoft.Exchange.AirSync.Command.WorkerThread()
Inner exception follows…
Exception type: System.IO.FileLoadException
Exception message: The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
Exception level: 1
Exception stack trace:
— Exception end —

Now that is an error I’ve never seen before. As a programmer, I know what it means … you’ve basically got some components that don’t match another. But … huh? He changed his password. Connected to the Exchange server directly (instead of remotely viewing logs & files) and saw Windows Update had dropped files and a reboot was pending. Which … some files replaced, others staged for replacement pending a reboot. *That* is some components not matching others. Rebooted our box, and voila … registration goes through, mailbox sync started.

I don’t know how many people allow auto-updates with a manual reboot on a production enterprise server (we manually patch and reboot during a scheduled maintenance window) where this could happen … but evidently Windows Update can get your Exchange server into a state where already configured clients are able to send and receive mail. But clients are unable to update passwords, and new clients cannot be configured.

Zoneminder Setup

I just installed ZoneMinder tonight. I don’t know if I missed a section in the documentation or something’s just missing — there’s doc for getting the build onto your box (or building your own from source). I didn’t want to install the package and all of its dependencies from their repo, so I manually installed the prereqs from the standard Fedora repositories. Installed the zoneminder rpm and switched from the ‘installation guide’ to the ‘user guide’. Which starts out on a web site. Umm … what web site? There’s definitely something missing here. Their package drops a config file in /etc/httpd/conf.d … *but* it presupposes that it is a dedicated server (which, in fairness, is the recommended configuration).

I edited the zoneminder.conf file and threw the whole thing in a VirtualHost tag, added an SSL cert for the hostname I’m using, and restarted Apache. OK, that’s better … I get *something*. Unfortunately ‘something’ is a massive MySQL error.

Some searching (rpm -ql zoneminder, then search through the files the package installed for something that looks good) yielded a config file at /etc/zm/zm.conf. Went in there, defined a database, user, and password. Created said database & user in mysql & assigned rights. Umm, stranger database error that leads me to believe the database hasn’t been initialized. Oops. LMGTFY. Found a SQL file at /usr/share/zoneminder/db/zm_create.sql and imported *that* … wasn’t sure if it was as simple as changing the zm database name in the SQL file to what I wanted or not, so I just used their default database name. Went back into MySQL and assigned the user I’d created earlier rights to the zm database and dropped *my* database. (Note, if you don’t use PHP date ‘stuff’, you may need to define the time zone in your php.ini file … I’d already set a default for other purposes).

Refresh and voila, I’ve got a system. You’ve got to click ‘start’ at the top for it to, well, start pulling in video feeds. I set up a VERY basic link to our WansView camera — ffmpg type, source is the full videostream.cgi URL, and remote method is “RTP/Unicast”. I can now see the video from our camera via the web site. WooHoo!

Now we need to get the motion detection set up & play with all of the options 🙂

USN Rollback

I had to recover my domain controller from the Hyper-V image backup. There’s some protection build into AD which prevents just randomly reverting a server. When you’ve got a larger domain, the built-in protection after unsupported restoration procedures serves a purpose. Pausing netlogon avoids having users log on against bad data. Disabling replication avoids propagating bad information out to the remainder of the network. The solution is simple – demote the DC, promote it again, and the DC returns to service. But when you have a single domain controller in a single domain in a single forest … well, there’s no other data around. What the recovered DC has is as good as it’s going to get (i.e. a change from 2AM is lost when I revert to my 10PM backup). And taking the entire domain down and building it overkill. You can, instead, basically tell AD to go with it. From the MS documentation:

To restore a previous version of a virtual domain controller VHD without system state data backup

  1. Using the previous VHD, start the virtual domain controller in DSRM, as described in the previous section. Do not allow the domain controller to start in normal mode. If you miss the Windows Boot Manager screen and the domain controller begins to start in normal mode, turn off the virtual machine to prevent it from completing startup. See the previous section for detailed instructions for entering DSRM.
  2. Open Registry Editor. To open Registry Editor, click Start, click Run, type regedit, and then click OK. If the User Account Control dialog box appears, confirm that the action it displays is what you want, and then click Yes. In Registry Editor, expand the following path: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters. Look for a value named DSA Previous Restore Count. If the value is there, make a note of the setting. If the value is not there, the setting is equal to the default, which is zero. Do not add a value if you do not see one there.
  3. Right-click the Parameters key, click New, and then click DWORD (32-bit) Value.
  4. Type the new name Database restored from backup, and then press ENTER.
  5. Double-click the value that you just created to open the Edit DWORD (32-bit) Value dialog box, and then type 1 in the Value data box. The Database restored from backup entry option is available on domain controllers that are running Windows 2000 Server with Service Pack 4 (SP4), Windows Server 2003 with the updates that are included in article 875495 (http://go.microsoft.com/fwlink/?LinkId=137182) in the Microsoft Knowledge Base installed, and Windows Server 2008.
  6. Restart the domain controller in normal mode.
  7. When the domain controller restarts, open Event Viewer. To open Event Viewer, click Start, click Control Panel, double-click Administrative Tools, and then double-click Event Viewer.
  8. Expand Application and Services Logs, and then click the Directory Services log. Ensure that events appear in the details pane.
  9. Right-click the Directory Services log, and then click Find. In Find what, type 1109, and then click Find Next.
  10. You should see at least an Event ID 1109 entry. If you do not see this entry, proceed to the next step. Otherwise, double-click the entry, and then review the text confirming that the update was made to the InvocationID:

 

  • Active Directory has been restored from backup media, or has been configured to host an application partition. 
    The invocationID attribute for this directory server has been changed. 
    The highest update sequence number at the time the backup was created is <time>
    
    InvocationID attribute (old value):<Previous InvocationID value>
    InvocationID attribute (new value):<New InvocationID value>
    Update sequence number:<USN>
    
    The InvocationID is changed when a directory server is restored from backup media or is configured to host a writeable application directory partition.
    
  • Close Event Viewer.
  • Use Registry Editor to verify that the value in DSA Previous Restore Count is equal to the previous value plus one. If this is not the correct value and you cannot find an entry for Event ID 1109 in Event Viewer, verify that the domain controller’s service packs are current. You cannot try this procedure again on the same VHD. You can try again on a copy of the VHD or a different VHD that has not been started in normal mode by starting over at step 1.
  • Close Registry Editor.

 

After following the instructions from Microsoft, I still had a problem — my DC has replication turned off & netlogon comes up paused. In regedit, locate HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters and delete the “Dsa Not Writable” key (value: dword:00000004). In a command prompt, run the following:

 

repadmin /options dchostname.example.com -DISABLE_OUTBOUND_REPL
repadmin /options dchostname.example.com -DISABLE_INBOUND_REPL

Reboot the DC. When it starts, netlogon should be running and replication.

PHP 7.0 and MySQL Libraries

At work, we have some servers running unsupported operating systems. New servers are being built, and applications are being migrated from the old servers to the new. I started with a fairly easy scenario – a PHP web site running on Windows 2008 is moving to 2012. The new web server was handed off to me, and I loaded PHP. With PHP 5.6 active support ending at the end of this year, it made sense to install PHP 7. Copied code, tested site. Umm, massive fail.

Way back in PHP 5.5, the ext/mysql stuff (ext\php_mysql.dll for Windows folks) was deprecated. And if you are like me, you had a lot of old code from back when that was the way to connect to a MySQL database. And as your MySQL was upgraded past 4.1, you had the DBA’s setting old_password on your ID so your code continued to work.  But the old mysql libraries have been removed in PHP 7… and you need to use MySQLi or pdo_mysql to communicate with your database now.

Which one? Depends on what you need – I’ve been using PDO because I don’t need a procedural API (MySQLi provides a procedural API, PDO does not). PDO supports a dozen or so database drivers, MySQLi is just MySQL … so I’ll be able to use the same basic code to connect to MySQL, MS SQL, Oracle, and db2 (plus a handful of others that I don’t anticipate actually using, but who knows what the future holds).

I found a site (http://archive.jnrbsn.com/2010/06/mysqli-vs-pdo-benchmarks) where the individual has benchmarked MySQLi and PDO and doesn’t find much difference on INSERT statements but does see a non-negligible difference on prepared and non-prepared SELECT statements. His post is fairly old, so I ran timed tests on my server using our existing data and found PDO was within a couple of milliseconds. Using either library requires some recoding, but it is fairly straightforward and I was using a script to rebuild my script with the new functions. So I have a nice new server with nice new PHP and nice new MySQL queries using PDO … hit the page to test it and I get a generic error. Add a few lines to my code so I get some sensible errors

     error_reporting(E_ALL);
     ini_set('display_errors',1);

 

Voila – umm, this is gonna be a problem:

Next PDOException: SQLSTATE[HY000][2054] The server requested authentication 
method unknown to the client 
in D:\vhtml\PKIHome\IssuedDeviceCerts\index.php:46

Stack trace:

#0
D:\vhtml\PKIHome\IssuedDeviceCerts\index.php(46):
PDO->__construct('mysql:host=acil...', 'uidsuppressed', 'pwdsuppressed',Array)

The ‘server requested authentication method unknown to the client” means that the new PDO and MySQLi (yes, I’ve tried both) cannot use the password as required for the currently running production code. And the library used in the currently running production code cannot use the password as required for PDO or MySQLi. I cannot just convert the code to the new method, drop it on the new server, cut over, and decommission the old box. There are two approaches that can be used:

**************************************************

#1 Recode against a development MySQL database

#2 Get new IDs using the new storage scheme

**************************************************

#1 If you have a development MySQL database, you can add a hosts file entry (or have your OS support team do so) to point your production database host to the development server. The development server should be refreshed with data from the production databases. The existing IDs that use the old password storage schema can be updated with the new password storage scheme (either you provide the current password or a new password can be set). You will then need to update your PHP code to use either PDO or MySQLi. The implementation CRQ to move to your new server then involves (a) having the DBAs update the production user ID to the new password storage scheme, (b) removing the hosts file from your server.

 

Advantage – You don’t need to change to a new user ID in your code.

Disadvantage – anything that uses this ID needs to be updated simultaneously. When the new password storage schema is used on the account, any client requiring the old password storage scheme will fail. If your ID is used for one specific application on one server, then this isn’t a big deal. If you
use the ID to write data from a batch server or middleware platform, and then read the data from a PHP site … you need to recode both to use a library that understands the new password storage scheme.

**************************************************

#2 The other option is to get a new ID created that uses the new password storage scheme and have the same permissions granted for that ID. You can then recode individual pages as they are moved to the new server, and the old ID can be removed when all of the sites using it have been moved.

 

Advantage –You don’t need to move everything at once.

Disadvantage – you are making more changes to your code and replacing all of your user IDs (if you have a MyODBC driver to link an Access table into the database or if you use the MyPHPAdmin site … you’ll need to remember the new account now).

**************************************************

This isn’t a fatal error that prevents the upgrades from being done, but it sure turned into more of an undertaking than I had originally anticipated! If you should happen to use PHP and MySQL using the old libraries and have a PHP 7 installation planned … it really isn’t just copy some files & update some function calls.

 

Geothermal Pricing

I’ll start out with an acknowledgement that what makes this comparison so shocking is the 30% federal tax credit. A straight comparison of a super high-end HVAC system with a geothermal system will be a completely different scenario if the tax credit expires.

Last year, we got the top of the line air-exchange heat pump. And we’ve had a lot of problems. Installation problems, air leakage problems, thermostat problems … all compounded by terrible difficulties getting service people out to sort the issues. And at the end of the day, the house isn’t comfortable. The “this is the only thermostat you can use” thermostat … well, first off all freezes up every now and again and the system is either ON or OFF at the time and stays there. But from a firmware / logic standpoint – there is nothing that looks at the relative humidity in our house and says “hey, we should drop the temperature set-point a degree or two to avoid living in a swamp”. Which wouldn’t be a problem if we could tie the thing into OpenHAB. But we cannot.

The system came with a 365 day 100% satisfaction guarantee … which, really, was the reason I was OK with installing it last year. Worst case, we ask for it to be removed & the entire contract price gets refunded. Well, we are not satisfied … but that means we’re back to shopping for an HVAC system.

I researched geothermal system manufacturers. Evidently there are only a few actual manufacturers whose product is sold under a lot of different labels. I contacted local installers to get a quote for each of the major systems I found. My expectation was that a geothermal system would be a couple thousand more than we paid for the air exchange heat pump once you take off the 30% tax credit money.

We got our first quote today — for 600$ less than the air exchange heat pump. It’ll be more to do vertical bores instead of horizontal bores, but that’s a decision to spend money for efficiency. I expect vertical bores will take us into the “couple grand more than we paid” territory. But I don’t see the point of high-end air exchange heat pump systems up North here — a two-stage geothermal system is going to be quite a bit more efficient, not engage the backup heating as often, and cost less than the top of the line variable speed air exchange systems.

The HVAC company that installed our current unit had horror stories of people having to rip out entire yards because of leaking tubes … but, thinking about it without needing to make a decision now … I suspect those are older installations. So, yeah, we have the possibility of leaking lines twenty years from now (lines are warrantied for 50 years, but you are still paying labor). And that would totally suck, but do I really expect the air-exchange heat pump sitting outside is going to be in service twenty years from now?