Sunday, March 2, 2014

Hacking Xfinity's Home Security Cameras

I have a series of webcams running, one of which is monitored by Motion, a motion detection system. When Motion sees activity on the camera, it starts recording it to a flash video file with a snapshot, and I have hacked together a dirty webpage that allows navigating the recorded archives. A cron job handles cleanup by deleting everything older than 3 days.

Recently, we got an Xfinity Home Security system installed. I've done some research and determined that the sensors work on the ZigBee protocol (which is pretty common for home security sensors) and a second (locked down) Wifi network. The Wifi network appears to be used to connect the touchscreen (Android based, pretty sure it's a rebranded iControl device) to the wireless cameras (iCamera-1000), and to allow remote access to the touchscreen for settings, monitoring, and alerts sent by the touchscreen.

So my wife wants to put one of the cameras pointing at our front door, and record people who come to the door. The Xfinity system would actually support this, but only in a limited sense and only by combining the camera with a motion sensor -- the system can be configured to record a video clip when the motion sensor triggers. But I don't want to waste a motion sensor (nor am I sure that the IR sensor would function properly outdoors in the cold). Given that I already have a system capable of detecting motion and recording it, I wanted to integrate the new camera(s) into that system.

But Xfinity doesn't want me in that "security router". The don't give you the admin credentials to access the router, nor do they even give you the WPA2 key for the Wifi network. But it turns out that I can add a NIC to my firewall/router running Linux and just plug that into the back of the security router. It happily obtains a DHCP address from the security router, and is able to communicate with the cameras! I needed to run tcpdump and access the cameras from the remote Xfinity app to sniff the HTTP (Basic Authentication) username and password, but was able to access the camera from Motion after that.

Based on the web page that the camera serves up, it's capable of streaming H.264 and MJPEG, but Motion only supports MJPEG, I think. I might experiment with H.264 at some point to see if I can get it working, but for now I'm happy with what I've got.

I'm hoping that I'll be able to eventually set up some sort of monitoring for sensor status and/or alarm status. One of the detractions of the Xfinity system is that the touchscreen is the only device with "speakers" and if it's situated in a remote part of the house (ours is usually upstairs in the master bedroom) it can't be heard throughout the house. Our is barely audible in the living room but impossible to hear from the basement (where I spend most of my time). I'd like to be able to have my PC "chime" when the sensors get tripped so I'll know if one of the kids is opening doors. But so far, I haven't been able to see any network activity between the control box and the world at large...

Tuesday, October 15, 2013

Squirrelly Javascript

Lately, I've been maintaining our (very old) Joomla website, which is running a (very old) version of RSForm!Pro. To be honest, RSForm!Pro seems like a pretty decent product and even the old version we're running has lots of hooks were I've been able to cobble together things. But I'm getting ahead of myself...

Build me a form...

So the powers that be wanted a form added to the website that would send an e-mail when it was submitted. This is exactly the sort of thing that RSForm is designed to do, so it was the obvious choice. If I had known then what I know now, I might have approached this differently, but anyway.

One of the requirements that became apparent was that we only wanted some fields to be visible when other fields were selected. This is reasonably easy to do, and I was able to quickly implement this with the built-in functionality of RSForm. Where it gets tricky is that it's hard to validate the input on the hidden fields, because there's no way to say that a value is required but only if the field is visible.

Custom validation rules

The way I accomplished this was to add to the "Scripts" section of the form a "called on form display" script that set up some custom javascript code:
$jscript = "<script type='text/javascript'>

function customValidation() {
    var ret = true;
    
    // Two radio buttons, at least one must be a non-default value
    if ( document.getElementById('radio0-0').checked &&
         document.getElementById('radio1-0').checked )
    {
        document.getElementById('component359').className='formError';
        ret = false;
    }
    else
    {
        document.getElementById('component359').className='formNoError';
    }

    return ret;
}
</script>";

$formLayout = $jscript . $formLayout;

This leverages RSForm's built in naming (component359 is the error div for the radio button, and the classes are already defined to display or not). Our Javascript code is "prefixed" onto the formLayout variable which already contains the HTML for the form. In this way, we get our Javascript function into the HTML page. Then, all we have to do is to get the submit button to call this code when pressed, by using the "Additional Attributes" that RSForm lets us define.
onclick="javascript:return customValidation();"


