/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>

#include "configthreads_common.h"
#include "ip6config_utils.h"
#include "globals.h"
#include "config_method.h"
#include "interfaces.h"


static ip6config_func_t *
lookup_func(ip6config_method_t method)
{
    switch (method) {
        case ip6config_method_automatic_e:
        case ip6config_method_rtadv_e: {
            return rtadv_thread;
            break;
        }
        case ip6config_method_manual_e: {
            return manual_thread;
            break;
        }
        case ip6config_method_6to4_e: {
            return stf_thread;
            break;
        }
        case ip6config_method_linklocal_e: {
            return linklocal_thread;
            break;
        }
        default: {
            break;
        }
    }
    return (NULL);
}

__private_extern__ ip6config_status_t
config_method_start(Service_t * service_p, ip6config_method_t method,
		    ip6config_method_data_t * data)
{
    start_event_data_t	start_data;
    ip6config_func_t *	func;
    interface_t * 	if_p = service_interface(service_p);

    /*	This is checking to make sure that we can perform some basic functions
     *	over the given interface. For everything but 6to4 we need to be able 
     *	to multicast, and we need to filter out loopback except on manual configs.
     */

    if (!(if_flags(if_p) & IFF_MULTICAST)) {
        switch (method) {
            case ip6config_method_6to4_e: {
                break;
            }
            default: {
		    if (if_ift_type(if_p) != IFT_L2VLAN) {
			    return (ip6config_status_invalid_operation_e);
		    }
            }
        }
    }
    if (if_flags(if_p) & IFF_LOOPBACK) {
        switch (method) {
            case ip6config_method_automatic_e:
            case ip6config_method_rtadv_e:
            case ip6config_method_6to4_e: {
                my_log(LOG_ERR, "CONFIG_METHOD_START %s: invalid config method for loopback",
                        if_name(if_p));
                return (ip6config_status_invalid_operation_e);
                break;
            }
            default:
                break;
        }
    }

    func = lookup_func(method);
    if (func == NULL) {
        return (ip6config_status_operation_not_supported_e);
    }
    start_data.config.data = data;
    return (*func)(service_p, IFEventID_start_e, &start_data);
}

__private_extern__ ip6config_status_t
config_method_state_change(Service_t * service_p, ip6_addrinfo_list_t * ip6_addrs)
{
    ip6config_func_t *	func;
    ip6config_status_t	status;

    func = lookup_func(service_p->method);
    if (func == NULL) {
        return (ip6config_status_operation_not_supported_e);
    }
    
    status = (*func)(service_p, IFEventID_state_change_e, ip6_addrs);
    return (status);
}

__private_extern__ ip6config_status_t
config_method_change(Service_t * service_p, ip6config_method_t method,
		     ip6config_method_data_t * data,
		     boolean_t * needs_stop)
{
    change_event_data_t	change_data;
    ip6config_func_t *	func;
    ip6config_status_t	status;

    *needs_stop = FALSE;

    func = lookup_func(method);
    if (func == NULL) {
        return (ip6config_status_operation_not_supported_e);
    }
    change_data.config.data = data;
    change_data.needs_stop = FALSE;
    status = (*func)(service_p, IFEventID_change_e, &change_data);
    *needs_stop = change_data.needs_stop;
    return (status);
}

__private_extern__ ip6config_status_t
config_method_ipv4_primary_change(Service_t * service_p, ip6config_method_t method,
		    ip6config_method_data_t * data)
{
    start_event_data_t	start_data;
    ip6config_func_t *	func;

    func = lookup_func(method);
    if (func == NULL) {
        return (ip6config_status_operation_not_supported_e);
    }
    start_data.config.data = data;
    return (*func)(service_p, IFEventID_ipv4_primary_change_e, &start_data);
}

static ip6config_status_t
config_method_event(Service_t * service_p, IFEventID_t event)
{
    ip6config_status_t	status = ip6config_status_success_e;
    ip6config_func_t *	func;
    ip6config_method_t	method = service_p->method;

    func = lookup_func(method);
    if (func == NULL) {
        SCLog(TRUE, LOG_INFO,
                CFSTR("config_method_event(%s): lookup_func(%d) failed"),
                IFEventID_names(event), method);
        status = ip6config_status_internal_error_e;
        goto done;
    }

    (*func)(service_p, event, NULL);

 done:
    return (status);

}

__private_extern__ ip6config_status_t
config_method_stop(Service_t * service_p)
{
    return (config_method_event(service_p, IFEventID_stop_e));
}

