/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * macnc_options.c
 * - handle dhcp/bootp options specific to the macNC
 */

/*
 * Modification History:
 *
 * December 15, 1997	Dieter Siegmund (dieter@apple)
 * - created
 * November 19, 1999 	Dieter Siegmund (dieter@apple)
 * - converted to regular C
 */
#import <unistd.h>
#import <stdlib.h>
#import <stdio.h>
#import <sys/types.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <string.h>
#import "rfc_options.h"
#import "macnc_options.h"

typedef struct {
    macNCtype_t		type;
    dhcptype_info_t		info;
} macNCtype_info_t;

static macNCtype_info_t macNCtype_info_table[] = {
    { macNCtype_pstring_e, 	{ 1, dhcptype_none_e, "PString" } },
    { macNCtype_afp_path_e, 	{ 0, dhcptype_none_e, "AFP path" } },
    { macNCtype_afp_password_e, { 8, dhcptype_none_e, "AFP password" } },
};

static int macNCtype_info_size = sizeof(macNCtype_info_table) 
     / sizeof(macNCtype_info_t);

typedef struct {
    macNCtag_t		tag;
    dhcptag_info_t	info;
} macNCtag_info_t;

static macNCtag_info_t macNCtag_info_table[] = {
 { macNCtag_client_version_e, {dhcptype_uint32_e, "macNC_client_version" } },
 { macNCtag_client_info_e, { dhcptype_opaque_e, "macNC_client_info" } },
 { macNCtag_server_version_e, { dhcptype_uint32_e, "macNC_server_version" } },
 { macNCtag_server_info_e, { dhcptype_opaque_e, "macNC_server_info" } },
 { macNCtag_user_name_e, { dhcptype_string_e, "macNC_user_name" } },
 { macNCtag_password_e, { macNCtype_afp_password_e, "macNC_password" } },
 { macNCtag_shared_system_file_e, 
       { macNCtype_afp_path_e, "macNC_shared_system_file" } },
 { macNCtag_private_system_file_e, 
       { macNCtype_afp_path_e, "macNC_private_system_file" } },
 { macNCtag_page_file_e, 
       { macNCtype_afp_path_e, "macNC_page_file" } },
 { macNCtag_MacOS_machine_name_e, 
       { dhcptype_string_e, "macNC_MacOS_machine_name" } },
 { macNCtag_shared_system_shadow_file_e,
       { macNCtype_afp_path_e, "macNC_shared_system_shadow_file" } },
 { macNCtag_private_system_shadow_file_e,
       { macNCtype_afp_path_e, "macNC_private_system_shadow_file" } },
};

static int macNCtag_info_size = sizeof(macNCtag_info_table) 
     / sizeof(macNCtag_info_t);

dhcptype_info_t *
macNCtype_info(int type)
{
    int 		i;
    dhcptype_info_t * 	t;

    for (i = 0; i < macNCtype_info_size; i++) {
	if (type == macNCtype_info_table[i].type)
	    return (&macNCtype_info_table[i].info);
    }
    t = dhcptype_info(type);
    if (t)
	return (t);
    return (NULL);
}

static dhcptag_info_t * 
macNCtag_info(int tag)
{
    int 		i;
    dhcptag_info_t * 	t;

    for (i = 0; i < macNCtag_info_size; i++) {
	if (tag == macNCtag_info_table[i].tag)
	    return (&macNCtag_info_table[i].info);
    }
    t = dhcptag_info(tag);
    if (t)
	return (t);
    return (NULL);
}

boolean_t
macNCopt_str_to_type(unsigned char * str, 
		     int type, void * buf, int * len_p,
		     unsigned char * err)
{
    dhcptype_info_t * 	type_info = macNCtype_info(type);

    if (err)
	err[0] = '\0';

    switch (type) {
      case macNCtype_afp_password_e: {
	  int len = strlen(str);
	  if (*len_p < AFP_PASSWORD_LEN) {
	      if (err)
		  sprintf(err, "%s: buffer too small (%d < %d)",
			  type_info->name, *len_p, AFP_PASSWORD_LEN);
	      return (FALSE);
	  }
	  if (len > AFP_PASSWORD_LEN) {
	      if (err)
		  sprintf(err, "%s: string too large (%d > %d)",
			  type_info->name, len, AFP_PASSWORD_LEN);
	      return (FALSE);
	  }
	  *len_p = AFP_PASSWORD_LEN;
	  bzero(buf, AFP_PASSWORD_LEN);
	  strncpy((u_char *)buf, str, len);
        }
	break;
      case macNCtype_pstring_e: {
	  int len = strlen(str);
	  if (*len_p < (len + 1)) {
	      if (err)
		  sprintf(err, "%s: buffer too small (%d < %d)",
			  type_info->name, *len_p, len + 1);
	      return (FALSE);
	  }
	  ((u_char *)buf)[0] = len;			/* string length */
	  bcopy(str, buf + 1, len);
	  *len_p = len + 1;
        }
	break;
      case macNCtype_afp_path_e:
	if (err)
	    sprintf(err, "%s: not supported", type_info->name);
	return (FALSE);
	break;
      default:
	return (dhcptype_from_str(str, type, buf, len_p, err));
	break;
    }
    return (TRUE);
}

