Making Your WordPress Site Harder to Break Into

If you are using an infrastructure-as-a-service offering to host your WordPress site, you might find the following suggestions useful. If you have web access to site administration, e.g., via CPanel, there’s not much more you can do beyond:

  • Install WordFence.
  • Pick a very long password for your admin account. Have a look at secondary authentication options [described below].
  • Keep your software up to date.
  • Have a long hard look at all of those plugins you’re using, and try to cut them to the bone.

Here are a few Linux specific things to consider:

  • Install fail2ban.
  • Install the fail2ban WordPress plugin.
  • Change the default port you are running sshd on from 22 to something above 1024. You might want to check the IANA port listing to avoid colliding with other daemons you may be running on your box. This is pretty painless to do. You could do the same for all of your other network services [obviously excluding your web site and mail transfer agent, if you’re using one].
  • Consider running TCP wrappers on your ssh daemon access. Be careful not to lock yourself out if you don’t have a fixed IP address from your service provider. I use BT: a bit of googling suggested a couple of likely address ranges [81. and 86.], but my get out of jail card is a static IP I can use with a VPN service, which I’ve also configured.
  • Consider restricting access to mysqld to localhost. You may have already done this during install. I have to admit I was pretty careless when I was installing the database in terms of notetaking so I just added a TCP wrapper to be on the safe side.
  • Have an extremely long password for your admin account. Mine is basically half a mile long in 8 point text.
  • Have a look at some secondary authentication [or at least Captcha style robot identification] mechanism on the login form. I’ve experimented with Duo in the past. Make sure you understand the implications of changing your mobile phone if you are using one of the app based mechanisms.
  • Consider chrooting your website. I don’t but I may.
  • Have a look at some of the security scanners, like ZAP. Again, be careful not to lock yourself out with fail2ban.

The rest of these are host specific. A good starting point is to run

nmap -n localhost

to confirm what network services you are running. For instance, I found that my Linux distro had an FTP server running out of the box which I didn’t know about. In my case it was as simple as

pgrep ftp

to get the process id, then

ps -ef | grep <process_number>

[substituting appropriately] to find what the process was, and then finally

apt-get remove <package_name>

For any other servers you need to run, there’s a pretty good chance that there is fail2ban config that you can use off the shelf.

Why I felt I needed to pay more attention…

A few months ago, based on the daily activity reports I get from WordFence, I started blocking ranges of addresses using iptables. So if I got repeated brute force attempt on my admin password from say 192.187.111.146 [this is a real example], I’d block the the entire range by doing:

iptables -A INPUT -s 192.187.0.0/16 -j DROP

This was pretty tedious, but I only needed to do it around once a week. For some reason [presumably it’s some sort of off the shelf attack script], the attack counts from specific IPs always seemed to top out at exactly 1460 hits. Last week, I got my daily email which showed someone had hit the site 49k times that day. By the time I got home and did some digging in the logs, it was totalling more than 80k for the week. I suddenly realised that my manual blocking was way too blunt a tool, so last weekend I wrote a log scanning  script launched by cron every 30 minutes. It pattern matches on the login form in the Apache log file and sends me an email if an individual IP tops out at over a certain threshold. The longer term plan was that I’d call another script that would automatically insert the block rule in iptables. There would be a bit of messing around with state management [seek to a point in the log file based on the timestamp to not go over the same ground twice or something] and it would have to be run as root, but it would be better than nothing. I happened to mention this to someone at work who told me about fail2ban, which is doing the same thing [but better than I could have implemented it].

I also had a dig around at ssh logs, and discovered that some kind soul was trying to brute force the root password, and was up to 8k attempts for the week. Subsequent digging showed that the same thing was happening with my Imap server, but on a smaller scale.

While what I’ve suggested here isn’t necessarily bullet proof, it’s a reasonable start. Bear in mind that the types of attacks I’m seeing are commensurate with the value of my site, which in commercial terms is zero. And oh, the irony, when my server gets brought to its knees next week by someone with serious intent :).

Pin Your Pics…