__private_extern__ ip6config_status_t
config_method_media(Service_t * service_p)
{
    return (config_method_event(service_p, IFEventID_media_e));
}

static boolean_t
ip6config_method_from_cfstring(CFStringRef m, ip6config_method_t * method)
{
    if (CFEqual(m, kSCValNetIPv6ConfigMethodManual)) {
        *method = ip6config_method_manual_e;
    }
    else if (CFEqual(m, kSCValNetIPv6ConfigMethodAutomatic)
		|| CFEqual(m, kSCValNetIPv6ConfigMethodRouterAdvertisement)) {
        *method = ip6config_method_rtadv_e;
    }
    else if (CFEqual(m, kSCValNetIPv6ConfigMethod6to4)) {
        *method = ip6config_method_6to4_e;
    }
    else {
        return (FALSE);
    }
    return (TRUE);
}

static void
ip6config_get_ip4_addresses(CFArrayRef addresses, stf_method_data_t * stf_data)
{
    int	count;
    int	i;

    count = CFArrayGetCount(addresses);
    if (count == 0) {
        my_log(LOG_ERR, "ip6config: 6TO4 - ipv4 address array empty");
        stf_data->ip4_addrs_list = NULL;
        return;
    }
    
    stf_data->ip4_addrs_list = (struct in_addr *) calloc(count, sizeof(struct in_addr));
    if (stf_data->ip4_addrs_list == NULL) {
        my_log(LOG_ERR,
                "ip6config: malloc stf_data->ip4_addrs_list failed");
        return;
    }

    stf_data->n_ip4 = count;
    
    for (i = 0; i < count; i++) {
        struct in_addr tmp_addr = { 0 };
        
        cfstring_to_numeric(AF_INET, CFArrayGetValueAtIndex(addresses, i), &tmp_addr);
        memcpy(&(stf_data->ip4_addrs_list[i]), &tmp_addr, sizeof(struct in_addr));
    }
    
    return;
}

__private_extern__ int
ip6config_get_6to4_address_data(SCDynamicStoreRef session, 
            ip6config_method_data_t * method_data)
{
    CFStringRef		ipv4_global_key = NULL;
    CFDictionaryRef	ipv4_global_dict = NULL;
    CFStringRef		primary_service = NULL;
    CFStringRef		service_key = NULL;
    CFDictionaryRef	ipv4_service_dict = NULL;
    CFArrayRef		addresses = NULL;
    int			error = -1;

    /* get State:/Network/Global/IPv4 */
    ipv4_global_key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
                                                            kSCDynamicStoreDomainState,
                                                            kSCEntNetIPv4);
    if (ipv4_global_key == NULL) {
        goto done;
    }
    
    ipv4_global_dict = my_SCDynamicStoreCopyValue(session, ipv4_global_key);
    if (ipv4_global_dict == NULL) {
        goto done;
    }
    
    /* get primary IPv4 service */
    primary_service = CFDictionaryGetValue(ipv4_global_dict, kSCDynamicStorePropNetPrimaryService);
    if (primary_service == NULL) {
        goto done;
    }
    
    /* get State:/Network/Service/<primary_service>/IPv4 */
    service_key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
                            kSCDynamicStoreDomainState,
                            primary_service,
                            kSCEntNetIPv4);
    if (service_key == NULL) {
        goto done;
    }
    
    ipv4_service_dict = my_SCDynamicStoreCopyValue(session, service_key);
    if (ipv4_service_dict == NULL) {
        goto done;
    }
    
    addresses = CFDictionaryGetValue(ipv4_service_dict, kSCPropNetIPv4Addresses);
    if (addresses == NULL || isA_CFArray(addresses) == NULL) {
        goto done;
    }
    
    ip6config_get_ip4_addresses(addresses, &method_data->stf_data);
    if (method_data->stf_data.ip4_addrs_list != NULL) {
        error = 0;
    }
    
done:
    my_CFRelease(&ipv4_global_key);
    my_CFRelease(&ipv4_global_dict);
    my_CFRelease(&service_key);
    my_CFRelease(&ipv4_service_dict);
    
    return (error);
}