static void
S_replace_separators(u_char * buf, int len, u_char sep, u_char new_sep)
{
    int i;

    for (i = 0; i < len; i++) {
	if (buf[i] == sep)
	    buf[i] = new_sep;
    }
    return;
}

boolean_t
macNCopt_encodeAFPPath(struct in_addr iaddr, u_short port,
		       u_char * volname, unsigned long dirID,
		       u_char pathtype, u_char * pathname,
		       u_char separator, void * buf,
		       int * len_p, u_char * err)
{
    void * 	buf_p = buf;
    int 	l;

    l = strlen(volname) + strlen(pathname);
    if (l > AFP_PATH_LIMIT) {
	if (err)
	    sprintf(err, "volume/path name length %d > %d-byte limit", l, 
		    AFP_PATH_LIMIT);
	return (FALSE);
    }

    if ((l + AFP_PATH_OVERHEAD) > *len_p) {
	if (err)
	    sprintf(err, "buffer too small: %d > %d", l + AFP_PATH_OVERHEAD, 
		    *len_p);
	return (FALSE);
    }
    *len_p = l + AFP_PATH_OVERHEAD;			/* option len */

    *((struct in_addr *)buf_p) = iaddr;		/* ip */
    buf_p += sizeof(iaddr);

    *((u_short *)buf_p) = port;			/* port */
    buf_p += sizeof(port);

    l = strlen(volname);			/* VolName */
    *((u_char *)buf_p) = l;
    buf_p++;
    if (l)
	bcopy(volname, (u_char *)buf_p, l);
    buf_p += l;

    *((u_long *)buf_p) = dirID;			/* DirID */
    buf_p += sizeof(dirID);

    *((u_char *)buf_p) = pathtype;		/* AFPPathType */
    buf_p += sizeof(pathtype);

    l = strlen(pathname);			/* PathName */
    *((u_char *)buf_p) = l;
    buf_p++;
    if (l) {
	bcopy(pathname, (u_char *)buf_p, l);
	S_replace_separators(buf_p, l, separator, AFP_PATH_SEPARATOR);
    }

    return (TRUE);
}

static void
print_pstring(unsigned char * option)

{
    int i;

    for (i = 0; i < option[0]; i++) {
	u_char ch = option[1 + i];
	printf("%c", ch ? ch : '.');
    }
}

static void
macNC_print_type(dhcptype_t type, void * opt, int option_len)
{
    int offset;
    unsigned char * option = opt;

    switch (type) {
      case macNCtype_afp_password_e:
	if (option_len != AFP_PASSWORD_LEN)
	    printf("bad password field\n");
	else {
	    u_char buf[9];
	    strncpy(buf, (u_char *)opt, AFP_PASSWORD_LEN);
	    buf[8] = '\0';
	    printf(buf);
	}
	break;
      case macNCtype_afp_path_e:
	offset = 0;
	printf("(");

	dhcptype_print(dhcptype_ip_e, option, option_len);
	offset += 4;

	printf(", ");
	dhcptype_print(dhcptype_uint16_e, option + offset, option_len);
	offset += 2;

	printf(", ");
	print_pstring(option + offset);
	offset += option[offset] + 1;

	printf(", ");
	dhcptype_print(dhcptype_uint32_e, option + offset, option_len);
	offset += 4;

	printf(", ");
	dhcptype_print(dhcptype_uint8_e, option + offset, option_len);
	offset += 1;

	printf(", ");
	print_pstring(option + offset);
	printf(")");
	break;

      case macNCtype_pstring_e: {
	print_pstring(option);
	break;
      }
      default:
	dhcptype_print(type, option, option_len);
	break;
    }
    return;
}

