Index: src/mlx/acars.py
===================================================================
--- src/mlx/acars.py	(revision 298)
+++ src/mlx/acars.py	(revision 743)
@@ -25,5 +25,5 @@
         self.state = state
         self.bookedFlight = gui.bookedFlight
-        
+
         self.blockTime = 0 if flight.blockTimeStart is None \
                          else aircraft.state.timestamp - flight.blockTimeStart
@@ -48,4 +48,26 @@
         else:
             return "Landed"
-        
+
+    def _serialize(self):
+        """Serialize the ACARS object for JSON-RPC."""
+        bookedFlight = self.bookedFlight
+        state = self.state
+
+        attrs = {}
+        attrs["longitude"] = state.longitude
+        attrs["latitude"] = state.latitude
+        attrs["pilotName"] = self.pilotName
+        attrs["numPassengers"] = bookedFlight.numPassengers
+        attrs["blockTime"] = self.getBlockTimeText()
+        attrs["callsign"] = bookedFlight.callsign
+        attrs["aircraftTypeName"] = bookedFlight.aircraftTypeName
+        attrs["departureICAO"] = bookedFlight.departureICAO
+        attrs["arrivalICAO"] = bookedFlight.arrivalICAO
+        attrs["altitude"] = state.altitude
+        attrs["groundSpeed"] = state.groundSpeed
+        attrs["event"] = self.getEventText()
+        attrs["tailNumber"] = bookedFlight.tailNumber
+
+        return ([], attrs)
+
 #------------------------------------------------------------------------------
Index: src/mlx/common.py
===================================================================
--- src/mlx/common.py	(revision 651)
+++ src/mlx/common.py	(revision 740)
@@ -15,4 +15,8 @@
 # used, which is the default on Windows or when the \c FORCE_PYGTK environment
 # variable is set.
+
+#---------------------------------------------------------------------------------------
+
+MAVA_BASE_URL = os.environ.get("MAVA_BASE_URL", "http://virtualairlines.hu")
 
 #-------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 733)
+++ src/mlx/config.py	(revision 738)
@@ -258,4 +258,5 @@
         if secondaryInstallation:
             self._updateURL += "/exp"
+        self._useRPC = True
 
         self._messageTypeLevels = {}
@@ -666,4 +667,16 @@
         if updateURL!=self._updateURL:
             self._updateURL = updateURL
+            self._modified = True
+
+    @property
+    def useRPC(self):
+        """Determine if RPC calls should be used."""
+        return self._useRPC
+
+    @useRPC.setter
+    def useRPC(self, useRPC):
+        """Set whether RPC calls should be used."""
+        if useRPC!=self._useRPC:
+            self._useRPC = useRPC
             self._modified = True
 
@@ -775,4 +788,5 @@
                                     Config.DEFAULT_UPDATE_URL +
                                     ("/exp" if secondaryInstallation else ""))
+        self._useRPC = self._getBoolean(config, "general", "useRPC", True)
 
         for aircraftType in const.aircraftTypes:
@@ -867,4 +881,7 @@
         config.set("update", "url", self._updateURL)
 
+        config.set("general", "useRPC",
+                   "yes" if self._useRPC else "no")
+
         config.add_section(Checklist.SECTION)
         config.add_section(ApproachCallouts.SECTION)
@@ -1001,4 +1018,5 @@
         print "  autoUpdate:", self._autoUpdate
         print "  updateURL:", self._updateURL
+        print "  useRPC:", self._useRPC
 
         print "  messageTypeLevels:"
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 750)
+++ src/mlx/const.py	(revision 737)
@@ -11,5 +11,5 @@
 
 ## The version of the program
-VERSION="0.37.1"
+VERSION="0.38"
 
 #-------------------------------------------------------------------------------
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 765)
+++ src/mlx/fsuipc.py	(revision 766)
@@ -1387,7 +1387,4 @@
         state.gearsDown = data[self._monidx_noseGear]==16383
 
-        if state.gearControlDown!=state.gearsDown:
-            print "gearControl:", data[self._monidx_gearControl], ", noseGear:", data[self._monidx_noseGear]
-
         state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
 
Index: src/mlx/gui/acars.py
===================================================================
--- src/mlx/gui/acars.py	(revision 649)
+++ src/mlx/gui/acars.py	(revision 747)
@@ -13,5 +13,5 @@
 # The ACARS tab.
 #
-# This module implements to \ref ACRS class, which displays the MAVA ACARS in a
+# This module implements to \ref ACARS class, which displays the MAVA ACARS in a
 # browser window using CEF
 
@@ -21,5 +21,5 @@
     """The flight info tab."""
     # The URL of the ACARS map
-    URL = "http://virtualairlines.hu/acars2/show.html"
+    URL = MAVA_BASE_URL + "/acars2/show.html"
 
     def __init__(self, gui):
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 732)
+++ src/mlx/gui/common.py	(revision 741)
@@ -279,4 +279,119 @@
                       else str(self._currentInteger))
         self._selfSetting = False
+
+#------------------------------------------------------------------------------
+
+class CredentialsDialog(gtk.Dialog):
+    """A dialog window to ask for a user name and a password."""
+    def __init__(self, gui, userName, password,
+                 titleLabel, cancelButtonLabel, okButtonLabel,
+                 userNameLabel, userNameTooltip,
+                 passwordLabel, passwordTooltip,
+                 infoText = None,
+                 rememberPassword = None,
+                 rememberLabel = None, rememberTooltip = None):
+        """Construct the dialog."""
+        super(CredentialsDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
+                                                titleLabel,
+                                                gui.mainWindow,
+                                                DIALOG_MODAL)
+        self.add_button(cancelButtonLabel, RESPONSETYPE_CANCEL)
+        self.add_button(okButtonLabel, RESPONSETYPE_OK)
+
+        contentArea = self.get_content_area()
+
+        contentAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                         xscale = 0.0, yscale = 0.0)
+        contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
+                                     padding_left = 8, padding_right = 8)
+
+        contentArea.pack_start(contentAlignment, False, False, 0)
+
+        contentVBox = gtk.VBox()
+        contentAlignment.add(contentVBox)
+
+        if infoText is not None:
+            label = gtk.Label(infoText)
+            label.set_alignment(0.0, 0.0)
+
+            contentVBox.pack_start(label, False, False, 0)
+
+        tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        tableAlignment.set_padding(padding_top = 24, padding_bottom = 0,
+                                   padding_left = 0, padding_right = 0)
+
+        table = gtk.Table(3, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+
+        tableAlignment.add(table)
+        contentVBox.pack_start(tableAlignment, True, True, 0)
+
+        label = gtk.Label(userNameLabel)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._userName = gtk.Entry()
+        self._userName.set_width_chars(16)
+        # FIXME: enabled the OK button only when there is something in thr
+        # user name and password fields
+        #self._userName.connect("changed",
+        #                       lambda button: self._updateForwardButton())
+        self._userName.set_tooltip_text(userNameTooltip)
+        self._userName.set_text(userName)
+        table.attach(self._userName, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._userName)
+
+        label = gtk.Label(passwordLabel)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        #self._password.connect("changed",
+        #                       lambda button: self._updateForwardButton())
+        self._password.set_tooltip_text(passwordTooltip)
+        self._password.set_text(password)
+        table.attach(self._password, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._password)
+
+        if rememberPassword is not None:
+            self._rememberButton = gtk.CheckButton(rememberLabel)
+            self._rememberButton.set_use_underline(True)
+            self._rememberButton.set_tooltip_text(rememberTooltip)
+            self._rememberButton.set_active(rememberPassword)
+            table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
+        else:
+            self._rememberButton = None
+
+    @property
+    def userName(self):
+        """Get the user name entered."""
+        return self._userName.get_text()
+
+    @property
+    def password(self):
+        """Get the password entered."""
+        return self._password.get_text()
+
+    @property
+    def rememberPassword(self):
+        """Get whether the password is to be remembered."""
+        return None if self._rememberButton is None \
+               else self._rememberButton.get_active()
+
+    def run(self):
+        """Run the dialog."""
+        self.show_all()
+
+        response = super(CredentialsDialog, self).run()
+
+        self.hide()
+
+        return response
 
 #------------------------------------------------------------------------------
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 728)
+++ src/mlx/gui/flight.py	(revision 764)
@@ -11,5 +11,5 @@
 import mlx.util as util
 from mlx.pirep import PIREP
-from mlx.i18n import xstr
+from mlx.i18n import xstr, getLanguage
 from mlx.sound import startSound
 import mlx.web as web
@@ -20,4 +20,6 @@
 import tempfile
 import threading
+import re
+import webbrowser
 
 #-----------------------------------------------------------------------------
@@ -61,5 +63,5 @@
 class Page(gtk.Alignment):
     """A page in the flight wizard."""
-    def __init__(self, wizard, title, help, completedHelp = None):
+    def __init__(self, wizard, id, title, help, completedHelp = None):
         """Construct the page."""
         super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
@@ -139,4 +141,6 @@
 
         self._wizard = wizard
+        self._id = id
+        self._nextPageID = None
 
         self._cancelFlightButton = None
@@ -144,4 +148,19 @@
         self._completed = False
         self._fromPage = None
+
+    @property
+    def id(self):
+        """Get the identifier of the page."""
+        return self._id
+
+    @property
+    def nextPageID(self):
+        """Get the identifier of the next page, if set."""
+        return self._nextPageID
+
+    @nextPageID.setter
+    def nextPageID(self, nextPageID):
+        """Set the identifier of the next page."""
+        self._nextPageID = nextPageID
 
     def setMainWidget(self, widget):
@@ -277,11 +296,11 @@
     def __init__(self, wizard):
         """Construct the login page."""
