gdhcp: Avoid reading invalid data in dhcp_get_option
[platform/upstream/connman.git] / gdhcp / server.c
old mode 100644 (file)
new mode 100755 (executable)
index 099c50c..52ea2a5
@@ -2,7 +2,7 @@
  *
  *  DHCP Server library with GLib integration
  *
- *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2009-2012  Intel Corporation. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
 #define OFFER_TIME (5*60)
 
 struct _GDHCPServer {
-       gint ref_count;
+       int ref_count;
        GDHCPType type;
-       gboolean started;
+       bool started;
        int ifindex;
        char *interface;
        uint32_t start_ip;
        uint32_t end_ip;
-       uint32_t server_nip;
+       uint32_t server_nip;    /* our address in network byte order */
        uint32_t lease_seconds;
        int listener_sockfd;
        guint listener_watch;
@@ -65,6 +65,7 @@ struct _GDHCPServer {
        GHashTable *nip_lease_hash;
        GHashTable *option_hash; /* Options send to client */
        GDHCPSaveLeaseFunc save_lease_func;
+       GDHCPLeaseAddedCb lease_added_cb;
        GDHCPDebugFunc debug_func;
        gpointer debug_data;
 };
@@ -80,7 +81,7 @@ static inline void debug(GDHCPServer *server, const char *format, ...)
        char str[256];
        va_list ap;
 
-       if (server->debug_func == NULL)
+       if (!server->debug_func)
                return;
 
        va_start(ap, format);
@@ -140,17 +141,17 @@ static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
        lease_mac = find_lease_by_mac(dhcp_server, mac);
 
        lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
-                                               GINT_TO_POINTER((int) yiaddr));
+                                       GINT_TO_POINTER((int) ntohl(yiaddr)));
        debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
 
-       if (lease_nip != NULL) {
+       if (lease_nip) {
                dhcp_server->lease_list =
                                g_list_remove(dhcp_server->lease_list,
                                                                lease_nip);
                g_hash_table_remove(dhcp_server->nip_lease_hash,
-                                       GINT_TO_POINTER((int) yiaddr));
+                               GINT_TO_POINTER((int) ntohl(yiaddr)));
 
-               if (lease_mac == NULL)
+               if (!lease_mac)
                        *lease = lease_nip;
                else if (lease_nip != lease_mac) {
                        remove_lease(dhcp_server, lease_mac);
@@ -161,7 +162,7 @@ static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
                return 0;
        }
 
-       if (lease_mac != NULL) {
+       if (lease_mac) {
                dhcp_server->lease_list =
                                g_list_remove(dhcp_server->lease_list,
                                                                lease_mac);
@@ -173,7 +174,7 @@ static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
        }
 
        *lease = g_try_new0(struct dhcp_lease, 1);
-       if (*lease == NULL)
+       if (!*lease)
                return -ENOMEM;
 
        return 0;
@@ -200,7 +201,7 @@ static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
        memset(lease, 0, sizeof(*lease));
 
        memcpy(lease->lease_mac, chaddr, ETH_ALEN);
-       lease->lease_nip = yiaddr;
+       lease->lease_nip = ntohl(yiaddr);
 
        if (expire == 0)
                lease->expire = time(NULL) + dhcp_server->lease_seconds;
@@ -224,18 +225,18 @@ static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
 }
 
 /* Check if the IP is taken; if it is, add it to the lease table */
-static gboolean arp_check(uint32_t nip, const uint8_t *safe_mac)
+static bool arp_check(uint32_t nip, const uint8_t *safe_mac)
 {
        /* TODO: Add ARP checking */
-       return TRUE;
+       return true;
 }
 
-static gboolean is_expired_lease(struct dhcp_lease *lease)
+static bool is_expired_lease(struct dhcp_lease *lease)
 {
        if (lease->expire < time(NULL))
-               return TRUE;
+               return true;
 
-       return FALSE;
+       return false;
 }
 
 static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
@@ -254,28 +255,27 @@ static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
                if ((ip_addr & 0xff) == 0xff)
                        continue;
 
-               lease = find_lease_by_nip(dhcp_server,
-                               (uint32_t) htonl(ip_addr));
-               if (lease != NULL)
+               lease = find_lease_by_nip(dhcp_server, ip_addr);
+               if (lease)
                        continue;
 
-               if (arp_check(htonl(ip_addr), safe_mac) == TRUE)
-                       return htonl(ip_addr);
+               if (arp_check(htonl(ip_addr), safe_mac))
+                       return ip_addr;
        }
 
        /* The last lease is the oldest one */
        list = g_list_last(dhcp_server->lease_list);
