Tuesday, 27 May 2014

Elevation of privileges but not as you know it

One of the more serious vulnerabilities in web applications is when someone gains privileges or permissions that they should not have. The most common cause is when someone accidentally or deliberately manages to turn their "regular" account into an administrator account. Because this is the most common scenario, I think we end up thinking the two things are the same, that elevation of privileges = swap regular account for admin account. I learned today that this isn't true.

We saw a very strange problem on an internally used system (but which is hosted on a public web site) where a new employee who should not have had access to it was able to login and be given someone else's account - the worst of both worlds! It would have been bad for an employee to be given another employees account but for an un-registered user (who to all intents and purposes is a stranger) to be given an employee's account is very bad.

It seemed weird and I saw various internet posts about people with a similar problem caused by certain things related to ASP.Net forms authentication. I tried decrypting cookies etc. to try and find out what was happening (were they really logged in as someone else or was it just a display problem) but this failed.

Ultimately, I was able to run the site in the debugger and was surprised to see that it was not the site itself at fault but the OAuth2 sign-in process which was returning the wrong email address and therefore making the site login as the incorrect user. In fact, I maintain the OAuth2 system so somehow it was my fault.

I had to access the database and step through the process to find out what was wrong and eventually tracked it down to a stored procedure that had a slight flaw and caused a potential timing attack. This affected all site we were securing but would only occur if multiple logins occurred within a short period - before the OAuth2 tokens expired.

The problem was that I was updating a table and changing the access code to an access token and then selecting details from the affected row (in theory) but since I had already updated the code column and set it to null, it couldn't be used to select the affected row and using the remainder of the where clause could return multiple entries - and as it happened, the web service would choose the first of these, which would always be the wrong one.

So I had managed to create an elevation of privileges vulnerability by a simple oversight in the design of the system. The moral? Test, test and test some more. Also, have a system which is designed to update or fix quickly so these bugs won't lie around for weeks or months until another "update" can be made.

Wednesday, 21 May 2014

domain.com being added to my DNS queries from Windows 7

While waiting for a DNS record to update, I was using nslookup to see the TTL information and noticed something funny. While looking up my.pixelpin.co.uk, I saw that one of the queries is to my.pixelpin.co.uk.domain.com, which seems weird since I am not part of a domain.

When I looked, domain.com is just a domain name registrar so this didn't seem right. After not finding much on Google, I eventually discovered that Windows has a built-in DNS search list. When you query dns records, it tags the entries from the list onto the end of the search string to try and resolve host names automatically.

For instance, if your domain was the same as your company name, you might have pixelpin.co.uk and if you attempt to lookup testserver, it will assume this means testserver.pixelpin.co.uk, That's all great but where did domain.com come from?

Well it appears that although this search list can and probably should be set centrally using Group policy, if you join a domain at any point, the domain is automatically added to the search list. In my case, I connected to a test domain at one point and guess what the default windows domain is called? domain.com!

This was ages ago and probably this means that a lot of DNS requests hit domain.com's nameservers from people like me.

More unusually, it can only be set in the Registry if you need to change it, it does not appear to be exposed via a GUI anywhere. The key is: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\SearchList

And the value is a comma-delimited set of domain names to fully qualify any DNS requests. I have naturally removed domain.com from there!

Backing up Linux box to online storage for cheap!

In my previous post, dear Theophilus, I described how to setup Amazon S3 storage and how to install s3tools in order to sync files to online. In this post, I will describe the mechanism I ended up with to backup various Linux boxes, mostly web servers, to S3 storage.

1) Setup S3 etc.

Already covered in the previous post

2) Install rsync

sudo apt-get install rsync

3) Create a script file to run the backup

Just using nano or your favourite text editor, create a script file, which will look like the following:

#!/bin/bash
BACKUPDIR="/home/luke/backupdir"    #temp directory for backup files
TAROUTPUT="/home/luke/backup.tar"   #backup output temp file

echo "Starting server backup..."    # Useful for emails
date +%H%M # Echoes the time to the console

# Create temp dir if not exists
if [[ ! -d $BACKUPDIR ]]; then
        mkdir $BACKUPDIR;
fi

#Apache configs
cp /etc/apache2/sites-available/default $BACKUPDIR
# other configs

#ssl certs
cp /etc/ssl/certs/mycert.crt $BACKUPDIR
# other certs

#sites
rsync -az /home/luke/sites/bugtracker $BACKUPDIR