You can do -- MAGICK!

Another requirement from management was that the form accept an e-mail address for the submitter, but that it also CC two optional addresses in addition. RSForm, unfortunately, has an issue with this. While it's easy to create the fields, and easy to add them to a "To" or "CC" line, since they're optional, the commas that would normally separate them need to be present (or not) depending on whether the fields have values (or not).

So we added a hidden field (ccs) to the form and added to our "Additional Attributes" on the submit button:
document.getElementById('ccs').value=
 [document.getElementById('cc1').value,
  document.getElementById('cc2').value].filter(function(e)
  {
   return(e!=='');
  }).join(',');

This means that the CCs field is now suitable for use in the CC field of the e-mail message. Nothing to it.

Subject of controversy

I use gmail to read my work mail. I started using it years ago and it's just stuck. Being able to search nearly instantly, as well as having the unique threaded model, has worked out well for me. But it makes it hard to track these form submissions as unique events because they all have the same subject. So I needed a way to add some sort of unique identifier to the submissions. I ended up using a timestamp since it would have a reasonable value and might be useful in other circumstances. I used the same trick of adding a hidden field, having my custom form validation javascript code set the value of the field when "Submit" is pressed, and then including the field value in the Subject.
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) { 
      return typeof args[number] != 'undefined'
        ? args[number]
        : match
      ;
    });
  };
}

if (!String.prototype.pad) {
  String.prototype.pad = function(width, z) {
    z = z || '0';
    return this.length >= width ? this : new Array(width - this.length + 1).join(z) + this;
  };
}

