server.vala 4.64 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* 
 * 
 * Copyright (C) 2011 Colomban Wendling <ban@herbesfolles.org>
 *                    Jonathan Michalon <studios.chalmion@no-log.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

21 22 23 24
using OVCC.Network;


/* TODO: add a Source to be notified of incoming messages asynchronously */
25

26 27
public class Server: ThreadedSocketService
{
28
  private List<Client>  clients = null;
29 30 31 32 33 34
  
  public string name { get; construct; }
  public uint   port { get; construct; default = 0xdead; }
  
  public signal void stopped ();
  
35 36 37
  private bool send_all (Message      message,
                         Client?      exclude = null,
                         Cancellable? cancel = null)
38 39
    throws Error
  {
40 41 42 43 44 45 46 47
    lock (this.clients) {
      foreach (var c in this.clients) {
        if (c != exclude) {
          c.send (message, cancel);
        }
      }
    }
    return true;
48
  }
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87

  private bool handle_login (LoginMessage msg, Client client)
  {
    bool ok = true;
    if (msg.status != LoginMessage.State.QUERY) {
      critical ("got LoginMessage with status different from QUERY");
      return false;
    }
    
    /* missing authentication check */
    if (msg.password == "") {
      debug ("rejecting auth as password is empty");
      msg.status = LoginMessage.State.MISSING_AUTHENTICATION;
      client.send (msg);
      return false;
    }

    /* already existing name check WARNING: lock needed */
    lock (this.clients) {
      foreach (Client c in this.clients) {
        if (c.login == msg.login) {
          ok = false;
          debug ("rejecting auth as login already connected");
          msg.status = LoginMessage.State.ALREADY_EXISTS;
          break;
        }
      }
      /* registration */
      if (ok) {
        debug ("accepting auth for %s", msg.login);
        msg.status = LoginMessage.State.OK;
        client.login    = msg.login;
        client.password = msg.password;
      }
    }
    client.send (msg);

    return ok;
  }
88 89 90 91 92 93
  
  private bool handle_connection (ThreadedSocketService service,
                                  SocketConnection      connection,
                                  Object?               source)
  {
    debug ("New connection");
94
    var client = new Client ();
95
    try {
96 97 98 99
      client.connect (connection);
    } catch (Error e) {
      warning ("error connecting client: %s", e.message);
      return false;
100 101
    }
    
102 103 104 105 106 107 108 109 110 111 112
    lock (this.clients) {
      this.clients.prepend (client);
    }
    try {
      client.send (new WelcomeMessage ("Welcome to server %s".printf (this.name)));
    } catch {}
    
    var running = true;
    while (running) {
      Message? msg;
      
113
      try {
114
        msg = client.receive ();
115 116 117 118
      } catch (Error e) {
        warning ("error receiving data: %s", e.message);
        break;
      }
119 120 121 122 123 124 125 126 127 128 129
      
      if (msg == null) {
        debug ("Invalid message received");
        continue;
      }
      
      debug ("Message type %s received", msg.get_type ().name ());
      if (msg is StringMessage) {
        debug (" --- %s", (msg as StringMessage).message);
      }
      switch (msg.message_type) {
130
        case MessageType.LOGIN:
131
          debug ("Login for '%s' with '%s'", (msg as LoginMessage).login,
132
                                             (msg as LoginMessage).password);
133 134
          handle_login ((msg as LoginMessage), client);

135
          break;
136 137 138 139 140
        case MessageType.DISCONNECT: running = false; break;
      }
    }
    lock (this.clients) {
      this.clients.remove (client);
141
    }
142
    try { client.disconnect (); } catch { /* we don't care if disconnection failed */ }
143 144 145 146 147 148 149
    debug ("Closed connection");
    return true;
  }
  
  construct
  {
    try {
150
      this.add_inet_port ((uint16) port, null);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    } catch (Error e) {
      critical ("failed to add address: %s", e.message);
    }
    this.run.connect (handle_connection);
  }
  
  public Server (string name,
                 uint16 port = 0xdead)
  {
    Object (name: name, port: port);
  }
  
  /* a bit ugly since a direct call to parent method will not trigger the
   * :stopped signal */
  public new void stop ()
  {
    base.stop ();
    this.stopped ();
  }
}