#postfix
rsync -az /etc/postfix $BACKUPDIR

#run mysql dump
mysqldump -u root --password='mypassword' --all-databases > $BACKUPDIR/mysqldump.sql

#Create tar archive from the temporary directory contents, delete existing first
if [[ -f $TAROUTPUT ]]; then
    rm $TAROUTPUT
fi

tar -czf $TAROUTPUT $BACKUPDIR > /dev/null

#upload and encrypt to s3 storage
s3cmd put -e $TAROUTPUT s3://myserverbackups/backup-myservername-$(date +%Y%m%d-%H%M)$

date +%H%M

echo "-----"
echo "Server backup complete"
exit 0

4) Things to note

a) Naturally, you can copy whatever you want to the backup directory before it is tar-ed. Be careful about directories or files that might have the same name. Usually, this will cause an error but potentially you could overwrite something.
b) use rsync for moving any large directories since it will only update files that have changed, meaning subsequent updates are usually very fast
c) I don't use the tar --update function since rsync appears to touch all files when checking them, which means tar thinks all the files have changed even if they haven't, which makes the tar grow each time.
d) s3cmd doesn't support encryption when using its sync function. This means if you need encryption (which I do) you have to put the whole tar archive each time. If you don't want/need encryption, then it would probably be quicker for you not to use tar but to use "s3cmd sync" between the backup dir and your s3 storage, which will make it much quicker
e) In the script above, I use my server name and the current date and time in the uploaded backup file to distinguish it from other backups that end up in the same place. Replace the filename with whatever you want. Note also that the bucket name, after the s3:// should be your bucket name from your S3 account.

5) Make the script runnable

chmod +x ./myscript

6) Test it

If you are planning to run this as your own user, then simply type ./myscript and see that it all runs as expected. If you are planning to run it from cron.d (the cron daemon which runs automatically) then it will run as root. This means two things, firstly, you need to configure s3cmd for root and secondly you need to test the script by running it as root. The easiest way is:

sudo -i
s3cmd --configure
(do whatever you need to configure s3cmd and make sure it tests OK)
/path/to/myscript

All things being well, this should run to completion and prove that the script is OK.

7) Setup cron to run the script

The cron daemon is designed to run scripts at regular intervals, your system will already run a whole host of things that rotate logs, check for updates etc. It is very easy to set this up.

sudo nano /etc/cron.d/myscript

to create a new script in the cron.d directory. This has the crontab format and will be setup to call your script from wherever it is. Remember, this will run as the root user! You can copy in the crontab comments from another script if you want but the two lines you need in the script are:

MAILTO=emailaddresstosendresultsto@mycompany.com
# Note by default, the script will email root@localhost, which may or may not resolve to anything useful
0 23 * * *     /path/to/myscript

The first line is self-explanatory and the comment says why, the second line calls the script but the start of the line defines the schedule to use to run the script. The example above has minutes = 0 and hours = 23 followed by 3 asterisks, which means it will run at 23:00 every day. The 3 stars are optional specifiers for the Day of month (only run on the x of the month), Month (only run during a certain month) and Day of Week (Only run on specific day of the week). There is plenty of online help for crontab which explains these in more detail if you care.

8) Sit back and wait

All things working correctly, you should get an email after the script runs with the output of your script, any errors and any information you have echoed to the console.

.htaccess: Invalid command 'RewriteEngine'

Nice Apache error here found in /var/log/apache2/error.log on a wordpress site.

Rewriting is used quite frequently in frameworks to make the URL that the user can see look nice and friendly even though under the covers they do not actually point to separate pages. This was a new server and clearly this module was not enabled by default. Simply run:

sudo a2enmod rewrite

and then

sudo service apache2 restart

And you should be away again....

Tuesday, 20 May 2014

More poor design decisions by Apple/Objective-C/iOS

I wrote on Facebook earlier that developing for iOS is like trying to run with your shoelaces tied. Every time you think you are gaining momentum (i.e. knowledge), you trip over again on something. When you are learning a new thing, it can be hard to work out what is wrong because perhaps you just don't understand it or perhaps the design of the framework is rubbish.

I really don't like any technology where it works because you learn all of its poor design decisions rather than because it is logical, consistent and instinctive. I dislike Java but for the most part, it is consistent and there are a few things that are a pain (like having to catch exceptions) but to be fair, these are few and far between compared to Objective-C.

