## modules/wk/which_keytypes.c

/* [<][>][^][v][top][bottom][index][help] */

## FUNCTIONS

This source file includes following functions.

1. matching
2. perform_regex_test
3. isipv6prefix_a
4. isipv6prefix
5. islen
6. isnet
7. isasnum
8. isnetname
9. ismaintainer
10. islimerick
11. isnichandle_joao
12. isnichandle
13. isaskeyword
14. isasmacro
15. isclnskeyword
16. ispeerkeyword
17. isnetlist
18. iscommunity
19. isaspref
20. isnetnum
21. isipaddr
22. ismask
23. isclnsprefix
24. issubdomname
25. isdomname
26. isname_a
27. isname_b
28. isname_ab
29. isname
30. isphone_a
31. isphone_b
32. isphone_ab
33. isphone
34. isemail
35. isbrace
36. isparen
37. wk_is_name
38. wk_is_nic_hdl
39. wk_is_email
40. wk_is_mntner
41. wk_is_key_cert
42. wk_is_iprange
43. wk_is_ipprefix
44. wk_is_ip6prefix
45. wk_is_netname
46. wk_is_net6name
47. wk_is_autnum
48. wk_is_assetname
49. wk_is_routesetname
50. wk_is_domain
51. wk_is_hostname
52. wk_is_limerick
53. WK_to_string
54. WK_new

    /***************************************
      $Revision: 1.8 $

      Example code: Determine which keys to look for.

      This is based on the C code that was reversed engineered from existing Perl
      code.  (~ottrey/which_table/which_table.c)

      ******************/ /******************
      Copyright (c) 1999                              RIPE NCC

      All Rights Reserved

      Permission to use, copy, modify, and distribute this software and its
      documentation for any purpose and without fee is hereby granted,
      provided that the above copyright notice appear in all copies and that
      both that copyright notice and this permission notice appear in
      supporting documentation, and that the name of the author not be
      used in advertising or publicity pertaining to distribution of the
      software without specific, written prior permission.

      THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
      ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
      AUTHOR 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.
      ***************************************/
    #include <stdio.h>
    #include <strings.h>
    #include <libgen.h>
    #include "isnic.h"
    #include "bitmask.h"
    #include "which_keytypes.h"

    #define DOMAINNAME "^[ ]*[a-zA-Z0-9--]*(\\.[a-zA-Z0-9--]+)*[ ]*$"

    #define LEN_MIN 0
    #define LEN_MAX 32

    #define NETLEN 16
    #define NETQUADS 4
    #define NETQUAD_MIN 0
    #define NETQUAD_MAX 255

    #define ASNUM_MIN 1
    #define ASNUM_MAX 65535
    #define ASNUM_NUMOFFSET 2   /* XXX - (This is really kludgy!) Offset to the number bit of ASNUM */

    #define VALIDIP6 "^[0-9A-F]{1,4}(:[0-9A-F]{1,4}){7}$"
    /*
      XXX Why doesn't this work?
    #define NET "^([0-9]{1,3}.){4}$"
    */
    #define NET "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$"

    #define ASNUM "^AS[1-9]+[0-9]*$"

    #define NETNAME "^[A-Z][A-Z0-9-]*$"

    #define MAINTAINER "^[A-Z][A-Z0-9-]*$"

    #define LIMERICK "^LIM-[A-Z0-9-]+$"

    #define KEYCERT "^PGPKEY-[0-9A-F]{8}$"

    #define ASMACRO "^AS-[A-Z]+$"

    #define ROUTESETNAME "^RS-[A-Z0-9-]*$"

    #define ASSETNAME "^AS-[A-Z0-9-]*$"

    #define AUTONICPREFIXREGULAR "^AUTO-"

    /*
      XXX This seems to be the same as the Perl code.  But I don't see where a " " is allowed for.
      I.e. Perl -> ^[a-zA-Z][\w\-\.\'\|\`]*$
      Does \w include [ ;:,?/}{()+*#] ?
    #define NAME_B "^[a-zA-Z][a-zA-Z_0-9.'|`-]*$"
    */
    #define NAME_B "^[a-zA-Z][a-zA-Z_0-9.'|`;:,?/}{()+*#&-]*$"

    #define PHONE_A "^[ ]*[+][0-9 ]*[(]{0,1}[0-9 -]+[)]{0,1}[0-9 -]*(ext\\.){0,1}[0-9 ]*$"

    #define VALIDIP4PREFIX

    #define EMAIL "^[.a-zA-Z0-9--]*@[a-zA-Z0-9--]*(\\.[a-zA-Z0-9--]+)*$"

    /*+ Keytype strings +*/
    char * const Keytypes[] = {
      "name",
      "nic_hdl",
      "email",
      "mntner",
      "key_cert",
      "iprange",
      "ipprefix",
      "ip6prefix",
      "netname",
      "net6name",
      "autnum",
      "assetname",
      "routesetname",
      "domain",
      "hostname",
      "limerick",
      NULL
    }; /* Keytypes[] */

    /*+ Peerword strings +*/
    const char * Peerword[] = {
                   "EGP",
                   "BGP",
                   "BGP4",
                   "IDRP",
                   "IGP",
                   "HELLO",
                   "IGRP",
                   "EIGRP",
                   "OSPF",
                   "ISIS",
                   "RIP",
                   "RIP2",
                   "OTHER",
                   ""
    }; /* Peerword[] */

    static int matching(char *string, char left_c, char right_c) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result;

      int i;
      int length;
      int count=0;

      length = strlen(string);

      for(i=0; i < length; i++) {
    /*
        switch ((int)string[i]) {
          case left_c:
          break;

          case right_c:
            count--;
          break;

          default:
        }
    */
        if (string[i] == left_c) {
          count++;
        }
        if (string[i] == right_c) {
          count--;
        }
      }

      if (count == 0) {
        /* Matching characters */
        result=1;
      }
      else {
        /* Non-matching characters */
        result=0;
      }

      return result;

    } /* matching() */

    static int perform_regex_test(const char *pattern, char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      char *return_value;
      int match;

      char *re;

      re = regcmp(pattern, (char*)0);
      if (regex(re, string) == NULL) {
        match = 0;
      }
      else {
        match = 1;
      }

      free(re);

      return match;

    }

    static int isipv6prefix_a(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
    /*
      printf("isipv6prefix\n");
    */
      int result='-';

      result = perform_regex_test(VALIDIP6, string);

      return result;
    }

    static int isipv6prefix(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
    /*
      printf("isipv6prefix\n");
    */
      int result='-';

      return result;
    }

    static int islen(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("islen\n");
    */
      int result='-';
      int length;

      length = strlen(string);

      if ((length <= LEN_MAX) && (length >= LEN_MIN)) {
        /* A valid length */
        result=1;
      }
      else if (length < 0) {
        /* An invalid length */
        result=-1;
      }
      else {
        /* An invalid length */
        result=0;
      }

      return result;
    }

    static int isnet(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isnet\n");
    */
      int result='-';
      char tmp_string[NETLEN];
      int quad_value;
      char *quad_value_str;

      /* First check if the string is in quad form */
      result = perform_regex_test(NET, string);

      /* Then check if the quad values are between NETQUAD_MIN and NETQUAD_MAX */
      if (result == 1) {
        strncpy(tmp_string, string, NETLEN);
        quad_value_str = strtok(tmp_string, ".");
        while (quad_value_str != NULL) {
          quad_value = atoi(quad_value_str);
          if ((quad_value < NETQUAD_MIN) || (quad_value > NETQUAD_MAX)) {
            /* an invalid value */
            result=0;
            break;
          }
          quad_value_str = strtok(NULL, ".");
        }
      }

      return result;
    }

    static int isasnum(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isasnum\n");
    */
      int result='-';
      int as_value;

      /* First check if the string matches an ASNUM */
      result = perform_regex_test(ASNUM, string);

      /* Then check if the value is between ASNUM_MIN and ASNUM_MAX */
      if (result == 1) {
        as_value = atoi(string+ASNUM_NUMOFFSET);
        if ((as_value < ASNUM_MIN) || (as_value > ASNUM_MAX)) {
          /* an invalid value */
          result=0;
        }
      }

      return result;
    }

    static int isnetname(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isnetname\n");
    */
      int result='-';

      result = perform_regex_test(NETNAME, string);

      return result;
    }

    static int ismaintainer(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("ismaintainer\n");
    */
      int result='-';

      result = perform_regex_test(MAINTAINER, string);

      return result;
    }

    static int islimerick(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("islimerick\n");
    */
      int result='-';

      result = perform_regex_test(LIMERICK, string);

      return result;
    }

    /*******************************************************
        # the problem is as follows:
        #
        # we can never find out which NIC handles are possible on the
        # globe since we don't know that they exist
        #
        # we want to solve this with once with DNS :
        #
        # RIPE.registries.int     CNAME whois.ripe.net
        # InterNIC.registries.int CNAME whois.internic.net
        # and so on...

        #
        # 1) it first does a basic syntax check
        #
        #    notes:
        #
        #    - catches InterNIC handles
        #    - catches the JP|JP-JP APNIC exceptions
        #    - limits the number of initials to three with a good reason:
        #      we have a much better chance to find syntax errors like:
        #      RIPE-DK13 and other problems like this
        #
        # 2) checks for valid suffixes
        #    - all 'source:' attribute values from sites that we mirror
        #      are allowed
        #    - country codes are allowed for APNIC compatibility
        #    - APNIC AP|CC-AU exceptions are handled correctly
        #    - -ORG organization InterNIC handles
        #    - -ARIN ARIN handles
        #    - -ORG-ARIN ARIN handles
    ********************************************************/
    static int isnichandle_joao(char *nichdl) {
    /* [<][>][^][v][top][bottom][index][help] */

      char *regexp, *match;
      char ret[1024];
      char *suffix;

      int i;

    /* set ret to the empty string *.
      ret[0]='\0';
    /** Return if there are any lower case characters */

      regexp = regcmp("[a-z]",(char *)0);
      match = regex(regexp,nichdl);
      free(regexp);
      if (match) return 0;

    /*
      # Japanese NIC handles
      #
      # leading zeros in the number part *are* allowed
      #
      # e.g. AB021JP AB199JP-JP
      #
    */
      regexp = regcmp("[A-Z]{2}[0-9]{3}JP(-JP){0,1}",(char *)0);
      match =  regex(regexp,nichdl);
      free(regexp);
      if (match) return 1;

    /*
      # Standard NIC handles
      #
      # leading zeros in the number part are *not* allowed
      #
      # InterNIC - TBQ, IP4
      # RIPE format - AB1-RIPE
      # APNIC use two letter country code suffix
      # Austraila have used -1-AU, -2-AU, -CC-AU suffix.
      # Internic used -ORG suffix
      # ARIN use -ARIN suffix
      # ARIN also use -ORG-ARIN suffix
      #
    */
      regexp = regcmp("^[A-Z]{2,4}([1-9][0-9]{0,5}){0,1}((-[^ ]+){0,1})$0$",(char *)0);
      match =  regex(regexp,nichdl,ret);

      free(regexp);

      if (match == NULL) {
        return 0;
      } else {
        if (ret[0] == '\0') {
          return 1;
        } else {
    /*   strip leading '-' */
          suffix = ret+1;
    /* suffix of local sources */
          for (i=0;i<=NUM_NICPOSTFIX;i++) {
            if ( !strcmp(suffix,nicpostfix[i]) ) {
              return 1;
            }
          }
    /* country codes */
          for (i=0;i<NUM_COUNTRIES;i++) {
            if ( !strcmp(suffix,countries[i]) ) {
              return 1;
            }
          }
    /* special suffix */
          for (i=0;i<NUM_SPECIAL;i++) {
            if ( !strcmp(suffix,special[i]) ) {
              return 1;
            }
          }
        }
      }
      return 0;
    } /* isnichandle_joao() */

    static int isnichandle(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      return isnichandle_joao(string);
    }

    static int isaskeyword(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isaskeyword\n");
    */
      int result='-';

      return result;
    }

    static int isasmacro(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isasmacro\n");
    */
      int result='-';

      result = perform_regex_test(ASMACRO, string);

      return result;
    }

    static int isclnskeyword(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isclnskeyword\n");
    */
      int result='-';

      return result;
    }

    static int ispeerkeyword(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("ispeerkeyword\n");
    */
      int result='-';
      int i;

      result=0;
      for (i=0; Peerword[i] != ""; i++) {
        if ( strcmp(Peerword[i], string) == 0 ) {
          result=1;
          break;
        }
      }

      return result;
    }

    static int isnetlist(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isnetlist\n");
    */
      int result='-';

      return result;
    }

    static int iscommunity(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("iscommunity\n");
    */
      int result='-';

      return result;
    }

    static int isaspref(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isaspref\n");
    */
      int result='-';

      return result;
    }

    static int isnetnum(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isnetnum\n");
    */
      int result='-';

      /* XXX - I don't see the difference between isnet and isnetnum */
      result=isnet(string);

      return result;
    }

    static int isipaddr(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isipaddr\n");
    */
      int result='-';

      return result;
    }

    static int ismask(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("ismask\n");
    */
      int result='-';

      return result;
    }

    static int isclnsprefix(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isclnsprefix\n");
    */
      int result='-';

      return result;
    }

    static int issubdomname(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("issubdomname\n");
    */
      int result='-';

      result = perform_regex_test(DOMAINNAME, string);

      return result;
    }

    static int isdomname(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isdomname\n");
    */
      int result='-';

      result = perform_regex_test(DOMAINNAME, string);

      return result;
    }

    /*
     I split the isname up into isname_a & isname_b.  And created isname_ab to join them together.
      - So I can test it properly.  -ottrey
     */
    static int isname_a(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isname_a\n");
    */
      int result='-';

      result = perform_regex_test(AUTONICPREFIXREGULAR, string);

      return result;
    }

    static int isname_b(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isname_b\n");
    */
      int result='-';

      result = perform_regex_test(NAME_B, string);

      return result;
    }

    static int isname_ab(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isname_ab\n");
    */
      int result='-';

      /* Note: the different logic here because I use 0 to be a match and 1 to not be a match.
         The Perl code uses the opposite. - ottrey */
      result = !(isname_a(string) && !isname_b(string));

      return result;
    }

    static int isname(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isname\n");
    */
      int result='-';

      return result;
    }

    static int isphone_a(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isphone_a\n");
    */
      int result='-';

      result = perform_regex_test(PHONE_A, string);

      return result;
    }
    static int isphone_b(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isphone_b\n");
    */
      int result='-';

      result = isparen(string);

      return result;
    }
    static int isphone_ab(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isphone_ab\n");
    */
      int result='-';

      /* Note: the different logic here because I use 0 to be a match and 1 to not be a match.
         The Perl code uses the opposite. - ottrey */
      result = !(!isphone_a(string) && !isphone_b(string));

      return result;
    }
    static int isphone(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isphone\n");
    */
      int result='-';

      return result;
    }

    static int isemail(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isemail\n");
    */
      int result='-';

      result = perform_regex_test(EMAIL, string);

      return result;
    }

    static int isbrace(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isbrace\n");
    */
      int result='-';

      result=matching(string, '{', '}');

      return result;
    }

    static int isparen(char *string) {
    /* [<][>][^][v][top][bottom][index][help] */
      /*
      printf("isparen\n");
    */
      int result='-';

      result=matching(string, '(', ')');

      return result;
    }

    /* ****** The new bunch  ******* */
    static int wk_is_name(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      /* Everything matches to name */
      return 1;

    } /* wk_is_name() */

    static int wk_is_nic_hdl(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isnichandle(key);

    } /* wk_is_nic_hdl() */

    static int wk_is_email(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isemail(key);

    } /* wk_is_email() */

    static int wk_is_mntner(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return ismaintainer(key);

    } /* wk_is_mntner() */

    static int wk_is_key_cert(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result=1;

      result = perform_regex_test(KEYCERT, key);

      return result;

    } /* wk_is_key_cert() */

    static int wk_is_iprange(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result=1;

      /* XXX This is not very strict - but will cut out a lot of invalids */
      /* XXX And has been given a bad name and does a few things. */
      /* XXX This needs work. */
    #define IPRANGE "^[0-9./ -]*$"
      result = perform_regex_test(IPRANGE, key);

      return result;

    } /* wk_is_iprange() */

    static int wk_is_ipprefix(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result=1;

      /* XXX INSERT check here! */
      return result;

    } /* wk_is_iprange() */

    static int wk_is_ip6prefix(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isipv6prefix_a(key);

    } /* wk_is_ip6prefix() */

    static int wk_is_netname(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isnetname(key);

    } /* wk_is_netname() */

    /* XXX Note: This function uses the same call as wk_is_netname(). */
    static int wk_is_net6name(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isnetname(key);

    } /* wk_is_netname() */

    static int wk_is_autnum(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isasnum(key);

    } /* wk_is_autnum() */

    static int wk_is_assetname(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result=1;

      result = perform_regex_test(ASSETNAME, key);

      return result;

    } /* wk_is_assetname() */

    static int wk_is_routesetname(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      int result=1;

      result = perform_regex_test(ROUTESETNAME, key);

      return result;

    } /* wk_is_routesetname() */

    static int wk_is_domain(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return isdomname(key);

    } /* wk_is_domname() */

    static int wk_is_hostname(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      /* XXX Why is there a hostname & a domainname? */
      /* Answer - hostname can be a domainname or an IP */
      return (isdomname(key) || wk_is_iprange(key));

    } /* wk_is_hostname() */

    static int wk_is_limerick(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */

      return islimerick(key);

    } /* wk_is_limerick() */

    /* WK_to_string() */
    /*++++++++++++++++++++++++++++++++++++++
      Convert the which keytypes bitmap into a string.

      mask_t wk The which keytypes mask to be converted.

      More:
      +html+ <PRE>
      Authors:
            ottrey
      +html+ </PRE><DL COMPACT>
      +html+ <DT>Online References:
      +html+ <DD><UL>
      +html+ </UL></DL>

      ++++++++++++++++++++++++++++++++++++++*/
    char *WK_to_string(mask_t wk) {
    /* [<][>][^][v][top][bottom][index][help] */

      return MA_to_string(wk, Keytypes);

    } /* WK_to_string() */

    /* WK_new() */
    /*++++++++++++++++++++++++++++++++++++++
      Create a new which keytypes bitmap.

      char *key The key to be examined.

      More:
      +html+ <PRE>
      Authors:
            ottrey
      +html+ </PRE><DL COMPACT>
      +html+ <DT>Online References:
      +html+ <DD><UL>
      +html+ </UL></DL>

      ++++++++++++++++++++++++++++++++++++++*/
    mask_t WK_new(char *key) {
    /* [<][>][^][v][top][bottom][index][help] */
      mask_t wk;

      wk = MA_new(MA_END);

      MA_set(&wk, WK_NAME,         wk_is_name(key));
      MA_set(&wk, WK_NIC_HDL,      wk_is_nic_hdl(key));
      MA_set(&wk, WK_EMAIL,        wk_is_email(key));
      MA_set(&wk, WK_MNTNER,       wk_is_mntner(key));
      MA_set(&wk, WK_KEY_CERT,     wk_is_key_cert(key));
      MA_set(&wk, WK_IPRANGE,      wk_is_iprange(key));
      MA_set(&wk, WK_IPPREFIX,     wk_is_ipprefix(key));
      MA_set(&wk, WK_IP6PREFIX,    wk_is_ip6prefix(key));
      MA_set(&wk, WK_NETNAME,      wk_is_netname(key));
      MA_set(&wk, WK_NET6NAME,     wk_is_net6name(key));
      MA_set(&wk, WK_AUTNUM,       wk_is_autnum(key));
      MA_set(&wk, WK_ASSETNAME,    wk_is_assetname(key));
      MA_set(&wk, WK_ROUTESETNAME, wk_is_routesetname(key));
      MA_set(&wk, WK_DOMAIN,       wk_is_domain(key));
      MA_set(&wk, WK_HOSTNAME,     wk_is_hostname(key));
      MA_set(&wk, WK_LIMERICK,     wk_is_limerick(key));

      return wk;

    } /* WK_new() */

/* [<][>][^][v][top][bottom][index][help] */