My year and a bit dalliance with Android concluded as of yesterday, when I took delivery of an iPhone 8. I’m going to start developing again [although I’m going to have to divide my attention with C#, which I’m learning at work].

I’ll need to dust down a couple of projects: I have a password manager which I wrote as an experiment to learn about the Touch ID back in the day. I also really need to update my apps on the App Store…

…All of which leads me in a round about way to Pin Your Pics. It’s had very modest downloads since I released it: very low hundreds. That was up until July of this year, when it appears to have been ‘discovered’ in some way. I’ve no idea why or how. Anyway, this is what the downloads look like for this month which, by comparison to before, is pretty interesting:

Downloads for Pin Your Pics

I’m sure there’s every chance that it will sink without trace again.

Regardless, at some point in the next few weeks I’ll have a look at screen resolution – when I wrote it, it was fixed to the 5S screen dimensions, I think. Also, there’s a perennial bug with the controller for launching the camera, which I’ll have another bash at.

Creating an Alexa Custom Skill on a Web Service

The short version: there is no short version. Implementing server side functionality inevitably involves a fair amount of legwork. Also, an advance warning: I’m a novice at the programming language I chose to implement my web service in – Java. For this reason, I’m only going to post code snippets for problems you need to solve. There may be better ways of doing this. Finally, I’m leaving my Skill permanently in development mode: I don’t intend to publish. While it’s something I find useful, this was primarily a learning exercise for me. If you’ve ended up here via Google, hopefully you’ll find something you can re-purpose.

I’ll come to the Amazon side of things in a bit. First up, you will need a server to host your web service. You have a couple of options here [as an alternative to Lambda]: first, you can host on your home network. If you have DHCP, the typical process is to configure your broadband router for DDNS, and have a port forwarding rule on to your server. I decided to take another approach, simply because I wanted to try it out. I’ve paid for a vm with a hosting outfit called OVH. They have a really nice setup, so I’ve spent less than £10 for a server for 3 months. I’ll probably keep it on permanently as I like the flexibility.

I decided to implement my web service as a servlet. There are plenty of tutorials on how to set up the stub methods [and all the various libraries and files structures you will need when you come to export a War file]. I used Eclipse as my IDE. My testing cycle – which I imagine is pretty common – was to run tomcat on my laptop where I was doing the development. I could then build and deploy to that locally, and then test the interface by POSTing data to it with the Chrome plugin Postman. When I had something I wanted to deploy to my vm, I exported the WAR file, scp’ed it to the OVH server’s web apps directory, removed the old War [and expanded directory] and restarted.

A couple of foundational steps on the remote server. The OVH setup is a vanilla install of your operating system of choice – I went with Debian. As well as installing your server software [tomcat] and its requirements [Java], you’ll need to create a user for it. As Amazon requires the server to run on a privileged port, you’ll need to take this into account. As you really won’t want to run the server as root, I googled around for a snippet of C code which I could adapt and compile, which could use setuid to start my server as another user. This is something to take care over if you intend to run your service in anger, and you have no choice: you have got to run your service on port 443.

The next step on the server side, related to that privileged port, is TLS. Because I tried a ‘real’ CA, which I thought was on Amazon’s supported list [Let’s Encrypt], I also went to the trouble of registering a name for my vm’s IP. If you’re going to stay in developer mode, you may not need to do this. When I realised I was getting TLS handshake errors, I changed the config and went with an openssl self-signed certificate.

Turning to the configuration set up on the Amazon side, it’s generally pretty well documented, but there are a couple of exceptions to this. First, in the Skill Information, you have to provide an Invocation Name. I know of one other developer who made the same mistake as me because of the way the debugging pop-up information is worded: I assumed that the invocation string needed to start with ‘ask’, or equivalent. It doesn’t. If you just want your skill to be invoked with ‘widget counter’, you just have those two words, and then kick it off on your Echo with ‘Alexa, ask widget counter’, followed by one of your sample utterances.

This seems incredibly obvious, but there is a warning on the testing page which states, ‘Please complete the Interaction Model tab to start testing this skill’, I went off on a wild goose chase thinking I’d done something else wrong. That warning is arguably one of the misleading features of the whole orchestration interface that Amazon provides. It stays there permanently, no matter what you do.

There are some good examples of the Intent Schema, and other parts of the interaction model already out there but one more can’t hurt. Before I get into it, a quick explanation of what my skill does. My server side code ‘screen scrapes’ a train operator’s web site every 10 minutes. I then parse the rendered HTML looking for specific information about service conditions. That information forms the basis of the response.

Intent Schema:

{
   "intents":[
      {
         "intent":"TrainTimes",
         "slots":[
            {
               "name":"TrainServices",
               "type":"LIST_OF_SERVICES"
            }
         ]
      }
    ]
}

The custom slot looks like:

LIST_OF_SERVICES Peterborough | Luton | Cambridge | Brighton

and then some sample utterances:

TrainTimes how is {TrainServices}
TrainTimes how is the {TrainServices} service
TrainTimes how is the {TrainServices} line

[Brief aside: why ‘train times’, when it’s not timing related? This is a vestige of trying to navigate the mistake I made with the invocation name. I have a strong accent and so was trying all sorts, so at one point it was called ‘big dog’ :)].

