/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2007-2012 Apple Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _FORTIFY_SOURCE 2

#include <CoreFoundation/CoreFoundation.h>
#include <sys/cdefs.h>
#include <sys/time.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#include <launch.h>
#include <pwd.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <Security/Security.h>
#include "helper.h"
#include "helper-server.h"
#include <xpc/private.h>

#if TARGET_OS_EMBEDDED
#define NO_SECURITYFRAMEWORK 1
#endif

#ifndef LAUNCH_JOBKEY_MACHSERVICES
#define LAUNCH_JOBKEY_MACHSERVICES "MachServices"
#define LAUNCH_DATA_MACHPORT 10
#define launch_data_get_machport launch_data_get_fd
#endif


int mDNSHelperLogEnabled = 0;
os_log_t  log_handle = NULL;

static dispatch_queue_t xpc_queue = NULL;
static int opt_debug;
static pthread_t idletimer_thread;

unsigned long maxidle = 15;
unsigned long actualidle = 3600;

CFRunLoopRef gRunLoop = NULL;
CFRunLoopTimerRef gTimer = NULL;


static void handle_sigterm(int sig)
{
    // os_log_debug(log_handle,"entry sig=%d", sig);	Can't use syslog from within a signal handler
    assert(sig == SIGTERM);
    helper_exit();
}

static void initialize_logging(void)
{
    log_handle   = os_log_create("com.apple.mDNSResponderHelper", "INFO");
    
    if (!log_handle)
    {
        // OS_LOG_DEFAULT is the default logging object, if you are not creating a custom subsystem/category
        os_log_error(OS_LOG_DEFAULT, "Could NOT create log handle in mDNSResponderHelper");
    }
    
}

static void initialize_id(void)
{
    static char login[] = "_mdnsresponder";
    struct passwd hardcode;
    struct passwd *pwd = &hardcode; // getpwnam(login);
    hardcode.pw_uid = 65;
    hardcode.pw_gid = 65;

    if (!pwd)
    {
        os_log(log_handle, "Could not find account name `%s'.  I will only help root.", login);
        return;
    }
    mDNSResponderUID = pwd->pw_uid;
    mDNSResponderGID = pwd->pw_gid;
}

static void diediedie(CFRunLoopTimerRef timer, void *context)
{
    os_log_info(log_handle, "entry %p %p %lu", timer, context, actualidle);
    
    assert(gTimer == timer);
    os_log_info(log_handle, "mDNSResponderHelper exiting after [%lu] seconds", actualidle);
    
    if (actualidle)
        helper_exit();
}

void pause_idle_timer(void)
{
    os_log_debug(log_handle,"entry");
    assert(gTimer);
    assert(gRunLoop);
    CFRunLoopRemoveTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode);
}

void unpause_idle_timer(void)
{
    os_log_debug(log_handle,"entry");
    assert(gRunLoop);
    assert(gTimer);
    CFRunLoopAddTimer(gRunLoop, gTimer, kCFRunLoopDefaultMode);
}

void update_idle_timer(void)
{
    os_log_debug(log_handle,"entry");
    assert(gTimer);
    CFRunLoopTimerSetNextFireDate(gTimer, CFAbsoluteTimeGetCurrent() + actualidle);
}

static void *idletimer(void *context)
{
    os_log_debug(log_handle,"entry context=%p", context);
    gRunLoop = CFRunLoopGetMain();

    unpause_idle_timer();

    for (;;)
    {
        // os_log_debug(log_handle,"Running CFRunLoop");
        CFRunLoopRun();
        sleep(1);
    }

    return NULL;
}

static int initialize_timer()
{
    gTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + actualidle, actualidle, 0, 0, diediedie, NULL);
    int err = 0;
    os_log_info(log_handle, "mDNSResponderHelper initialize_timer() started");

    if (0 != (err = pthread_create(&idletimer_thread, NULL, idletimer, NULL)))
        os_log(log_handle, "Could not start idletimer thread: %s", strerror(err));

    return err;
}

/* 
 Reads the user's program arguments for mDNSResponderHelper
 For now we have only one option: mDNSHelperDebugLogging which is used to turn on mDNSResponderHelperLogging
 
 To turn ON mDNSResponderHelper Verbose Logging,
 1] sudo defaults write /Library/Preferences/com.apple.mDNSResponderHelper.plist mDNSHelperDebugLogging -bool YES
 2] sudo reboot
 
 To turn OFF mDNSResponderHelper Logging,
 1] sudo defaults delete /Library/Preferences/com.apple.mDNSResponderHelper.plist

 To view the current options set,
 1] plutil -p /Library/Preferences/com.apple.mDNSResponderHelper.plist
 OR
 1] sudo defaults read /Library/Preferences/com.apple.mDNSResponderHelper.plist
*/

