#!/bin/sh
# 
# Copyright (c) 2000 Carnegie Mellon University.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer. 
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#
# 3. The name "Carnegie Mellon University" must not be used to
#    endorse or promote products derived from this software without
#    prior written permission. For permission or any other legal
#    details, please contact  
#      Office of Technology Transfer
#      Carnegie Mellon University
#      5000 Forbes Avenue
#      Pittsburgh, PA  15213-3890
#      (412) 268-4387, fax: (412) 268-7395
#      tech-transfer@andrew.cmu.edu
#
# 4. Redistributions of any form whatsoever must retain the following
#    acknowledgment:
#    "This product includes software developed by Computing Services
#     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
#
# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
exec perl -x -S $0 ${1+"$@"} # -*-perl-*-
#!perl -w
# $Id: snmpgen,v 1.17 2004/11/19 17:05:48 shadow Exp $

if ($] !~ /^5\..*/) {
  # uh-oh. this isn't perl 5.
  foreach (split(/:/, $ENV{PATH})) { # try to find "perl5".
    exec("$_/perl5", "-w", "-x", "-S", $0, @ARGV) if (-x "$_/perl5");
  }
  # we failed. bail.
  die "Your perl is too old; I need perl 5.\n";
}

# load the real script. this is isolated in an 'eval' so perl4 won't
# choke on the perl5-isms.
eval join("\n", <DATA>);
if ($@) { die "$@"; }

__END__
require 5;

#Tim Martin
# 2/10/2000

use Getopt::Long;
my $opt_extra = undef;

$ret = GetOptions("extra:s");
if (!$ret || $#ARGV != 0) { 
    print STDERR "snmpgen [--extra=trailer.in] app.snmp\n";
    exit;
}

$infile = $ARGV[0];

if ($infile =~ m|.*/(.*)\.snmp|) {
    $basename = $1;
} elsif ($infile =~ m|(.*)\.snmp|) {
    $basename = $1;
} else {
    $basename = $infile;
}
print "basename $basename\n";
$outheader = "$basename.h";
$outprog = "$basename.c";

open (INPUT,"<$infile");

my $linenum = 0;
my $found = 0;
my $base = "NOT";
my $num_cmds = 0;

my %T; # maps names to types
my %D; # maps names to descs
my %O; # maps names to oids

push @Varlist, "LISTEND";

#first find the BASE
while (defined ($line = <INPUT>)) {
    $linenum++;
    
    if ($line =~ /^#/) {
	# comment
	next;
    }
    if ($line =~ /^\s*$/) {
	# just whitespace. ignore
	next;
    }

    if ($line =~ /^define\s+(\w+)\s+((\d|\.|\[\w+\])+)/)
    {
      $defname = $1;
      $defoid = $2;

      #resolve definitions
      while ($defoid =~ /(\[(\w+)\])/)
      {
	my $f = 0;
	foreach my $a (keys %Definelist)
	{
	  if ($a eq $2)
	  {
	    $defoid =~ s/\[\w+\]/$Definelist{$a}/;
	    $f = 1;
	  }
	}	
	if ($f == 0) {
	  die "Unable to resolve definition $2";
	}
      }

      $Definelist{$defname} = $defoid;
      next;
    }

    if ($line =~ /^var\s+(\w+)/) {
      push @Varlist, $1;
      next;
    }

    if ($line =~ /^BASE\s+((\d+|\.|\{\w+\}|\[\w+\])+)/) {
	$base = $1;

	#resolve definitions
	while ($base =~ /(\[(\w+)\])/)
	{
	  my $f = 0;
	  foreach my $a (keys %Definelist)
	  {
	    if ($a eq $2)
	    {
	      $base =~ s/\[\w+\]/$Definelist{$a}/;
	      $f = 1;
	    }
	  }	
	  if ($f == 0) {
	    die "Unable to resolve definition $2";
	  }
	}

	#add lowest base to register list	
	if ($base =~ /((\d+\.)+\d+)/) {
	  push @Registerlist, $1;
	}

	undef @baseVlist;
	#xxx check all vars in varlist
	while ($base =~ /(\{(\w+)\})/)
	{
	  push @baseVlist, $2;
	  $base =~ s/\{\w+\}/%d/;	  	  
	}

	$basecount = 0;	
	next;
    }

    chomp $line;
    ($type, $name, $desc, $oid, $dummy) = split(/\s*,\s*/, $line, 5);

    if (!(defined $oid) || (defined $dummy)) {
	die "syntax error on line $linenum\n";
    }

    if ($oid eq "auto") {
	$oid = $base . ".$basecount";
	$basecount++;
    } else {
        $oid = $base . "." . $oid;
    }

    $T{$name} = $type;
    $D{$name} = $desc;
    $O{$name} = $oid;
    $Ovlist{$name} = [ @baseVlist ];
}
    