boolean_t
macNC_print_option(void * vopt)
{
    u_char *    	opt = vopt;
    u_char 		tag = opt[TAG_OFFSET];
    u_char 		option_len = opt[LEN_OFFSET];
    u_char * 		option = opt + OPTION_OFFSET;
    dhcptag_info_t * 	entry;

    entry = macNCtag_info(tag);
    if (entry == NULL)
	return (FALSE);
    {	
	dhcptype_info_t * type = macNCtype_info(entry->type);
	
	if (type == NULL) {
	    printf("unknown type %d\n", entry->type);
	    return (FALSE);
	}
	printf("%s (%s): ", entry->name, type->name);
	if (tag == dhcptag_dhcp_message_type_e)
	    printf("%s ", dhcp_msgtype_names(*option));
	macNC_print_type(entry->type, option, option_len);
	printf("\n");
    }
    return (TRUE);
}

void
macNCopt_print(dhcpol_t * list)
{
    int 		i;

    printf("Options count is %d\n", dhcpol_count(list));
    for (i = 0; i < dhcpol_count(list); i++) {
	unsigned char * option = dhcpol_element(list, i);
	if (macNC_print_option(option) == FALSE) 
	    printf("undefined tag %d len %d\n", option[TAG_OFFSET], 
		   option[LEN_OFFSET]);
    }
}

#ifdef TESTING

/**
 **
 ** Testing 1 2 3
 **
 **/
u_char test[] = 
{
    dhcptag_subnet_mask_e,
    4,
    255, 255, 252, 0,
    
    dhcptag_router_e,
    12,
    17, 202, 40, 1,
    17, 202, 41, 1,
    17, 202, 42, 1, 
    
    dhcptag_domain_name_server_e,
    4,
    17, 128, 100, 12,
    
    dhcptag_host_name_e,
    7,
    's', 'i', 'e', 'g', 'd', 'i', '7',
    
    dhcptag_pad_e,
    
    dhcptag_all_subnets_local_e,
    1,
    0,
    
    dhcptag_vendor_specific_e,
    24,
    't', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't',
    234, 212, 0, 1, 2, 3, 4, 5, 6, 7,

    macNCtag_user_name_e,
    10,
    'M', 'a', 'c', 'N', 'C', ' ', '#', ' ', '1', '9',

    macNCtag_shared_system_file_e,
    29,
    17, 202, 40, 191,
    0x20, 0x00,
    4, 'a', 'b', 'c', 'd',
    0, 0, 0, 0,
    2,
    12, 0, 'e', 0, 'f', 0, 'g', 'h', 'i', 0, 'j', 'k', 'l',
    
    dhcptag_end_e,
};

#if 0
u_char test[] = {
0x01, 0x04, 0xff, 0x00, 0x00, 0x00, 0x03, 0x04, 0x0f, 0x03, 0x03, 0x09, 0x06, 
0x04, 0x0f, 0x03, 0x03, 0x09, 0xe8, 0x09, 0x08, 0x6d, 0x61, 0x63, 0x6e, 0x63, 0x30, 0x30, 0x30,
0xed, 0x09, 0x08, 0x6d, 0x61, 0x63, 0x6e, 0x63, 0x30, 0x30, 0x30, 0xe9, 0x09, 0x08, 0x74, 0x65,
0x73, 0x74, 0x69, 0x6e, 0x67, 0x32, 0xea, 0x18, 0x0f, 0x03, 0x03, 0x05, 0x02, 0x24, 0x00, 0x06,
0x64, 0x75, 0x63, 0x61, 0x74, 0x73, 0x09, 0x31, 0x35, 0x2e, 0x33, 0x2e, 0x33, 0x2e, 0x31, 0x37,
0xeb, 0x18, 0x0f, 0x03, 0x03, 0x05, 0x02, 0x24, 0x00, 0x06, 0x64, 0x75, 0x63, 0x61, 0x74, 0x73,
0x09, 0x31, 0x35, 0x2e, 0x33, 0x2e, 0x33, 0x2e, 0x31, 0x37, 0xec, 0x18, 0x0f, 0x03, 0x03, 0x05,
0x02, 0x24, 0x00, 0x06, 0x64, 0x75, 0x63, 0x61, 0x74, 0x73, 0x09, 0x31, 0x35, 0x2e, 0x33, 0x2e,
0x33, 0x2e, 0x31, 0x37, 0xff,
};
#endif