Let me give you today's example.

I have to write a custom alert dialog. Why? Not because I need to do something outlandish but because prior to iOS 7, you could rotate the alert dialog using setTransform which was useful when you had to hand-crank the rotation of the device since Apple decided you can't force a rotation. Anyway, as from iOS 7, this is no longer possible for unclear reasons and what really annoys me is that it breaks existing code. The correct way to make changes is to either deprecate something or create a new class and recommend that developers change to the new one, they have only succeeded in forcing even more people to write custom UI classes, something which will only lead to less consistency across apps rather than more.

Anyway, that is another story. As part of this dialog, I wanted to add the buttonTitleAtIndex method as per the UIAlertDialog because I was using it. In order to do this, I thought I would create a dictionary that would map indices to button titles and this function could simply use that to retrieve the button title. So, how to do this? Well, first off, I created an NSDictionary and then tried to use it. I couldn't. Why? Because the NSDictionary is NOT MUTABLE. What does that mean? You can't change it once it is initialised. So only useful if you know the contents when you first create it. You have to use the NSMutableDictionary if you want mutable contents. Now, to me, this is a poor decision. Most languages would use const to create an immutable object - that was even available in C++ - which makes it nice and obvious. The idea that you use two separate classes just to distinguish their mutability does not meet the OO concept that class = identity. In other words, two classes are two different things. An NSDictionary and an NSMutableDictionary are not two different things in identity, they just behave slightly differently.

Well, once that was worked out, I tried to use it to store a key/value pair. In my case, very simple, an integer (or NSInteger) and a string (NSString*). I added the object and immediately got a compiler error: Cannot convert NSInteger to id. What on earth is NSCopying? It turns out it is a protocol (read interface) that says an object can be copied, which is fair enough but why would keys in a dictionary need to be copyable? Because the dictionary copies the key when the item is inserted. Hmm, OK but why isn't my NSInteger copyable? Surely a simple object primitive would define all these types of methods but...oh wait....NSInteger is NOT a class, it is a typedef for long (i.e. a hack). So Apple created a massive framework of classes but couldn't be bothered to do it for the humble basic types. .Net does it, Java does it and C++ doesn't pretend that primitives are objects by type-deffing them into class-style names.

So, the solution was to use another class NSNumber, which is a class, and which can therefore be used as a key and utilising its static method numberWithInt to convert the integer into an NSNumber. So all of these poor design decisions take what I would expect to be an obvious action in any language and turn what would look like this in Java/.Net:
myDictionary[itemIndex] = myObject;

or
myDictionary.AddObject(itemIndex, myObject);

into this in Obj-C/iOS
[[self myDictionary] setObject:myObject forKey:[NSNumber numberWithInt:itemIndex]];

Is this really the correct way to write production code that should be maintainable and readable?

Tuesday, 13 May 2014

Backup Amazon EC2 Linux box to S3 Storage

I'm looking for a simple and cheap way to backup my web servers to disk and there are obviously some options. SpiderOak has a free low tier and although it has certain features, such as full encryption and various ways to get at your data, I have found it to be unreliable, sometimes it never finishes and requires a machine reboot. Also, it never deletes anything so as time goes by, your allowance is eaten up and attempting to delete these old versions using the command line, although simple in theory, seems to lock up or fail more often than not.

I thought I would set up a backup from the boxes directly to S3 storage which is cheap and local to the AWS boxes.

1) Get the box in a known-good state, install s3cmd and take a snapshot.

This is just good practice. Before I start mucking around with things, if I back everything up as a snapshot, I can go back to the start if I get anything wrong. So:

> sudo apt-get update && sudo apt-get upgrade
> sudo apt-get install s3cmd
> sudo reboot now

Next, go into your AWS management portal and click on Volumes under the Elastic Block Store:


Select the volume that is attached to your instance (you can see this in the Attachment Information column) and under the actions menu, choose Create Snapshot. This happens as a background process, so you don't need to wait for it.

2) Setup Amazon S3 and get the credentials

Skip this section if you already have S3 setup and you know the credentials (you might however want to create an additional bucket for your backups)