The rest of the Amazon interface is pretty straightforward, such as the uploading of self signed certificate [if that’s the approach you choose to take]. One final step before moving away from the interface is the testing. The example question, ‘how is the peterborough service’ gets transformed into the following blob of JSON:

{
  "session": {
    "sessionId": "SessionId.[GUID here]",
    "application": {
      "applicationId": "amzn1.ask.skill.[another GUID here]"
    },
    "attributes": {},
    "user": {
      "userId": "amzn1.ask.account.[long account string here]"
    },
    "new": true
  },
  "request": {
    "type": "IntentRequest",
    "requestId": "EdwRequestId.[one more GUID here]",
    "locale": "en-GB",
    "timestamp": "2017-02-06T13:43:37Z",
    "intent": {
      "name": "TrainTimes",
      "slots": {
        "TrainServices": {
          "name": "TrainServices",
          "value": "Peterborough"
        }
      }
    }
  },
  "version": "1.0"
}

You will obviously need to check that your web service is able to parse this. As per my comments on  my testing cycle, I copied this into Postman as the raw content of a POST.

I’ll briefly go into some more detail about how my implementation works, because when you get into the business end of parsing the input JSON, you need to map it back to some data to respond with.

The parsing of the train company website as a skill isn’t my first bite at the implementation. I started by trying to write a widget for Android. It’s not as simple as just grabbing the HTML, though: the site uses Ajax to render the results. I ended up using HtmlUnit. Because it’s pretty slow, I implemented a class outside the servlet which uses a combination of a ServletContextListener, and then a ScheduledExectuorService. I got the example code from here to work from. The one gotcha is that your web.xml needs to refer to the class.

Next we get on to the hacky part of passing the information on to the servlet. What I did – having the listener write to a flat file which the servlet reads – is a reflection of my novice levels with Java.

So, returning to the list of general problems to solve: having received the POST data, your service then needs to get hold of the data, and then parse the JSON. For the first part, I tried  a few different options and then settled on the getBody implementation I found here.

The parsing of the JSON itself: it’s the first time I’ve ever done this and it feels way more complicated than it should be. I tried a couple of different parsing libraries, but ended up using JSON.simple.

Going back to the JSON data from the Amazon test interface, you have two consecutive objects, the session and the request. For an industrial strength implementation, there are things you need to do with the session data [I’ve also skipped over the TLS client certificate validation, which Amazon stipulates. While perfectly sensible for security purposes, it’s not necessary to get your development implementation up and running.

So the request itself contains a series of embedded objects that you need to unpack, in turn, until I finally get to the slots data. Your implementation will vary, but mine looks like this:

        JSONParser parser = new JSONParser();
        try {
            JSONObject obj = (JSONObject) parser.parse(postData);
            JSONObject jrequest = (JSONObject) obj.get("request");
            log.warning("request: " + jrequest.toString());
            JSONObject intent = (JSONObject) jrequest.get("intent");
            log.warning("intent: " + intent.toString());
            JSONObject slots = (JSONObject) intent.get("slots");
            log.warning("slots: " + slots.toString());
            JSONObject trainSvces = (JSONObject) slots.get("TrainServices");
            log.warning("trainSvces: " + trainSvces.toString());
            String trainSvceVal = (String) trainSvces.get("value");
            log.warning("trainSvceVal: " + trainSvceVal);
            resultString = trainSvceVal;
        } catch (ParseException e) {
            log.warning("ParseException error: ");
            ...
        } 

Again, for a ‘proper’ implementation, you are going to have to test each of the sub-objects. My code is taking a leap of faith :).