-        super(LoginPage, self).__init__(wizard, xstr("login"),
-                                        xstr("loginHelp"))
+        super(LoginPage, self).__init__(wizard, "login",
+                                        xstr("login"), xstr("loginHelp"))
 
         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
                                   xscale = 0.0, yscale = 0.0)
 
-        table = gtk.Table(4, 2)
+        table = gtk.Table(3, 2)
         table.set_row_spacings(4)
         table.set_col_spacings(32)
@@ -321,9 +340,7 @@
         table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
 
-        self._entranceExam = gtk.CheckButton(xstr("login_entranceExam"))
-        self._entranceExam.set_use_underline(True)
-        self._entranceExam.set_tooltip_text(xstr("login_entranceExam_tooltip"))
-        self._entranceExam.connect("toggled", self._setControls)
-        table.attach(self._entranceExam, 1, 2, 3, 4, ypadding = 12)
+        self.addButton(xstr("button_login_register"),
+                       clicked = self._registerClicked,
+                       tooltip = xstr("button_login_register_tooltip"))
 
         self.addButton(xstr("button_offline"),
@@ -335,10 +352,4 @@
         self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))
 
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is being performed."""
-        return self._entranceExam.get_active() and \
-               self._pilotID.get_text()!=""
 
     @property
@@ -379,10 +390,10 @@
         pilotID = self._pilotID.get_text()
         password = self._password.get_text()
-        entranceExam = self._entranceExam.get_active()
-        self._password.set_sensitive(not entranceExam)
-        self._rememberButton.set_sensitive(password!="" and not entranceExam)
-        self._entranceExam.set_sensitive(pilotID!="")
-        self._loginButton.set_sensitive(pilotID!="" and
-                                        (password!="" or entranceExam))
+        self._rememberButton.set_sensitive(password!="")
+        self._loginButton.set_sensitive(pilotID!="" and password!="")
+
+    def _registerClicked(self, button):
+        """Called when the Register button was clicked."""
+        self._wizard.jumpPage("register")
 
     def _offlineClicked(self, button):
@@ -396,6 +407,5 @@
         self._wizard.login(self._handleLoginResult,
                            self._pilotID.get_text(),
-                           self._password.get_text(),
-                           self.entranceExam)
+                           self._password.get_text())
 
     def _handleLoginResult(self, returned, result):
@@ -413,5 +423,8 @@
 
             config.save()
-            self._wizard.nextPage()
+            if result.rank=="STU":
+                self._wizard.jumpPage("student")
+            else:
+                self._wizard.nextPage()
 
 #-----------------------------------------------------------------------------
@@ -423,5 +436,6 @@
         help = xstr("flightsel_help")
         completedHelp = xstr("flightsel_chelp")
-        super(FlightSelectionPage, self).__init__(wizard, xstr("flightsel_title"),
+        super(FlightSelectionPage, self).__init__(wizard, "flightsel",
+                                                  xstr("flightsel_title"),
                                                   help, completedHelp = completedHelp)
 
@@ -603,5 +617,5 @@
         """Called when the forward button was clicked."""
         if self._completed:
-            self._wizard.jumpPage(self._nextDistance, finalize = False)
+            self._wizard.jumpPage(self._nextID, finalize = False)
         else:
             self._flightSelected()
@@ -658,30 +672,30 @@
                                          const.PLANE_AWAY)
         else:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
+            self._nextID = "connect"
+            self._wizard.jumpPage("connect")
 
     def _fleetRetrieved(self, fleet):
         """Called when the fleet has been retrieved."""
         if fleet is None:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
+            self._nextID = "connect"
+            self._wizard.jumpPage("connect")
         else:
             plane = fleet[self._wizard._bookedFlight.tailNumber]
             if plane is None:
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
+                self._nextID = "connect"
+                self._wizard.jumpPage("connect")
             elif plane.gateNumber is not None and \
                  not fleet.isGateConflicting(plane):
                 self._wizard._departureGate = plane.gateNumber
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
+                self._nextID = "connect"
+                self._wizard.jumpPage("connect")
             else:
-                self._nextDistance = 1
-                self._wizard.nextPage()
+                self._nextID = "gatesel"
+                self._wizard.jumpPage("gatesel")
 
     def _planeUpdated(self, success):
         """Callback for the plane updating."""
-        self._nextDistance = 2
-        self._wizard.jumpPage(2)
+        self._nextID = "connect"
+        self._wizard.jumpPage("connect")
 
     def _getSaveDialog(self):
@@ -786,5 +800,6 @@
     def __init__(self, wizard):
         """Construct the gate selection page."""
-        super(GateSelectionPage, self).__init__(wizard, xstr("gatesel_title"),
+        super(GateSelectionPage, self).__init__(wizard, "gatesel",
+                                                xstr("gatesel_title"),
                                                 xstr("gatesel_help"))
 
@@ -849,5 +864,5 @@
             self._gateSelected()
         else:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
 
     def _rowActivated(self, flightList, path, column):
@@ -871,5 +886,5 @@
         """Callback for the plane updating call."""
         if success is None or success:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
         else:
             dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
@@ -887,7 +902,554 @@
         """Called when the fleet has been retrieved."""
         if fleet is None:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
         else:
             self.activate()
+
+#-----------------------------------------------------------------------------
+
+class RegisterPage(Page):
+    """A page to enter the registration data."""
+
+    # The minimal year of birth
+    _minYearOfBirth = 1900
+
+    # The maximal year of birth
+    _maxYearOfBirth = datetime.date.today().year
+
+    # The regular expression to check the e-mail address with
+    _emailAddressRE = re.compile("[^@]+@[^@]+\.[^@]+")
+
+    def __init__(self, wizard):
+        """Construct the registration page."""
+        super(RegisterPage, self).__init__(wizard, "register",
+                                           xstr("register_title"),
+                                           xstr("register_help"))
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(12, 3)
+        table.set_row_spacings(4)
+        table.set_col_spacings(32)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_name1"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._name1 = gtk.Entry()
+        self._name1.connect("changed", self._updateButtons)
+        self._name1.set_tooltip_text(xstr("register_name1_tooltip"))
+        self._name1.set_width_chars(15)
+        table.attach(self._name1, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._name1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_name2"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._name2 = gtk.Entry()
+        self._name2.connect("changed", self._updateButtons)
+        self._name2.set_tooltip_text(xstr("register_name2_tooltip"))
+        self._name2.set_width_chars(15)
+        table.attach(self._name2, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._name2)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_year_of_birth"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+
+        self._yearOfBirth = gtk.SpinButton()
+        self._yearOfBirth.set_increments(step = 1, page = 10)
+        self._yearOfBirth.set_range(min = RegisterPage._minYearOfBirth,
+                                    max = RegisterPage._maxYearOfBirth)
+        self._yearOfBirth.set_numeric(True)
+        self._yearOfBirth.set_tooltip_text(xstr("register_year_of_birth_tooltip"))
+        self._yearOfBirth.set_width_chars(5)
+        self._yearOfBirth.connect("changed", self._updateButtons)
+        self._yearOfBirth.connect("value-changed", self._updateButtons)
+        alignment.add(self._yearOfBirth)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._yearOfBirth)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_email"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._emailAddress = gtk.Entry()
+        self._emailAddress.connect("changed", self._updateButtons)
+        self._emailAddress.set_tooltip_text(xstr("register_email_tooltip"))
+        table.attach(self._emailAddress, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._emailAddress)
+
+        self._emailAddressPublic = gtk.CheckButton(xstr("register_email_public"))
+        self._emailAddressPublic.set_use_underline(True)
+        self._emailAddressPublic.set_tooltip_text(xstr("register_email_public_tooltip"))
+        table.attach(self._emailAddressPublic, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_vatsim_id"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        self._vatsimID = IntegerEntry()
+        self._vatsimID.connect("changed", self._updateButtons)
+        self._vatsimID.set_tooltip_text(xstr("register_vatsim_id_tooltip"))
+        self._vatsimID.set_width_chars(7)
+        alignment.add(self._vatsimID)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._vatsimID)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_ivao_id"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        self._ivaoID = IntegerEntry()
+        self._ivaoID.connect("changed", self._updateButtons)
+        self._ivaoID.set_tooltip_text(xstr("register_ivao_id_tooltip"))
+        self._ivaoID.set_width_chars(7)
+        alignment.add(self._ivaoID)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._ivaoID)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_phone_num"))
+        label.set_use_underline(True)
+        label.set_use_markup(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._phoneNumber = gtk.Entry()
+        self._phoneNumber.set_tooltip_text(xstr("register_phone_num_tooltip"))
+        table.attach(self._phoneNumber, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._phoneNumber)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_nationality"))
+        label.set_use_underline(True)
+        label.set_use_markup(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+
+        self._nationality = gtk.Entry()
+        self._nationality.set_tooltip_text(xstr("register_nationality_tooltip"))
+        table.attach(self._nationality, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._nationality)
+
+        placeholder = gtk.Label()
+        placeholder.set_text(xstr("register_password_mismatch"))
+        placeholder.set_use_markup(True)
+        placeholder.set_child_visible(False)
+        placeholder.hide()
+        table.attach(placeholder, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_password"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        self._password.connect("changed", self._updateButtons)
+        self._password.set_tooltip_text(xstr("register_password_tooltip"))
+        table.attach(self._password, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._password)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        self._passwordStatus = gtk.Label()
+        alignment.add(self._passwordStatus)
+        table.attach(alignment, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_password2"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._password2 = gtk.Entry()
+        self._password2.set_visibility(False)
+        self._password2.connect("changed", self._updateButtons)
+        self._password2.set_tooltip_text(xstr("register_password2_tooltip"))
+        table.attach(self._password2, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._password2)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        self._password2Status = gtk.Label()
+        alignment.add(self._password2Status)
+        table.attach(alignment, 2, 3, row, row+1)
+
+        row += 1
+
+        self._rememberButton = gtk.CheckButton(xstr("remember_password"))
+        self._rememberButton.set_use_underline(True)
+        self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
+        table.attach(self._rememberButton, 1, 2, row, row+1)
+
+        cancelButton = \
+          self.addButton(xstr("button_cancel"))
+        cancelButton.connect("clicked", self._cancelClicked)
+
+        self._registerButton = \
+          self.addButton(xstr("button_register"), default = True,
+                              tooltip = xstr("button_register_tooltip"))
+        self._registerButton.connect("clicked", self._registerClicked)
+
+        self._updateButtons()
+
+    @property
+    def name1(self):
+        """Get the first name component entered."""
+        return self._name1.get_text()
+
+    @property
+    def name2(self):
+        """Get the second name component entered."""
+        return self._name2.get_text()
+
+    @property
+    def yearOfBirth(self):
+        """Get the year of birth."""
+        yearOfBirthText = self._yearOfBirth.get_text()
+        return int(yearOfBirthText) if yearOfBirthText else 0
+
+    @property
+    def emailAddress(self):
+        """Get the e-mail address."""
+        return self._emailAddress.get_text()
+
+    @property
+    def emailAddressPublic(self):
+        """Get the whether the e-mail address is public."""
+        return self._emailAddressPublic.get_active()
+
+    @property
+    def vatsimID(self):
+        """Get the VATSIM ID."""
+        return self._vatsimID.get_int()
+
+    @property
+    def ivaoID(self):
+        """Get the IVAO ID."""
+        return self._ivaoID.get_int()
+
+    @property
+    def phoneNumber(self):
+        """Get the phone number."""
+        return self._phoneNumber.get_text()
+
+    @property
+    def nationality(self):
+        """Get the nationality."""
+        return self._nationality.get_text()
+
+    @property
+    def password(self):
+        """Get the password."""
+        return self._password.get_text()
+
+    @property
+    def rememberPassword(self):
+        """Get whether the password should be remembered."""
+        return self._rememberButton.get_active()
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._yearOfBirth.set_value(0)
+        self._yearOfBirth.set_text("")
+        self._updateButtons()
+
+    def _updateButtons(self, widget = None):
+        """Update the sensitive state of the buttons"""
+        yearOfBirth = self.yearOfBirth
+
+        emailAddress = self.emailAddress
+        emailAddressMatch = RegisterPage._emailAddressRE.match(emailAddress)
+
+        vatsimID = self.vatsimID
+        ivaoID = self.ivaoID
+
+        password = self.password
+        password2 = self._password2.get_text()
+        if not password:
+            self._passwordStatus.set_text("")
+        elif len(password)<5:
+            self._passwordStatus.set_text(xstr("register_password_too_short"))
+        else:
+            self._passwordStatus.set_text(xstr("register_password_ok"))
+        self._passwordStatus.set_use_markup(True)
+
+        if len(password)<5 or not password2:
+            self._password2Status.set_text("")
+        elif password!=password2:
+            self._password2Status.set_text(xstr("register_password_mismatch"))
+        else:
+            self._password2Status.set_text(xstr("register_password_ok"))
+        self._password2Status.set_use_markup(True)
+
+        sensitive = \
+            len(self.name1)>0 and len(self.name2)>0 and \
+            yearOfBirth>=RegisterPage._minYearOfBirth and \
+            yearOfBirth<=RegisterPage._maxYearOfBirth and \
+            emailAddressMatch is not None and \
+            (vatsimID>=800000 or ivaoID>=100000) and \
+            len(password)>=5 and password==password2
+
+        self._registerButton.set_sensitive(sensitive)
+
+    def _cancelClicked(self, button):
+        """Called when the Cancel button is clicked."""
+        self.goBack()
+
+    def _registerClicked(self, button):
+        """Called when the Register button is clicked."""
+        nameOrder = xstr("register_nameorder")
+
+        if nameOrder=="eastern":
+            surName = self.name1
+            firstName = self.name2
+        else:
+            surName = self.name2
+            firstName = self.name1
+
+        nationality = self.nationality.lower()
+
+        if getLanguage().lower()=="hu" or nationality.find("hung")!=-1 or \
+           nationality.find("magyar")!=-1:
+            requestedNameOrder = "eastern"
+        else:
+            requestedNameOrder = "western"
+
+        registrationData = web.Registration(surName, firstName,
+                                            requestedNameOrder,
+                                            self.yearOfBirth,
+                                            self.emailAddress,
+                                            self.emailAddressPublic,
+                                            self.vatsimID, self.ivaoID,
+                                            self.phoneNumber, self.nationality,
+                                            self.password)
+        print "Registering with data:"
+        print "  name:", self.name1, self.name2, registrationData.firstName, registrationData.surName, requestedNameOrder
+        print "  yearOfBirth:", self.yearOfBirth, registrationData.yearOfBirth
+        print "  emailAddress:", self.emailAddress, registrationData.emailAddress
+        print "  emailAddressPublic:", self.emailAddressPublic, registrationData.emailAddressPublic
+        print "  vatsimID:", self.vatsimID, registrationData.vatsimID
+        print "  ivaoID:", self.ivaoID, registrationData.ivaoID
+        print "  phoneNumber:", self.phoneNumber, registrationData.phoneNumber
+        print "  nationality:", self.nationality, registrationData.nationality
+
+        gui = self._wizard.gui
+        gui.beginBusy(xstr("register_busy"))
+        gui.webHandler.register(self._registerResultCallback, registrationData)
+
+    def _registerResultCallback(self, returned, result):
+        """Called when the registration result is available."""
+        gobject.idle_add(self._handleRegisterResult, returned, result)
+
+    def _handleRegisterResult(self, returned, result):
+        """Handle the registration result."""
+        gui = self._wizard.gui
+
+        gui.endBusy()
+
+        print "Registration result:"
+        print "  returned:", returned
+        if returned:
+            print "  registered:", result.registered
+            if result.registered:
+                print "  pilotID", result.pilotID
+                print "  loggedIn", result.loggedIn
+            print "  emailAlreadyRegistered:", result.emailAlreadyRegistered
+            print "  invalidData:", result.invalidData
+
+        registrationOK = returned and result.registered
+
+        message = xstr("register_ok") if registrationOK \
+                  else xstr("register_failed")
+        secondaryMessage = None
+        if registrationOK:
+            if result.loggedIn:
+                secondaryMessage = xstr("register_info") % (result.pilotID,)
+            else:
+                secondaryMessage = xstr("register_nologin") % (result.pilotID,)
+            messageType = MESSAGETYPE_INFO
+
+            config = gui.config
+            config.pilotID = result.pilotID
+            config.rememberPassword = self.rememberPassword
+            if config.rememberPassword:
+                config.password = self.password
+            else:
+                config.password = ""
+
+            config.save()
+        elif returned and result.emailAlreadyRegistered:
+            secondaryMessage = xstr("register_email_already")
+            messageType = MESSAGETYPE_ERROR
+        elif returned and result.invalidData:
+            secondaryMessage = xstr("register_invalid_data")
+            messageType = MESSAGETYPE_ERROR
+        else:
+            secondaryMessage = xstr("register_error")
+            messageType = MESSAGETYPE_ERROR
+
+        dialog = gtk.MessageDialog(parent = gui.mainWindow,
+                                   type = messageType,
+                                   message_format = message)
+        dialog.set_title(WINDOW_TITLE_BASE + " - " +
+                         xstr("register_result_title"))
+        dialog.format_secondary_markup(secondaryMessage)
+
+        dialog.add_button(xstr("button_ok"), 0)
+
+        dialog.run()
+        dialog.hide()
+
+        if registrationOK:
+            if result.loggedIn:
+                self._wizard.nextPage()
+            else:
+                self._wizard.jumpPage("login")
+
+#-----------------------------------------------------------------------------
+
+class StudentPage(Page):
+    """A page displayed to students after logging in."""
+    _entryExamStatusQueryInterval = 60*1000
+
+    def __init__(self, wizard):
+        """Construct the student page."""
+        super(StudentPage, self).__init__(wizard, "student",
+                                          xstr("student_title"),
+                                          xstr("student_help"))
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.5, yscale = 0.0)
+
+        table = gtk.Table(2, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(32)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        labelAlignment = gtk.Alignment(xalign=0.0, yalign = 0.5,
+                                       xscale=0.0, yscale = 0.0)
+        label = gtk.Label(xstr("student_entry_exam_status"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row + 1, xoptions = 0)
+
+        alignment = gtk.Alignment(xalign=0.0, yalign = 0.5,
+                                  xscale=1.0, yscale = 0.0)
+        self._entryExamStatus = gtk.Label()
+        self._entryExamStatus.set_use_markup(True)
+        self._entryExamStatus.set_alignment(0.0, 0.5)
+        alignment.add(self._entryExamStatus)
+        table.attach(alignment, 1, 2, row, row + 1)
+
+        row += 1
+
+        buttonAlignment = gtk.Alignment(xalign=0.0, xscale=1.0)
+        button = self._entryExamButton = gtk.Button(xstr("student_entry_exam"))
+        button.set_use_underline(True)
+        button.connect("clicked", self._entryExamClicked)
+        button.set_tooltip_text(xstr("student_entry_exam_tooltip"))
+
+        buttonAlignment.add(button)
+        table.attach(buttonAlignment, 0, 2, row, row + 1,
+                     ypadding = 4)
+
+    def activate(self):
+        """Activate the student page."""
+        loginResult = self._wizard.loginResult
+        self._entryExamLink = loginResult.entryExamLink
+
+        self._updateEntryExamStatus(loginResult.entryExamPassed)
+
+        self._getEntryExamStatus()
+
+    def _entryExamClicked(self, button):
+        """Called when the entry exam button is clicked."""
+        webbrowser.open(self._entryExamLink)
+
+    def _getEntryExamStatus(self):
+        """Initiate the query of the entry exam status after the interval."""
+        gobject.timeout_add(StudentPage._entryExamStatusQueryInterval,
+                            lambda: self._wizard.gui.webHandler. \
+                            getEntryExamStatus(self._entryExamStatusCallback))
+
+    def _entryExamStatusCallback(self, returned, result):
+        """Called when the entry exam status is available."""
+        gobject.idle_add(self._handleEntryExamStatus, returned, result)
+
+    def _handleEntryExamStatus(self, returned, result):
+        """Called when the entry exam status is availabe."""
+        print "_handleEntryExamStatus", returned, result
+        if returned:
+            self._entryExamLink = result.entryExamLink
+            self._updateEntryExamStatus(result.entryExamPassed)
+            self._getEntryExamStatus()
+
+    def _updateEntryExamStatus(self, passed):
+        """Update the entry exam status display and button."""
+        self._entryExamStatus.set_text(xstr("student_entry_exam_passed")
+                                       if passed else
+                                       xstr("student_entry_exam_not_passed"))
+        self._entryExamStatus.set_use_markup(True)
+        self._entryExamButton.set_sensitive(not passed)
 
 #-----------------------------------------------------------------------------
@@ -901,5 +1463,6 @@
                "Then press the Connect button to connect to the simulator."
         completedHelp = "The basic data of your flight can be read below."
-        super(ConnectPage, self).__init__(wizard, xstr("connect_title"),
+        super(ConnectPage, self).__init__(wizard, "connect",
+                                          xstr("connect_title"),
                                           xstr("connect_help"),
                                           completedHelp = xstr("connect_chelp"))
@@ -1084,5 +1647,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(PayloadPage, self).__init__(wizard, xstr("payload_title"),
+        super(PayloadPage, self).__init__(wizard, "payload",
+                                          xstr("payload_title"),
                                           xstr("payload_help"),
                                           completedHelp = xstr("payload_chelp"))
@@ -1311,5 +1875,6 @@
     current time from the flight simulator."""
     def __init__(self, wizard):