Click on the S3 icon in the AWS management portal, either from the front page or under the Services Menu. If you have not already done so, click Create Bucket and give it a useful name. When choosing a region, it will be cheaper and quicker to use the same region as your instances but it will be much more secure to write it to another region so the chance of losing the server and the storage at the same time is much lower. Personally, I prefer the local faster option and will take a chance on the whole data center not getting destroyed! (You could also run a job to duplicate the bucket to another region but I'm not going to do that here).

It is good practice to add a user just for uploading backups. This user can be restricted to an appropriate level so that it someone compromised the machine/user the damage they could do is limited. By default, I will not allow this user to view, only to upload. I can add the view permission if I ever need to restore from backup.

To add a new user, click on the IAM icon under the services menu, which opens the Identity and Access Management control panel. Click on Users and Create New Users. Enter a name (such as backup) and copy the access key and secret key from the dialog that comes up. Note that these keys are like passwords. Do not publish them and do NOT put them in source code in a public repository. It has happened and attackers can find them and sometimes use them to create instances (often for Bitcoin mining!) at your expense. Note that you cannot re-download these, so if you haven't copied or downloaded them, you would need to recreate them at a later date.

You can now create a group and give it access to your S3 bucket. This makes it easier to add additional users later on for other machines. Click Groups and Create New Group. Give it a name (like backup) and then press Continue. The next page links this group to certain permissions on a certain resource. You could use a present template but the policy generator option allows more fine-grained control so click that radio button and press Select. As with many permissions, this allows an allow or a deny policy to be created. Since this is our first policy, we will choose Allow and then select Amazon S3 as the AWS Service. Under actions, I selected Put Object, Delete Object, List Objects, Get Object and List Bucket. I'm not sure yet whether all of these are required but you definitely need List Bucket for the s3cmd setup to work. Once you have chosen the actions, you need to specify which resource these permissions apply to using Amazon Resource Notation. In this case, the format is arn:aws:s3:::bucketname where bucketname is the bucket you created earlier. You need to extend this for the sub directories of the bucket name but rather than adding another nearly identical statement, we will edit the policy after the group is created.

Once the statement has been added, you will need to add a global permission since s3cmd needs to be able to list all buckets. Use 'Allow', 'Amazon S3' and ListAllMyBuckets for the Action. In the resource name, put arn:aws:s3:::* and then click Add Statement.

Once you press Continue and the group is created, we need to edit part of the permission policy since the permissions only currently apply to the bucket itself, not its contents. Click the Permissions tab at the bottom and next to the first policy statement, click Manage Policy. This should bring up an editor that has the permissions for the resource arn:aws:s3:::bucketname. Edit the resource block and add a comma after the existing entry and add another entry for "arn:aws:s3:::bucketname/*" so it looks like this:

"Resource": [
        "arn:aws:s3:::bucketname",
        "arn:aws:s3:::bucketname/*"
      ]

And press Apply Policy

Once the policy is updated, select it and click the Users tab at the bottom and Add Users to Group. Add the user you created earlier. This should be all you need to access your storage account using those user credentials.

3) Setup s3 tools on your box

> s3cmd --configure

Enter your access key and secret followed by an encryption password. Encryption will be slower but you should never really trust a cloud storage unit to be secure from either nosey governments or people who might find a vulnerability in the cloud service. Enter the path to gpg (I selected the default), also say Yes to https. It is obviously important to test access so allow s3 tools to do this and fix any errors you get.

If you get a 403, you might not have setup the permissions correctly or, obviously, you might have mistyped your key and secret. If all goes well, you should see something like:

Please wait...
Success. Your access key and secret key worked fine :-)
Now verifying that encryption works...
Success. Encryption and decryption worked fine :-)
> Save settings? [y/N] y
Configuration saved to '/home/ubuntu/.s3cfg'

That should be the setup finished correctly.

4) Setup your sync

s3cmd acts a bit like an FTP program and can copy, delete, list objects and buckets etc. Most of these commands are very intuitive (and full details can be found here):

List buckets
> s3cmd ls

Copy between buckets
> s3cmd cp s3://bucket1 s3://bucket2

Upload (put) local file into s3 bucket
> s3cmd put filename s://bucketname

Download a file
> s3cmd get s3://bucket/filename localfilename

But most importantly for us is probably the sync command:

> s3cmd sync directory s3://bucket/

This will initially upload all files from the directory but then only new or changed files. If you want to delete remote files that have been deleted locally, use the --delete-removed option.

Note that sync does NOT use encryption currently. If you want encryption, you need to use put with the -e and --recursive options. I will write another post if I can work out a practical backup solution which includes encryption, only uploads increments (i.e. only the changes) and allows easy storage of multiple backups (perhaps each day for a week and then 4 weeks and then 3 months etc).