main()
{
    u_char 	err[256];
    dhcpol_t 	list;

    dhcpol_init(&list);
    if (dhcpopt_parse_buffer(&list, test, sizeof(test), err)
	!= dhcpopt_ret_success_e) {
	printf("parse test failed, %s\n", err);
	exit(1);
    }
    macNCopt_print(&list);
    exit(0);
#if 0
    {
	struct in_addr	iaddr;
	int i = sizeof(iaddr);
	
	if ([options str:"17.202.42.129" ToType:dhcptype_ip_e 
	     Buffer:(void *)&iaddr Length:&i] == FALSE) {
	    printf("conversion failed %s\n", [options errString]);
	}
	else {
	    printf("ip address should be 17.202.42.129: ");
	    [options printType:dhcptype_ip_e Size:i Option:(void *)&iaddr
	     Length:i];
	    printf("\n");
	}
    }
    {
	unsigned char buf[32] = "Mac NC #33";
	unsigned char buf2[34];
	int len = sizeof(buf2);
	
	if ([options str:buf ToType:macNCtype_pstring_e Buffer:buf2
	     Length:&len] == FALSE) {
	    printf("conversion failed %s\n", [options errString]);
	}
	else {
	    printf("macNCtype string should be %s:", buf);
	    [options printType:macNCtype_pstring_e Size:0 Option:buf2
	     Length:len];
	    printf("\n");
	}
    }
    {
	struct in_addr	iaddr[10];
	int l = sizeof(iaddr);
	u_char * strList[] = { "17.202.40.1", "17.202.41.1", "17.202.42.1",
			     "17.202.43.1" };
	int num = sizeof(strList) / sizeof(*strList);

	if ([options strList:strList Number:num Tag:dhcptag_router_e
	     Buffer:(void *)iaddr Length:&l] == FALSE) {
	    printf("conversion failed %s\n", [options errString]);
	}
	else {
	    [options printType:dhcptype_ip_mult_e Size:4 Option:(void *)iaddr
	     Length:l];
	    printf("\n");
	}
    }
    {
	u_char buf[100];
	u_char * strList[] = { "17.86.91.2", "0x100", "0", "greatVolumeName",
				   "/spectacular/path/name/eh" };
	int l = sizeof(buf);
	int num = sizeof(strList) / sizeof(*strList);

	if ([macNCOptions strList:strList Number:num 
	     Tag:macNCtag_system_path_shared_e Buffer:(void *)buf Length:&l
	     ErrorString:[options errString]] == FALSE) {
	    printf("conversion failed %s\n", [options errString]);
	}
	else {
	    printf("conversion OK\n");
	    [options printType:macNCtype_afp_path_e Size:0 Option:(void *)buf
	     Length:l];
	    printf("\n");
	}
    }
    {
	u_char buf[100];
	int l = sizeof(buf);
	struct in_addr iaddr;

	iaddr.s_addr = inet_addr("17.202.101.100");
	if ([macNCOptions encodeAFPPath
	     : iaddr 
	     : 0x1234
	     : "volumeName"
	     : AFP_DIRID_NULL
	     : AFP_PATHTYPE_LONG
	     : "this:is:the:path" 
	     : ':'
	     Into: (void *)buf 
	     Length: &l 
	     ErrorString: [options errString]] == FALSE) {
	    printf("conversion path failed %s\n", [options errString]);
	}
	else {
	    printf("conversion OK\n");
	    [options printType:macNCtype_afp_path_e Size:0 Option:(void *)buf
	     Length:l];
	    printf("\n");
	}
	
    }
    [options free];
    options = nil;
    { 
	unsigned char buf[300];
	int len = sizeof(buf);
	id o = [[macNCOptions alloc] initWithBuffer:(void *)buf Size:len];

	if (o == nil) {
	    printf("initWithBuffer failed\n");
	}
	else {
	    if ([o addOption:macNCtag_user_name_e FromString:"Mac NC # 22"] 
		== FALSE
		||
		[o addOption:dhcptag_subnet_mask_e FromString:"255.255.255.0"]
		== FALSE
		||
		[o addOption:dhcptag_end_e Length:0 Data:0] == FALSE
		) {
		printf("%s", [o errString]);
	    }
	    else {
		[o parse];
		[o print];
	    }
	}
    }
	
#endif
    
    exit(0);
}
#endif TESTING

