#!/usr/bin/perl

use strict;

my @rewritesyms = (
    "krb5_cc_end_seq_get",
    "krb5_config_get_string",
    "krb5_set_default_in_tkt_etypes",
    "krb5_get_pw_salt",
    "krb5_free_salt",
    "krb5_string_to_key_data_salt",
    "krb5_free_keyblock_contents",
    "krb5_set_real_time",
    "krb5_mk_req_extended",
    "krb5_free_keyblock",
    "krb5_auth_con_getremotesubkey",
    "krb5_auth_con_getlocalsubkey",
    "krb5_set_password",
    "krb5_set_password_using_ccache",
    "krb5_realm_compare",
    "krb5_get_renewed_creds",
    "krb5_get_validated_creds",
    "krb5_get_init_creds_keytab",
    "krb5_prompter_posix",
    "krb5_string_to_deltat",
    "krb5_get_all_client_addrs",
    "krb5_kt_get_type",
    "krb5_kt_add_entry",
    "krb5_kt_remove_entry",
    "krb5_mk_req",
    "krb5_kt_get_name",
    "krb5_rd_req",
    "krb5_free_ticket",
    "krb5_build_principal_va",
    "krb5_build_principal_va_ext",
    "krb5_cc_cache_match",
    "krb5_cc_close",
    "krb5_cc_default",
    "krb5_cc_get_config",
    "krb5_cc_get_full_name",
    "krb5_cc_get_name",
    "krb5_cc_get_principal",
    "krb5_cc_get_type",
    "krb5_cc_initialize",
    "krb5_cc_move",
    "krb5_cc_new_unique",
    "krb5_cc_resolve",
    "krb5_cc_store_cred",
    "krb5_cc_switch",
    "krb5_cc_retrieve_cred",
    "krb5_cc_remove_cred",
    "krb5_cc_get_kdc_offset",
    "krb5_cc_set_kdc_offset",
    "krb5_cc_next_cred",
    "krb5_cccol_last_change_time",
    "krb5_crypto_init",
    "krb5_crypto_getblocksize",
    "krb5_crypto_destroy",
    "krb5_decrypt_ivec",
    "krb5_encrypt_ivec",
    "krb5_crypto_getenctype",
    "krb5_generate_random_keyblock",
    "krb5_get_wrapped_length",
    "krb5_copy_creds_contents",
    "krb5_copy_data",
    "krb5_copy_principal",
    "krb5_data_copy",
    "krb5_data_free",
    "krb5_data_zero",
    "krb5_free_context",
    "krb5_free_cred_contents",
    "krb5_free_creds",
    "krb5_free_principal",
    "krb5_sname_to_principal",
    "krb5_get_credentials",
    "krb5_get_error_string",
    "krb5_get_default_principal",
    "krb5_get_error_message",
    "krb5_get_init_creds_opt_alloc",
    "krb5_get_init_creds_opt_free",
    "krb5_get_init_creds_opt_set_canonicalize",
    "krb5_get_init_creds_opt_set_forwardable",
    "krb5_get_init_creds_opt_set_proxiable",
    "krb5_get_init_creds_opt_set_renew_life",
    "krb5_get_init_creds_opt_set_tkt_life",
    "krb5_get_init_creds_password",
    "krb5_get_kdc_cred",
    "krb5_get_kdc_sec_offset",
    "krb5_init_context",
    "krb5_init_context_flags",
    "krb5_make_principal",
    "krb5_parse_name",
    "krb5_parse_name_flags",
    "krb5_principal_compare",
    "krb5_principal_get_realm",
    "krb5_timeofday",
    "krb5_unparse_name",
    "krb5_us_timeofday",
    "krb5_kt_start_seq_get",
    "krb5_kt_end_seq_get",
    "krb5_xfree",
    "krb5_kt_next_entry",
    "krb5_kt_free_entry",
    "gsskrb5_extract_authz_data_from_sec_context",
    "krb5_sendauth",
    "krb5_free_ap_rep_enc_part",
    "krb5_free_error",
    "krb5_recvauth",
    "krb5_recvauth_match_version",
    "krb5_mk_priv",
    "krb5_rd_priv",
    "krb5_mk_safe",
    "krb5_rd_safe",
    "krb5_set_home_dir_access",
    "krb5_verify_init_creds",
    "krb5_verify_init_creds_opt_init",
    "krb5_verify_init_creds_opt_set_ap_req_nofail",
    "krb5_kuserok",
    "com_right",
    "com_right_r",
    "gss_import_name",
    "krb5_appdefault_boolean",
    "krb5_appdefault_string",
    );

