<?php
// IPplan v4.86a
// Aug 24, 2001
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// 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 the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// workaround for funky behaviour of nested includes with older
// versions of php
require_once(dirname(__FILE__)."/config.php");

define("DBF_API_VER", "1");

// generic errors
//$myError[1]    = my_("Could not connect to database");
//$myError[2]    = my_("Incorrect parameters");
//$myError[3]    = my_("Incorrect API version");
//$myError[50]   = my_("Invalid IP address!");
// specific errors for DisplayBase
//$myError[100]  = 

class IPplanDbf {

    var $ds;
    // default search expression type
    var $expr="RLIKE";
    // default size search - larger than
    var $size=0;
    // default dhcp search - 1 to search for DHCP subnets only
    var $dhcp=0;

// open a database connection
    function IPplanDbf() {

        // create a connection id
        $this->ds = &ADONewConnection(DBF_TYPE);
        $this->ds->debug = DBF_DEBUG;
        // some local locales only accept 24 hour date formats - MSSQL?
        if (DBF_TYPE=="mssql" or DBF_TYPE=="ado_mssql" or DBF_TYPE=="odbc_mssql") {
            $this->ds->fmtTimeStamp = "'Y-m-d H:i:s'";
        }
        if (DBF_PERSISTENT) {
            // Fix issue with Reverse DNS imports that use nested database calls
            if (DBF_TYPE=="mysql" or DBF_TYPE=="maxsql" or DBF_TYPE=="postgres7")
                $this->ds->autoRollback = FALSE;
            if ($this->ds->
                PConnect(DBF_HOST, DBF_USER, DBF_PASSWORD, DBF_NAME)) {
                $this->ds->SetFetchMode(ADODB_FETCH_ASSOC);
                return $this->ds;
            }
        }
        else {
            if ($this->ds->
                Connect(DBF_HOST, DBF_USER, DBF_PASSWORD, DBF_NAME) != false) {
                $this->ds->SetFetchMode(ADODB_FETCH_ASSOC);
                return $this->ds;
            }
        }
        // kill connection info if error - probably bogus database name
        unset($this->ds);
        return false;
    }

// start of transaction for transaction aware tables
    function DbfTransactionStart() {

        if (DBF_TRANSACTIONS)
            $this->ds->BeginTrans();

    }

// end of transaction for transaction aware tables
    function DbfTransactionEnd() {

        if (DBF_TRANSACTIONS)
            $this->ds->CommitTrans();

    }

// rollback of transaction for transaction aware tables
    function DbfTransactionRollback() {

        if (DBF_TRANSACTIONS)
            $this->ds->RollBackTrans();

    }

// add a new ipaddress entry in the ipaddr table if it does not already exist
// Else it just updates existing record
    function DeleteIP($ipaddr, $baseindex) {

        // lock the row for update - at the same time get current row
        if($row=$this->GetIPDetails($baseindex, $ipaddr)) {
            // log the entire row to the auditlog for history - field
            // is too small!

            $this->ds->RowLock("ipaddr", "baseindex=$baseindex AND ipaddr=$ipaddr");
        }

        $row["event"]=132;
        $row["user"]=getAuthUsername();
        $row["action"]="delete ip details";
        $row["baseindex"]=$baseindex;
        $row["ip"]=inet_ntoa($ipaddr);

        $this->ds->Execute("DELETE FROM ipaddr
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr") and
        $this->ds->Execute("DELETE FROM ipaddradd
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr") and
        $this->AuditLog($row);

    }

// add a new ipaddress entry in the ipaddr table if it does not already exist
// Else it just updates existing record
    function AddIP($ipaddr, $baseindex, $user, $location, $telno, $macaddr, $descrip, $hname, $info) {

        $userid = getAuthUsername();

        // add main data to ipaddr table emulate mysql replace
        // (try update, if fails due to non-existant record, them
        // insert) to ipaddr table
        // change to SELECT, test, then UPDATE or INSERT to prevent key errors
        // RE 30/3/2005 - should use FOR UPDATE on SELECT, but don't know compat?
        // did away with using Affected_Rows as this returns 0 if no rec to update

        // THIS GETS DONE TWICE!!! ONCE FOR AUDITLOG AND AGAIN HERE!
        if($this->ds->GetRow("SELECT ipaddr
                FROM ipaddr
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr")) {   // should have FOR UPDATE here!
        $result = &$this->ds->Execute("UPDATE ipaddr
                SET userinf=".$this->ds->qstr($user).",
                location=".$this->ds->qstr($location).",
                telno=".$this->ds->qstr($telno).",
                macaddr=".$this->ds->qstr($macaddr).",
                descrip=".$this->ds->qstr($descrip).",
                hname=".$this->ds->qstr($hname).",
                lastmod=".$this->ds->DBTimeStamp(time()).",
                userid=".$this->ds->qstr($userid)."
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr");
        }
        else {
            $result = &$this->ds->Execute("INSERT INTO ipaddr
                    (userinf, location, telno, macaddr, descrip, hname,
                     baseindex, ipaddr, lastmod, userid)
                    VALUES
                    (".$this->ds->qstr($user).",
                     ".$this->ds->qstr($location).",
                     ".$this->ds->qstr($telno).",
                     ".$this->ds->qstr($macaddr).",
                     ".$this->ds->qstr($descrip).",
                     ".$this->ds->qstr($hname).",
                     $baseindex, 
                     $ipaddr,
                     ".$this->ds->DBTimeStamp(time()).",
                     ".$this->ds->qstr($userid).")");
        }

        // always try to update record - record could not exist, which
        // is OK, but always add record if info is not blank

        // AddIP could be called from import functions or create subnet
        // to add DNS or nmap records - do not add record if info is empty - waste?
        // add serialized info from userdefined template
        // to ipaddradd table
        if($this->ds->GetRow("SELECT ipaddr
                FROM ipaddradd
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr")) {   // should have FOR UPDATE here!
            $result = &$this->ds->Execute("UPDATE ipaddradd
                    SET info=".$this->ds->qstr($info)."
                    WHERE baseindex=$baseindex AND
                    ipaddr=$ipaddr");
        // this generates a "duplicate key" error if no update
        // should be OK under normal circumstances, but generates error under
        // debug mode turned on
        }
        else {
            if (!empty($info)) {
                $result = &$this->ds->Execute("INSERT INTO ipaddradd
                        (info, baseindex, ipaddr)
                        VALUES
                        (".$this->ds->qstr($info).",
                         $baseindex,
                         $ipaddr)");
            }
        }
    }

// update an existing IP record - only updates relevant field
    function UpdateIP($ipaddr, $baseindex, $field, $value) {

        $userid = getAuthUsername();

        $result = &$this->ds->Execute("UPDATE ipaddr
                           SET $field=".$this->ds->qstr($value).",
                              lastmod=".$this->ds->DBTimeStamp(time()).",
                              userid=".$this->ds->qstr($userid)."
                           WHERE baseindex=$baseindex AND ipaddr=$ipaddr");

        // record does not exist, error
        return $this->ds->Affected_Rows();
    }

// modify a single ip address or netrange
// supply baseindex as arg
// assumes info is valid!!!
    function ModifyIP($ipaddr, $baseindex, $user, $location,
                      $telno, $macaddr, $descrip, $hname, $info) {

        if (is_array($ipaddr)) {
            // dont use REPLACE here as maybe only one field
            // must be updated causing others to be cleared!!!
            foreach($ipaddr as $value) {

                // lock the row for update - at the same time get current row
                if($row=$this->GetIPDetails($baseindex, $value)) {
                    // log the entire row to the auditlog for history - field
                    // is too small!
                    // probably better to rather just insert into a duplicate
                    // ipaddr table, or to have a trigger event do something
                    $row["event"]=130;
                    $row["user"]=getAuthUsername();
                    $row["action"]="log old row contents";
                    $row["baseindex"]=$baseindex;
                    $row["ip"]=inet_ntoa($value);
                    $this->AuditLog($row);

                    $this->ds->RowLock("ipaddr", "baseindex=$baseindex AND ipaddr=$value");
                }

                if ($user) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "userinf", $user))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                if ($location) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "location",
                                $location))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                if ($descrip) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "descrip",
                                $descrip))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                if ($telno) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "telno", $telno))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                if ($macaddr) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "macaddr", $macaddr))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                // special case - called from DNS scripts to update hostname field
                if ($hname) {
                    if (!$this->
                            UpdateIP($value, $baseindex, "hname", $hname))
                        $this->AddIP($value, $baseindex, $user,
                                $location, $telno, $macaddr, $descrip, $hname, $info);
                }
                // this does not generate a trigger if multiple recs are updated!!!
                $this->
                    AuditLog(sprintf
                            (my_
                             ("User %s modified ip details %s index %u"),
                             getAuthUsername(),
                             inet_ntoa($value), $baseindex));
            }
        }
        else {
            // lock the row for update - at the same time get current row
            if($row=$this->GetIPDetails($baseindex, $ipaddr)) {
                // log the entire row to the auditlog for history - field
                // is too small!
                // probably better to rather just insert into a duplicate
                // ipaddr table, or to have a trigger event do something
                $row["event"]=130;
                $row["user"]=getAuthUsername();
                $row["action"]="log old row contents";
                $row["baseindex"]=$baseindex;
                $row["ip"]=inet_ntoa($ipaddr);
                $this->AuditLog($row);
                    
                $this->ds->RowLock("ipaddr", "baseindex=$baseindex AND ipaddr=$ipaddr");
            }
            $this->AddIP($ipaddr, $baseindex, $user, $location, $telno, $macaddr,
                         $descrip, $hname, $info);
            $this->AuditLog(array("event"=>131, "action"=>"modify ip details", "ip"=>inet_ntoa($ipaddr),
                    "user"=>getAuthUsername(), "ipaddr"=>$ipaddr, "baseindex"=>$baseindex,
                    "userinf"=>$user, "location"=>$location, "telno"=>$telno, "macaddr"=>$macaddr, 
                    "descrip"=>$descrip, "hname"=>$hname));
        }
    }