open (OUTPUT_H, ">$outheader");

print OUTPUT_H <<EOF
/* $outheader -- statistics push interface
 * generated automatically from $infile by snmpgen
 *
 * Copyright 2000 Carnegie Mellon University
 *
 * No warranty, yadda yadda
 */                                       
                                          
#ifndef ${basename}_H    
#define ${basename}_H

EOF
;

foreach my $a (keys %Definelist)
{
  print OUTPUT_H "#define SNMPDEFINE_$a \"$Definelist{$a}\"\n";
}

print OUTPUT_H <<EOF

#ifndef USING_SNMPGEN

#define snmp_connect()
#define snmp_close()
#define snmp_increment(a, b)
#ifdef __GNUC__
#define snmp_increment_args(args...)
#else
#define snmp_increment_args(args)
#endif
#define snmp_set(a, b)
#define snmp_set_str(a, b)
#define snmp_set_oid(a, b)
#define snmp_set_time(a, b)
#define snmp_getdescription(a)
#define snmp_getoid(a, b, c, d)
#define snmp_setvariable(a, b)

typedef void ${basename}_t;

#else

typedef enum {
EOF
;

# Avoid putting a comma after the last item as not every C compiler
# accepts it
my $initialized = 0;
foreach my $name (keys %T) {
    print OUTPUT_H ",\n" if $initialized;
    print OUTPUT_H "    $name";
    $initialized = 1;
}

print OUTPUT_H <<EOF
} ${basename}_t;

typedef enum {
EOF
;

$initialized = 0;
foreach my $name (@Varlist) {
    print OUTPUT_H ",\n" if $initialized;
    print OUTPUT_H "    VARIABLE_$name";
    $initialized = 1;
}


print OUTPUT_H <<EOF

} ${basename}_variable_t;

int snmp_connect(void);

int snmp_close(void);          
                                    
/* only valid on counters */
int snmp_increment(${basename}_t cmd, int);
int snmp_increment_args(${basename}_t cmd, int incr, ...);

/* only valid on values */
int snmp_set(${basename}_t cmd, int);

int snmp_set_str(${basename}_t cmd, char *value);

int snmp_set_oid(${basename}_t cmd, char *str);

int snmp_set_time(${basename}_t cmd, time_t t);
                                    
const char *snmp_getdescription(${basename}_t cmd); 
 
const char *snmp_getoid(const char *name, ${basename}_t cmd, char* buf, int buflen); 

void snmp_setvariable(${basename}_variable_t, int);

#endif /* USING_SNMPGEN */
 
#endif /* ${basename}_H */ 

EOF
;

close OUTPUT_H;

open (OUTPUT_C,">$outprog");

print OUTPUT_C <<EOF
/* $outprog -- automatically generated from $infile by snmpgen */

#ifdef USING_SNMPGEN

/* We disable this code for now since it doesn't actually work and wastes
 * resources.  At some point in time, we'll make it work again as it would 
 * be useful to gather aggregate statistics on what commands are being used 
 * so we can better tune the server.  This change closes bug #1191. 
 * New bug 1267 opened to re-enable the feature.
 */

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <stdarg.h>

#include "index.h"
#include "$outheader"

extern int close(int);