static int
ip6config_6to4_relay_is_numeric_address(CFStringRef address, 
            relay_address_t * relay_address)
{
    struct in6_addr	tmp6_addr;
    struct in_addr	tmp4_addr;
    int			err;
    
    if ((err = cfstring_to_numeric(AF_INET6, address, &tmp6_addr)) == 0) {
        relay_address->addr_type = relay_address_type_ipv6_e;
        memcpy(&relay_address->relay_address_u.ip6_relay_addr, 
                &tmp6_addr, sizeof(struct in6_addr));
    }
    else {
        my_log(LOG_DEBUG, "ip6config_6to4_relay_is_numeric_address: NOT IP6 ADDRESS");
        if ((err = cfstring_to_numeric(AF_INET, address, &tmp4_addr)) == 0) {
            relay_address->addr_type = relay_address_type_ipv4_e;
            relay_address->relay_address_u.ip4_relay_addr = tmp4_addr;
        }
        else {
            my_log(LOG_DEBUG, "ip6config_6to4_relay_is_numeric_address: NOT IP4 ADDRESS");
        }
    }
    
    return (err);
}

static int
ip6config_get_6to4_relay_address(CFStringRef address, 
                relay_address_t * relay_address)
{
    if (isA_CFString(address) == NULL) {
        my_log(LOG_DEBUG, "ip6config_get_6to4_relay_address: address not CFString");
        return (-1);
    }
    
    if (ip6config_6to4_relay_is_numeric_address(address, relay_address) != 0) {
        int	len;
        char	buf[256];
        
        len = cfstring_to_cstring(address, buf, sizeof(buf));
        
        if (len == 0) {
            my_log(LOG_DEBUG, 
                    "ip6config_get_6to4_relay_address: length of address is 0");
            return (-1);
        }
        
        relay_address->relay_address_u.dns_relay_addr = malloc(len + 1);
        if (relay_address->relay_address_u.dns_relay_addr == NULL) {
            my_log(LOG_DEBUG, "ip6config_get_6to4_relay_address: malloc relay failed");
            return (-1);
        }
        
        relay_address->addr_type = relay_address_type_dns_e;
        memcpy(relay_address->relay_address_u.dns_relay_addr, buf, (len + 1));
    }
    
    return (0);
}