my @proxysyms = (
    "gss_accept_sec_context",
    "gss_acquire_cred",
    "gss_add_cred",
    "gss_add_oid_set_member",
    "gss_canonicalize_name",
    "gss_compare_name",
    "gss_context_time",
    "gss_create_empty_oid_set",
    "gss_delete_sec_context",
    "gss_display_name",
    "gss_display_status",
    "gss_duplicate_name",
    "gss_export_name",
    "gss_export_sec_context",
    "gss_get_mic",
    "gss_import_sec_context",
    "gss_indicate_mechs",
    "gss_init_sec_context",
    "gss_inquire_context",
    "gss_inquire_cred",
    "gss_inquire_cred_by_mech",
    "gss_inquire_names_for_mech",
    "gss_krb5_ccache_name",
    "gss_krb5_copy_ccache",
    "gss_krb5_export_lucid_sec_context",
    "gss_krb5_free_lucid_sec_context",
    "gss_krb5_set_allowable_enctypes",
    "gss_oid_equal",
    "gss_oid_to_str",
    "gss_process_context_token",
    "gss_release_buffer",
    "gss_release_cred",
    "gss_release_name",
    "gss_release_oid",
    "gss_release_oid_set",
    "gss_seal",
    "gss_test_oid_set_member",
    "gss_unseal",
    "gss_unwrap",
    "gss_verify_mic",
    "gss_wrap",
    "gss_wrap_size_limit",
    "krb5_cc_start_seq_get",
    "krb5_cc_default_name",
    "krb5_cc_destroy",
    "krb5_cccol_cursor_free",
    "krb5_cccol_cursor_new",
    "krb5_cccol_cursor_next",
    "krb5_free_host_realm",
    "krb5_get_default_realm",
    "krb5_get_host_realm",
    "krb5_gss_register_acceptor_identity",
    "krb5_cc_set_default_name",
    "krb5_kt_resolve",
    "krb5_kt_default",
    "krb5_kt_default_name",
    "krb5_kt_close",
    "krb5_kt_destroy",
    "krb5_auth_con_free",
    "krb5_auth_con_init",
    "krb5_auth_con_genaddrs",
    "krb5_auth_con_getlocalseqnumber",
    "krb5_auth_con_getremoteseqnumber",
    "krb5_auth_con_setflags",
    "krb5_auth_con_getflags",
    "krb5_clear_error_message",
    "krb5_free_error_message",
    "krb5_set_default_realm",
    "krb5_set_error_message",
    "krb5_vset_error_message",
    "com_err",
    "com_err_va",
    "reset_com_err_hook",
    "set_com_err_hook",
    );


sub gen_header {
    my $sym;
    my $syms = shift;
    print "void __attribute__((constructor)) heim_load_frameworks(void);\n";
    print "void heim_load_functions(void);\n";
    foreach $sym (@$syms) {
	print "extern void (*fun_${sym})();\n";
    }
}

sub gen_loader {
    my $sym;
    my $syms = shift;
    print "#include <dlfcn.h>\n";
    print "#include <stdio.h>\n";
    print "#include <stdlib.h>\n";
    print "#include <syslog.h>\n";
    print "#include <dispatch/dispatch.h>\n";
    print "#include \"heim-sym.h\"\n";
    print "\n";
    print "static void *hf = NULL;\n";
    print "static void *gf = NULL;\n";
    print "void heim_load_frameworks(void) {\n";
    print "hf = dlopen(\"/System/Library/PrivateFrameworks/Heimdal.framework/Heimdal\", RTLD_LAZY|RTLD_LOCAL);\n";
    print "gf = dlopen(\"/System/Library/Frameworks/GSS.framework/GSS\", RTLD_LAZY|RTLD_LOCAL);\n";
    printf "}\n";
    printf "\n";

    print "void load_functions(void) {\n";
    foreach $sym (@$syms) {
	my $lib;
	if ($sym =~ /gss/) {
	    $lib = "gf";
	} else {
	    $lib = "hf";
	}
	print "fun_${sym} = dlsym(${lib}, \"$sym\");\n";
	print "if (!fun_${sym}) { syslog(LOG_ERR, \"${sym} failed loading\"); }\n";
    }
    printf "}\n";
    printf "\n";
    print "void heim_load_functions(void) {\n";
    print "static dispatch_once_t once = 0;\n";
    print "dispatch_once(&once, ^{ load_functions(); });\n";
    print ("}\n\n");
}