// create a new subnet
// baseaddr is an int, not an ip address
// assumes info is valid!!!
// returns the last inserted baseindex, 0 on failure
    function CreateSubnet($baseaddr, $subnetsize, $descrip, $cust, $dhcp, $grp) {

        $userid = getAuthUsername();

        // no other options defined for baseopt, so always make 1 or 0
        $result = &$this->ds->Execute("INSERT INTO base
                              (baseaddr, subnetsize, descrip, admingrp,
                               baseopt, customer, userid, lastmod)
                           VALUES
                              ($baseaddr, $subnetsize,
                              ".$this->ds->qstr($descrip).",
                              ".$this->ds->qstr($grp).",
                               $dhcp,
                               $cust,
                              ".$this->ds->qstr($userid).",
                              ".$this->ds->DBTimeStamp(time()).")");

        if (DBF_TYPE == "mysql" or DBF_TYPE == "maxsql") {
            return $this->ds->Insert_ID();
        }
        else {
            // emulate getting the last insert_id
            $result = &$this->ds->Execute("SELECT baseindex 
                               FROM base
                               WHERE baseaddr=$baseaddr AND customer=$cust");
            $temprow = $result->FetchRow();
            return $temprow["baseindex"];
        }
    }

// check if user is part of customer admin group
    function TestCustomerGrp($baseindex, $userid) {

        // could use GetRow here
        $result = &$this->ds->Execute("SELECT customer.admingrp AS admingrp
                            FROM base, customer, usergrp
                            WHERE base.baseindex=$baseindex AND
                               base.customer=customer.customer AND
                               customer.admingrp=usergrp.grp AND
                               usergrp.userid=".$this->ds->qstr($userid));

        if ($row = $result->FetchRow()) {
            return $row["admingrp"];
        }
        else
            return 0;

    }

// check if user can create/modify customers
    function TestCustomerCreate($userid) {

        // could use GetRow here
        $result = &$this->ds->Execute("SELECT usergrp.grp
                            FROM usergrp, grp
                            WHERE usergrp.userid=".$this->ds->qstr($userid)." AND
                               usergrp.grp=grp.grp AND
                               grp.createcust=".$this->ds->qstr('Y'));

        if ($row = $result->FetchRow()) {
            return $row["grp"];
        }
        else
            return 0;

    }

// find the subnets admin group
    function GetBaseGrp($baseindex) {

        // could use GetRow here
        $result = &$this->ds->Execute("SELECT admingrp
                           FROM base
                           WHERE baseindex=$baseindex");

        if ($row = $result->FetchRow()) {
            return $row["admingrp"];
        }
        else
            return 0;

    }

// delete a subnet
// supply baseindex as arg
// assumes info is valid!!!
// no need for customer as baseindex makes delete unique!!!
    function DeleteSubnet($baseindex) {

        $result = &$this->ds->Execute("DELETE FROM base
                         WHERE baseindex=$baseindex") and 
        $result = &$this->ds->Execute("DELETE FROM ipaddr
                         WHERE baseindex=$baseindex") and
        $result = &$this->ds->Execute("DELETE FROM baseadd
                         WHERE baseindex=$baseindex");

        return $result;
    }

// takes groups array and generates SQL for where clause
    function grpSQL($grps) {

        $string = " IN (";
        foreach($grps as $value) {
            $string .= $this->ds->qstr($value).",";
        }
        return substr($string, 0, -1).")";
    }

// test if subnets overlap within a customer only
// no need to pass groups as validity of user to access this customer
// would have been determined already through user auth
    function GetDuplicateSubnet($baseaddr, $subnetsize, $cust) {

        // get baseaddr and subnetsize so that info is available for
        // importbase - we need to check for EXACT matches there
        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'lastmod');
        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'swipmod');
        return $this->ds->
            Execute("SELECT baseaddr, subnetsize, baseindex, descrip,
                            $sqllastmod AS lastmod, userid, $sqlswipmod AS swipmod
                         FROM base
                         WHERE (($baseaddr BETWEEN baseaddr AND 
                            baseaddr + subnetsize - 1) OR
                            ($baseaddr < baseaddr AND 
                            $baseaddr+$subnetsize > 
                               baseaddr + subnetsize - 1)) AND
                            customer=$cust");
    }

// test if bounds overlap, ie. group can interact with subnet
// returns 1 if action allowed (create etc), zero if action not allowed
    function TestBounds($boundsaddr, $boundssize, $grp) {

        $result = &$this->ds->Execute("SELECT count(*) AS cnt
                         FROM bounds
                         WHERE grp=".$this->ds->qstr($grp));
        $row = $result->FetchRow();
        // no bounds, group can do anything
        if ($row["cnt"] == 0) {
            return 1;
        }

        $result = &$this->ds->Execute("SELECT boundsaddr
                               FROM bounds
                               WHERE (($boundsaddr BETWEEN boundsaddr AND 
                                  boundsaddr + boundssize - 1) AND
                                  ($boundsaddr+$boundssize-1 BETWEEN boundsaddr AND 
                                      boundsaddr + boundssize - 1)) AND
                                  grp=".$this->ds->qstr($grp));

        if ($result->FetchRow()) {
            return 1;
        }
        return 0;
    }

// test if subnets overlap between customers - looks at all customers
// need groups here as some users may not see all customers
    function GetDuplicateSubnetAll($baseaddr, $subnetsize, $grps = 0) {

        $sql = "";

        if (!empty($grps)) {
            if (!$this->TestGrpsAdmin($grps)) {
                // add groups to search in if required - no index here!!!
                $sql .= " AND customer.admingrp ".$this->grpSQL($grps);
            }
        }

        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'base.lastmod');
        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'base.swipmod');
        return $this->ds->
            Execute("SELECT base.baseaddr, base.subnetsize, base.baseindex,
                            base.descrip, customer.custdescrip, 
                            customer.customer, $sqllastmod AS lastmod, base.userid, 
                            $sqlswipmod AS swipmod
                         FROM base, customer
                         WHERE (($baseaddr BETWEEN base.baseaddr AND 
                            base.baseaddr + base.subnetsize - 1) OR
                            ($baseaddr < base.baseaddr AND 
                            $baseaddr+$subnetsize > 
                               base.baseaddr + base.subnetsize - 1)) AND
                            base.customer = customer.customer
                            $sql");
    }

// find subnets that are NOT part of any netranges
    function GetBaseNoArea($descrip, $cust, $grps = 0) {

        $sql = "";

        if ($descrip) {
            $sql=$this->mySearchSql("base.descrip", $this->expr, $descrip);
        }
        // does user want to search on subnet size?
        if ($this->size > 0) {
            $sql.=" AND base.subnetsize > $this->size ";
        }

        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'base.lastmod');
        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'base.swipmod');
        return $this->ds->
            Execute("SELECT base.baseindex, base.subnetsize, 
                          base.descrip, base.baseaddr, base.admingrp,
                          $sqllastmod AS lastmod, base.userid, $sqlswipmod AS swipmod
                        FROM base
                        LEFT JOIN netrange ON base.baseaddr BETWEEN netrange.rangeaddr 
                          AND netrange.rangeaddr+netrange.rangesize-1 
                          AND base.customer=netrange.customer
                        WHERE base.customer=$cust
                          AND netrange.rangeaddr IS NULL
                          $sql
                        ORDER BY
                           base.baseaddr");
    }

// find base address info from an area
// not all fields are used, but get them anyway so function is
// more generic
// if all customer, must filter on groups
    function GetBaseFromArea($areaindex, $descrip, $cust, $grps = 0) {

        $sql = "";

        if ($descrip) {
            $sql=$this->mySearchSql("base.descrip", $this->expr, $descrip);
        }
        // does user want to search on subnet size?
        if ($this->size > 0) {
            $sql.=" AND base.subnetsize > $this->size ";
        }

        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'base.lastmod');
        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'base.swipmod');
        // if duplicate netranges allowed we need to suppress them
        // take care of order here relative to other SQL
        if ($cust == 0) {
            if (!empty($grps)) {
                if (!$this->TestGrpsAdmin($grps)) {
                    // add groups to search in if required - no index here!!!
                    // add at begining of SQL due to GROUP by above!
                    $sql.= " AND customer.admingrp ".$this->grpSQL($grps);
                }
            }
            return $this->ds->
                Execute("SELECT DISTINCT base.baseindex, base.subnetsize, 
                              base.descrip, base.baseaddr, base.admingrp,
                              $sqllastmod AS lastmod, base.userid, $sqlswipmod AS swipmod,
                              customer.custdescrip, customer.customer
                            FROM base, netrange, customer
                            WHERE netrange.areaindex=$areaindex 
                              AND base.baseaddr BETWEEN netrange.rangeaddr 
                              AND netrange.rangeaddr+netrange.rangesize-1 
                              AND base.customer=customer.customer
                              $sql
                            ORDER BY
                               base.baseaddr");
        }
        else {
            return $this->ds->
                Execute("SELECT DISTINCT base.baseindex, base.subnetsize, 
                              base.descrip, base.baseaddr, base.admingrp,
                              $sqllastmod AS lastmod, base.userid, $sqlswipmod AS swipmod
                            FROM base, netrange
                            WHERE netrange.areaindex=$areaindex 
                              AND base.baseaddr BETWEEN netrange.rangeaddr 
                              AND netrange.rangeaddr+netrange.rangesize-1 
                              AND base.customer=$cust
                              AND netrange.customer=$cust
                              $sql
                            ORDER BY
                               base.baseaddr");
        }
    }

// find base address info
// not all fields are used, but get them anyway so function is
// more generic
    function GetBase($startnum, $endnum, $descrip, $cust, $grps = 0) {

        $sql = "";
        
        if ($descrip) {
            $sql=$this->mySearchSql("base.descrip", $this->expr, $descrip);
        }
        // does user want to search on subnet size?
        if ($this->size > 0) {
            $sql.=" AND base.subnetsize > $this->size ";
        }
        // NOTE: this should be & if there are additional bit masks
        if ($this->dhcp > 0) {
            $sql.=" AND base.baseopt > 0 ";
        }

        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'base.lastmod');
        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'base.swipmod');
        if ($cust == 0) {
            if (!empty($grps)) {
                if (!$this->TestGrpsAdmin($grps)) {
                    // add groups to search in if required - no index here!!!
                    $sql .= " AND customer.admingrp ".$this->grpSQL($grps);
                }
            }
            return $this->ds->
                Execute("SELECT base.baseindex, base.subnetsize, 
                              base.descrip, base.baseaddr, base.admingrp, 
                              $sqllastmod AS lastmod, base.userid, $sqlswipmod AS swipmod,
                              customer.custdescrip, customer.customer
                            FROM base, customer
                            WHERE base.baseaddr BETWEEN $startnum AND $endnum 
                              AND base.customer=customer.customer
                              $sql
                            ORDER BY
                               base.baseaddr");
        }
        else {
            return $this->ds->
                Execute("SELECT base.baseindex, base.subnetsize, 
                              base.descrip, base.baseaddr, base.admingrp, 
                              $sqllastmod AS lastmod, base.userid, $sqlswipmod AS swipmod
                            FROM base
                            WHERE base.baseaddr BETWEEN $startnum AND $endnum 
                              $sql
                              AND base.customer=$cust
                            ORDER BY
                               base.baseaddr");
        }
    }

// find base address info from a baseindex
// added customer as field - required as backlink in modifyipform - 13/3/2005
    function GetBaseFromIndex($baseindex) {

        $sqlswipmod = $this->ds->SQLDate("M d Y H:i:s", 'swipmod');
        return $this->ds->Execute("SELECT baseindex, subnetsize, 
                             descrip, baseaddr, $sqlswipmod AS swipmod, customer,
                             baseopt
                           FROM base
                           WHERE baseindex=$baseindex");
    }

// find base address info from a ip
    function GetBaseFromIP($ip, $cust) {

        // query looks odd, but record may not exist, but would still
        // like to know which baseaddr it should belong to
        return $this->ds->Execute("SELECT baseindex, subnetsize, 
                             descrip, baseaddr
                           FROM base
                           WHERE $ip BETWEEN baseaddr AND
                              baseaddr+subnetsize-1 AND
                              customer=$cust");
    }

// get all the ip records from a subnet based on a baseindex
// could return nothing as there are no records in database for
// entries where all fields are blank
    function GetSubnetDetails($baseindex, $sql="") {

        // no use converting ipaddr to quad as there may be holes in database
        // which needs to be calculated at runtime
        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'lastmod');
        return $this->ds->Execute("SELECT userinf, location, telno, 
                             descrip, hname, ipaddr, $sqllastmod AS lastmod, 
                             userid, lastpol, macaddr
                           FROM ipaddr
                           WHERE baseindex=$baseindex $sql
                           ORDER BY
                              ipaddr");
    }

// get one ip record from a subnet based on a baseindex and ipaddr
// could return nothing as there are no records in database for
// entries where all fields are blank
    function GetIPDetails($baseindex, $ipaddr) {

        // no use converting ipaddr to quad as there may be holes in database
        // which needs to be calculated at runtime
        $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'lastmod');
        return $this->ds->GetRow("SELECT userinf, location, telno, 
                             descrip, hname, ipaddr, $sqllastmod AS lastmod, userid
                           FROM ipaddr
                           WHERE baseindex=$baseindex AND ipaddr=$ipaddr");
    }

// update the poll date of the address - will add a record if required
    function UpdateIPPoll($baseindex, $ipaddr) {

        $result = $this->ds->Execute("UPDATE ipaddr
                SET lastpol=".$this->ds->DBTimeStamp(time())."
                WHERE baseindex=$baseindex AND
                ipaddr=$ipaddr");
        if ($this->ds->Affected_Rows() == 0) {
            $result = $this->ds->Execute("INSERT INTO ipaddr
                    (userinf, location, telno, descrip, hname,
                     baseindex, ipaddr, lastmod, lastpol, userid)
                    VALUES
                    (".$this->ds->qstr("").",
                     ".$this->ds->qstr("").",
                     ".$this->ds->qstr("").",
                     ".$this->ds->qstr("Unknown - added by IPplan poller").",
                     ".$this->ds->qstr("").",
                     $baseindex, 
                     $ipaddr,
                     ".$this->ds->DBTimeStamp(time()).",
                     ".$this->ds->DBTimeStamp(time()).",
                     ".$this->ds->qstr("POLLER").")");
        }

    }

// check if user has full rights to view everthing
    function TestGrpsAdmin($grps) {

        if (!empty($grps)) {
            $result = $this->GetGrpsInfo($grps);
            while ($row = $result->FetchRow()) {
                if ($row["grpopt"] & 1)
                    return TRUE;
            }
        }
        return FALSE;

    }

// get all the customer info
    function GetGrps() {

        return $this->ds->Execute("SELECT grpdescrip, grp
                            FROM grp
                            ORDER BY grpdescrip");
    }

// get all the info for groups user belongs to
// don't pass empty grps array as you will generate database error
    function GetGrpsInfo($grps) {

        $sql = $this->grpSQL($grps);

        return $this->ds->
            Execute("SELECT grpdescrip, grp, createcust, grpopt
                            FROM grp
                            WHERE grp $sql");
    }

// get all the customers
    function GetCustomer($sql="") {

        if (empty($sql)) {
            return $this->ds->Execute("SELECT custdescrip, customer, admingrp, crm
                    FROM customer
                    ORDER BY custdescrip");
        }
        else {
            return $this->ds->Execute("SELECT custdescrip, customer, admingrp, crm
                    FROM customer
                    WHERE $sql
                    ORDER BY custdescrip");
        }
    }

// get all the customer info
    function GetCustomerInfo($cust) {

        return $this->ds->Execute("SELECT *
                               FROM custinfo
                               WHERE customer=$cust");
    }

// get all the customer DNS info
    function GetCustomerDNSInfo($cust) {

        return $this->ds->Execute("SELECT hname, ipaddr
                               FROM revdns
                               WHERE customer=$cust
                               ORDER BY horder");
    }

// get customer description
    function GetCustomerDescrip($cust) {

        $row = $this->ds->GetRow("SELECT custdescrip
                            FROM customer
                            WHERE customer=$cust");

        return $row["custdescrip"];

    }

// get particular customer group info
// if cust = 0, get info for all customers
// autoincrement field cannot be 0, so database integrity retained
    function GetCustomerGrp($cust) {

        if ($cust == 0) {
            return $this->ds->
                Execute("SELECT custdescrip, customer, admingrp
                            FROM customer
                            ORDER BY custdescrip");
        }
        else {
            return $this->ds->
                Execute("SELECT custdescrip, customer, admingrp
                            FROM customer
                            WHERE customer=$cust");
        }
    }

// get all the netrange info
    function GetRangeInArea($cust, $areaindex) {

        return $this->ds->
            Execute("SELECT rangeaddr, rangesize, descrip, rangeindex
                            FROM netrange
                            WHERE areaindex=$areaindex
                            ORDER BY rangeaddr");
    }

// get all the netrange info
    function GetRange($cust, $rangeindex) {

        if (!$rangeindex) {
            return $this->ds->
                Execute("SELECT rangeaddr, rangesize, descrip, rangeindex
                               FROM netrange
                               WHERE customer=$cust
                               ORDER BY rangeaddr");
        }
        else {
            return $this->ds->
                Execute("SELECT rangeaddr, rangesize, descrip, rangeindex
                               FROM netrange
                               WHERE rangeindex=$rangeindex
                               ORDER BY rangeaddr");
        }
    }

// get all the area info
    function GetArea($cust, $areaindex) {

        // all areas for customer
        if ($areaindex==0) {
            return $this->ds->Execute("SELECT areaaddr, descrip, areaindex
                               FROM area
                               WHERE customer=$cust
                               ORDER BY areaaddr");
        }
        // only areas that have netranges defined
        else if ($areaindex==-1) {
            return $this->ds->Execute("SELECT area.areaaddr, area.descrip, area.areaindex
                               FROM area, netrange
                               WHERE area.customer=$cust AND 
                                netrange.areaindex=area.areaindex
                               ORDER BY area.areaaddr");
        }
        // get specific area
        else {
            return $this->ds->Execute("SELECT areaaddr, descrip, areaindex
                               FROM area
                               WHERE areaindex=$areaindex
                               ORDER BY areaaddr");
        }
    }

// get md5 checksum of records to change
// ip is an integer ip address or array of integer ip addresses
    function GetMD5($ip, $baseindex) {

        $sql="";
        if (is_array($ip)) {
            foreach($ip as $value) {
                $sql .= "$value,";
            }
        }
        else {
            $sql = "$ip,";
        }
        // chop extra , if array was passed
        $sql = substr($sql, 0, strlen($sql) - 1);

        // optimised for mysql
        if (DBF_TYPE == "mysql" or DBF_TYPE == "maxsql") {
            $result =
                &$this->ds->Execute("SELECT md5(sum(lastmod)) AS md5str
                             FROM ipaddr
                             WHERE ipaddr IN($sql) AND baseindex=$baseindex
                             GROUP BY lastmod");

            $row = $result->FetchRow();
            return $row["md5str"];
        }
        else {
            $sqllastmod = $this->ds->SQLDate("M d Y H:i:s", 'lastmod');
            $result = &$this->ds->Execute("SELECT $sqllastmod AS lastmod
                             FROM ipaddr
                             WHERE ipaddr IN($sql) AND baseindex=$baseindex");

            $md5str="";
            while ($row = $result->FetchRow()) {
                $md5str .= $row["lastmod"];
            }
            return md5($md5str);
        }
    }

// put some stuff in the audit log
// $message is either a string or 
// $message is associative array with at least one index called "event"
// if you do not want to send a trigger, use a string as parameter
// events must be unique, user_trigger function is in ipplanlib.php
// eg array("event"=>100)
// eg array("event"=>100, "action"=>"add zone", "domain"=>"example.com")
// see TRIGGERS file for more info
    function AuditLog($message) {

        if (AUDIT) {
            if (is_string($message)) {
                $this->ds->Execute("INSERT INTO auditlog
                        (action, userid, dt)
                        VALUES
                        (".$this->ds->qstr(substr($message,0,254)).",
                         ".$this->ds->qstr(getAuthUsername()).",
                         ".$this->ds->DBTimeStamp(time()).")");
            }
            else if (is_array($message)) {
                // step through array
                $newmsg="";
                foreach ($message as $key=>$value) {
                    $newmsg .= "$key=$value, ";
                }
                $newmsg = substr($newmsg, 0, strlen($newmsg)-2);
                // message could be long, so wrap over multiple log lines
                $msgarr = explode("\n", wordwrap($newmsg, 250, "\n... "));

                foreach($msgarr as $value) {
                    $this->ds->Execute("INSERT INTO auditlog
                            (action, userid, dt)
                            VALUES
                            (".$this->ds->qstr(substr($value,0,254)).",
                             ".$this->ds->qstr(getAuthUsername()).",
                             ".$this->ds->DBTimeStamp(time()).")");
                }

                // call external trigger function
                if (EXT_FUNCTION) {
                    user_trigger($message);
                }
            }

        }
    }

    // build an sql query for the search - $var contain field, type is expression,
    // search is what to search for
    // returns nothing if no search
    function mySearchSql($var, $expr, $search, $addand=TRUE) {

        $sql="";
        if (!empty($search)) {
            switch ($expr) {
                case "NLIKE":
                    $sql="$var NOT LIKE ".$this->ds->qstr("%$search%");
                    break;
                case "EXACT":
                    $sql="$var = ".$this->ds->qstr("$search");
                    break;
                case "RLIKE":
                // default is RLIKE, need to protect for DBF's without RLIKE
                    if (preg_ispreg($search)) {  // invalid regex
                        return "";
                    }
                    if (DBF_TYPE=="mysql" or DBF_TYPE=="maxsql") {
                        $sql="$var RLIKE ".$this->ds->qstr("$search");
                        break;
                    }
                    if (DBF_TYPE=="postgres7") {
                        $sql="$var ~ ".$this->ds->qstr("$search");
                        break;
                    }
                case "NRLIKE":
                    if (preg_ispreg($search)) {  // invalid regex
                        return "";
                    }
                    if (DBF_TYPE=="mysql" or DBF_TYPE=="maxsql") {
                        $sql="$var NOT RLIKE ".$this->ds->qstr("$search");
                        break;
                    }
                    if (DBF_TYPE=="postgres7") {
                        $sql="$var NOT ~ ".$this->ds->qstr("$search");
                        break;
                    }
                case "LIKE":
                    $sql="$var LIKE ".$this->ds->qstr("%$search%");
                    break;
                case "END":
                    $sql="$var LIKE ".$this->ds->qstr("%$search");
                    break;
                // default is START search
                default:
                    $sql="$var LIKE ".$this->ds->qstr("$search%");
            }
            // should there be an AND?
            if ($addand) {
                $sql = "AND ".$sql;
            }
        }

        return $sql;

    }

}

class Base extends IPplanDbf {

    // form variables
    var $cust;
    var $areaindex;
    var $rangeindex;
    var $ipaddr = "";
    var $searchin = 0;
    var $descrip = "";
    // local class variables
    var $grps;
    var $custdescrip = "";
    var $site = "";
    var $start;
    var $end;
    var $subnetsize = 0;   // used for searching over a netrange - called from findfree
    var $errstr = "";
    var $err = 0;

    function SetGrps($grps) {

        $this->grps = $grps;

    }

    function SetIPaddr($ipaddr) {

        $this->ipaddr = $ipaddr;

    }

    function SetSearchIn($searchin) {

        $this->searchin = $searchin;

    }

    function SetDescrip($descrip) {

        $this->descrip = $descrip;

    }

    // if size is set, SetIPaddr must be called
    function SetSubnetSize($subnetsize) {

        $this->subnetsize = $subnetsize;

    }

    function FetchBase($cust, $areaindex, $rangeindex) {

        // use local function variables as they may change
        $this->cust = $cust;
        $this->rangeindex = $rangeindex;
        $this->areaindex = $areaindex;

        // set start and end address according to netrange
        if ($this->rangeindex) {
            // should only return one row here!
            $result = $this->GetRange($this->cust, $this->rangeindex);
            $row = $result->FetchRow();

            $this->start = inet_ntoa($row["rangeaddr"]);
            $this->end =
                inet_ntoa($row["rangeaddr"] + $row["rangesize"] - 1);
            $this->site = " (".$row["descrip"].")";
        }
        else {
            if ($this->ipaddr) {
                if ($this->subnetsize) {
                    $this->start = $this->ipaddr;
                    $this->end = inet_ntoa(inet_aton($this->ipaddr)+$this->subnetsize-1);
                }
                else {
                    $this->start = completeIP($this->ipaddr, 1);
                    $this->end = completeIP($this->ipaddr, 2);
                }

                if (testIP($this->start) or testIP($this->end)) {
                    $this->err = 50;    // Invalid IP address!
                    $this->errstr = my_("Invalid IP address!");
                    return FALSE;
                }
            }
            else {
                $this->start = DEFAULTROUTE;
                $this->end = ALLNETS;
            }
        }

        $startnum = inet_aton($this->start);
        $endnum = inet_aton($this->end);

        // pager could have made cust = 0
        if ($this->cust == 0) {
            $this->custdescrip = "All";
        }
        else {
            $this->custdescrip = $this->GetCustomerDescrip($this->cust);
        }
        if (strtolower($this->custdescrip) == "all")
            $this->cust = 0;

        if ($this->areaindex == -1) { // all subnets not part of an area
            $result = 
                $this->GetBaseNoArea($this->descrip, $this->cust, $this->grps);
        }
        else if ($this->areaindex and ! $this->rangeindex) {
            $result =
                $this->GetBaseFromArea($this->areaindex, $this->descrip,
                                       $this->cust, $this->grps);
        }
        else {

            // search in subnet - finds subnets with exact ip address match
            // useful for finding from where an attack comes if you have IP
            if ($this->searchin == 0) {
                $result =
                    $this->GetBase($startnum, $endnum, $this->descrip,
                                   $this->cust, $this->grps);
            }
            else {
                if ($this->cust == 0) {
                    $result =
                        $this->GetDuplicateSubnetAll($startnum, 1,
                                                     $this->grps);
                }
                else {
                    $result =
                        $this->GetDuplicateSubnet($startnum, 1,
                                                  $this->cust);
                }
            }
        }

        return $result;
    }
}

?>