#define DEFAULT_PREFIX_LENGTH 64
/* from RFC3068 */
#define DEFAULT_6TO4_RELAY_ADDRESS_INIT \
	{{{ 0x20, 0x02, 0xc0, 0x58, 0x63, 0x01, 0x00, 0x00, \
	    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}

__private_extern__ ip6config_method_data_t*
ip6config_method_data_from_dict(CFDictionaryRef dict,
			       ip6config_method_t * method)
{
    CFStringRef			config_method;
    int				count = 0;
    int				i;
    CFArrayRef			addresses = NULL;
    CFArrayRef			prefixLens = NULL;
    CFStringRef			relay_address = NULL;
    ip6config_method_data_t *	method_data = NULL;
    int				method_data_len = 0;

    config_method = CFDictionaryGetValue(dict, kSCPropNetIPv6ConfigMethod);
    if (config_method == NULL || ip6config_method_from_cfstring(config_method, method) == FALSE) {
        my_log(LOG_ERR, "ip6config: configuration method is missing/invalid");
        goto error;
    }

    if (*method == ip6config_method_6to4_e) {
        method_data_len = sizeof(*method_data) +
                            (count * sizeof(method_data->ip6[0]));
        method_data = (ip6config_method_data_t *) malloc(method_data_len);
        if (method_data == NULL) {
            my_log(LOG_ERR,
                    "ip6config: malloc method_data failed");
            goto error;
        }

        bzero(method_data, method_data_len);
        method_data->n_ip6 = count;
        
        relay_address = CFDictionaryGetValue(dict, kSCPropNet6to4Relay);
        if (relay_address == NULL) {
            /* by default, we use the 6to4 anycast address */
            struct in6_addr	tmp6_addr = DEFAULT_6TO4_RELAY_ADDRESS_INIT;
            
            method_data->stf_data.relay_address.addr_type = relay_address_type_ipv6_e;
            memcpy(&method_data->stf_data.relay_address.relay_address_u.ip6_relay_addr, 
                    &tmp6_addr, sizeof(struct in6_addr));
        }
        else if (ip6config_get_6to4_relay_address(relay_address, 
                    &method_data->stf_data.relay_address) != 0) {
            my_log(LOG_INFO, "ip6config: bad 6to4 relay address");
            goto error;
        }
    }
    else {
        addresses = CFDictionaryGetValue(dict, kSCPropNetIPv6Addresses);
        prefixLens = CFDictionaryGetValue(dict, kSCPropNetIPv6PrefixLength);
        
        /* All must be same size */
        if (addresses) {
            if (isA_CFArray(addresses)) {
                count = CFArrayGetCount(addresses);
                if (count == 0) {
                    my_log(LOG_ERR, "ip6configd: address array empty");
                    goto error;
                }
            }
            if (prefixLens) {
                if (isA_CFArray(prefixLens)) {
                    if (count != CFArrayGetCount(prefixLens)) {
                        my_log(LOG_ERR, 
                                "ip6configd: address/prefix arrays not same size");
                        goto error;
                    }
                }
                else {
                    prefixLens = NULL;
                }
            }
        }
        
        if (*method == ip6config_method_manual_e && addresses == NULL) {
            my_log(LOG_ERR, 
                    "ip6configd: manual method requires an address");
            goto error;
        }
        method_data_len = sizeof(*method_data) +
                        (count * sizeof(method_data->ip6[0]));
        method_data = (ip6config_method_data_t *) malloc(method_data_len);
        if (method_data == NULL) {
            my_log(LOG_ERR,
                "ip6config: malloc method_data failed");
            goto error;
        }
    
        bzero(method_data, method_data_len);
        method_data->n_ip6 = count;
    
        for (i = 0; i < count; i++) {
            struct in6_addr tmp_addr = IN6ADDR_ANY_INIT;
            
            cfstring_to_numeric(AF_INET6, CFArrayGetValueAtIndex(addresses, i), &tmp_addr);
            memcpy(&(method_data->ip6[i].addr), &tmp_addr, sizeof(struct in6_addr));

            if (prefixLens) {
                CFNumberRef	val = isA_CFNumber(CFArrayGetValueAtIndex(prefixLens, i));
        
                if (val) {
                    CFNumberGetValue(val, kCFNumberIntType, &(method_data->ip6[i].prefixLen));
                }
                else {
                    my_log(LOG_ERR, "ip6config: error getting prefixlen");
                    goto error;
                }
            }
            else {
                /* we use the default if none was specified - this is a 
                 * workaround for Radar 3227716.
                 */
                method_data->ip6[i].prefixLen = DEFAULT_PREFIX_LENGTH;
            }
        }
    }

    return (method_data);

 error:
    if (method_data)
        free(method_data);
    return (NULL);
}

__private_extern__ int
ip6config_address_data_from_state(CFDictionaryRef dict, 
            ip6_addrinfo_list_t * ip6_addrs)
{
    int		i, count;
    CFArrayRef	addresses = NULL;
    CFArrayRef	prefixLens = NULL;
    CFArrayRef	flags = NULL;

    addresses = CFDictionaryGetValue(dict, kSCPropNetIPv6Addresses);
    prefixLens = CFDictionaryGetValue(dict, kSCPropNetIPv6PrefixLength);
    flags = CFDictionaryGetValue(dict, kSCPropNetIPv6Flags);

    /* All must be same size */
    if (!addresses || !prefixLens || !flags) {
        my_log(LOG_DEBUG, 
            "ip6config_address_data_from_state: no addresses/prefixlens/flags in dictionary");
        return (-1);
    }
    
    count = CFArrayGetCount(addresses);
    if (count == 0) {
        my_log(LOG_ERR,
            "ip6config_address_data_from_state: address array empty");
        return (-1);
    }
    if (count != CFArrayGetCount(prefixLens)) {
        my_log(LOG_ERR,
            "ip6config_address_data_from_state: address/prefixlen arrays not same size");
        return (-1);
    }
    if (count != CFArrayGetCount(flags)) {
        my_log(LOG_ERR,
            "ip6config_address_data_from_state: address/flags arrays not same size");
        return (-1);
    }

    ip6_addrs->addr_list = calloc(count, sizeof(*ip6_addrs->addr_list));
    if (ip6_addrs->addr_list == NULL) {
        my_log(LOG_DEBUG, "ip6config_address_data_from_state: error allocating addr_list");
        return (-1);
    }
    
    for (i = 0; i < count; i++) {
        CFNumberRef	val;
        struct in6_addr tmp_addr = IN6ADDR_ANY_INIT;
        
        cfstring_to_numeric(AF_INET6, CFArrayGetValueAtIndex(addresses, i), &tmp_addr);
        memcpy(&ip6_addrs->addr_list[i].addr, &tmp_addr, sizeof(struct in6_addr));
        
        val = isA_CFNumber(CFArrayGetValueAtIndex(prefixLens, i));
        if (val) {
            CFNumberGetValue(val, kCFNumberIntType, &ip6_addrs->addr_list[i].prefixlen);
        }
            
        val = isA_CFNumber(CFArrayGetValueAtIndex(flags, i));
        if (val) {
            CFNumberGetValue(val, kCFNumberIntType, &ip6_addrs->addr_list[i].flags);
        }
    }

    ip6_addrs->n_addrs = count;
    return (0);
}