sub gen_32 {

    my $sym;
    my $syms = shift;
    my $prefix = shift;

    my $num = 0;

    print "#ifdef __i386__\n";
    print "	.text\n\n";

    foreach $sym (@$syms) {
	$num++;
	print ".globl ${prefix}_${sym}\n";
	print "${prefix}_${sym}:\n";
	print "\tpushl	%ebp\n";
	print "\tmovl	%esp, %ebp\n";
	print "\tsubl	\$72, %esp\n";
	print "\tcall _heim_load_functions\n";
	print "\taddl	\$72, %esp\n";
	print "\tmovl	%ebp, %esp\n";
	print "\popl	%ebp\n";
	print "\tcall L0${num}\n";
	print "L0${num}:\n";
	print "\tpopl %edx\n";
	print "\tleal L_fun_${sym}\$non_lazy_ptr-\"L0${num}\"(%edx), %eax\n";
	print "\tmovl (%eax), %edx\n";
	print "\tmovl (%edx), %edx\n";
	print "\tjmp *%edx\n";
	print "\n";
    }
    
    foreach $sym (@$syms) {
	print ".comm _fun_${sym},4,2\n";
    }
    print "	.section __IMPORT,__pointers,non_lazy_symbol_pointers\n";
    foreach $sym (@$syms) {
	print "L_fun_${sym}\$non_lazy_ptr:\n";
	print "	.indirect_symbol _fun_${sym}\n";
	print "\t.long 0\n";
    }
    print "\t.subsections_via_symbols\n";

    print "#endif\n";
}

sub gen_64 {

    my $sym;
    my $syms = shift;
    my $prefix = shift;

    print "#ifdef __x86_64__\n";
    print ".text\n\n";

    foreach $sym (@$syms) {
	print "	.globl ${prefix}_${sym}\n";
	print "${prefix}_${sym}:\n";
	print "\tpushq	%rbp\n";
	print "\tmovq	%rsp, %rbp\n";
	print "\tsubq	\$208, %rsp\n";
	print "\tpushq %rdi\n";
	print "\tpushq %rsi\n";
        print "\tpushq %rdx\n";
	print "\tpushq %rcx\n";
	print "\tpushq %r8\n";
	print "\tpushq %r9\n";
	print "\tcall	_heim_load_functions\n";
	print "\tpopq %r9\n";
	print "\tpopq %r8\n";
	print "\tpopq %rcx\n";
        print "\tpopq %rdx\n";
	print "\tpopq %rsi\n";
	print "\tpopq %rdi\n";
	print "\taddq	\$208, %rsp\n";
	print "\tmovq	%rbp, %rsp\n";
	print "\tpopq   %rbp\n";
	print "\tmovq    _fun_${sym}\@GOTPCREL(%rip), %r11\n";
	print "\tmovq    (%r11), %r11\n";
	print "\tjmp	*%r11\n";
	print "\n";
    }
    
    foreach $sym (@$syms) {
	print ".comm _fun_${sym},8,3\n";
    }
    print "#endif\n";
}

sub gen_ppc {

    my $sym;
    my $syms = shift;
    my $prefix = shift;

    print "#ifdef __ppc__\n";
    print ".section __TEXT,__text,regular,pure_instructions\n";
    print ".section __TEXT,__picsymbolstub1,symbol_stubs,pure_instructions,32\n";
    print ".machine ppc7400\n";
    print ".text\n";

    foreach $sym (@$syms) {
	print ".globl ${prefix}_${sym}\n";
	print "${prefix}_${sym}:\n";
	print "\tbl	_abort\n";
	print "\n";
    }

    foreach $sym (@$syms) {
	print ".comm _fun_${sym},4,2\n";
    }

    print "#endif\n";
}


print "/* generated file, no dont edit */\n";

my @all = (@rewritesyms, @proxysyms);

gen_32(\@rewritesyms, "_heim") if ($ARGV[0] eq '3');
gen_64(\@rewritesyms, "_heim") if ($ARGV[0] eq '6');
gen_ppc(\@rewritesyms, "_heim") if ($ARGV[0] eq 'p');
gen_32(\@proxysyms, "") if ($ARGV[0] eq '3p');
gen_64(\@proxysyms, "") if ($ARGV[0] eq '6p');
gen_ppc(\@proxysyms, "") if ($ARGV[0] eq 'pp');

gen_header(\@all) if ($ARGV[0] eq 'h');
gen_loader(\@all) if ($ARGV[0] eq 'l');

