June 16, 2008

World-readable plaintext passwords and toddler murder

What do world readable plaintext passwords and toddler murder have in common? They're both easy.

Oh, right... not to mention they're both bad! I, for one, have accepted our not-evil corporate overlords and have been using GMail since my full-time-student unbecoming. As a result, I was looking at the GMail notifiers available in the Ubuntu repository.

One, called cgmail, was written in Python and had a fairly beautiful codebase. cgmail tied nicely into gconf and had everything going for it. I totally would be using it if it didn't crash ten times during five minutes of configuration. [*]

Another, called gmail-notify, worked perfectly. The source looked like it was written by a Java programmer (you know, making a "main" method for classes and such) who didn't believe in refactoring or PEP8, which made me a little sad. What made me really sad was finding that it stored my password in plaintext in a word readable file, and I had never gotten any warning.

This is a bug on the part of two parties: the MOTU who maintains this package (I'll be submitting a bug report) and the creator of the program. The Gentoo wiki has a page on the ability to install via portage, from which I quote:

elog "Warning: if you check the 'save username and password' option"
elog "your password will be stored in plaintext in ~/.notifier.conf"
elog "with world-readable permissions. If this concerns you, do not"
elog "check the 'save username and password' option."

Ideally this would read: "There is no 'save username and password' option." Just to recap some things:

  1. Do design your program to allow for plugins that tie into keyring managers,

  2. Don't knowingly put some of my most sensitive data where any user on the system can read it, and

  3. Don't, for God's sake, let me install a program that does this without telling me!

I don't mind that Pidgin stores my password in plaintext because it's an Instant Messaging client and it's as careful as possible to use file permissions as protection. gmail-notify used my default umask, which is clearly not good enough, to protect perhaps the most personal data that I have.

You know who you should really feel sorry for, though? Linux-using toddlers.

Footnotes

[*]

I'll probably end up contributing to this project.

A senseless default - press F1 to continue

In lieu of mandatory manual configuration, many devices ship with a default (AKA "stock") configuration, which may then be altered at the owner's will. This falls under the best practice category of "sensible default" design, where the creator chooses a sensible set defaults in order to convenience a client.

"Keyboard error or no keyboard present; Press F1 to continue" is an example of the failure to exercise this practice: because of a mysterious CMOS reset, my headless server becomes useless.

Java GridLayout can't center extra space

Java's GridLayout design appears to lack some forethought. You can't center the elements that are laid out by Java's GridLayout class if the space cannot be evenly distributed among the number of columns (or rows). You can do left-to-right, right-to-left, top-to-bottom, and/or bottom-to-top, but you cannot center. This seems quite silly.

In the end, I wound up subclassing GridLayout and fixing the mistake. GridBagLayout was an inappropriate alternative, since GridBagLayout relies on the preferredWidth of its constituent elements to lay out a container and I just wanted a grid! A simple grid that centers elements in the container if it can't use up all the space can be found inline:

import java.awt.*;

/**
 * Centers the lopsided extra pixel distribution from GridLayout to within
 * one-half pixel margin of error.
 *
 * @author Chris Leary
 */
public class CenteredGridLayout extends GridLayout {

    public CenteredGridLayout() {}

    public CenteredGridLayout(int rows, int cols) {
        super(rows, cols);
    }

    public CenteredGridLayout(int rows, int cols, int hgap, int vgap) {
        super(rows, cols, hgap, vgap);
    }

    /**
     * @return  3-tuple (starts, perObj, realDims).
     *          starts are starting pixel deltas in relation to the
     *              parent container
     *          perObj = (pixelsPerObjX, pixelsPerObjY)
     *          realDims = (rowsX, colsY); automatically calculated if not
     *              set to 0 on the container
     */
    private int[][] lcHelper(Container parent, int componentCount) {
        /*
         * The available space for actual objects is:
         *  parent.width - leftInset - rightInset - hgap * (cols - 1);
         *
         * Note that the (cols - 1) is because the last item needs no hgap.
         *
         * If the available space isn't evenly divisible into the number of
         * columns, we have to distribute the remainder evenly across the
         * insets.
         *
         * If the remainder isn't evenly divisible into the two insets, the
         * right/bottom inset is given the extra pixel.
         */
        int rows = getRows();
        int cols = getColumns();
        Insets insets = parent.getInsets();
        /* Calculate dimensions if not explicitly given. */
        int realCols = (cols == 0)
            ? (int) Math.ceil(((double) componentCount) / rows)
            : cols;
        int realRows = (rows == 0)
            ? (int) Math.ceil(((double) componentCount) / cols)
            : rows;

        /* Helper values. */
        int hInset = insets.left + insets.right;
        int vInset = insets.bottom + insets.top;
        int parentHeight = parent.getHeight();
        int parentWidth = parent.getWidth();

        /* Distribution calculations. */
        int hGapTotal = (realCols - 1) * getHgap();
        int vGapTotal = (realRows - 1) * getVgap();
        int widthPerItem = (parentWidth - hInset - hGapTotal) / realCols;
        int heightPerItem = (parentHeight - vInset - vGapTotal) / realRows;
        int extraWidth = parentWidth
            - (widthPerItem * realCols + hGapTotal);
        int extraHeight = parentHeight
            - (heightPerItem * realRows + vGapTotal);

        /* Package values in containers for return. */
        int[] starts = { /* x, y */
            insets.left + extraWidth / 2,
            insets.top + extraHeight / 2};
        int[] perObj = { widthPerItem, heightPerItem };
        int[] realDims = { realCols, realRows };
        return new int[][] { starts, perObj, realDims };
    }

    /**
     * Set bounds for objects within parent.
     * @param parent    Container being layed out.
     */
    @Override
    public void layoutContainer(Container parent) {
        synchronized (parent.getTreeLock()) {
            int componentCount = parent.getComponentCount();
            if (componentCount == 0) {
                return; /* Nothing to lay out. */
            }
            /* Unpack data calculated by helper. */
            int[][] params = lcHelper(parent, componentCount);
            int[] starts = params[0];
            int[] perObj = params[1];
            int[] realDims = params[2];
            int realCols = realDims[0];
            int realRows = realDims[1];

            /* Move down the height per object plus vertical gap
             * per row. */
            for (
                int y = starts[1], row = 0;
                row < realRows;
                y += perObj[1] + getVgap(), row++
            ) {
                /* Move over the width per object plus horizontal gap per
                 * row. */
                for (
                    int x = starts[0], col = 0;
                    col < realCols;
                    x += perObj[0] + getHgap(), col++
                ) {
                    int arrayIndex = row * realCols + col;
                    parent.getComponent(arrayIndex)
                        .setBounds(x, y, perObj[0], perObj[1]);
                }
            }
        }
    }
}

Here's an illustration of the normal GridLayout failing to allocate my space evenly, using a left-to-right and top-to-bottom setting:

There are 8 pixels of extra space maximum, since 8 pixels isn't evenly divisible over the nine cells.

Mozilla Prism, I run to thee...

I'm having trouble fighting feelings for Mozilla Prism.

At first, it seems like a fairly stupid concept. For one, I already run a browser — why do I need a dedicated XULRunner task that can't switch URIs? [*] The obvious alternative is to just open a new browser window and run my web apps in there. I can think of a few other non-advantages (not to be confused with disadvantages):

The only real advantage that I can come up with is the beautiful separation of concerns inherent in a necessarily separate Mozilla Prism instance. Sure, extensions allow me similar behavior; however, the locked tabs (or [insert similar solution here]) are still present within my general browser window.

For one example, I tend to keep a single browser instance at 1024px width and tab the crap out of everything, but I like Google Calendar to be full screen — Mozilla Prism allows me to be neurotic without negatively affecting my work flow (point in case: maximizing and restoring Firefox over and over).

Additionally, when I have a whole browser window dedicated to a locked GMail tab, I don't really need to look at my bookmarks or an address bar. These tend to make me turn my supposedly "dedicated" Firefox window into another general browsing window with a multitude of tabs, at which point I get too disorganized (across multiple general browsing windows) to be optimally productive. Mozilla prism spawns a new single-URI instance on click, which prevents me from digressing too much.

In the end, I guess the minimalism that comes with the separation of concerns just tends to jive well with my routine work flow. Kudos to the project for coming up with and implementing an idea that I never would have — despite its theoretical disadvantages, I've certainly enjoyed it in practice.

I should probably also mention that I'd be very surprised if Mozilla Prism were designed with my needs in mind. I've been assuming that the whole impetus for the project was to give things like Google Docs the look and feel that rivals that of native applications, as opposed to using launchers that affect your web browser.

Footnotes

[*]

The difference between URIs and URLs is explained on Wikipedia.

SML is improving, though benchmarks are getting worse

In the following, jenkinsInt is a function that uses no references — it takes an integer, calculates the Jenkins One-at-a-time hash code on a number of bytes corresponding to its width, and returns that value as an integer. Pretty simple, all in all.

Standard ML of New Jersey v110.54 [built: Sat Sep 24 16:19:34 2005]
val jenkinsInt = fn : int -> int
- jenkinsInt(4);
jenkinsInt: 4
Segmentation fault (core dumped)

Segfault!? Clearly this is a failure of the runtime, so I turned to version <tt>.62</tt>, which is known to be slower:

Standard ML of New Jersey v110.62 [built: Wed May  9 18:06:38 2007]
val jenkinsInt = fn : int -> int
- jenkinsInt(4);
jenkinsInt: 4
uncaught exception Overflow [overflow]
raised at: file stdin

At least we get a nice error message in the newer version. It was a lot easier to fix the error once I knew what was really happening.

For kicks, here's my (fixed) implementation. I don't guarantee its correctness — I wrote it hastily for a class project.

structure Hashes = struct
  val () = if Word.wordSize <> Option.valOf(Int.precision)
           then print("WARNING: unexpected int size\n")
           else ()

  val maxIntInverse = if Int.maxInt = NONE then ~1
                      else ~1 * Option.valOf(Int.maxInt)

  (* I'm not sure how much optimization the compiler does, so I'm precomputing
   * these values -- they're quite frequently used if a lot of hashing is being
   * done. *)
  val zero = Word.fromInt(0)
  val three = Word.fromInt(3)
  val six = Word.fromInt(6)
  val eight = Word.fromInt(8)
  val ten = Word.fromInt(10)
  val eleven = Word.fromInt(11)
  val fifteen = Word.fromInt(15)
  val sixteen = Word.fromInt(16)
  val twenty_four = Word.fromInt(24)
  val two_hundred_fifty_five = Word.fromInt(255)

  fun jenkinsByte(key:Word.word, hash:Word.word):Word.word =
    let
      val hash = hash + key
      val hash = hash + Word.<<(hash, ten)
      val hash = Word.xorb(hash, Word.>>(hash, six))
    in
      hash
    end

  fun jenkinsChar(key:char, hash:Word.word):Word.word =
    let val key = Word.fromInt(Char.ord(key)) in
      jenkinsByte(key, hash)
    end

  fun jenkinsFinish(hash:Word.word):int =
    let
      val hash = hash + Word.<<(hash, three)
      val hash = Word.xorb(hash, Word.>>(hash, eleven))
      val hash = hash + Word.<<(hash, fifteen)
      val result = Word.toIntX(hash)
      val result = if result < maxIntInverse then maxIntInverse else result
    in
      Int.abs(result)
    end

  fun jenkinsChars(cs:char list):int =
    let
      val hash = zero
      val hash = List.foldl(jenkinsChar)(hash)(cs)
      val result = Word.toIntX(hash)
      val result = if result < maxIntInverse then maxIntInverse else result
      val absResult = Int.abs(result)
    in
      absResult
    end

  fun jenkinsInt(i:int):int =
    let
      val w = Word.fromInt(i)
      val b1 = Word.andb(w, two_hundred_fifty_five)
      val b2 = Word.>>(w, eight)
      val b3 = Word.>>(w, sixteen)
      val b4 = Word.>>(w, twenty_four)
      val hash = List.foldl(jenkinsByte)(zero)([b1, b2, b3, b4])
    in
      jenkinsFinish(hash)
    end

  fun jenkinsString(s:string):int = jenkinsChars(String.explode(s))
end