Friday, 9 May 2014

iOS/Objective-C Unrecognized selector sent to instance

Further to my earlier rant about Objective-C and how it mostly sucks, here is another error that most of us have probably seen. Unfortunately, it is worded in a way that assumes you understand the specific terms used in Objective-C like messages, selectors, actions etc. none of which are familiar to people from a C background despite the claims by the Apple fanbois that Obj-C is just a superset of C.

Selectors and Objective-C

In plain English, this error message means that a message was passed to an object and the message did not have a matching handler. What am I talking about? Well in normal C/C++, you would call a function/method either on an object or in the global namespace. The compiler will fail if it cannot link the call to the function itself. In Obj-C, for some reason, they have chosen a message based system, so you are not calling functions but sending messages to objects (that's all that horrible square bracket and colon stuff), which allows you to pass messages that are not actually present in the target object since you might be handling these at runtime. As with any late-bound language, this means that things will compile and potentially fail at runtime and the most common error is what you can see above.
Error Example
See the code below. The top block shows the method (or message handler) defined in a class and note that it takes one parameter. The second block calls this method but does not pass the parameter expected.

-(void) doSomething:(UIView*)view
{
    // Do something here
}
-(void) someOtherFunction
{
    [self doSomething];    // Error at RUNTIME - unrecognized selector sent to instance
    [self doSomething:nil];    // OK since parameter is passed
}

It gets worse!

I had another error of the same sort but in this case, I was passing the name of a function to @selector() to be used as a gesture handler for one of my views. Fortunately in my case, I had already done this successfully for a double-tap and was trying to sort the single tap. The code was basically identical but one worked and the other gave me, "unrecognized selector..." at runtime. It was only when I looked VERY carefully that I noticed the working function was passed to @selector as handleDoubleTap: and the other as handleSingleTap. Notice the difference? I didn't for ages but one has a colon after it and the other one doesn't. 

Why does this matter? The colon is implying that the handle function is called handleDoubleTap and TAKES A SINGLE PARAMETER - which it does. If you miss out the colon, it is saying, "call a function named handleSingleTap that takes NO PARAMETERS". In a decent language, this would either fail at compile-time because there is no function with one parameter or it would not require you to tell it how many parameters the function has because the runtime would be looking for a named function THAT TAKES THE PARAMETERS IT REQUIRES - in this case, a single pointer to a UIGestureRecognizer.

Conclusion

These kinds of subtle and hard to understand errors are what make people avoid your language. I'm quite sure that if it was not still used for MacOS and iOS, no-one would choose this language. It is archaic and poorly written and sadly, it seems, has not really improved over time. Perhaps if it was completely different from C, then my mindset would expect a completely unique language like Python or Ruby and I wouldn't be expecting it to work like C (when it doesn't) but the pretence just leads people like me with a lot of C-style experience to completely confused. Java, C# and C++ were all logical extensions to C-style syntax but this stuff is nasty. Hopefully, you will still be productive and one day Apple will consider using a normal language for development, even something as old as C++ (which they don't use for some reason). Otherwise, perhaps one of these cross-platform tools like Xamarin or PhoneGap will remove the need for us to endure Objective-C.

Tuesday, 6 May 2014

Why I think Google+ is a failure

Let's face it, when Google+ came along, it was trying to claw some people away from Facebook. Well, how do you win "customers" from a rival? You can do one of a number of things but they usually involve:

1) Do something cheaper. FB is already free so not much scope here for G+ to improve
2) Do something that your rival doesn't. Nothing obvious here that G+ supports that is not on FB.
3) Don't do something that your rival does. The main annoyance with Facebook is related to privacy and advertising but since this pays for both FB and G+, there is no difference here either.
4) You do the same thing but in a better way. Well despite the fact that FB has more information on screen, they have the right balance in my opinion between usability and functionality. I find G+ unintuitive.
5) All of my friends are on Facebook and hardly any on G+, partly because of 1-4 above!

Like various Google products, as of late, they have departed their slick origins and now have weird and wonderful UX that to be honest confuses me, even though I'm a developer. Look at the weird Google bar that they have at the top of loads of sites. A range of options including a text link, two icons, a text button and then an image button - nothing like consistency. It just doesn't lead me anywhere obvious.