static mDNSBool HelperPrefsGetValueBool(CFStringRef key, mDNSBool defaultVal)
{
    CFBooleanRef boolean;
    mDNSBool result = defaultVal;
    
    boolean = CFPreferencesCopyAppValue(key, kmDNSHelperProgramArgs);
    if (boolean)
    {
        if (CFGetTypeID(boolean) == CFBooleanGetTypeID())
            result = CFBooleanGetValue(boolean) ? mDNStrue : mDNSfalse;
        CFRelease(boolean);
    }
    
    return result;
}


// Verify Client's Entitlement
static mDNSBool check_entitlement(xpc_connection_t conn, const char *password)
{
    mDNSBool entitled = mDNSfalse;
    xpc_object_t ent = xpc_connection_copy_entitlement_value(conn, password);
    
    if (ent)
    {
        if (xpc_get_type(ent) == XPC_TYPE_BOOL && xpc_bool_get_value(ent))
        {
            entitled = mDNStrue;
        }
        xpc_release(ent);
    }
    else
    {
        os_log(log_handle, "client entitlement is NULL");
    }
    
    if (!entitled)
        os_log(log_handle, "entitlement check failed -> client is missing entitlement!");
    
    return entitled;
}


static void handle_request(xpc_object_t req)
{
    mDNSu32 helper_mode = 0;
    int error_code = 0;
    
    xpc_connection_t remote_conn = xpc_dictionary_get_remote_connection(req);
    xpc_object_t response = xpc_dictionary_create_reply(req);
    
    // switch here based on dictionary to handle different requests from mDNSResponder
    if ((xpc_dictionary_get_uint64(req, kHelperMode)))
    {
        os_log_info(log_handle, "Getting mDNSResponder request mode");
        helper_mode = (mDNSu32)(xpc_dictionary_get_uint64(req, kHelperMode));
    }
   
    switch (helper_mode)
    {
        case bpf_request:
        {
            os_log_info(log_handle, "Calling new RequestBPF()");
            RequestBPF();
            break;
        }
            
        case set_name:
        {
            const char *old_name;
            const char *new_name;
            int pref_key = 0;
            
            pref_key = (int)(xpc_dictionary_get_uint64(req, kPrefsNameKey));
            old_name = xpc_dictionary_get_string(req, kPrefsOldName);
            new_name = xpc_dictionary_get_string(req, kPrefsNewName);
            
            os_log_info(log_handle, "Calling new SetName() oldname: %s newname: %s key:%d", old_name, new_name, pref_key);
            PreferencesSetName(pref_key, old_name, new_name);
            break;
        }
            
        case p2p_packetfilter:
        {
            pfArray_t pfports;
            pfArray_t pfprotocols;
            const char *if_name;
            uint32_t cmd;
            uint32_t count;
            
            cmd = xpc_dictionary_get_uint64(req, "pf_opcode");
            if_name = xpc_dictionary_get_string(req, "pf_ifname");
            count = xpc_dictionary_get_uint64(req, "pf_count");
            
            pfports[0]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port0");
            pfports[1]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port1");
            pfports[2]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port2");
            pfports[3]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_port3");
            
            pfprotocols[0]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol0");
            pfprotocols[1]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol1");
            pfprotocols[2]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol2");
            pfprotocols[3]  = (uint16_t)xpc_dictionary_get_uint64(req, "pf_protocol3");
            
            os_log_info(log_handle,"Calling new PacketFilterControl()");
            PacketFilterControl(cmd, if_name, count, pfports, pfprotocols);
            break;
        }
            
        case user_notify:
        {
            const char *title;
            const char *msg;
            title = xpc_dictionary_get_string(req, "notify_title");
            msg   = xpc_dictionary_get_string(req, "notify_msg");
            
            os_log_info(log_handle,"Calling new UserNotify() title:%s msg:%s", title, msg);
            UserNotify(title, msg);
            break;
        }
            
        case power_req:
        {
            int key, interval;
            key        = xpc_dictionary_get_uint64(req, "powerreq_key");
            interval   = xpc_dictionary_get_uint64(req, "powerreq_interval");
            
            os_log_info(log_handle,"Calling new PowerRequest() key[%d] interval[%d]", key, interval);
            PowerRequest(key, interval, &error_code);
            break;
        }
            
        case send_wakepkt:
        {
            const char *ether_addr;
            const char *ip_addr;
            int iteration;
            unsigned int if_id;
            
            if_id = (unsigned int)xpc_dictionary_get_uint64(req, "interface_index");
            ether_addr = xpc_dictionary_get_string(req, "ethernet_address");
            ip_addr = xpc_dictionary_get_string(req, "ip_address");
            iteration = (int)xpc_dictionary_get_uint64(req, "swp_iteration");
            
            os_log_info(log_handle, "Calling new SendWakeupPacket() ether_addr[%s] ip_addr[%s] if_id[%d] iteration[%d]",
                           ether_addr, ip_addr, if_id, iteration);
            SendWakeupPacket(if_id, ether_addr, ip_addr, iteration);
            break;
        }
            
        case set_localaddr_cacheentry:
        {
            int if_index, family;
            
            if_index = xpc_dictionary_get_uint64(req, "slace_ifindex");
            family   = xpc_dictionary_get_uint64(req, "slace_family");
            
            const uint8_t* ip  = xpc_dictionary_get_data(req, "slace_ip", NULL);
            const uint8_t* eth = xpc_dictionary_get_data(req, "slace_eth", NULL);
            
            os_log_info(log_handle, "Calling new SetLocalAddressCacheEntry() if_index[%d] family[%d] ", if_index, family);

            SetLocalAddressCacheEntry(if_index, family, ip, eth, &error_code);
            
            /*
            static int v6addr_to_string(const v6addr_t addr, char *buf, size_t buflen)
            {
                if (NULL == inet_ntop(AF_INET6, addr, buf, buflen))
                {
                    os_log(log_handle, "inet_ntop failed: %s", strerror(errno));
                    return -1;
                }
                else
                {
                    return 0;
                }
            }

            ethaddr_t eth = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 } ;
            const uint8_t* slace_ip = NULL;
            v6addr_t addr_ipv6;
            size_t ip_len;
            slace_ip = xpc_dictionary_get_data(req, "slace_ip", &ip_len);
            if (slace_ip && (ip_len == sizeof(v6addr_t)))
            {
                os_log(log_handle, "mDNSResponderHelper: doing memcpy()");
                memcpy(&addr_ipv6, slace_ip, ip_len);
            }
            char test_ipv6_str[46];
            v6addr_to_string(addr_ipv6, test_ipv6_str, sizeof(test_ipv6_str));
            os_log(log_handle, "mDNSResponderHelper: handle_request: set_localaddr_cacheentry: test_ipv6_str is %s", test_ipv6_str);
            */
                        
            break;
        }
            
        case send_keepalive:
        {
            uint16_t lport, rport, win;
            uint32_t seq, ack;
            
            lport = xpc_dictionary_get_uint64(req, "send_keepalive_lport");
            rport = xpc_dictionary_get_uint64(req, "send_keepalive_rport");
            seq   = xpc_dictionary_get_uint64(req, "send_keepalive_seq");
            ack   = xpc_dictionary_get_uint64(req, "send_keepalive_ack");
            win   = xpc_dictionary_get_uint64(req, "send_keepalive_win");
            
            const uint8_t* sadd6 = xpc_dictionary_get_data(req, "send_keepalive_sadd", NULL);
            const uint8_t* dadd6 = xpc_dictionary_get_data(req, "send_keepalive_dadd", NULL);
            
            os_log_info(log_handle, "helper-main: handle_request: send_keepalive: lport is[%d] rport is[%d] seq is[%d] ack is[%d] win is[%d]",
                           lport, rport, seq, ack, win);
            
            SendKeepalive(sadd6, dadd6, lport, rport, seq, ack, win);
            break;
        }
    
        case retreive_tcpinfo:
        {
            uint16_t lport, rport;
            int family;
            uint32_t seq, ack;
            uint16_t win;
            int32_t  intfid;
            
            lport    = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_lport");
            rport    = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_rport");
            family   = xpc_dictionary_get_uint64(req, "retreive_tcpinfo_family");
            
            const uint8_t* laddr = xpc_dictionary_get_data(req, "retreive_tcpinfo_laddr", NULL);
            const uint8_t* raddr = xpc_dictionary_get_data(req, "retreive_tcpinfo_raddr", NULL);
            
            os_log_info(log_handle, "helper-main: handle_request: retreive_tcpinfo: lport is[%d] rport is[%d] family is [%d]",
                           lport, rport, family);
            
            RetrieveTCPInfo(family, laddr, lport, raddr, rport, &seq, &ack, &win, &intfid, &error_code);
            
            if (response)
            {
                xpc_dictionary_set_uint64(response, "retreive_tcpinfo_seq",  seq);
                xpc_dictionary_set_uint64(response, "retreive_tcpinfo_ack",  ack);
                xpc_dictionary_set_uint64(response, "retreive_tcpinfo_win",  win);
                xpc_dictionary_set_uint64(response, "retreive_tcpinfo_ifid", intfid);
            }
            
            os_log_info(log_handle, "helper-main: handle_request: retreive_tcpinfo: seq is[%d] ack is[%d] win is [%d] intfid is [%d]",
                           seq, ack, win, intfid);

            break;
        }
            
        case autotunnel_setkeys:
        {
            uint16_t lport, rport;
            int replace_del;
            const char *fqdnstr;
            
            lport        = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_lport");
            rport        = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_rport");
            replace_del  = xpc_dictionary_get_uint64(req, "autotunnelsetkeys_repdel");

            const uint8_t* local_inner  =  xpc_dictionary_get_data(req, "autotunnelsetkeys_localinner", NULL);
            const uint8_t* local_outer  =  xpc_dictionary_get_data(req, "autotunnelsetkeys_localouter", NULL);
            const uint8_t* remote_inner =  xpc_dictionary_get_data(req, "autotunnelsetkeys_remoteinner", NULL);
            const uint8_t* remote_outer =  xpc_dictionary_get_data(req, "autotunnelsetkeys_remoteouter", NULL);
            
            fqdnstr = xpc_dictionary_get_string(req, "autotunnelsetkeys_fqdnStr");
            
            os_log_info(log_handle, "helper-main: handle_request: autotunnel_setkeys: lport is[%d] rport is[%d] replace_del is [%d]",
                        lport, rport, replace_del);
            

            HelperAutoTunnelSetKeys(replace_del, local_inner, local_outer, lport, remote_inner, remote_outer, rport, fqdnstr, &error_code);

            break;
        }

        
        case keychain_getsecrets:
        {
            unsigned int num_sec  = 0;
            unsigned long secrets = 0;
            unsigned int sec_cnt  = 0;
            
            os_log_info(log_handle,"Calling new KeyChainGetSecrets()");
            
            KeychainGetSecrets(&num_sec, &secrets, &sec_cnt, &error_code);
            
            if (response)
            {
                xpc_dictionary_set_uint64(response, "keychain_num_secrets",   num_sec);
                xpc_dictionary_set_data(response, "keychain_secrets", (void *)secrets, sec_cnt);
                xpc_dictionary_set_uint64(response, "keychain_secrets_count", sec_cnt);
            }
            
            os_log_info(log_handle,"helper-main: handle_request: keychain_getsecrets: num_secrets is %d, secrets is %lu, secrets_Cnt is %d",
                           num_sec, secrets, sec_cnt);
            
            if (secrets)
                vm_deallocate(mach_task_self(), secrets, sec_cnt);
            
            break;
        }
            
        default:
        {
            os_log(log_handle, "handle_request: Unrecognized mode!");
            error_code  = kHelperErr_UndefinedMode;
            break;
        }
    }
    
    // Return Response Status back to the client (essentially ACKing the request)
    if (response)
    {
        xpc_dictionary_set_uint64(response, kHelperReplyStatus, kHelperReply_ACK);
        xpc_dictionary_set_int64(response, kHelperErrCode, error_code);
        xpc_connection_send_message(remote_conn, response);
        xpc_release(response);
    }
    else
    {
        os_log(log_handle, "handle_requests: Response Dictionary could not be created!");
        return;
    }
    
}