-       if (list == NULL)
+       if (!list)
                return 0;
 
        lease = list->data;
-       if (lease == NULL)
+       if (!lease)
                return 0;
 
-        if (is_expired_lease(lease) == FALSE)
+        if (!is_expired_lease(lease))
                return 0;
 
-        if (arp_check(lease->lease_nip, safe_mac) == FALSE)
+        if (!arp_check(lease->lease_nip, safe_mac))
                return 0;
 
        return lease->lease_nip;
@@ -317,7 +317,7 @@ static uint32_t get_interface_address(int index)
        struct sockaddr_in *server_ip;
        uint32_t ret = 0;
 
-       sk = socket(PF_INET, SOCK_DGRAM, 0);
+       sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        if (sk < 0) {
                perror("Open socket error");
                return 0;
@@ -358,18 +358,18 @@ GDHCPServer *g_dhcp_server_new(GDHCPType type,
        }
 
        dhcp_server = g_try_new0(GDHCPServer, 1);
-       if (dhcp_server == NULL) {
+       if (!dhcp_server) {
                *error = G_DHCP_SERVER_ERROR_NOMEM;
                return NULL;
        }
 
        dhcp_server->interface = get_interface_name(ifindex);
-       if (dhcp_server->interface == NULL) {
+       if (!dhcp_server->interface) {
                *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
                goto error;
        }
 
-       if (interface_is_up(ifindex) == FALSE) {
+       if (!interface_is_up(ifindex)) {
                *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
                goto error;
        }
@@ -396,7 +396,7 @@ GDHCPServer *g_dhcp_server_new(GDHCPType type,
        dhcp_server->ref_count = 1;
        dhcp_server->ifindex = ifindex;
        dhcp_server->listener_sockfd = -1;
-       dhcp_server->listener_watch = -1;
+       dhcp_server->listener_watch = 0;
        dhcp_server->listener_channel = NULL;
        dhcp_server->save_lease_func = NULL;
        dhcp_server->debug_func = NULL;
@@ -413,7 +413,7 @@ error:
 }
 
 
-static uint8_t check_packet_type(struct dhcp_packet *packet)
+static uint8_t check_packet_type(struct dhcp_packet *packet, uint16_t packet_len)
 {
        uint8_t *type;
 
@@ -423,9 +423,9 @@ static uint8_t check_packet_type(struct dhcp_packet *packet)
        if (packet->op != BOOTREQUEST)
                return 0;
 
-       type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
+       type = dhcp_get_option(packet, packet_len, DHCP_MESSAGE_TYPE);
 
-       if (type == NULL)
+       if (!type)
                return 0;
 
        if (*type < DHCP_MINTYPE)
@@ -450,7 +450,8 @@ static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
        packet->flags = client_packet->flags;
        packet->gateway_nip = client_packet->gateway_nip;
        packet->ciaddr = client_packet->ciaddr;
-       dhcp_add_simple_option(packet, DHCP_SERVER_ID, dhcp_server->server_nip);
+       dhcp_add_option_uint32(packet, DHCP_SERVER_ID,
+                                       ntohl(dhcp_server->server_nip));
 }
 
 static void add_option(gpointer key, gpointer value, gpointer user_data)
@@ -460,7 +461,7 @@ static void add_option(gpointer key, gpointer value, gpointer user_data)
        struct in_addr nip;
        struct dhcp_packet *packet = user_data;
 
-       if (option_value == NULL)
+       if (!option_value)
                return;
 
        switch (option_code) {
@@ -470,8 +471,8 @@ static void add_option(gpointer key, gpointer value, gpointer user_data)
                if (inet_aton(option_value, &nip) == 0)
                        return;
 
-               dhcp_add_simple_option(packet, (uint8_t) option_code,
-                                                               nip.s_addr);
+               dhcp_add_option_uint32(packet, (uint8_t) option_code,
+                                                       ntohl(nip.s_addr));
                break;
        default:
                return;
@@ -485,28 +486,28 @@ static void add_server_options(GDHCPServer *dhcp_server,
                                add_option, packet);
 }
 
-static gboolean check_requested_nip(GDHCPServer *dhcp_server,
+static bool check_requested_nip(GDHCPServer *dhcp_server,
                                        uint32_t requested_nip)
 {
        struct dhcp_lease *lease;
 
        if (requested_nip == 0)
-               return FALSE;
+               return false;
 
-       if (ntohl(requested_nip) < dhcp_server->start_ip)
-               return FALSE;
+       if (requested_nip < dhcp_server->start_ip)
+               return false;
 
-       if (ntohl(requested_nip) > dhcp_server->end_ip)
-               return FALSE;
+       if (requested_nip > dhcp_server->end_ip)
+               return false;
 
        lease = find_lease_by_nip(dhcp_server, requested_nip);
-       if (lease == NULL)
-               return TRUE;
+       if (!lease)
+               return true;
 
-       if (is_expired_lease(lease) == FALSE)
-               return FALSE;
+       if (!is_expired_lease(lease))
+               return false;
 
-       return TRUE;
+       return true;
 }
 
 static void send_packet_to_client(GDHCPServer *dhcp_server,
@@ -529,7 +530,7 @@ static void send_packet_to_client(GDHCPServer *dhcp_server,
        dhcp_send_raw_packet(dhcp_pkt,
                dhcp_server->server_nip, SERVER_PORT,
                ciaddr, CLIENT_PORT, chaddr,
-               dhcp_server->ifindex);
+               dhcp_server->ifindex, false);
 }
 
 static void send_offer(GDHCPServer *dhcp_server,
@@ -543,12 +544,12 @@ static void send_offer(GDHCPServer *dhcp_server,
        init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
 
        if (lease)
-               packet.yiaddr = lease->lease_nip;
-       else if (check_requested_nip(dhcp_server, requested_nip) == TRUE)
-               packet.yiaddr = requested_nip;
+               packet.yiaddr = htonl(lease->lease_nip);
+       else if (check_requested_nip(dhcp_server, requested_nip))
+               packet.yiaddr = htonl(requested_nip);
        else
-               packet.yiaddr = find_free_or_expired_nip(
-                               dhcp_server, client_packet->chaddr);
+               packet.yiaddr = htonl(find_free_or_expired_nip(
+                                       dhcp_server, client_packet->chaddr));
 
        debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
 
@@ -559,14 +560,14 @@ static void send_offer(GDHCPServer *dhcp_server,
 
        lease = add_lease(dhcp_server, OFFER_TIME,
                                packet.chaddr, packet.yiaddr);
-       if (lease == NULL) {
+       if (!lease) {
                debug(dhcp_server,
                                "Err: No free IP addresses. OFFER abandoned");
                return;
        }
 
-       dhcp_add_simple_option(&packet, DHCP_LEASE_TIME,
-                               htonl(dhcp_server->lease_seconds));
+       dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME,
+                                               dhcp_server->lease_seconds);
        add_server_options(dhcp_server, &packet);
 
        addr.s_addr = packet.yiaddr;
@@ -579,7 +580,7 @@ static void save_lease(GDHCPServer *dhcp_server)
 {
        GList *list;
 
-       if (dhcp_server->save_lease_func == NULL)
+       if (!dhcp_server->save_lease_func)
                return;
 
        for (list = dhcp_server->lease_list; list; list = list->next) {
@@ -590,28 +591,31 @@ static void save_lease(GDHCPServer *dhcp_server)
 }
 
 static void send_ACK(GDHCPServer *dhcp_server,
-               struct dhcp_packet *client_packet, uint32_t yiaddr)
+               struct dhcp_packet *client_packet, uint32_t dest)
 {
        struct dhcp_packet packet;
        uint32_t lease_time_sec;
        struct in_addr addr;
 
        init_packet(dhcp_server, &packet, client_packet, DHCPACK);
-       packet.yiaddr = yiaddr;
+       packet.yiaddr = htonl(dest);
 
        lease_time_sec = dhcp_server->lease_seconds;
 
-       dhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
+       dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec);
 
        add_server_options(dhcp_server, &packet);
 
-       addr.s_addr = yiaddr;
+       addr.s_addr = htonl(dest);
 
        debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
 
        send_packet_to_client(dhcp_server, &packet);
 
        add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
+
+       if (dhcp_server->lease_added_cb)
+               dhcp_server->lease_added_cb(packet.chaddr, packet.yiaddr);
 }
 
 static void send_NAK(GDHCPServer *dhcp_server,
@@ -626,7 +630,7 @@ static void send_NAK(GDHCPServer *dhcp_server,
        dhcp_send_raw_packet(&packet,
                        dhcp_server->server_nip, SERVER_PORT,
                        INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
-                       dhcp_server->ifindex);
+                       dhcp_server->ifindex, false);
 }
 
 static void send_inform(GDHCPServer *dhcp_server,
@@ -647,6 +651,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
        struct dhcp_lease *lease;
        uint32_t requested_nip = 0;
        uint8_t type, *server_id_option, *request_ip_option;
+       uint16_t packet_len;
        int re;
 
        if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
@@ -657,87 +662,87 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
        re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
        if (re < 0)
                return TRUE;
+       packet_len = (uint16_t)(unsigned int)re;
 
-       type = check_packet_type(&packet);
+       type = check_packet_type(&packet, packet_len);
        if (type == 0)
                return TRUE;
 
-       server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
+       server_id_option = dhcp_get_option(&packet, packet_len, DHCP_SERVER_ID);
        if (server_id_option) {
-               uint32_t server_nid = dhcp_get_unaligned(
-                                       (uint32_t *) server_id_option);
+               uint32_t server_nid =
+                       get_unaligned((const uint32_t *) server_id_option);
 
                if (server_nid != dhcp_server->server_nip)
                        return TRUE;
        }
 
-       request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
+       request_ip_option = dhcp_get_option(&packet, packet_len, DHCP_REQUESTED_IP);
        if (request_ip_option)
-               requested_nip = dhcp_get_unaligned(
-                                       (uint32_t *) request_ip_option);
+               requested_nip = get_be32(request_ip_option);
 
        lease = find_lease_by_mac(dhcp_server, packet.chaddr);
 
        switch (type) {
-               case DHCPDISCOVER:
-                       debug(dhcp_server, "Received DISCOVER");
+       case DHCPDISCOVER:
+               debug(dhcp_server, "Received DISCOVER");
 
-                       send_offer(dhcp_server, &packet, lease, requested_nip);
+               send_offer(dhcp_server, &packet, lease, requested_nip);
                break;
-               case DHCPREQUEST:
-                       debug(dhcp_server, "Received REQUEST NIP %d",
+       case DHCPREQUEST:
+               debug(dhcp_server, "Received REQUEST NIP %d",
                                                        requested_nip);
-                       if (requested_nip == 0) {
-                               requested_nip = packet.ciaddr;
-                               if (requested_nip == 0)
-                                       break;
-                       }
-
-                       if (lease && requested_nip == lease->lease_nip) {
-                               debug(dhcp_server, "Sending ACK");
-                               send_ACK(dhcp_server, &packet,
-                                               lease->lease_nip);
+               if (requested_nip == 0) {
+                       requested_nip = ntohl(packet.ciaddr);
+                       if (requested_nip == 0)
                                break;
-                       }
+               }
 
-                       if (server_id_option || lease == NULL) {
-                               debug(dhcp_server, "Sending NAK");
-                               send_NAK(dhcp_server, &packet);
-                       }
+               if (lease && requested_nip == lease->lease_nip) {
+                       debug(dhcp_server, "Sending ACK");
+                       send_ACK(dhcp_server, &packet,
+                               lease->lease_nip);
+                       break;
+               }
+
+               if (server_id_option || !lease) {
+                       debug(dhcp_server, "Sending NAK");
+                       send_NAK(dhcp_server, &packet);
+               }
 
                break;
-               case DHCPDECLINE:
-                       debug(dhcp_server, "Received DECLINE");
+       case DHCPDECLINE:
+               debug(dhcp_server, "Received DECLINE");
 
-                       if (server_id_option == NULL)
-                               break;
+               if (!server_id_option)
+                       break;
 
-                       if (request_ip_option == NULL)
-                               break;
+               if (!request_ip_option)
+                       break;
 
-                       if (lease == NULL)
-                               break;
+               if (!lease)
+                       break;
 
-                       if (requested_nip == lease->lease_nip)
-                               remove_lease(dhcp_server, lease);
+               if (requested_nip == lease->lease_nip)
+                       remove_lease(dhcp_server, lease);
 
                break;
-               case DHCPRELEASE:
-                       debug(dhcp_server, "Received RELEASE");
+       case DHCPRELEASE:
+               debug(dhcp_server, "Received RELEASE");
 
-                       if (server_id_option == NULL)
-                               break;
+               if (!server_id_option)
+                       break;
 
-                       if (lease == NULL)
-                               break;
+               if (!lease)
+                       break;
 
-                       if (packet.ciaddr == lease->lease_nip)
-                               lease_set_expire(dhcp_server, lease,
-                                                               time(NULL));
+               if (packet.ciaddr == lease->lease_nip)
+                       lease_set_expire(dhcp_server, lease,
+                                       time(NULL));
                break;
-               case DHCPINFORM:
-                       debug(dhcp_server, "Received INFORM");
-                       send_inform(dhcp_server, &packet);
+       case DHCPINFORM:
+               debug(dhcp_server, "Received INFORM");
+               send_inform(dhcp_server, &packet);
                break;
        }
 
@@ -750,16 +755,16 @@ int g_dhcp_server_start(GDHCPServer *dhcp_server)
        GIOChannel *listener_channel;
        int listener_sockfd;
 
-       if (dhcp_server->started == TRUE)
+       if (dhcp_server->started)
                return 0;
 
        listener_sockfd = dhcp_l3_socket(SERVER_PORT,
-                               dhcp_server->interface);
+                                       dhcp_server->interface, AF_INET);
        if (listener_sockfd < 0)
                return -EIO;
 
        listener_channel = g_io_channel_unix_new(listener_sockfd);
-       if (listener_channel == NULL) {
+       if (!listener_channel) {
                close(listener_sockfd);
                return -EIO;
        }
@@ -785,7 +790,7 @@ int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
 {
        struct in_addr nip;
 
-       if (option_value == NULL)
+       if (!option_value)
                return -EINVAL;
 
        debug(dhcp_server, "option_code %d option_value %s",
@@ -810,18 +815,27 @@ int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
 void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
                                GDHCPSaveLeaseFunc func, gpointer user_data)
 {
-       if (dhcp_server == NULL)
+       if (!dhcp_server)
                return;
 
        dhcp_server->save_lease_func = func;
 }
 
+void g_dhcp_server_set_lease_added_cb(GDHCPServer *dhcp_server,
+                                                       GDHCPLeaseAddedCb cb)
+{
+       if (!dhcp_server)
+               return;
+
+       dhcp_server->lease_added_cb = cb;
+}
+
 GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
 {
-       if (dhcp_server == NULL)
+       if (!dhcp_server)
                return NULL;
 
-       g_atomic_int_inc(&dhcp_server->ref_count);
+       __sync_fetch_and_add(&dhcp_server->ref_count, 1);
 
        return dhcp_server;
 }
@@ -843,10 +857,10 @@ void g_dhcp_server_stop(GDHCPServer *dhcp_server)
 
 void g_dhcp_server_unref(GDHCPServer *dhcp_server)
 {
-       if (dhcp_server == NULL)
+       if (!dhcp_server)
                return;
 
-       if (g_atomic_int_dec_and_test(&dhcp_server->ref_count) == FALSE)
+       if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1)
                return;
 
        g_dhcp_server_stop(dhcp_server);
@@ -860,12 +874,6 @@ void g_dhcp_server_unref(GDHCPServer *dhcp_server)
        g_free(dhcp_server);
 }
 
-void g_dhcp_server_load_lease(GDHCPServer *dhcp_server, unsigned int expire,
-                               unsigned char *mac, unsigned int lease_ip)
-{
-       add_lease(dhcp_server, expire, mac, lease_ip);
-}
-
 int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
                const char *start_ip, const char *end_ip)
 {
@@ -884,9 +892,10 @@ int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
        return 0;
 }
 
-void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, unsigned int lease_time)
+void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
+                                       unsigned int lease_time)
 {
-       if (dhcp_server == NULL)
+       if (!dhcp_server)
                return;
 
        dhcp_server->lease_seconds = lease_time;
@@ -895,7 +904,7 @@ void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, unsigned int lease_t
 void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
                                GDHCPDebugFunc func, gpointer user_data)
 {
-       if (dhcp_server == NULL)
+       if (!dhcp_server)
                return;
 
        dhcp_server->debug_func = func;