int variable_value[$#Varlist+1];
int variable_tmpvalue[$#Varlist+1];

int varvalue(${basename}_variable_t var)
{
    if (variable_tmpvalue[var]!=-1)
        return variable_tmpvalue[var];

    return variable_value[var];
}

const char *snmp_getdescription(${basename}_t evt)
{
    switch (evt) {
EOF
;

foreach my $a (keys %T)
{
    print OUTPUT_C "        case $a: return $D{$a};\n";
}

print OUTPUT_C <<EOF
    }
    return NULL;
}

const char *snmp_getoid(const char *name __attribute__((unused)),
			${basename}_t evt, char *buf, int buflen)
{
    switch (evt) {
EOF
;

foreach my $a (keys %T)
{
  print OUTPUT_C "        case $a: snprintf(buf,buflen,\"$O{$a}\"";
  foreach my $b (@{ $Ovlist{$a} })
    {
      print OUTPUT_C ",varvalue(VARIABLE_$b)";
    }
  print OUTPUT_C "); return buf;\n";

}


$snmp.= "    default: return \"0.0.0\";\n";
$snmp.= "  }\n";
$snmp.= "}\n";
    
print OUTPUT_C <<EOF
    }
    return NULL;
}

#define SOCK_PATH "/tmp/.snmp_door"

static int mysock = -1;
static struct sockaddr_un remote;
static int sockaddr_len = 0;

static void snmp_send(char *str)
{
    if (mysock == -1) return;

    if (sendto(mysock, str, strlen(str), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return;
    }

    return;
}

int snmp_connect(void)
{
    int s;
    int fdflags;
    int lup;

    if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
	return 1;
    }

    for (lup=0;lup < $#Varlist+1; lup++)
        variable_tmpvalue[lup] = -1;

    remote.sun_family = AF_UNIX;
    strlcpy(remote.sun_path, SOCK_PATH, sizeof(remote.sun_path));
    sockaddr_len = strlen(remote.sun_path) + sizeof(remote.sun_family);

    /* put us in non-blocking mode */
    fdflags = fcntl(s, F_GETFD, 0);
    if (fdflags != -1) fdflags = fcntl(s, F_SETFL, O_NONBLOCK | fdflags);
    if (fdflags == -1) { close(s); return -1; }

    mysock = s;
EOF
;

foreach my $oid (@Registerlist) {
    print OUTPUT_C "    snmp_send(\"R $oid\\n\");\n";
}


print OUTPUT_C <<EOF
    return 0;
}

int snmp_close(void)
{
    if (mysock > -1)
	close(mysock);

    return 0;
}

int snmp_increment_args(${basename}_t cmd, int incr, ...)
{
    char tosend[256]; /* xxx UDP max size??? */
    char buf[256];

      va_list ap; /* varargs thing */
      ${basename}_variable_t vval;
      int ival;

      if (mysock == -1) return 1;

      va_start(ap, incr);

      do {
          vval = va_arg(ap, ${basename}_variable_t); /* get the next arg */

          if (vval!=VARIABLE_LISTEND)
          {
              ival = va_arg(ap, int); /* get the next arg */
              variable_tmpvalue[vval] = ival;              
          }

      } while ( vval != VARIABLE_LISTEND);

      va_end(ap);

    snprintf(tosend, sizeof(tosend),"C %s %d\\n",snmp_getoid(NULL,cmd,buf,sizeof(buf)), incr);

    if (sendto(mysock, tosend, strlen(tosend), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return 1;
    }

    /* set tmp variables back */
    va_start(ap, incr);

      do {
          vval = va_arg(ap, ${basename}_variable_t); /* get the next arg */

          if (vval!=VARIABLE_LISTEND)
          {
              ival = va_arg(ap, int); /* get the next arg */
              variable_tmpvalue[vval] = -1;              
          }

      } while ( vval != VARIABLE_LISTEND);

      va_end(ap);

    return 0;
}

int snmp_increment(${basename}_t cmd, int incr)
{
    char tosend[256]; /* xxx UDP max size??? */
    char buf[256];

    if (mysock == -1) return 1;

    snprintf(tosend, sizeof(tosend),"C %s %d\\n",snmp_getoid(NULL,cmd,buf,sizeof(buf)), incr);

    if (sendto(mysock, tosend, strlen(tosend), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return 1;
    }

    return 0;
}

int snmp_set(${basename}_t cmd, int value)
{
    char tosend[256];
    char buf[256];

    if (mysock == -1) return 1;

    snprintf(tosend, sizeof(tosend),"I %s %d\\n",snmp_getoid(NULL,cmd,buf,sizeof(buf)), value);

    if (sendto(mysock, tosend, strlen(tosend), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return 1;
    }

    return 1;
}

int snmp_set_str(${basename}_t cmd, char *value)
{
    char tosend[256];
    char buf[256];

    if (mysock == -1) return 1;

    snprintf(tosend, sizeof(tosend),"S %s %s\\n",snmp_getoid(NULL,cmd,buf,sizeof(buf)), value);

    if (sendto(mysock, tosend, strlen(tosend), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return 1;
    }

    return 1;
}

int snmp_set_time(${basename}_t cmd, time_t t)
{
    char tosend[256];
    char buf[256];

    if (mysock == -1) return 1;

    snprintf(tosend, sizeof(tosend),"T %s %lu\\n",snmp_getoid(NULL,cmd,buf,sizeof(buf)), t);

    if (sendto(mysock, tosend, strlen(tosend), 0, (struct sockaddr *) &remote, sockaddr_len) == -1) {
	return 1;
    }

    return 1;
}

/* should use SNMPDEFINE's as parameter */
int snmp_set_oid(${basename}_t cmd, char *str)
{
   return snmp_set_str(cmd,str);
}

void snmp_setvariable(${basename}_variable_t name, int value)
{
    variable_value[name] = value;
}

#endif


EOF
;

if (defined $opt_extra) {
   open (INPUT_IN,"<$opt_extra");
   while( <INPUT_IN> )
   {
       print OUTPUT_C;
   }
   close INPUT_IN;
} 

close OUTPUT_C;