static void accept_client(xpc_connection_t conn)
{
    int c_pid = xpc_connection_get_pid(conn);
    
    if (!(check_entitlement(conn, kHelperService)))
    {
        os_log(log_handle, "accept_client: Helper Client PID[%d] is missing Entitlement. Cancelling connection", c_pid);
        xpc_connection_cancel(conn);
        return;
    }
    
    xpc_retain(conn);
    xpc_connection_set_target_queue(conn, xpc_queue);
    xpc_connection_set_event_handler(conn, ^(xpc_object_t req_msg)
    {
        xpc_type_t type = xpc_get_type(req_msg);
                                         
        if (type == XPC_TYPE_DICTIONARY)
        {
            os_log_info(log_handle,"accept_client:conn:[%p] client[%d](mDNSResponder) requesting service", (void *) conn, c_pid);
            handle_request(req_msg);
        }
        else // We hit this case ONLY if Client Terminated Connection OR Crashed
        {
            os_log(log_handle, "accept_client:conn:[%p] client[%d](mDNSResponder) teared down the connection (OR Crashed)", (void *) conn, c_pid);
            // handle_termination();
            xpc_release(conn);
        }
    });
    
    xpc_connection_resume(conn);
}


static void init_helper_service(const char *service_name)
{
    
    xpc_connection_t xpc_listener = xpc_connection_create_mach_service(service_name, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
    if (!xpc_listener || xpc_get_type(xpc_listener) != XPC_TYPE_CONNECTION)
    {
        os_log(log_handle, "init_helper_service: Error Creating XPC Listener for mDNSResponderHelperService !!");
        return;
    }
    
    os_log_info(log_handle,"init_helper_service: XPC Listener for mDNSResponderHelperService Listening");
    
    xpc_queue = dispatch_queue_create("com.apple.mDNSHelper.service_queue", NULL);
    
    xpc_connection_set_event_handler(xpc_listener, ^(xpc_object_t eventmsg)
    {
        xpc_type_t type = xpc_get_type(eventmsg);
                                         
        if (type == XPC_TYPE_CONNECTION)
        {
            os_log_info(log_handle,"init_helper_service: new mDNSResponderHelper Client %p", eventmsg);
            accept_client(eventmsg);
        }
        else if (type == XPC_TYPE_ERROR) // Ideally, we would never hit these cases below
        {
            os_log(log_handle, "init_helper_service: XPCError: %s", xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
            return;
        }
        else
        {
            os_log(log_handle, "init_helper_service: Unknown EventMsg type");
            return;
        }
    });
    
    xpc_connection_resume(xpc_listener);
}


int main(int ac, char *av[])
{
    char *p = NULL;
    long n;
    int ch;

    while ((ch = getopt(ac, av, "dt:")) != -1)
    {
        switch (ch)
        {
            case 'd':
                opt_debug = 1;
                break;
            case 't':
                n = strtol(optarg, &p, 0);
                if ('\0' == optarg[0] || '\0' != *p || n > LONG_MAX || n < 0)
                {
                    fprintf(stderr, "Invalid idle timeout: %s\n", optarg);
                    exit(EXIT_FAILURE);
                }
                maxidle = n;
                break;
            case '?':
            default:
                fprintf(stderr, "Usage: mDNSResponderHelper [-d] [-t maxidle]\n");
                exit(EXIT_FAILURE);
        }
    }
    ac -= optind;
    av += optind;
    (void)ac; // Unused
    (void)av; // Unused

    initialize_logging();
    initialize_id();

    mDNSHelperLogEnabled = HelperPrefsGetValueBool(kPreferencesKey_mDNSHelperLog, mDNSHelperLogEnabled);

// Currently on Fuji/Whitetail releases we are keeping the logging always enabled.
// Hence mDNSHelperLogEnabled is set to true below by default.
    mDNSHelperLogEnabled      = 1;

    os_log_info(log_handle,"mDNSResponderHelper Starting to run");

#ifndef NO_SECURITYFRAMEWORK
    // We should normally be running as a system daemon.  However, that might not be the case in some scenarios (e.g. debugging).
    // Explicitly ensure that our Keychain operations utilize the system domain.
    if (opt_debug)
        SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
#endif

    if (maxidle)
        actualidle = maxidle;

    signal(SIGTERM, handle_sigterm);

    if (initialize_timer())
        exit(EXIT_FAILURE);
    for (n=0; n<100000; n++)
        if (!gRunLoop)
            usleep(100);
    
    if (!gRunLoop)
    {
        os_log(log_handle, "gRunLoop not set after waiting");
        exit(EXIT_FAILURE);
    }

    init_helper_service(kHelperService);
    os_log_info(log_handle,"mDNSResponderHelper is now running");
    dispatch_main();
    
}

// Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion
// e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4"
// To expand "version" to its value before making the string, use STRINGIFY(version) instead
#define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s
#define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)

// For convenience when using the "strings" command, this is the last thing in the file
// The "@(#) " pattern is a special prefix the "what" command looks for
const char VersionString_SCCS[] = "@(#) mDNSResponderHelper " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";

#if _BUILDING_XCODE_PROJECT_
// If the process crashes, then this string will be magically included in the automatically-generated crash log
const char *__crashreporter_info__ = VersionString_SCCS + 5;
asm (".desc ___crashreporter_info__, 0x10");
#endif