Having lost the will to live with the parsing, I have to admit that, having figured out which piece of the train-related data the web service needs to respond with, I then print the JSON straight out in the required format.

 

        response.setContentType("application/json;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("{");
        out.println("  \"response\": {");
        out.println("      \"outputSpeech\": {");
        out.println("         \"type\": \"PlainText\",");
        out.println("         \"text\": \"" + resultsString + "\"");
        out.println("    },");
        out.println("    \"shouldEndSession\": true");
        out.println("    }");
        out.println("}"); 

So, not exactly a thing of beauty, but it works.

If I have any better ideas for future skills to implement, I’ll start with the certificate that Amazon needs. There’s an outfit called StartSSL which it looks like they accept, but which requires email based validation on an address corresponding to the domain. I started looking at setting up PostFix for this – one for another day.

pfSense: Adding a Second LAN

While this is undoubtedly a beginner’s question, it’s one that I spent most of yesterday wrestling with. I also really struggled to find information on it: how to add a second LAN.

There are plenty of ways of achieving what I want on my network – to subdivide it between devices I trust, and ones I don’t [or at least trust less, such as my IP camera]. The new machine I got to run pfSense on has 4 network interfaces, so I decided to run two LANs straight off the adaptors [leaving one spare for a possible future experiment with IoT nonsense].

Adding the interface is well documented, as is the ‘default allow’ you’ll need to set in the firewall rules. What you also need to do is to configure a DHCP server, which is under the Services menu in the WebGUI. You’ll see there is an entry already configured for the first LAN, which you can use to figure out the settings. Obviously, this assumes that you configured the LAN for DHCP during the setup, which almost everyone is going to want to do.

I set an address range of .2 – .254, and then configured both the DNS server and the Gateway on .1. You’ll also have to set the ‘enable DHCP’ checkbox at the top, which is disabled by default.

While it’s obvious in retrospect, I went in completely the wrong direction, thinking it was something to do with routing rules. Routing is all well and good, but I was never going to get very far without an IP address :).

pfSense on a Celeron J1900

I spent the weekend setting up pfSense on a new piece of kit that I got last week. A reseller on Amazon is selling a bare bones box with 4 ethernet ports, a Celeron J1900 processor, 2Gb of RAM and a 64Gb SSD for £170, which I thought would be perfect for the task. It’s probably a little over-spec’ed if anything, but it’s a really lovely piece of kit.

I spent the entirety of the install process navigating various options in the Aptio BIOS interface. I had two issues. The first was pretty trivial, which was setting the boot order via ‘HD BBS Priorities’. The option above, ‘Boot Option’ seemed like the more likely but didn’t have any effect.

The second took me hours to figure out. I could see lots of Google hits for BSD installs and runtime issues, but nothing that fit the problem that I was having. During the install a command called bsdlabel hung, and then returned an error, ‘WRITE_EPDMA_QUEUED’, followed by ‘CAM status: command timeout’.

To cut a [very] long story short, I fixed it by setting SATA CONFIG -> SATA Mode -> IDE Mode.

It took me so long to get the install working I’ve not had a chance to play with pfSense itself yet, other than to prove it’s working. One immediate challenge I’ve yet to figure out is how to access the web interface if it’s ‘north’ of a wireless access point….

IP Camera Data Privacy

I’ve had a few goes at setting up the Motion package on my Raspberry Pi, but I’ve finally abandoned it. In its stead, we recently bought an IP camera manufactured by a company called Annke which, at the time of writing, is among the best selling surveillance cameras on Amazon. It’s a nice piece of kit but given that it only cost £40 and has a lot of moving parts, it’s not one that I expect to survive down the years.

I thought it would be interesting to proxy the traffic on my phone to see what’s happening.

My iPhone isn’t jailbroken, which would have been a showstopper if the server that the app is talking to was using certificate pinning. It’s not. The first call is over plain HTTP. I’m not going to copy it here, because some of the payload is decodes to binary, and there’s a possibility that I might be broadcasting my own password. Doh!

So the first call is a GET to a server running on Amazon’s cloud service, listening on port 7080. I checked the IANA registry: while there is something assigned to that ‘officially’ [some identity management software called empowerid] I think it’s a coincidence, and it’s probably just a web server of some kind running on a non standard port. The response doesn’t report back the server software name. Included in the GET parameters, there are a series of comma and slash [url encoded] separated parameters which are base64 encoded. These decode into binary, and could be anything. Included among the readable parameters is my username.

The response back is a block of JSON, referencing different URLs on the same server, which a geolocation service reliably informs me is in a data centre operated by an outfit called OVH in Roubaix, northern France. The URLs have helpful prefixes: ‘signal’, ‘debug’, ‘ping’, and ‘ntp’ among them. Not all of the URLs are referring to web traffic: there’s one reference to telnet, which is a blast from the past, and another called ‘binnet://’ which is sufficiently non standard that Google keeps insisting on telling me about ‘bonnets’ :). That final ‘binnet’ URL refers back to the original AWS server.

The app then does a second GET to a server, this time in Tampa, Florida. I’m not going to break this one down in any sort of detail because the server refuses the connection, so it can’t be too important!

Next, the app opens a TLS connection to the French server, and does 10 separate GETs. The 3rd of these includes my password, which I registered on first run.

Here’s what I imagine is happening: the camera is going to be polling the server in France cyclically, asking the question, ‘do I need to transmit to you yet?’ When I connect to the same server via the phone app, the answer comes back as a ‘yes’. The camera starts to transmit, and the server then relays the stream back to my app. WireShark should be able to give me some pointers, but I’m running out of time to look at it today. If I find anything interesting or contradictory when I do get round to looking at it, I’ll do a separate post on it.

By the way, the app seems to be using JavaScript to instantiate the video stream in HTML5. Apple have a video from the WWDC in 2013 on exactly this topic.

So in summary, it looks like the video stream of our back garden / the cat / my wife and I occasionally waving at the camera ends up in France, with the server there ‘joining’ the connection from the camera to the app.

The end state with my Raspberry Pi was, well, while not necessary secure in and of its own right, certainly wasn’t nearly as ‘mediated’, shall we say. I set up our broadband router with a port forwarding rule and configuration for a DDNS service. That meant we could connect from our phones to the web server integrated into the Motion package. That was all over vanilla HTTP – hey, at least I set a 401 password!

While I can’t do anything about how the camera operates – a trade-off I’m willing to make based on pure utility – I’m probably going to take a look at partitioning off the network with a proper firewall behind the broadband router [which can be configured to operate as a modem only]. I’ll put all of the ‘less trusted’ devices on their own little segment. Pfsense seems to be the way to go.

I’m sure it’s nothing personal…

Like probably the vast majority of people who are running WordPress for more than a few months, my site is frequently being hit with automated attacks. I’ve only recently noticed this in my logs so I thought it would be interesting to have a closer look.

Around the turn of the year, for reasons I can’t recall, I happened to look at the raw access logs and noticed a lot of references to ‘xmlrpc.php’, which look like this:

142.4.4.190 - - [31/Jan/2016:18:13:42 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

This is a real log file entry, and is a classic example of an XMLRPC bruteforce amplification attack: someone has posted 58k at this page, to try and bruteforce the admin password. I disabled the mechanism – and just verified that it’s working this morning [two months later :)], as the 200 server response is a bit more polite than I would have expected.

At the same time I installed [yet another] plugin, which rate limits failed admin password authentication attempts. It started triggering last week with repeated admin authentication failures from a machine in Hanoi. In my latest access log file [31st January to about half an hour ago], I have 1500 POST attempts which look like this:

123.30.140.199 - - [26/Feb/2016:13:37:47 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 3766 "-" "-"

I’ve not paid much attention to log formats in a long time so I had to google what those final two hyphens are: a blank referer [note to my wife on the spelling :)] and user agent field respectively. The blank user agent is indicative of some sort of automated attack and, by virtue of the fact that the person who’s running it hasn’t even bothered to make it look like a real browser, one that isn’t particularly sophisticated.

The logging pattern suggests what you’d expect: someone has harvested a set of servers that are running WordPress [how? by virtue of having the common pages that WordPress hosts. So a 200 in response to a GET for a ~/wp-login.php page, for instance], and is stepping through them.

This is another indicator of the lack of sophistication:

123.30.140.199 - - [26/Feb/2016:16:41:35 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:37 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:38 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:44 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:46 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:58 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:59 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:05 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:06 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:13 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:14 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:20 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:21 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:22 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:23 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"

What’s happening here is that some software I’m running is blocking the user’s IP address after 10 authentication failures, shown by the 403, which is the server returning a ‘Forbidden’. What I’ve deleted from the log extract above is  that there are a total of 25 Forbidden responses by the server in a row: the attack software isn’t checking the server response codes, which is a waste of resource on their part.

I’ve had a bit of a trawl through my logs and am seeing similar, albeit less determined attacks like this, coming from all sorts of far flung places:

62.109.19.98 - - [13/Feb/2016:07:46:48 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

That’s another XMLRPC bruteforce amplification attack, from Russia. A geolocation site reckons this one…

204.232.224.64 - - [12/Feb/2016:07:12:33 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

…is in San Antonio, Texas. Interesting that the byte sizes being posted through are identical: 58,043. Again, that’s indicative of the same off the shelf attack software running with a pre-canned payload. Let’s do one more of these:

1.83.251.239 - - [11/Feb/2016:02:19:14 +0000] "POST /blog/xmlrpc.php HTTP/1.1" 200 45387 "-" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"

I can honestly say that since I first started messing around on the internet in 1992, I’ve never seen an IP address that starts with 1. The geolocation service dutifully informs me that the machine that sent this parcel of good intention is located in Xi’an in China. At least they’ve spiced things up a bit with a different sized payload.

So here’s a thing: I have a couple of blog posts on this site about a holiday we had in Vietnam. I blogged about a holiday to China that included a trip to Xi’an. I’ve also got a posting about a work trip to Russia. So… Russia and China are massive, populous countries. But Xi’an, in China? That looks like a pattern to me. I wonder if the bundle of joy – malware, whatever it is – that would be deposited on my site if it were to be compromised is tailored or localised in some way or other, based on the occurrences of those locations.

 

As per the title, and the obvious lack of finesse, I know that my server is just one on what’s probably a very long list of candidates that these automated attacks are hitting. WordPress has had something of a chequered history from a security point of view: it’s a natural target. While I’ve done the easy stuff to shore it up – like blocking a blank user agent – the options are relatively limited. That’s fine, given the fairly low-rent nature of the stuff being thrown at it, but I’d really prefer not to be distributing malware to people. Migrating off WordPress looks like it would be a pain so if the ancillary approaches start to look like they’re too much trouble I’ll just delete the site.

Building an Electronic Programme Guide [part 3]

This is my third and final write-up on the development of an electronic programme guide app. As of part 2, the main scrolling view with the programme details, scaled to length, are displayed.

Next up I wanted to have the ability to display programme details. This seemed like it was going to be pretty straightforward: when building the per-programme view, I included a button, which is transparent, and has the same dimensions as the view itself. The first problem is associating the programme details with the button itself. There is plenty of discussion on StackOverflow about how to do this in the least offensive way. I went with a category and then object association. This allowed me to set the GUID as a property for the button.

I rebuilt the app, hit the button and… entered into a fortnight of debugging an EXC_BAD_ACCESS error. I knew what the problem was: the ARC memory management was dereferencing the button object once it was set. I tried lots of different options, such as adding the buttons to an array, set with various properties, and passing the array back to the main view controller. Nothing worked until I did more reading around the property attributes, and ended up redefining the Interface Builder defaults for the scrolling contentView to:

@property (nonatomic, strong) IBOutlet UIView *contentView;

That ‘strong’ means that everything in the view is held in memory. It has to be said that the app is very heavy on memory – as a direct consequence of that view object retention. It routinely occupies 63Mb in my testing.

Next up is the popup that is rendered. So finding the programme itself is pretty easy, using an NSPredicate based on the GUID. What proved a bit harder to deal with is if the main view [the ‘contentView’ for the scrollView] is zoomed. As you have to add the popup view to the zoomed parent, the former is going to inherit the zoom setting. I couldn’t think of an elegant way around this so I worked around it in stages. First off, the popup sits on a blurred view of the current background:

// This is quite neat: make a CGRect of the currently visible part of the scrollview:
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:contentView];
visualEffectView = [[UIVisualEffectView alloc] initWithFrame:visibleRect];
visualEffectView.effect = blurEffect;
visualEffectView.frame = contentView.bounds;
[contentView addSubview:visualEffectView];

Next, I register the scrollView offset in a property:

scrollOffSet = scrollView.contentOffset;

…set the zoomScale to 1, and disable the ability to zoom:

[scrollView setZoomScale:1.0];
scrollView.scrollEnabled = NO;
scrollView.maximumZoomScale = 1.0;
scrollView.minimumZoomScale = 1.0;

Placing the programme details subview is then relative to the currently visible rectangle:

float xForLittleView = visibleRect.origin.x + 30 ;
float yForLittleView = visibleRect.origin.y + 100;

CGRect progViewRect = CGRectMake(xForLittleView, yForLittleView, 350, 500);

I then have to undo the various view settings when the button to dismiss the view is touched:

[visualEffectView removeFromSuperview];
[littleView removeFromSuperview];
scrollView.scrollEnabled = YES;
scrollView.maximumZoomScale = 2.0;
scrollView.minimumZoomScale = 0.8;
[scrollView setZoomScale:zoomScale];
[scrollView setContentOffset:scrollOffSet];

It’s all a bit clunky, but it works. I imagine that this sort of interface plumbing actually happens quite a lot behind the scenes. That said, I may have missed a trick to do it in an easier way.

I’ll call out two more details that I wrestled with. The first is a search facility on the programme title. I wanted the NSPredicate to support as many search terms as the user entered. My initial idea was to split the UITextField input on spaces, and then loop through the resulting array, appending to a stringWithFormat, where all but the first element would be in the form:

AND (title CONTAINS[c][/c] %@)

Having experimented with this, it appears that predicateWithFormat has to have the actual string passed to it, as opposed to a variable containing the string. Which I have to say strikes me as a little odd. The functional upshot of this is that I couldn’t support a variable number of search terms. I support up to three, and construct a separate predicateWithFormat for each possibility.

One final problem that I couldn’t find a fix for was implementing a UITableView’s delegates in a class that I pass the view into as a parameter. I couldn’t find a way of getting the cellForRowAtIndexPath delegate method to be called. The conclusion I came to with this was that it was setting the delegate to self, when ‘self’ was the custom object, rather than the view. It was largely a cosmetic thing [I’ve noticed that for complicated apps, I have a tendency to pile way to much code into the main viewController] so it was easily solved.

Here’s what may be the final version of the app looks like, showing a search result in the popup view, and the to/from dates for the EPG coverage:

Search Results

Search Results

The other buttons that I haven’t talked about explicitly are an ability to switch between days, and initiate a download of EPG data – but which are pretty straightforward. What’s still either ugly or hasn’t been fully implemented is the download progress indicator, and also the what’s-on-now quick look on the Apple Watch, as I want to have a mess around with something completely unrelated to this app: the motion detection capability.

I did add a quick fix to ‘justify’ the right hand side of the ‘table’ of programmes. Formerly they were falling off the contentView. I simply check if the rightmost width of the cell is going to be greater than the width of the contentView. If so, I set it to be the same as the width.

So that’s it. I have a pretty serviceable EPG app, which I use myself over the ad-funded variant I had before, which I guess is a fair indicator of utility. Main lesson learned: not knowing what those property attributes meant tripped me up really badly!