/* ********************************************************************	*
 * ni_fallbackhwaddr.c	version 0.01	2-3-09				*
 *									*
 *     COPYRIGHT 2008-2009 Michael Robinton <michael@bizsystems.com>	*
 *									*
 * This program is free software; you can redistribute it and/or modify	*
 * it under the terms of either:					*
 *									*
 *  a) the GNU General Public License as published by the Free		*
 *  Software Foundation; either version 2, or (at your option) any	*
 *  later version, or							*
 *									*
 *  b) the "Artistic License" which comes with this distribution.	*
 *									*
 * This program is distributed in the hope that it will be useful,	*
 * but WITHOUT ANY WARRANTY; without even the implied warranty of	*
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See either	*
 * the GNU General Public License or the Artistic License for more 	*
 * details.								*
 *									*
 * You should have received a copy of the Artistic License with this	*
 * distribution, in the file named "Artistic".  If not, I'll be glad 	*
 * to provide one.							*
 *									*
 * You should also have received a copy of the GNU General Public 	*
 * License along with this program in the file named "Copying". If not, *
 * write to the 							*
 *									*
 *	Free Software Foundation, Inc.					*
 *	59 Temple Place, Suite 330					*
 *	Boston, MA  02111-1307, USA					*
 *									*
 * or visit their web page on the internet at:				*
 *									*
 *	http://www.gnu.org/copyleft/gpl.html.				*
 * ********************************************************************	*/

#include "localconf.h"