Another example is Street View. Once, it was simple and usable but go there now and a whole load of things are all over the picture. The left arrow at the top is not to turn the camera, it goes back to the map. Another load of icons thrown onto a canvas with little regard for consistency.

Even GMail itself does strange things and in the journey to putting loads of CSS3 whizz-bangs in, they have lost the natural interface it once was.

Sorry Google, I love you but you are well off course with your consumer offerings just now.

Thursday, 1 May 2014

My rant about iOS development and why it sucks.

Developing an app for iOS is a tragic experience. I have multiple languages and frameworks under my belt and despite all the expected pains of learning a new language, most of these have been as expected. C#, Java, C, C++, PHP and even things like Python have all been reasonably logical. The tools are fairly easy to use and what happens is expected.

Enter iOS. Or rather, enter Objective-C, XCode, iOS and Cocoa.

Let us start with the language. Objective C might be old but in my opinion it is not logical at all. It is supposed to be "C on steroids" but I find it more like C with hemorrhoids. C++ works fine and is pretty logical and even QT which is C++ with messaging/events was really nice and easy to understand and consistent but Objective-C anything but. Square brackets all over the place but sometimes you can use dot notation. Trying to create function prototypes is a nasty business because it is hard to know what all the names on the prototype mean, are they types? labels? parameter names? Trying to chain things together like you might do in C => myObj->something->somethingelse() doesn't seem to work most of the time, you have to put brackets in funny places. Just when you thought you were getting to grips with it, you find out that you can't have a method pass a local parameter into another method if the parameter has the same name as the parameter in the function you are calling - for some reason, this confuses the compiler.

What about XCode? A very poor excuse for an IDE. It harks back to the bad old days when project settings were just really long lists of technical mumbo jumbo that anyone new to the game would not understand. Can you leave it all on the defaults? Maybe but what about supporting all the various versions of iOS with 32 and 64 bits? I've had to change these settings even for the basic stuff I'm doing and it's not pretty. Navigation feels awkward and since I'm used to Visual Studio, this is a very poor product in comparison. Little icon buttons where the icons don't mean anything so you have to click on them all anyway to find out where stuff is. In Visual Studio, you have a Properties window will categories. The storyboard editor is another wonder to behold. The dragging around is fraught with all kinds of problems and even trying to move and resize items can do really weird stuff with sizes and offsets. The wiring up of outlets and delegates etc. is properly weird and involves a lot of legwork. There are also a whole host of things which can only be done in code apparently. Round corners on a button? Either in code or in a free-format list on the object, there is no property for it.

So iOS itself. Well, for some reason, they dropped standard buttons. What does that mean? If I just want a good old common-or-garden button with text and perhaps some nice animation when pressed with a background colour, I have to do this myself. The colour and text is easy enough but rounded corners require hacking and to make the background change colour when clicked? A whole world of pain. Why were these removed in iOS7? "because a button might be an image or text or something else". True but it might also just be a normal button! Apple's documentation is woeful. It is hard to navigate, most of it looks auto-generated, the examples are either non-existent, out of date or very simplistic. You can find an event like UIApplicationWillChangeStatusBarOrientation but no information on what the delegate function needs to look like. It assumes you already know that. In fact, their documentation is so poor that even if you search for one of their classes or methods, you usually find 5 StackOverflow questions in the Google results before any hit on apple.com.

In fact, it could be said that there is a lot of help online for iOS questions but the reality is that many of the answers are either for earlier versions of iOS (and it can be hard to tell if this is the case when you are learning the framework) or they involve what seem like copious amounts of code for something simple (e.g. how can I detect a change in orientation) or the answer is not even correct, especially when related to what versions support what.

I have spent two days trying to do very basic things. 1) Handling device rotation (fine if you want everything to be auto and a royal pain if not) and 2) Pinch and zoom of an image (very easy if you use a scrollview according to Apple) but this neither scrolls, zooms and it displays the child image at completely the wrong size by default.

All-in-all, I feel very sorry for iOS developers. You basically have to learn to be really hacky, involved and low-level just to work out really basic ideas. I'm amazed at how good some of the iOS apps are but sadly, I have written an entire Android app in Java (another language I had to learn just for the Android project) in about the same time it has taken me to try and modify a few things on an iOS app that was written for us by someone else. The Android docs are far better, the way to do something is much clearer, all the things you would expect to do in a mobile app are exposed and easy to get hold of and Eclipse is a far friendlier environment than XCode.

I want to like it because it is not Java but I don't.