function validateGoFields() {
    var ret = true;

    var now=new Date(new Date().getTime() - 420 * 60000);
    document.getElementById('timestamp').value = 
        new String('{0}{1}{2}-{3}{4}{5}').format(
            now.getUTCFullYear().toString().pad(4),
            now.getUTCMonth().toString().pad(2),
            now.getUTCDate().toString().pad(2),
            now.getUTCHours().toString().pad(2),
            now.getUTCMinutes().toString().pad(2),
            now.getUTCSeconds().toString().pad(2)
        );
I, by the way, am not really that smart. A lot of that was stolen from StackExchange and massaged to work the way I wanted it to.

(It's worth pointing out that the 420 nonsense is not a social commentary, it's -7 hours * 60 min/hr to give our timestamp in [hopefully] local time for the managers in California.)

One day I plan to learn Javascript so I can do this better.

Thursday, January 10, 2013

404 error handler

A long time ago, we reorganized our website in such a way that all the old URLs that had been indexed in search engines and immortalized in e-mails and blog posts were suddenly nonfunctional. I considered using something like mod_rewrite to redirect the old traffic to the new URLs, but this would be problematic for some URLs (like the URLs into our forum).

Now, this isn't rocket science, but I came up with a way to have my cake and eat it too. I set up an ErrorDocument handler called 404.php that was a script that can redirect people based on their incoming URLs, Referrers, etc:
<?php                                                                          
# Useful _SERVER variables:
# REDIRECT_URL - original URL of request
# REQUEST_URI  - original URI of request?

$map = array(
    "/old/url/path" => "http://www.domain.com/new/path/"
);
if ( $map[$_SERVER['REQUEST_URI']] ) # if the request is in the map
{
    header("HTTP/1.0 302 Moved");
    header("Location: ".$map[$_SERVER['REQUEST_URI']]);
    exit();
}
if ( substr($_SERVER['REQUEST_URI'], 0, 9) == "/download" )                    
{
    header("HTTP/1.0 302 Moved");
    header("Location: http://www.domain.com/new/download/path/");
    exit();
}
?>

I checked our access logs for 404 errors a few times a week and added into the map the most common hits.
Well, I thought it was clever...

Wednesday, October 3, 2012

How to avoid hard-coding Id values

So I have seen some tricks for avoiding hard-coding Id values in your Apex code, which I think is a good practice to follow, but they've always been sort of kludgey and inelegant. Today I saw one that I didn't realize you could use, and it deserves a place here:
    Id theId = [SELECT Id FROM <table> WHERE <criteria>].Id;
This is simple and elegant and exactly what I was looking for.

For loops and SOQL queries

One of my favorite loops in Apex is the for(collection) loop. It's very useful for iterating through a collection. It allows you to do something like:
    // Fix Asset records with null Product2Id fields.
    List<Asset> assets = [SELECT Id, Product_Family__c, Product2Id
                          FROM Asset
                          WHERE Product2Id = null
                         ];
    for(Asset asset : assets)
    {
        // fix asset.Product2Id field here
    }
    update assets;
As useful as this is, it turns out that there is a better way. If you use the for([SOQL]) loop instead, Apex will actually run the SOQL query and automatically querymore each time the loop recycles. This means that you don't have to generate a complete collection and use up the heap space to store the entire query result set. This version looks like:
    // Fix Asset records with null Product2Id fields.
    List<Asset> updatedAssets = new List<Asset>();
    for(Asset asset : [SELECT Id, Product_Family__c,Product2Id
                       FROM Asset
                       WHERE Product2Id = null
                      ])
    {
        // fix asset.Product2Id here
        updatedAssets.add(asset);
    }
    update updatedAssets;
I'm not sure how to bulkify the update without maintaining a collection of records, which sort of negates the benefits (in cases like this were all records get updated anyway) of using this form of loop. But in a case where not all records would be updated this form could save considerable heap space. I didn't try it but it appears that there is another way to perform this operation:
    // Fix Asset records with null Product2Id fields.
    for(Asset[] assets : [SELECT Id, Product_Family__c,Product2Id
                          FROM Asset
                          WHERE Product2Id = null
                         ])
    {
        for(Asset asset : assets)
        {
            // fix asset.Product2Id here
        }
        update assets;
    }
This reduces the heap used to store the entire DML update at the expense of the number of DML operations performed. It will update records 200-at-a-time but only 200 record will be put onto the heap at a time. The choice of which form to use is probably situationally dependent, but knowing the options obviously is the biggest part of the battle.

Monday, April 23, 2012

Custom Bulk OpportunityLineItem Edit

So we have a business logic for handling annual maintenance contract renewals with our customers. The idea is that there is a Opportunity RecordType for renewal orders, and custom fields on the OpportunityLineItems (only accessible on the custom Opportunity type) for linking to the license records for the customer. (Custom objects are used to track the maintenance renewals.) When the Opportunity is closed, an Apex trigger creates the necessary custom objects to extend the maintenance expiration periods.

It's relatively easy to add custom formula fields to your "Opportunity Products", and you can add these fields to the "mini" Page Layout so that they appear on the Opportunity. But our reps routinely use the "Edit All" button to get the OpportunityLineItems in a grid layout and edit them all. The problem with this is that on the Edit All page there is no identifying information for each line item so it's a bit problematic and trial and error to update them all.

Professor Google showed me lots of people who felt that it should be very easy to use a VisualForce page with a custom controller to do the job. But it was short on details about how an Opportunity Controller could access the associated Line Item data. Lots of experimentation proved that it was actually very simple, once you realize that your page needs to use the Opportunity controller, not the OpportunityLineItem:
<apex:page standardController="Opportunity" showHeader="true">
  <apex:sectionHeader title="Bulk Edit for {!opportunity.Name}" description="Edit products for this opportunity." />
  <apex:messages />
  <apex:form >
  <apex:pageBlock >
  <apex:pageMessages />
  <apex:pageBlockButtons >
    <apex:commandButton value="Quick Save" action="{!quicksave}" />
    <apex:commandButton value="Save" action="{!save}" />
    <apex:commandButton value="Cancel" action="{!cancel}" />
  </apex:pageBlockButtons>
  
  <apex:pageBlockTable value="{!opportunity.OpportunityLineItems}" var="o">
    <apex:column headerValue="License" value="{!o.License_Master__r.Name}" />
    <apex:column headerValue="Product Code" value="{!o.License_Product_Code__c}" />
    <apex:column headerValue="End User" value="{!o.License_Master__r.End_User_Account__r.Name}" />
    <apex:column headerValue="Seats">
      <apex:inputField value="{!o.Quantity}" />
    </apex:column>
    <apex:column headerValue="List Price" value="{!o.MPU__c}" />
    <apex:column headerValue="Sales Price">
      <apex:inputField value="{!o.UnitPrice}" />
    </apex:column>
    <apex:column headerValue="Maintenance Term">
      <apex:inputField value="{!o.Maintenance_Term__c}" />
    </apex:column>
    <apex:column headerValue="SLA">
      <apex:inputField value="{!o.Maintenance_SLA__c}" />
    </apex:column>
    <apex:column headerValue="Maintenance Fee Override">
      <apex:inputFIeld value="{!o.Maintenance_Fee_Override__c}" />
    </apex:column>
  </apex:pageBlockTable>
  </apex:pageBlock>
  </apex:form>
</apex:page>
Ironically, by using the VisualForce page this way, it becomes very easy to add additional details from the related custom objects (for example, o.License_Master__r.End_User_Account__r.Name) without adding additional custom formula fields to the OpportunityLineItems. I suspect that if you want these fields displayed on the Opportunity detail page, you'd have to resort to formula fields.

While this page works great with respect to displaying the data we want to work with using standard Opportunity controller, clicking on the "Save" button only saves the Opportunity -- edits to the line items' details are lost. This can be corrected with a simple controller extension:
<apex:page standardController="Opportunity" extensions="OpportunityBulkLineItemEdit" showHeader="true">
  <apex:sectionHeader title="Bulk Edit for {!opportunity.Name}" description="Edit products for this opportunity." />
  <apex:messages />
  <apex:form >
  <apex:pageBlock >
  <apex:pageMessages />
  <apex:pageBlockButtons >
    <apex:commandButton value="Quick Save" action="{!quicksaveAll}" />
    <apex:commandButton value="Save" action="{!saveAll}" />
    <apex:commandButton value="Cancel" action="{!cancel}" />
  </apex:pageBlockButtons>
  
  <apex:pageBlockTable value="{!opportunity.OpportunityLineItems}" var="o">
    <apex:column headerValue="License" value="{!o.License_Master__r.Name}" />
    <apex:column headerValue="Product Code" value="{!o.License_Product_Code__c}" />
    <apex:column headerValue="End User" value="{!o.License_Master__r.End_User_Account__r.Name}" />
    <apex:column headerValue="Seats">
      <apex:inputField value="{!o.Quantity}" />
    </apex:column>
    <apex:column headerValue="List Price" value="{!o.MPU__c}" />
    <apex:column headerValue="Sales Price">
      <apex:inputField value="{!o.UnitPrice}" />
    </apex:column>
    <apex:column headerValue="Maintenance Term">
      <apex:inputField value="{!o.Maintenance_Term__c}" />
    </apex:column>
    <apex:column headerValue="SLA">
      <apex:inputField value="{!o.Maintenance_SLA__c}" />
    </apex:column>
    <apex:column headerValue="Maintenance Fee Override">
      <apex:inputFIeld value="{!o.Maintenance_Fee_Override__c}" />
    </apex:column>
  </apex:pageBlockTable>
  </apex:pageBlock>
  </apex:form>
</apex:page>
Along with the new controller extension APEX class:
public with sharing class OpportunityBulkLineItemEdit {
// Controller extension to enable saving line items edited in bulk
        private final Opportunity opp;
        private final ApexPages.StandardController sc;
        
        public OpportunityBulkLineItemEdit(ApexPages.StandardController stdCtrl) {
                this.sc = stdCtrl;
                this.opp = (Opportunity) this.sc.getRecord();
        }
        
        public PageReference saveAll() {
                update this.opp.opportunitylineitems;
                return this.sc.save();
        }
    
    public PageReference quicksaveAll() {
        update this.opp.opportunitylineitems;
        this.sc.save();
        return null;
    }
}
And, of course, it's tempting to not write tests for such a small amount of code, but in the interest of doing it right, I wrote some (that work for my environment, anyway). The trick to the tests was that we need to look up and use the products from our existing pricebooks, which as of API 23.0, are not available to tests, so we need to use the "UseAllData=true" directive:
public class OpportunityBulkLineItemEdit_Test {

    static OpportunityBulkLineItemEdit ext;
    static PageReference pref;
    static Opportunity opp;
    
    private static void init() {
        Account acct = new Account(Name = 'Test account');
        insert acct;
        
        RecordType rt = [SELECT Id FROM RecordType WHERE DeveloperName = 'SO_Maint_Renewal' AND sObjectType='Opportunity'];
        Pricebook2 pb = [SELECT Id From Pricebook2 WHERE IsStandard=true];
        
        Product2 prod = new Product2(
            Description = 'Test maintenance renewal product',
            Family = 'Maintenance',
            IsActive = true,
            Name = 'Maintenance Renewal'
            );
        insert prod;    
        
        PriceBookEntry pbe = new PricebookEntry(
            Product2Id = prod.Id,
            Pricebook2Id = pb.Id,
            UnitPrice = 0.00,
            IsActive = true,
            UseStandardPrice=FALSE
 );
        insert pbe;
        
        opp = new Opportunity(
            AccountId = acct.Id,
            Name = 'auto',
            RecordTypeId = rt.Id,
            CloseDate = date.today(),
            StageName = 'Upside',
            Pricebook2Id = pb.Id,
            Account_Rep__c = UserInfo.getUserId()
        );
        
        insert opp;

        List<license_master__c> licenses = [SELECT Id, Name FROM License_Master__c LIMIT 10];
        List<opportunitylineitem> items = new List<opportunitylineitem>();
        
        for(Integer count = 1; count < 3; count++)
        {
            OpportunityLineItem oli = new OpportunityLineItem(
                OpportunityId = opp.Id,
                PricebookEntryId = pbe.Id,
                Quantity = count * 2,
                UnitPrice = count * 10,
                License_Master__c = licenses.get(count).Id
            );
            items.add(oli);
        }
        insert items;
        
        pref = Page.BulkOLIEdit;
        pref.getParameters().put('id',opp.Id);
        Test.setCurrentPage(pref);
        
        ApexPages.StandardController con = new ApexPages.StandardController(opp);
        ext = new OpportunityBulkLineItemEdit(con);
    }
    
    @isTest (SeeAllData=true)
    static void testSaveAll()
    {
        init();
        
        Test.startTest();
        ext.saveAll();
        Test.stopTest();
    }
    
    @isTest (SeeAllData=true)
    static void testQuicksaveAll()
    {
        init();
        Test.startTest();
        ext.quicksaveAll();
        Test.stopTest();
    }  
}
And there, for posterity, is how I have implemented this functionality. Enjoy!

Thursday, February 16, 2012

Sharing ssh-agents across logins...

This is just cool.

I have this sort of weird setup.  In my day-to-day work, I need to use Windows because some tools are only available for Windows, but also because my company's products are remote connectivity products and typically have Windows clients.  So I have a Windows box with 2 20" monitors and my keyboard and mouse and all that good stuff hooked up there.

But I do most of my work in Linux.  Not only am I typically working on remote machines, either hosted at our corporate headquarters thousands of miles away, or at a datacenter somewhere, I also have my laptop here running Linux.  But to exercise our connectivity products, I usually don't work on the laptop's console.  For those cases where I do need to work on the laptop's console, I use Synergy to remotely "control" the laptop's console.  this way, I can pretend I have a 3-monitor setup, one of which just happens to be Linux.

I've had this setup for years and it's mostly worked pretty good, with one exception.  I'm forever having to start new ssh-agents and ssh-add my private keys to them.  The console is typically semi-decent, depending on how it's configured it generally has an agent autostarted and often it will ask me to unlock the key and I'll be good to go.  But all of my remote sessions start bare.

So I stumbled across a posting on SuperUser dealing with exactly this issue.  The (not top but IMHO best) answer seems to be a few simple lines added to your .bashrc:

check-ssh-agent() {
[ -S "$SSH_AUTH_SOCK" ] && { ssh-add -l >& /dev/null || \
                              [ $? -ne 2 ]; }
}
check-ssh-agent || export SSH_AUTH_SOCK=~/.tmp/ssh-agent.sock
check-ssh-agent || \
        eval "$(ssh-agent -s -a ~/.tmp/ssh-agent.sock)" > /dev/null
This will start a new agent if one doesn't exist, otherwise it will "piggy-back" onto the existing one.  Very slick, IMHO, although it doesn't interact with the keyring stuff being done on the console.  (But again, since most of my work is done via remote sessions, that's fine with me.)

Search This Blog