unsigned char *
ni_fallbackhwaddr(u_int af, void * vifr)
{
    struct nifreq * ifr = (struct nifreq * )vifr;
    int i, n, dflgs, ppa, fd = -1;
    char * p;
#ifdef HAVE_STRUCT_IFDEVEA
    struct ifdevea physaddr;
#endif
#ifdef HAVE_NETIO_H
    struct fis * ioctl_arg;
#endif
#ifdef HAVE_SYS_DLPI_H
    char name[IFNAMSIZ +5], dlpbuf[DL_MAXIMUM];
    struct strbuf ctrl;
    union DL_primitives dlp;
    dl_info_ack_t * dlpi = (dl_info_ack_t *)dlpbuf;
# ifdef DL_ATTACH_REQ
    dl_ok_ack_t * dlpa = (dl_ok_ack_t *)dlpbuf;
    dl_bind_ack_t * dlpb = (dl_bind_ack_t *)dlpbuf;
    dl_phys_addr_ack_t * dlpp = (dl_phys_addr_ack_t *)dlpbuf;
# endif
#endif

#if defined (SIOCGIFHWADDR) || defined (SIOCGENADR) || (defined (HAVE_STRUCT_IFDEVEA) && defined (SIOCPHYSADDR))

    if ((fd = ni_clos_reopn_dgrm(fd,af)) < 0)
        goto dgrm_failed;

# ifdef SIOCGIFHWADDR
    if (ioctl(fd,SIOCGIFHWADDR,ifr) != -1 && NI_MAC_NOT_ZERO(&ifr->ni_saddr.sa_data)) {
	close(fd);
        return (unsigned char *)(&ifr->ni_saddr.sa_data);
    }
# endif

# ifdef SIOCGENADDR
    if (ioctl(fd,SIOCGENADDR,ifr) != -1 && NI_MAC_NOT_ZERO(&ifr->ni_char)) {
	close(fd);
    	return (unsigned char *)&ifr->ni_char;
    }
# endif

# if defined (HAVE_STRUCT_IFDEVEA) && defined (SIOCSPHYSADDR)
    bzero(phys.ifr_name,sizeof(physaddr));
    strlcpy(physaddr.ifr_name,ifr->ni_ifr_name,IFNAMSIZ);
    if (ioctl(fd,SIOCPHYSADDR,&physaddr) >= 0 && NI_MAC_NOT_ZERO(physaddr.current_pa)) {
        memcpy(&ifr->ni_char,&physaddr.current_pa[0]);
	close(fd);
        return (unsigned char *)&ifr->ni_char;
    }
# endif

    close(fd);
  dgrm_failed:

#endif	/* all that stuff above	*/

/*	ok, could not get the MAC address the easy way, try harder	*/

#ifdef HAVE_NIT_IF_H
    if ((fd = open("/dev/nit",0)) >= 0) {
    	if (ioctl(fd,NIOCBIND,ifr) >= 0 &&
    	    ioctl(fd,SIOCGIFADDR) >= 0) {
    	    close(fd);
    	    if (NI_MAC_NOT_ZERO(&ifr->ni_char))
    	        return (unsigned char *)&ifr->ni_char;
    	else
    	    close(fd);
    }
#endif

/* http://www.informatik.uni-frankfurt.de/doc/man/hpux/lan.7.html	*/

#ifdef HAVE_NETIO_H
    if ((fd = open(&ifr->ni_ifr_name,O_RDONLY)) >= 0) {
	bzero(&ioctl_arg,sizeof(struct fis));
	ioctl_arg.reqtype = LOCAL_ADDRESS;
	if (ioctl(fd,NETSTAT,&ioctl_arg) >= 0) {
	    close(fd);
	    if (ioctl_arg.vtype == 6) {
		memcpy(&ifr->ni_char,&ioctl_arg.value.s[0],6);
		return (unsigned char *)&ifr->ni_char;
	    }
	}
	close(fd);
    }
#endif

#ifdef HAVE_SYS_DLPI_H
    ppa = -1;
    sprintf(name,"/dev/%s",&ifr->ni_ifr_name);
    if ((fd = open(name,O_RDWR)) < 0) {
/*	try opening without the ppa number	*/
	n = strlen(name);
	p = name;
	p += 5;
	for(i=5;i<n;i++,p++) {
	    if (isdigit(*p)) {
		if (ppa < 0) {
		    ppa = *p - '0';	/* terminate the name at the ppa number start	*/
		    *p = '\0';		/* but continue for multi digit ppa numbers	*/
		}
		else {
		    ppa *= 10;
		    ppa += (*p - '0');
		}
	    }
	}
	if (ppa < 0 || (fd = open(name,O_RDWR)) < 0) {
/*	as a last resort, try /dev/dlpi		*/
	    if ((fd = open("/dev/dlpi",O_RDWR)) < 0)
		goto hwaddr_error;
	}
    }
/* get INFO and hardware mac address	*/
    bzero(&ctrl,sizeof(ctrl));
    dlp.dl_primitive = DL_INFO_REQ;
    ctrl.buf = (char *)&dlp;
    ctrl.len = DL_INFO_REQ_SIZE;
    if (putmsg(fd,&ctrl,NULL,0) < 0)
	goto dlpi_error;
    ctrl.buf = (char *)&dlpbuf;
    ctrl.len = 0;
    ctrl.maxlen = DL_MAXIMUM;
    i = RS_HIPRI;	/* flags */
    bzero(dlpbuf,DL_MAXIMUM);
    if (getmsg(fd,&ctrl,NULL,&i) < 0)
	goto dlpi_error;
    if (dlpi->dl_primitive != DL_INFO_ACK)
    	goto dlpi_error;
/* have hardware mac, might not be current mac	*/
    memcpy(&ifr->ni_ifr_name,(dlpbuf + dlpi->dl_addr_offset),6);

# ifdef DL_ATTACH_REQ

    if (ppa < 0 || dlpi->dl_provider_style != DL_STYLE2)
	goto dlpi_ret_hw;
    bzero(&ctrl,sizeof(ctrl));
    dlp.attach_req.dl_primitive = DL_ATTACH_REQ;
    dlp.attach_req.dl_ppa = ppa;
    ctrl.buf = (char *)&dlp;
    ctrl.len = DL_ATTACH_REQ_SIZE;
    if (putmsg(fd,&ctrl,NULL,0) < 0)
	goto dlpi_ret_hw;
    ctrl.buf = (char *)&dlpbuf;
    ctrl.len = 0;
    ctrl.maxlen = DL_MAXIMUM;
    i = RS_HIPRI;	/* flags */
    bzero(dlpbuf,DL_OK_ACK_SIZE);
    if (getmsg(fd,&ctrl,NULL,&i) < 0)
	goto dlpi_ret_hw;
    if (dlpa->dl_primitive != DL_OK_ACK)
	goto dlpi_ret_hw;

# endif	/* DL_ATTACH_REQ */

    bzero(&ctrl,sizeof(ctrl));
    dlp.dl_primitive = DL_PHYS_ADDR_REQ;
    dlp.physaddr_req.dl_addr_type = DL_CURR_PHYS_ADDR;
    ctrl.buf = (char *)&dlp;
    ctrl.len = DL_PHYS_ADDR_REQ_SIZE;
    if (putmsg(fd,&ctrl,NULL,0) < 0)
	goto dlpi_ret_hw;
    ctrl.buf = (char *)&dlpbuf;
    ctrl.len = 0;
    ctrl.maxlen = DL_MAXIMUM;
    i = RS_HIPRI;	/* flags */
    bzero(dlpbuf,DL_MAXIMUM);
    if (getmsg(fd,&ctrl,NULL,&i) < 0)
	goto dlpi_ret_hw;
    if (dlpi->dl_primitive != DL_PHYS_ADDR_ACK)
    	goto dlpi_ret_hw;
/* have current MAC address, return it	*/
    memcpy(ifr->ni_ifr_name,(dlpbuf + dlpp->dl_addr_offset),6);

  dlpi_ret_hw:
	close(fd);
        return (unsigned char *)&ifr->ni_ifr_name[0];

  dlpi_error:
	close(fd);
#endif	/* HAVE_SYS_DLPI_H	*/

  hwaddr_error:
    errno = ENOSYS;
    return NULL;
}