-        super(TimePage, self).__init__(wizard, xstr("time_title"),
+        super(TimePage, self).__init__(wizard, "time",
+                                       xstr("time_title"),
                                        xstr("time_help"),
                                        completedHelp = xstr("time_chelp"))
@@ -1432,5 +1997,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(RoutePage, self).__init__(wizard, xstr("route_title"),
+        super(RoutePage, self).__init__(wizard, "route",
+                                        xstr("route_title"),
                                         xstr("route_help"),
                                         completedHelp = xstr("route_chelp"))
@@ -1600,8 +2166,8 @@
         if self._wizard.gui.config.useSimBrief and \
            self._wizard.usingSimBrief is not False:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("simbrief_setup")
         else:
             self._wizard.usingSimBrief = False
-            self._wizard.jumpPage(3)
+            self._wizard.jumpPage("fuel")
 
 #-----------------------------------------------------------------------------
@@ -1762,5 +2328,5 @@
         """Construct the setup page."""
 
-        super(SimBriefSetupPage, self).__init__(wizard,
+        super(SimBriefSetupPage, self).__init__(wizard, "simbrief_setup",
                                                 xstr("simbrief_setup_title"),
                                                 xstr("simbrief_setup_help"),
@@ -2081,5 +2647,5 @@
 
                 self._wizard.usingSimBrief = False
-                self._wizard.jumpPage(2, fromPageShift = 1)
+                self._wizard.jumpPage("fuel", fromPageShift = 1)
 
     def _getPlan(self):
@@ -2166,5 +2732,5 @@
         """Construct the setup page."""
 
-        super(SimBriefingPage, self).__init__(wizard,
+        super(SimBriefingPage, self).__init__(wizard, "simbrief_result",
                                               xstr("simbrief_result_title"), "")
 
@@ -2422,5 +2988,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(FuelPage, self).__init__(wizard, xstr("fuel_title"),
+        super(FuelPage, self).__init__(wizard, "fuel",
+                                       xstr("fuel_title"),
                                        xstr("fuel_help_pre") +
                                        xstr("fuel_help_post"),
@@ -2474,7 +3041,7 @@
             self._pump()
         elif self._wizard.usingSimBrief:
-            self._wizard.jumpPage(3)
+            self._wizard.jumpPage("takeoff")
         else:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("briefing1")
 
     def _setupTanks(self, tankData):
@@ -2522,5 +3089,5 @@
             if self._wizard.usingSimBrief:
                 self._wizard.gui.startMonitoring()
-                self._wizard.jumpPage(3)
+                self._wizard.jumpPage("takeoff")
             else:
                 bookedFlight = self._wizard._bookedFlight
@@ -2591,9 +3158,13 @@
         self._departure = departure
 
-        title = xstr("briefing_title") % (1 if departure else 2,
+        number = 1 if departure else 2
+
+        title = xstr("briefing_title") % (number,
                                           xstr("briefing_departure")
                                           if departure
                                           else xstr("briefing_arrival"))
-        super(BriefingPage, self).__init__(wizard, title, xstr("briefing_help"),
+        super(BriefingPage, self).__init__(wizard,
+                                           "briefing%d" % (number,),
+                                           title, xstr("briefing_help"),
                                            completedHelp = xstr("briefing_chelp"))
 
@@ -2782,5 +3353,6 @@
     def __init__(self, wizard):
         """Construct the takeoff page."""
-        super(TakeoffPage, self).__init__(wizard, xstr("takeoff_title"),
+        super(TakeoffPage, self).__init__(wizard, "takeoff",
+                                          xstr("takeoff_title"),
                                           xstr("takeoff_help"),
                                           completedHelp = xstr("takeoff_chelp"))
@@ -3270,5 +3842,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(CruisePage, self).__init__(wizard, xstr("cruise_title"),
+        super(CruisePage, self).__init__(wizard, "cruise",
+                                         xstr("cruise_title"),
                                          xstr("cruise_help"))
 
@@ -3384,5 +3957,6 @@
     def __init__(self, wizard):
         """Construct the landing page."""
-        super(LandingPage, self).__init__(wizard, xstr("landing_title"),
+        super(LandingPage, self).__init__(wizard, "landing",
+                                          xstr("landing_title"),
                                           xstr("landing_help"),
                                           completedHelp = xstr("landing_chelp"))
@@ -3698,5 +4272,6 @@
         """Construct the finish page."""
         help = xstr("finish_help") + xstr("finish_help_goodtime")
-        super(FinishPage, self).__init__(wizard, xstr("finish_title"), help)
+        super(FinishPage, self).__init__(wizard, "finish",
+                                         xstr("finish_title"), help)
 
         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
@@ -4256,4 +4831,6 @@
         self._pages.append(FlightSelectionPage(self))
         self._pages.append(GateSelectionPage(self))
+        self._pages.append(RegisterPage(self))
+        self._pages.append(StudentPage(self))
         self._pages.append(ConnectPage(self))
         self._payloadPage = PayloadPage(self)
@@ -4328,5 +4905,5 @@
     def entranceExam(self):
         """Get whether an entrance exam is about to be taken."""
-        return self._loginPage.entranceExam
+        return self._loginResult is not None and self._loginResult.rank=="STU"
 
     @property
@@ -4551,9 +5128,14 @@
     def nextPage(self, finalize = True):
         """Go to the next page."""
-        self.jumpPage(1, finalize)
-
-    def jumpPage(self, count, finalize = True, fromPageShift = None):
+        nextPageID = self._pages[self._currentPage].nextPageID
+        self.jumpPage(1 if nextPageID is None else nextPageID, finalize)
+
+    def jumpPage(self, countOrID, finalize = True, fromPageShift = None):
         """Go to the page which is 'count' pages after the current one."""
-        self.setCurrentPage(self._currentPage + count,
+        if isinstance(countOrID, str):
+            targetIndex = self._getIndexOf(countOrID)
+        else:
+            targetIndex = self._currentPage + countOrID
+        self.setCurrentPage(targetIndex,
                             finalize = finalize, fromPageShift = fromPageShift)
 
@@ -4616,5 +5198,5 @@
         #self.setCurrentPage(10)
 
-    def login(self, callback, pilotID, password, entranceExam):
+    def login(self, callback, pilotID, password):
         """Called when the login button was clicked."""
         self._loginCallback = callback
@@ -4624,5 +5206,4 @@
             pilotID = loginResult.pilotID
             password = loginResult.password
-            entranceExam = loginResult.entranceExam
             busyMessage = xstr("reload_busy")
         else:
@@ -4633,6 +5214,5 @@
 
         self.gui.webHandler.login(self._loginResultCallback,
-                                  pilotID, password,
-                                  entranceExam = entranceExam)
+                                  pilotID, password)
 
     def reloadFlights(self, callback):
@@ -4682,5 +5262,4 @@
         """Handle the login result."""
         self.gui.endBusy()
-        returned = True
         isReload = self._loginResult is not None
         if returned:
@@ -4788,3 +5367,13 @@
                 self._arrivalBriefingPage.setMETAR(metar)
 
+    def _getIndexOf(self, pageID):
+        """Get the index for the given page ID.
+
+        It is an assertion failure if the ID is not found."""
+        for index in range(0, len(self._pages)):
+            page = self._pages[index]
+            if page.id==pageID:
+                return index
+        assert False
+
 #-----------------------------------------------------------------------------
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 733)
+++ src/mlx/gui/gui.py	(revision 755)
@@ -90,5 +90,10 @@
         self._sendBugReportCallback = None
 
-        self.webHandler = web.Handler()
+        self._credentialsCondition = threading.Condition()
+        self._credentialsAvailable = False
+        self._credentialsUserName = None
+        self._credentialsPassword = None
+
+        self.webHandler = web.Handler(config, self._getCredentialsCallback)
         self.webHandler.start()
 
@@ -442,4 +447,6 @@
                                     self._mainWindow)
             self._updater.start()
+        else:
+            self.updateDone()
 
         cef.initialize(self._programDirectory, self._cefInitialized)
@@ -452,4 +459,8 @@
 
         self._disconnect()
+
+    def updateDone(self):
+        """Called when the update is done (and there is no need to restart)."""
+        gobject.idle_add(self._updateDone)
 
     def connected(self, fsType, descriptor):
@@ -1635,2 +1646,78 @@
         """Set the anti-ice on indicator."""
         self._wizard.landingAntiIceOn = value
+
+    def _getCredentialsCallback(self):
+        """Called when the web handler asks for the credentials."""
+        # FIXME: this is almost the same as
+        # SimBriefSetupPage._getCredentialsCallback
+        with self._credentialsCondition:
+            self._credentialsAvailable = False
+
+            gobject.idle_add(self._getCredentials)
+
+            while not self._credentialsAvailable:
+                self._credentialsCondition.wait()
+
+            return (self._credentialsUserName, self._credentialsPassword)
+
+    def _getCredentials(self):
+        """Get the credentials."""
+        # FIXME: this is almost the same as
+        # SimBriefSetupPage._getCredentials
+        with self._credentialsCondition:
+            config = self.config
+
+            dialog = CredentialsDialog(self, config.pilotID, config.password,
+                                       xstr("login_title"),
+                                       xstr("button_cancel"),
+                                       xstr("button_ok"),
+                                       xstr("label_pilotID"),
+                                       xstr("login_pilotID_tooltip"),
+                                       xstr("label_password"),
+                                       xstr("login_password_tooltip"),
+                                       xstr("login_info"),
+                                       config.rememberPassword,
+                                       xstr("remember_password"),
+                                       xstr("login_remember_tooltip"))
+            response = dialog.run()
+
+            if response==RESPONSETYPE_OK:
+                self._credentialsUserName = dialog.userName
+                self._credentialsPassword = dialog.password
+                rememberPassword = dialog.rememberPassword
+
+                config.pilotID = self._credentialsUserName
+
+                config.password = \
+                  self._credentialsPassword if rememberPassword else ""
+                config.rememberPassword = rememberPassword
+
+                config.save()
+            else:
+                self._credentialsUserName = None
+                self._credentialsPassword = None
+
+            self._credentialsAvailable = True
+            self._credentialsCondition.notify()
+
+    def _updateDone(self):
+        """Called when the update is done.
+
+        It checks if we already know the PID, and if not, asks the user whether
+        to register."""
+        if not self.config.pilotID and not self.config.password:
+            dialog = gtk.MessageDialog(parent = self._mainWindow,
+                                       type = MESSAGETYPE_QUESTION,
+                                       message_format = xstr("register_ask"))
+
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.format_secondary_markup(xstr("register_ask_sec"))
+
+            dialog.add_button(xstr("button_cancel"), 0)
+            dialog.add_button(xstr("button_register"), 1)
+            dialog.set_default_response(1)
+
+            result = dialog.run()
+            dialog.hide()
+            if result == 1:
+                self._wizard.jumpPage("register")
Index: src/mlx/gui/prefs.py
===================================================================
--- src/mlx/gui/prefs.py	(revision 684)
+++ src/mlx/gui/prefs.py	(revision 738)
@@ -276,4 +276,6 @@
 
         self._updateURL.set_text(config.updateURL)
+
+        self._useRPC.set_active(config.useRPC)
 
         self._settingFromConfig = False
@@ -320,4 +322,5 @@
         config.autoUpdate = self._autoUpdate.get_active()
         config.updateURL = self._updateURL.get_text()
+        config.useRPC = self._useRPC.get_active()
 
     def _buildGeneral(self):
@@ -814,4 +817,10 @@
         updateURLBox.pack_start(self._updateURL, True, True, 4)
 
+        self._useRPC = gtk.CheckButton(xstr("prefs_use_rpc"))
+        mainBox.pack_start(self._useRPC, False, False, 16)
+
+        self._useRPC.set_use_underline(True)
+        self._useRPC.set_tooltip_text(xstr("prefs_use_rpc_tooltip"))
+
         return mainAlignment
 
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 300)
+++ src/mlx/gui/update.py	(revision 755)
@@ -124,4 +124,5 @@
         if not self._waitAfterFinish:
             gobject.idle_add(self._progressWindow.hide)
+            self._gui.updateDone()
 
     def downloadingManifest(self):
@@ -286,4 +287,6 @@
         if self._restart:
             self._gui.restart()
+        else:
+            self._gui.updateDone()
 
 #-------------------------------------------------------------------------------
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 437)
+++ src/mlx/pirep.py	(revision 743)
@@ -4,4 +4,6 @@
 import const
 import cPickle as pickle
+import datetime
+import time
 
 #------------------------------------------------------------------------------
@@ -19,4 +21,9 @@
 class PIREP(object):
     """A pilot's report of a flight."""
+    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
+                     const.FLIGHTTYPE_OLDTIMER : "OT",
+                     const.FLIGHTTYPE_VIP : "VIP",
+                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
+
     @staticmethod
     def _formatLine(timeStr, line):
@@ -24,4 +31,9 @@
         some other things."""
         return "[" + timeStr + "]-[" + line + "]"
+
+    @staticmethod
+    def formatTimestampForRPC(t):
+        """Format the given timestamp for RPC."""
+        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(t))
 
     @staticmethod
@@ -91,4 +103,29 @@
         self.faultLineIndexes = logger.faultLineIndexes
 
+    @property
+    def flightTypeText(self):
+        """Get the text representation of the flight type."""
+        return PIREP._flightTypes[self.flightType]
+
+    @property
+    def blockTimeStartText(self):
+        """Get the beginning of the block time in string format."""
+        return PIREP.formatTimestampForRPC(self.blockTimeStart)
+
+    @property
+    def flightTimeStartText(self):
+        """Get the beginning of the flight time in string format."""
+        return PIREP.formatTimestampForRPC(self.flightTimeStart)
+
+    @property
+    def flightTimeEndText(self):
+        """Get the end of the flight time in string format."""
+        return PIREP.formatTimestampForRPC(self.flightTimeEnd)
+
+    @property
+    def blockTimeEndText(self):
+        """Get the end of the block time in string format."""
+        return PIREP.formatTimestampForRPC(self.blockTimeEnd)
+
     def getACARSText(self):
         """Get the ACARS text.
@@ -152,2 +189,39 @@
             print u"Failed saving PIREP to %s: %s" % (path, error)
             return error
+
+    def _serialize(self):
+        """Serialize the PIREP for JSON-RPC."""
+        attrs = {}
+        attrs["log"] = self.getACARSText()
+        attrs["numPassengers"] = self.numPassengers
+        attrs["numCrew"] = self.numCrew
+        attrs["cargoWeight"] = self.cargoWeight
+        attrs["bagWeight"] = self.bagWeight
+        attrs["mailWeight"] = self.mailWeight
+        attrs["flightType"] = self.flightTypeText
+        attrs["online"] = 1 if self.online else 0
+        attrs["blockTimeStart"] = self.blockTimeStartText
+        attrs["blockTimeEnd"] = self.blockTimeEndText
+        attrs["flightTimeStart"] = self.flightTimeStartText
+        attrs["flightTimeEnd"] = self.flightTimeEndText
+        attrs["timeComment"] = self.getTimeComment()
+        attrs["fuelUsed"] = self.fuelUsed
+        attrs["departureRunway"] = self.departureRunway
+        attrs["arrivalRunway"] = self.arrivalRunway
+        attrs["departureMETAR"] = self.departureMETAR
+        attrs["arrivalMETAR"] = self.arrivalMETAR
+        attrs["filedCruiseLevel"] = self.filedCruiseAltitude / 100.0
+        attrs["cruiseLevel"] = self.cruiseAltitude / 100.0
+        attrs["sid"] = self.sid
+        attrs["route"] = self.route
+        attrs["star"] = self.star
+        attrs["approachType"] = self.approachType
+        attrs["comments"] = self.comments
+        attrs["flightDefects"] = self.flightDefects
+        attrs["ratingText"] = self.getRatingText()
+        attrs["rating"] = max(0.0, self.rating)
+        attrs["flownDistance"] = self.flownDistance
+        # FIXME: it should be stored in the PIREP when it is sent later
+        attrs["flightDate"] = datetime.date.today().strftime("%Y-%m-%d")
+
+        return ([], attrs)
Index: src/mlx/rpc.py
===================================================================
--- src/mlx/rpc.py	(revision 764)
+++ src/mlx/rpc.py	(revision 764)
@@ -0,0 +1,338 @@
+import const
+import rpccommon
+
+from common import MAVA_BASE_URL
+
+import jsonrpclib
+import hashlib
+import datetime
+
+#---------------------------------------------------------------------------------------
+
+class RPCObject(object):
+    """Base class for objects read from RPC calls.
+
+    It is possible to construct it from a dictionary."""
+    def __init__(self, value, instructions = {}):
+        """Construct the object.
+
+        value is the dictionary returned by the call.
+
+        info is a mapping from names to 'instructions' on what to do with the
+        corresponding values. If the instruction is None, it will be ignored.
+        If the instruction is a function, the value will be passed to it and
+        the return value will be stored in the object.
+
+        For all other names, the value will be stored as the same-named
+        attribute."""
+        for (key, value) in value.iteritems():
+            if key in instructions:
+                instruction = instructions[key]
+                if instruction is None:
+                    continue
+
+                value = instruction(value)
+            setattr(self, key, value)
+
+#---------------------------------------------------------------------------------------
+
+class Reply(RPCObject):
+    """The generic reply structure."""
+
+#---------------------------------------------------------------------------------------
+
+class BookedFlight(RPCObject):
+    """A booked flight."""
+    # FIXME: copied from web.BookedFlight
+    TYPECODE2TYPE = { "736"  : const.AIRCRAFT_B736,
+                      "73G"  : const.AIRCRAFT_B737,
+                      "738"  : const.AIRCRAFT_B738,
+                      "73H"  : const.AIRCRAFT_B738C,
+                      "733"  : const.AIRCRAFT_B733,
+                      "734"  : const.AIRCRAFT_B734,
+                      "735"  : const.AIRCRAFT_B735,
+                      "DH4"  : const.AIRCRAFT_DH8D,
+                      "762"  : const.AIRCRAFT_B762,
+                      "763"  : const.AIRCRAFT_B763,
+                      "CR2"  : const.AIRCRAFT_CRJ2,
+                      "F70"  : const.AIRCRAFT_F70,
+                      "LI2"  : const.AIRCRAFT_DC3,
+                      "TU3"  : const.AIRCRAFT_T134,
+                      "TU5"  : const.AIRCRAFT_T154,
+                      "YK4"  : const.AIRCRAFT_YK40,
+                      "146"  : const.AIRCRAFT_B462 }
+
+    # FIXME: copied from web.BookedFlight
+    @staticmethod
+    def _decodeAircraftType(typeCode):
+        """Decode the aircraft type from the given typeCode."""
+        if typeCode in BookedFlight.TYPECODE2TYPE:
+            return BookedFlight.TYPECODE2TYPE[typeCode]
+        else:
+            raise Exception("Invalid aircraft type code: '" + typeCode + "'")
+
+    # FIXME: copied from web.BookedFlight
+    @staticmethod
+    def getDateTime(date, time):
+        """Get a datetime object from the given textual date and time."""
+        return datetime.datetime.strptime(date + " " + time,
+                                          "%Y-%m-%d %H:%M:%S")
+
+    # The instructions for the construction
+    _instructions = {
+        "numPassengers" : int,
+        "numCrew" : int,
+        "bagWeight" : int,
+        "cargoWeight" : int,
+        "mailWeight" : int,
+        "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value)
+        }
+
+    def __init__(self, value):
+        """Construct the booked flight object from the given RPC result
+        value."""
+        super(BookedFlight, self).__init__(value, BookedFlight._instructions)
+        self.departureTime = \
+          BookedFlight.getDateTime(self.date, self.departureTime)
+        self.arrivalTime = \
+          BookedFlight.getDateTime(self.date, self.arrivalTime)
+        if self.arrivalTime<self.departureTime:
+            self.arrivalTime += datetime.timedelta(days = 1)
+
+#---------------------------------------------------------------------------------------
+
+class Plane(rpccommon.Plane, RPCObject):
+    """An airplane in the fleet."""
+    _instructions = {
+        "status" : lambda value: rpccommon.Plane.str2status(value),
+        "gateNumber" : lambda value: value if value else None
+        }
+
+    def __init__(self, value):
+        """Construct the plane."""
+        RPCObject.__init__(self, value, instructions = Plane._instructions)
+
+#---------------------------------------------------------------------------------------
+
+class Fleet(rpccommon.Fleet):
+    """The fleet."""
+    def __init__(self, value):
+        """Construct the fleet."""
+        super(Fleet, self).__init__()
+        for planeValue in value:
+            self._addPlane(Plane(planeValue))
+
+#---------------------------------------------------------------------------------------
+
+class Registration(object):
+    """Data for registration."""
+    def __init__(self, surName, firstName, nameOrder,
+                 yearOfBirth, emailAddress, emailAddressPublic,
+                 vatsimID, ivaoID, phoneNumber, nationality, password):
+        """Construct the registration data."""
+        self.surName = surName
+        self.firstName = firstName
+        self.nameOrder = nameOrder
+        self.yearOfBirth = yearOfBirth
+        self.emailAddress = emailAddress
+        self.emailAddressPublic = 1 if emailAddressPublic is True else \
+          0 if emailAddressPublic is False else emailAddressPublic
+        self.vatsimID = "" if vatsimID is None else vatsimID
+        self.ivaoID = "" if ivaoID is None else ivaoID
+        self.phoneNumber = phoneNumber
+        self.nationality = nationality
+        self.password = password
+
+#---------------------------------------------------------------------------------------
+
+class RPCException(Exception):
+    """An exception thrown by RPC operations."""
+    def __init__(self, result, message = None):
+        """Construct the exception."""
+        self._result = result
+        if message is None:
+            message = "RPC call failed with result code: %d" % (result,)
+        super(RPCException, self).__init__(message)
+
+    @property
+    def result(self):
+        """Get the result code."""
+        return self._result
+
+#---------------------------------------------------------------------------------------
+
+class Client(object):
+    """The RPC client interface."""
+    # Result code: OK
+    RESULT_OK = 0
+
+    # Result code: the login has failed
+    RESULT_LOGIN_FAILED = 1
+
+    # Result code: the given session ID is unknown (it might have expired).
+    RESULT_SESSION_INVALID = 2
+
+    # Result code: some database error
+    RESULT_DATABASE_ERROR = 3
+
+    # Result code: invalid data
+    RESULT_INVALID_DATA = 4
+
+    # Result code: the flight does not exist
+    RESULT_FLIGHT_NOT_EXISTS = 101
+
+    # Result code: the flight has already been reported.
+    RESULT_FLIGHT_ALREADY_REPORTED = 102
+
+    # Result code: a user with the given e-mail address already exists
+    RESULT_EMAIL_ALREADY_REGISTERED = 103
+
+    def __init__(self, getCredentialsFn):
+        """Construct the client."""
+        self._getCredentialsFn = getCredentialsFn
+
+        self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php")
+
+        self._userName = None
+        self._passwordHash = None
+        self._sessionID = None
+        self._loginCount = 0
+
+    @property
+    def valid(self):
+        """Determine if the client is valid, i.e. there is a session ID
+        stored."""
+        return self._sessionID is not None
+
+    def setCredentials(self, userName, password):
+        """Set the credentials for future logins."""
+
+        self._userName = userName
+
+        md5 = hashlib.md5()
+        md5.update(password)
+        self._passwordHash = md5.hexdigest()
+
+        self._sessionID = None
+
+    def register(self, registrationData):
+        """Register with the given data.
+
+        Returns a tuple of:
+        - the error code,
+        - the PID if there is no error."""
+        reply = Reply(self._server.register(registrationData))
+
+        return (reply.result,
+                reply.value["pid"] if reply.result==Client.RESULT_OK else None)
+
+    def login(self):
+        """Login using the given previously set credentials.
+
+        The session ID is stored in the object and used for later calls.
+
+        Returns the name of the pilot on success, or None on error."""
+        self._sessionID = None
+
+        reply = Reply(self._server.login(self._userName, self._passwordHash))
+        if reply.result == Client.RESULT_OK:
+            self._loginCount += 1
+            self._sessionID = reply.value["sessionID"]
+            return (reply.value["name"], reply.value["rank"])
+        else:
+            return None
+
+    def getFlights(self):
+        """Get the flights available for performing."""
+        flights = []
+
+        value = self._performCall(lambda sessionID:
+                                  self._server.getFlights(sessionID))
+        for flightData in value:
+            flights.append(BookedFlight(flightData))
+
+        flights.sort(cmp = lambda flight1, flight2:
+                     cmp(flight1.departureTime, flight2.departureTime))
+
+        return flights
+
+    def getEntryExamStatus(self):
+        """Get the status of the exams needed for joining MAVA."""
+        value = self._performCall(lambda sessionID:
+                                  self._server.getEntryExamStatus(sessionID))
+        return (value["entryExamPassed"], value["entryExamLink"],
+                value["checkFlightStatus"])
+
+    def getFleet(self):
+        """Query and return the fleet."""
+        value = self._performCall(lambda sessionID:
+                                  self._server.getFleet(sessionID))
+
+        return Fleet(value)
+
+    def updatePlane(self, tailNumber, status, gateNumber):
+        """Update the state and position of the plane with the given tail
+        number."""
+        status = rpccommon.Plane.status2str(status)
+        self._performCall(lambda sessionID:
+                          self._server.updatePlane(sessionID, tailNumber,
+                                                   status, gateNumber))
+
+    def addPIREP(self, flightID, pirep):
+        """Add the PIREP for the given flight."""
+        (result, _value) = \
+          self._performCall(lambda sessionID:
+                            self._server.addPIREP(sessionID, flightID, pirep),
+                            acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
+                                             Client.RESULT_FLIGHT_NOT_EXISTS])
+        return result
+
+    def updateOnlineACARS(self, acars):
+        """Update the online ACARS from the given data."""
+        self._performCall(lambda sessionID:
+                          self._server.updateOnlineACARS(sessionID, acars))
+
+    def _performCall(self, callFn, acceptResults = []):
+        """Perform a call using the given call function.
+
+        acceptResults should be a list of result codes that should be accepted
+        besides RESULT_OK. If this list is not empty, the returned value is a
+        tuple of the result code and the corresponding value. Otherwise only
+        RESULT_OK is accepted, and the value is returned.
+
+        All other error codes are converted to exceptions."""
+        numAttempts = 0
+        while True:
+            reply = Reply(callFn(self._ensureSession()))
+            numAttempts += 1
+            result = reply.result
+            if result==Client.RESULT_SESSION_INVALID:
+                self._sessionID = None
+                if numAttempts==3:
+                    raise RPCException(result)
+            elif result!=Client.RESULT_OK and result not in acceptResults:
+                raise RPCException(result)
+            elif acceptResults:
+                return (result, reply.value)
+            else:
+                return reply.value
+
+    def _ensureSession(self):
+        """Ensure that there is a valid session ID."""
+        while self._sessionID is None:
+            if self._userName is not None and self._passwordHash is not None:
+                if not self.login():
+                    self._userName = self._passwordHash = None
+
+            if self._userName is None or self._passwordHash is None:
+                (self._userName, password) = self._getCredentialsFn()
+                if self._userName is None:
+                    raise RPCException(Client.RESULT_LOGIN_FAILED)
+
+                md5 = hashlib.md5()
+                md5.update(password)
+                self._passwordHash = md5.hexdigest()
+
+        return self._sessionID
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/rpccommon.py
===================================================================
--- src/mlx/rpccommon.py	(revision 742)
+++ src/mlx/rpccommon.py	(revision 742)
@@ -0,0 +1,93 @@
+# Some common objects for RPC communication
+
+#------------------------------------------------------------------------------
+
+import const
+
+#------------------------------------------------------------------------------
+
+class Plane(object):
+    """Information about an airplane in the fleet."""
+    @staticmethod
+    def str2status(letter):
+        """Convert the given letter into a plane status."""
+        return const.PLANE_HOME if letter=="H" else \
+               const.PLANE_AWAY if letter=="A" else \
+               const.PLANE_PARKING if letter=="P" else \
+               const.PLANE_UNKNOWN
+
+    @staticmethod
+    def status2str(status):
+        """Convert the given status into the corresponding letter code."""
+        return "H" if status==const.PLANE_HOME else \
+               "A" if status==const.PLANE_AWAY else \
+               "P" if status==const.PLANE_PARKING else ""
+
+    def _setStatus(self, letter):
+        """Set the status from the given letter."""
+        self.status = Plane.str2status(letter)
+
+    def __repr__(self):
+        """Get the representation of the plane object."""
+        s = "<Plane: %s %s" % (self.tailNumber,
+                               "home" if self.status==const.PLANE_HOME else \
+                               "away" if self.status==const.PLANE_AWAY else \
+                               "parking" if self.status==const.PLANE_PARKING \
+                               else "unknown")
+        if self.gateNumber is not None:
+            s += " (gate " + self.gateNumber + ")"
+        s += ">"
+        return s
+
+#------------------------------------------------------------------------------
+
+class Fleet(object):
+    """Information about the whole fleet."""
+    def __init__(self):
+        """Construct the fleet information by reading the given file object."""
+        self._planes = {}
+
+    def isGateConflicting(self, plane):
+        """Check if the gate of the given plane conflicts with another plane's
+        position."""
+        for p in self._planes.itervalues():
+            if p.tailNumber!=plane.tailNumber and \
+               p.status==const.PLANE_HOME and \
+               p.gateNumber==plane.gateNumber:
+                return True
+
+        return False
+
+    def getOccupiedGateNumbers(self):
+        """Get a set containing the numbers of the gates occupied by planes."""
+        gateNumbers = set()
+        for p in self._planes.itervalues():
+            if p.status==const.PLANE_HOME and p.gateNumber:
+                gateNumbers.add(p.gateNumber)
+        return gateNumbers
+
+    def updatePlane(self, tailNumber, status, gateNumber = None):
+        """Update the status of the given plane."""
+        if tailNumber in self._planes:
+            plane = self._planes[tailNumber]
+            plane.status = status
+            plane.gateNumber = gateNumber
+
+    def _addPlane(self, plane):
+        """Add the given plane to the fleet."""
+        self._planes[plane.tailNumber] = plane
+
+    def __iter__(self):
+        """Get an iterator over the planes."""
+        for plane in self._planes.itervalues():
+            yield plane
+
+    def __getitem__(self, tailNumber):
+        """Get the plane with the given tail number.
+
+        If the plane is not in the fleet, None is returned."""
+        return self._planes[tailNumber] if tailNumber in self._planes else None
+
+    def __repr__(self):
+        """Get the representation of the fleet object."""
+        return self._planes.__repr__()
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 570)
+++ src/mlx/web.py	(revision 764)
@@ -2,4 +2,9 @@
 import const
 import util
+from rpc import Registration
+import rpc
+import rpccommon
+
+from common import MAVA_BASE_URL
 
 import threading
@@ -255,5 +260,5 @@
 #------------------------------------------------------------------------------
 
-class Plane(object):
+class Plane(rpccommon.Plane):
     """Information about an airplane in the fleet."""
     def __init__(self, s):
@@ -263,4 +268,6 @@
         The first field is the tail number, the second field is the gate
         number, the third field is the plane's status as a character."""
+        super(Plane, self).__init__()
+
         try:
             words = s.split(" ")
@@ -269,8 +276,5 @@
 
             status = words[2] if len(words)>2 else None
-            self.status = const.PLANE_HOME if status=="H" else \
-                          const.PLANE_AWAY if status=="A" else \
-                          const.PLANE_PARKING if status=="P" else \
-                          const.PLANE_UNKNOWN
+            self._setStatus(status)
 
             gateNumber = words[1] if len(words)>1 else ""
@@ -281,24 +285,12 @@
             self.tailNumber = None
 
-    def __repr__(self):
-        """Get the representation of the plane object."""
-        s = "<Plane: %s %s" % (self.tailNumber,
-                               "home" if self.status==const.PLANE_HOME else \
-                               "away" if self.status==const.PLANE_AWAY else \
-                               "parking" if self.status==const.PLANE_PARKING \
-                               else "unknown")
-        if self.gateNumber is not None:
-            s += " (gate " + self.gateNumber + ")"
-        s += ">"
-        return s
-
-
-#------------------------------------------------------------------------------
-
-class Fleet(object):
+#------------------------------------------------------------------------------
+
+class Fleet(rpccommon.Fleet):
     """Information about the whole fleet."""
     def __init__(self, f):
         """Construct the fleet information by reading the given file object."""
-        self._planes = {}
+        super(Fleet, self).__init__()
+
         while True:
             line = readline(f)
@@ -306,47 +298,5 @@
 
             plane = Plane(line)
-            if plane.tailNumber is not None:
-                self._planes[plane.tailNumber] = plane
-
-    def isGateConflicting(self, plane):
-        """Check if the gate of the given plane conflicts with another plane's
-        position."""
-        for p in self._planes.itervalues():
-            if p.tailNumber!=plane.tailNumber and \
-               p.status==const.PLANE_HOME and \
-               p.gateNumber==plane.gateNumber:
-                return True
-
-        return False
-
-    def getOccupiedGateNumbers(self):
-        """Get a set containing the numbers of the gates occupied by planes."""
-        gateNumbers = set()
-        for p in self._planes.itervalues():
-            if p.status==const.PLANE_HOME and p.gateNumber:
-                gateNumbers.add(p.gateNumber)
-        return gateNumbers
-
-    def updatePlane(self, tailNumber, status, gateNumber = None):
-        """Update the status of the given plane."""
-        if tailNumber in self._planes:
-            plane = self._planes[tailNumber]
-            plane.status = status
-            plane.gateNumber = gateNumber
-
-    def __iter__(self):
-        """Get an iterator over the planes."""
-        for plane in self._planes.itervalues():
-            yield plane
-
-    def __getitem__(self, tailNumber):
-        """Get the plane with the given tail number.
-
-        If the plane is not in the fleet, None is returned."""
-        return self._planes[tailNumber] if tailNumber in self._planes else None
-
-    def __repr__(self):
-        """Get the representation of the fleet object."""
-        return self._planes.__repr__()
+            self._addPlane(plane)
 
 #------------------------------------------------------------------------------
@@ -663,9 +613,51 @@
 #------------------------------------------------------------------------------
 
+class RPCRequest(Request):
+    """Common base class for RPC requests.
+
+    It stores the RPC client received from the handler."""
+    def __init__(self, client, callback):
+        """Construct the request."""
+        super(RPCRequest, self).__init__(callback)
+        self._client = client
+
+#------------------------------------------------------------------------------
+
+class Register(RPCRequest):
+    """A registration request."""
+    def __init__(self, client, callback, registrationData):
+        """Construct the request."""
+        super(Register, self).__init__(client, callback)
+        self._registrationData = registrationData
+
+    def run(self):
+        """Perform the registration."""
+
+        registrationData = self._registrationData
+
+        (resultCode, pilotID) = self._client.register(registrationData)
+        result = Result()
+        result.registered = resultCode==rpc.Client.RESULT_OK
+        if result.registered:
+            result.pilotID = pilotID
+
+            self._client.setCredentials(pilotID, registrationData.password)
+            loginResult = self._client.login()
+            result.loggedIn = loginResult is not None
+
+        result.invalidData = \
+          resultCode==rpc.Client.RESULT_INVALID_DATA
+        result.emailAlreadyRegistered = \
+          resultCode==rpc.Client.RESULT_EMAIL_ALREADY_REGISTERED
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class Login(Request):
     """A login request."""
     iso88592decoder = codecs.getdecoder("iso-8859-2")
 
-    def __init__(self, callback, pilotID, password, entranceExam):
+    def __init__(self, callback, pilotID, password):
         """Construct the login request with the given pilot ID and
         password."""
@@ -674,5 +666,4 @@
         self._pilotID = pilotID
         self._password = password
-        self._entranceExam = entranceExam
 
     def run(self):
@@ -686,43 +677,29 @@
         password = md5.hexdigest()
 
-        if self._entranceExam:
-            url = "http://www.virtualairlines.hu/ellenorzo/getflightplan.php?pid=%s" % \
-                  (pilotID,)
-        else:
-            url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
-                  (pilotID, password)
+        url = MAVA_BASE_URL + "/leker2.php?pid=%s&psw=%s" %  (pilotID, password)
 
         result = Result()
-        result.entranceExam = self._entranceExam
 
         f = urllib2.urlopen(url, timeout = 10.0)
 
         status = readline(f)
-        if self._entranceExam:
-            result.loggedIn = status != "#NOEXAM"
-        else:
-            result.loggedIn = status == ".OK."
+        result.loggedIn = status == ".OK."
 
         if result.loggedIn:
             result.pilotID = self._pilotID
             result.password = self._password
+            result.rank = "FO"
             result.flights = []
-            # FIXME: this may not be the correct behaviour
-            # for an entrance exam, but the website returns
-            # an error
-            if self._entranceExam:
-                result.pilotName = result.pilotID
-                result.exams = ""
-            else:
-                result.pilotName = self.iso88592decoder(readline(f))[0]
-                result.exams = readline(f)
-
-                while True:
-                    line = readline(f)
-                    if not line or line == "#ENDPIREP": break
-
-                    flight = BookedFlight(line)
-                    flight.readFromWeb(f)
-                    result.flights.append(flight)
+
+            result.pilotName = self.iso88592decoder(readline(f))[0]
+            result.exams = readline(f)
+
+            while True:
+                line = readline(f)
+                if not line or line == "#ENDPIREP": break
+
+                flight = BookedFlight(line)
+                flight.readFromWeb(f)
+                result.flights.append(flight)
 
             result.flights.sort(cmp = lambda flight1, flight2:
@@ -736,4 +713,57 @@
 #------------------------------------------------------------------------------
 
+class LoginRPC(RPCRequest):
+    """An RPC-based login request."""
+    def __init__(self, client, callback, pilotID, password):
+        """Construct the login request with the given pilot ID and
+        password."""
+        super(LoginRPC, self).__init__(client, callback)
+
+        self._pilotID = pilotID
+        self._password = password
+
+    def run(self):
+        """Perform the login request."""
+        result = Result()
+
+        self._client.setCredentials(self._pilotID, self._password)
+        loginResult = self._client.login()
+        result.loggedIn = loginResult is not None
+        if result.loggedIn:
+            result.pilotID = self._pilotID
+            result.pilotName = loginResult[0]
+            result.rank = loginResult[1]
+            result.password = self._password
+            result.flights = self._client.getFlights()
+            if result.rank=="STU":
+                reply = self._client.getEntryExamStatus()
+                result.entryExamPassed = reply[0]
+                result.entryExamLink = reply[1]
+                result.checkFlightStatus = reply[2]
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class GetEntryExamStatus(RPCRequest):
+    """A request to get the entry exam status."""
+    def __init__(self, client, callback):
+        """Construct the request."""
+        super(GetEntryExamStatus, self).__init__(client, callback)
+
+    def run(self):
+        """Perform the query."""
+        result = Result()
+
+        reply = self._client.getEntryExamStatus()
+
+        result.entryExamPassed = reply[0]
+        result.entryExamLink = reply[1]
+        result.checkFlightStatus = reply[2]
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class GetFleet(Request):
     """Request to get the fleet from the website."""
@@ -745,5 +775,5 @@
     def run(self):
         """Perform the login request."""
-        url = "http://www.virtualairlines.hu/onlinegates_get.php"
+        url = MAVA_BASE_URL + "/onlinegates_get.php"
 
         f = urllib2.urlopen(url, timeout = 10.0)
@@ -751,4 +781,20 @@
         result.fleet = Fleet(f)
         f.close()
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class GetFleetRPC(RPCRequest):
+    """Request to get the fleet from the website using RPC."""
+    def __init__(self, client, callback):
+        """Construct the request with the given client and callback function."""
+        super(GetFleetRPC, self).__init__(client, callback)
+
+    def run(self):
+        """Perform the login request."""
+        result = Result()
+
+        result.fleet = self._client.getFleet()
 
         return result
@@ -767,9 +813,7 @@
     def run(self):
         """Perform the plane update."""
-        url = "http://www.virtualairlines.hu/onlinegates_set.php"
-
-        status = "H" if self._status==const.PLANE_HOME else \
-                 "A" if self._status==const.PLANE_AWAY else \
-                 "P" if self._status==const.PLANE_PARKING else ""
+        url = MAVA_BASE_URL + "/onlinegates_set.php"
+
+        status = Plane.status2str(self._status)
 
         gateNumber = self._gateNumber if self._gateNumber else ""
@@ -784,4 +828,26 @@
         result = Result()
         result.success = line == "OK"
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class UpdatePlaneRPC(RPCRequest):
+    """RPC request to update the status and the position of a plane in the
+    fleet."""
+    def __init__(self, client, callback, tailNumber, status, gateNumber = None):
+        """Construct the request."""
+        super(UpdatePlaneRPC, self).__init__(client, callback)
+        self._tailNumber = tailNumber
+        self._status = status
+        self._gateNumber = gateNumber
+
+    def run(self):
+        """Perform the plane update."""
+        self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
+
+        # Otherwise an exception is thrown
+        result = Result()
+        result.success = True
 
         return result
@@ -898,9 +964,4 @@
 class SendPIREP(Request):
     """A request to send a PIREP to the MAVA website."""
-    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
-                     const.FLIGHTTYPE_OLDTIMER : "OT",
-                     const.FLIGHTTYPE_VIP : "VIP",
-                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
-
     _latin2Encoder = codecs.getencoder("iso-8859-2")
 
@@ -912,6 +973,5 @@
     def run(self):
         """Perform the sending of the PIREP."""
-        url = "http://www.virtualairlines.hu/malevacars.php"
-        #url = "http://localhost:15000"
+        url = MAVA_BASE_URL + "/malevacars.php"
 
         pirep = self._pirep
@@ -932,5 +992,5 @@
         data["mail"] = str(pirep.mailWeight)
 
-        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
+        data["flttype"] = pirep.flightTypeText
         data["onoff"] = "1" if pirep.online else "0"
         data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
@@ -980,4 +1040,26 @@
 #------------------------------------------------------------------------------
 
+class SendPIREPRPC(RPCRequest):
+    """A request to send a PIREP to the MAVA website via the RPC interface."""
+
+    def __init__(self, client, callback, pirep):
+        """Construct the sending of the PIREP."""
+        super(SendPIREPRPC, self).__init__(client, callback)
+        self._pirep = pirep
+
+    def run(self):
+        """Perform the sending of the PIREP."""
+        pirep = self._pirep
+        resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep)
+
+        result = Result()
+        result.success = resultCode==rpc.Client.RESULT_OK
+        result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
+        result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class SendACARS(Request):
     """A request to send an ACARS to the MAVA website."""
@@ -993,5 +1075,5 @@
         print "Sending the online ACARS"
 
-        url = "http://www.virtualairlines.hu/acars2/acarsonline.php"
+        url = MAVA_BASE_URL  + "/acars2/acarsonline.php"
 
         acars = self._acars
@@ -1027,4 +1109,20 @@
 #------------------------------------------------------------------------------
 
+class SendACARSRPC(RPCRequest):
+    """A request to send an ACARS to the MAVA website via JSON-RPC."""
+    def __init__(self, client, callback, acars):
+        """Construct the request for the given PIREP."""
+        super(SendACARSRPC, self).__init__(client, callback)
+        self._acars = acars
+
+    def run(self):
+        """Perform the sending of the ACARS."""
+        print "Sending the online ACARS via JSON-RPC"
+
+        self._client.updateOnlineACARS(self._acars)
+        return Result()
+
+#------------------------------------------------------------------------------
+
 class SendBugReport(Request):
     """A request to send a bug report to the project homepage."""
@@ -1063,5 +1161,5 @@
     It can process one request at a time. The results are passed to a callback
     function."""
-    def __init__(self):
+    def __init__(self, config, getCredentialsFn):
         """Construct the handler."""
         super(Handler, self).__init__()
@@ -1071,16 +1169,40 @@
 
         self.daemon = True
-
-    def login(self, callback, pilotID, password, entranceExam = False):
+        self._config = config
+        self._rpcClient = rpc.Client(getCredentialsFn)
+        if config.rememberPassword:
+            self._rpcClient.setCredentials(config.pilotID, config.password)
+
+    def register(self, callback, registrationData):
+        """Enqueue a registration request."""
+        self._addRequest(Register(self._rpcClient, callback, registrationData))
+
+    def login(self, callback, pilotID, password):
         """Enqueue a login request."""
-        self._addRequest(Login(callback, pilotID, password, entranceExam))
+        request = \
+          LoginRPC(self._rpcClient, callback, pilotID, password) \
+          if self._config.useRPC else Login(callback, pilotID, password)
+
+        self._addRequest(request)
+
+    def getEntryExamStatus(self, callback):
+        """Get the entry exam status."""
+        self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
 
     def getFleet(self, callback):
         """Enqueue a fleet retrieval request."""
-        self._addRequest(GetFleet(callback))
+        request = \
+          GetFleetRPC(self._rpcClient, callback,) if self._config.useRPC \
+          else GetFleet(callback)
+        self._addRequest(request)
 
     def updatePlane(self, callback, tailNumber, status, gateNumber = None):
         """Update the status of the given plane."""
-        self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
+        request = \
+          UpdatePlaneRPC(self._rpcClient, callback,
+                         tailNumber, status, gateNumber) \
+          if self._config.useRPC \
+          else UpdatePlane(callback, tailNumber, status, gateNumber)
+        self._addRequest(request)
 
     def getNOTAMs(self, callback, departureICAO, arrivalICAO):
@@ -1094,9 +1216,15 @@
     def sendPIREP(self, callback, pirep):
         """Send the given PIREP."""
-        self._addRequest(SendPIREP(callback, pirep))
+        request = \
+          SendPIREPRPC(self._rpcClient, callback, pirep) if self._config.useRPC \
+          else SendPIREP(callback, pirep)
+        self._addRequest(request)
 
     def sendACARS(self, callback, acars):
         """Send the given ACARS"""
-        self._addRequest(SendACARS(callback, acars))
+        request = \
+          SendACARSRPC(self._rpcClient, callback, acars) if self._config.useRPC \
+          else SendACARS(callback, acars)
+        self._addRequest(request)
 
     def sendBugReport(self, callback, summary, description, email):
