home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pp / pp-6.0 / Lib / table / tb_getdomain.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-12-18  |  14.8 KB  |  688 lines

  1. /*
  2. tb_getdomain.c - maps an alias domain into a normalised one and optionally,
  3.         maps a normalised domain into its next hop out.
  4. */
  5.  
  6. # ifndef lint
  7. static char Rcsid[] = "@(#)$Header: /xtel/pp/pp-beta/Lib/table/RCS/tb_getdomain.c,v 6.0 1991/12/18 20:24:28 jpo Rel $";
  8. # endif
  9.  
  10. /*
  11.  * $Header: /xtel/pp/pp-beta/Lib/table/RCS/tb_getdomain.c,v 6.0 1991/12/18 20:24:28 jpo Rel $
  12.  *
  13.  * $Log: tb_getdomain.c,v $
  14.  * Revision 6.0  1991/12/18  20:24:28  jpo
  15.  * Release 6.0
  16.  *
  17.  */
  18.  
  19.  
  20.  
  21. #include "util.h"
  22. #include "list_rchan.h"
  23. #include <isode/cmd_srch.h>
  24.  
  25. #define MASK_PREFBIT        1
  26. char        *bits2str();
  27. int        str2bits();
  28. static int    scorebits();
  29. static int     dom_lookup ();
  30. static int    fillin_results();
  31.  
  32.  
  33. /* ---------------------  Begin     Routines --------------------------------- */
  34.  
  35.  
  36.  
  37. /* --- *** Start Description *** ---
  38.  
  39. tb_getdomain (dom_key, chan_key, normalised, order_pref, ppsubdom)
  40.  
  41.     dom_key        -      Could be either an Alias, NRS Short from,
  42.                     or a Normalised domain name.
  43.     chan_key    -    The Next Hop hostname. This is used
  44.                     as a key to the channel table.
  45.     normalised    -    Normalised version of (dom_key)
  46.     order_pref    -    UK only, USA only, UK pref, USA pref.
  47.     ppsubdom    -    is this a sub domain - if so which
  48.  
  49. Example:
  50.     dom_key        =    blah.torch.co
  51.     chan_key    =    torch.co.uk
  52.     normalised    =    blah.torch.co.uk
  53.     order_pref    =    CH_UK_PREF
  54.  
  55. Routine:
  56.     - Reads the domain table
  57.     - Updates normalised.
  58.     - Optionally updates chan_key if ptr given.
  59.     - Returns OK or NOTOK.
  60.  
  61. Note:
  62.     - This Routine always returns the char* normalised in USA order
  63.  
  64. --- *** End Description *** --- */
  65.  
  66.  
  67.  
  68. /* ------------------------------------------------------------------------ */
  69.  
  70. static int loopCount; /* stop infinite recursion for synonyms */
  71. #define MAXLOOPS    10
  72.  
  73. static int    hitTable;
  74. /*   */
  75. /* interface for general parsing */
  76.  
  77. int tb_getdomain (dom_key, chan_key, normalised, order_pref, ppsubdom)
  78. char            *dom_key;
  79. char            *chan_key;
  80. char            *normalised;
  81. int            order_pref;
  82. char            **ppsubdom;
  83. {
  84.     Table    *Domain;
  85.     int    retval;
  86.  
  87.     if ((Domain = tb_nm2struct ("domain")) == NULLTBL) {
  88.         PP_LOG (LLOG_FATAL, 
  89.             ("No table 'domain' defined"));
  90.         return (NOTOK);
  91.     }
  92.     
  93.     hitTable = FALSE;
  94.     loopCount = 0;
  95.     if ((retval = tb_getdomain_aux(Domain, dom_key, order_pref,
  96.                        chan_key, normalised, 
  97.                        ppsubdom)) == NOTOK
  98.         && hitTable == FALSE)
  99.         /* try for default */
  100.         if ((retval = tb_getdomain_aux(Domain, "*", order_pref,
  101.                            chan_key, normalised,
  102.                            ppsubdom)) == OK) {
  103.             (void) strcpy(normalised, dom_key);
  104.             PP_NOTICE(("Using default route for '%s'",
  105.                    dom_key));
  106.         }
  107.     return retval;
  108. }
  109.  
  110. /*   */
  111. /* interface for mtatable identification */
  112.  
  113. int tb_getdomain_table (table, dom_key, chan_key,
  114.             normalised, order_pref, ppsubdom)
  115. Table            *table;
  116. char            *dom_key;
  117. char            *chan_key;
  118. char            *normalised;
  119. int            order_pref;
  120. char            **ppsubdom;
  121. {
  122.     loopCount = 0;
  123.     return tb_getdomain_aux(table, dom_key, order_pref,
  124.                 chan_key, normalised, ppsubdom);
  125. }
  126.  
  127. /*   */
  128.  
  129. int tb_getdomain_aux (table, dom_key, order_pref,
  130.               chan_key, normalised,
  131.               ppsubdom)
  132. Table            *table;     /* domain table */
  133. char            *dom_key;    /* what we're looking up */
  134. int            order_pref;    /* which dmn ordering we allow */
  135. char            *chan_key;    /* routing pointer */
  136. char            *normalised;    /* normalised dmn */
  137. char            **ppsubdom;    /* pointer to local tables */
  138. {
  139.     char    tbuf[BUFSIZ],
  140.         usa_norm[BUFSIZ],
  141.         usa_chan[BUFSIZ],
  142.         uk_norm[BUFSIZ],
  143.         uk_chan[BUFSIZ],
  144.         *usa_ord[LINESIZE],
  145.         *uk_ord[LINESIZE],
  146.         *str;
  147.     char    *uksd = NULLCP, *ussd = NULLCP;
  148.     int    count,
  149.         usa_score,
  150.         uk_score;
  151.  
  152.     if (dom_key == NULLCP)
  153.         return NOTOK;
  154.  
  155.     if (*dom_key == '.' || dom_key [strlen (dom_key) - 1] == '.')
  156.         return (NOTOK);
  157.  
  158.     *ppsubdom = NULLCP;
  159.     *normalised = '\0';
  160.     if (chan_key)
  161.         *chan_key = '\0';
  162.  
  163.     PP_DBG (("Lib/table/tb_getdomain (domain='%s', order_pref=%d)",
  164.          dom_key, order_pref));
  165.  
  166.     /* -- do not destroy input -- */
  167.     (void) strcpy (tbuf, dom_key);
  168.  
  169.  
  170.     /* -- get things into known order and count parts of name -- */
  171.     if ((order_pref & CH_UK_ONLY) != 0)
  172.         count = str2bits (tbuf, '.', uk_ord, usa_ord);
  173.     else
  174.         count = str2bits (tbuf, '.', usa_ord, uk_ord);
  175.  
  176.  
  177.     /* -- look up the usa order name - exact match -- */
  178.  
  179.     str = bits2str (usa_ord, '.');
  180.     /* try for foo.bar */
  181.     if (dom_lookup (table, str, order_pref,
  182.             usa_chan, usa_norm, ppsubdom, 
  183.             0, TRUE, usa_ord, count) == OK ||
  184.         /* try for *.foo.bar */
  185.         dom_lookup (table, str, order_pref,
  186.             usa_chan, usa_norm, ppsubdom, 
  187.             0, FALSE, usa_ord, count) == OK) {
  188.         fillin_results(usa_norm, usa_chan, normalised, chan_key);
  189.         return OK;
  190.     }
  191.     /* -- ok, if the other way around is allowed - try it -- */
  192.     if ((order_pref & MASK_PREFBIT) != 0) {
  193.         str = bits2str (uk_ord, '.');
  194.         /* try for bar.foo */
  195.         if (dom_lookup (table, str, order_pref, 
  196.                 uk_chan, uk_norm, ppsubdom, 
  197.                 0, TRUE, uk_ord, count) == OK ||
  198.             /* try for *.bar.foo */
  199.             dom_lookup(table, str, order_pref, 
  200.                    uk_chan, uk_norm, ppsubdom,
  201.                    0, FALSE, uk_ord, count) == OK) {
  202.             fillin_results(uk_norm, uk_chan,
  203.                        normalised, chan_key);
  204.             return OK;
  205.         }
  206.     }
  207.  
  208.     uksd = ussd = NULLCP;
  209.  
  210.     /* -- score usa order -count the number of matching bits -- */
  211.     usa_score = scorebits (table, usa_ord, count, 
  212.                    usa_norm, usa_chan, 
  213.                    &ussd, order_pref);
  214.  
  215.     /* -- also score the reverse order if appropriate -- */
  216.     if ((order_pref & MASK_PREFBIT) != 0)
  217.         uk_score = scorebits (table, uk_ord, count, 
  218.                       uk_norm, uk_chan, 
  219.                       &uksd, order_pref);
  220.     else
  221.         uk_score = -1;
  222.  
  223.     /* -- no matches - can't normalise this thing -- */
  224.     if (uk_score == -1 && usa_score == -1) {
  225.         if (uksd) free(uksd);
  226.         if (ussd) free(ussd);
  227.         return NOTOK;
  228.     }
  229.  
  230.  
  231.     /* -- usa order is the best fit -- */
  232.     if (usa_score >= uk_score) {
  233.         *ppsubdom = ussd;
  234.         if (uksd) free(uksd);
  235.         fillin_results(usa_norm, usa_chan, normalised, chan_key);
  236.     } else {
  237.         /* -- uk order is the best fit -- */
  238.         *ppsubdom = uksd;
  239.         if (ussd) free(ussd);
  240.         fillin_results(uk_norm, uk_chan, normalised, chan_key);
  241.     }
  242.     return OK;
  243. }
  244.  
  245. /*   */
  246. /* find best match ( -1 == no match ) */
  247.  
  248. static int scorebits (table, comps, numComps, norm, chan, subdom, order_pref)
  249. Table    *table;
  250. char    *comps[];
  251. int    numComps;
  252. char    norm[];
  253. char    chan[];
  254. char    **subdom;
  255. int    order_pref;
  256. {
  257.     int    i;
  258.     char    *tbuf[LINESIZE], *str, **ptr;
  259.  
  260.     PP_DBG (("Lib/table/scorebits()"));
  261.  
  262.     for (i = 0, ptr = comps; *ptr != NULL; ptr++)
  263.         tbuf[i++] = *ptr;
  264.  
  265.     tbuf[i] = NULLCP;
  266.  
  267.     /* numComps -1 cos already looked up whole domain */
  268.     for (i = numComps-1; i > 0; i--) {
  269.         str = bits2str (&tbuf[numComps - i], '.');
  270.         switch (dom_lookup(table, str, order_pref,
  271.                    chan, norm, subdom, 
  272.                    numComps - i, FALSE,
  273.                    comps, numComps)) {
  274.             case OK:
  275.             case DONE:
  276.             /* decreasing i so best hit first hit */
  277.             return i;
  278.             default:
  279.             break;
  280.         }
  281.     }
  282.  
  283.     /* no hits */
  284.     norm[0] = '\0';
  285.     if (chan)
  286.         chan[0] = '\0';
  287.     return NOTOK;
  288. }
  289.  
  290. /*   */
  291.  
  292. static int check_rule();
  293.  
  294. static int dom_lookup (table, name, order_pref,
  295.                chan, norm, subdom, nbits,
  296.                full, comps, numComps)
  297. Table    *table;        /* table */
  298. char    *name;        /* what we're looking up */
  299. int    order_pref;    /* which dmn ordering we allow */
  300. char    *chan;        /* routing pointer */
  301. char    *norm;        /* normalised dmn */
  302. char    **subdom;    /* pointer to local tables */
  303. int    nbits;        /* number of non matched subdomains */
  304. int    full;        /* looking for * or exact */
  305. char    *comps[];    /* components of dmn */
  306. int    numComps;    /* number of components */
  307. {
  308.     char *vec[10];
  309.     int    vecp, i;
  310.     int    first = TRUE;
  311.     char    buf[BUFSIZ], pnorm[BUFSIZ];
  312.     char    value[BUFSIZ];
  313.  
  314.     norm[0] = '\0';
  315.     chan[0] = '\0';
  316.     *subdom = NULLCP;
  317.  
  318.     /* fillin * if not full */    
  319.     if (full == TRUE) 
  320.         (void) strcpy(buf, name);
  321.     else if (name != NULLCP)
  322.         (void) sprintf (buf, "*.%s", name);
  323.     else     
  324.         /* default route */
  325.         (void) strcpy(buf, "*");
  326.  
  327.     for (;;) {
  328.         if (tb_k2val (table, buf, value, first) == NOTOK)
  329.             return NOTOK;
  330.  
  331.         first = 0;
  332.         hitTable = TRUE;
  333.  
  334.         /* split into different rules */
  335.         if ((vecp = sstr2arg (value, 10, vec, "|")) < 1)
  336.             return NOTOK;
  337.         
  338.         for (i = 0; i < vecp; i++) {
  339.             switch (check_rule(table, vec[i], buf,
  340.                        pnorm, chan, subdom,
  341.                        nbits, full)) {
  342.                 case OK:
  343.                 reconstruct_dom(norm, pnorm, numComps - nbits,
  344.                         comps, numComps);
  345.                 return OK;
  346.                 case DONE:
  347.                 if (++loopCount > MAXLOOPS) {
  348.                     PP_LOG(LLOG_EXCEPTIONS,
  349.                            ("possible synonym loop '%s' in table '%s'",
  350.                         buf, table->tb_name));
  351.                     return NOTOK;
  352.                 }
  353.                 if (pnorm[0] == '*') {
  354.                     char    *ix;
  355.                     ix = pnorm + strlen("*.");
  356.                     reconstruct_dom(buf, ix, 
  357.                             numComps - nbits,
  358.                             comps, numComps);
  359.                 } else 
  360.                     (void) strcpy(buf, pnorm);
  361.                 if (tb_getdomain_aux(table, buf, 
  362.                              order_pref,
  363.                              chan, norm, 
  364.                              subdom) == OK)
  365.                     return OK;
  366.                 break;
  367.                 default:
  368.                 break;
  369.             }
  370.         }
  371.     }
  372. }
  373.  
  374. /*   */
  375.  
  376. /* check one rule */
  377.  
  378. #define    KEY_NORM 1
  379. #define KEY_KEY    2
  380. #define KEY_NORMKEY    3
  381. #define KEY_LOCAL 4
  382. #define KEY_VALID 5
  383. #define KEY_MAX    6
  384. #define KEY_MIN 7
  385. #define KEY_SYNONYM 8
  386.  
  387. static CMD_TABLE    tbl_domainKeys[] = {
  388.     "norm",        KEY_NORM,
  389.     "mta",        KEY_KEY,
  390.     "norm+mta",    KEY_NORMKEY,
  391.     "mta+norm",    KEY_NORMKEY,
  392.     "local",    KEY_LOCAL,
  393.     "valid",    KEY_VALID,
  394.     "max",        KEY_MAX,
  395.     "min",        KEY_MIN,
  396.     "synonym",    KEY_SYNONYM,
  397.     0,        -1
  398.     };
  399.  
  400. static int check_rule (table, rule, lhs, norm, chan, subdom, nbits, full)
  401. Table    *table;
  402. char    *rule, *lhs,
  403.     *norm, *chan, **subdom;
  404. int    nbits, full;
  405. {
  406.     int    maxsub, minsub;
  407.     int    vecp;
  408.     char    *vec[10], *val;
  409.     int    synon, i, canUse, normSet;
  410.  
  411.     synon = FALSE;
  412.     canUse = FALSE;
  413.     normSet = FALSE;
  414.  
  415.     /* set default upper and lower bounds */
  416.     if (full == TRUE)
  417.         maxsub = minsub = 0;
  418.     else {
  419.         maxsub = NOTOK;
  420.         minsub = 1;
  421.     }
  422.  
  423.     if ((vecp = sstr2arg (rule, 10, vec, " \t")) < 1)
  424.         return NOTOK;
  425.     
  426.     for (i = 0; i < vecp; i++) {
  427.         
  428.         if (!isstr(vec[i]))
  429.             continue;
  430.  
  431.         if ((val = index(vec[i], '=')) != NULLCP)
  432.             *val++ = '\0';
  433.  
  434.         switch (cmd_srch(vec[i], tbl_domainKeys)) {
  435.             case KEY_SYNONYM:
  436.             if (val == NULLCP || *val == '\0') 
  437.                 PP_LOG(LLOG_EXCEPTIONS,
  438.                        ("Illegal format in table '%s' for '%s': No value given with key '%s'",
  439.                     table->tb_name, lhs, vec[i]));
  440.             else {
  441.                 (void) strcpy(norm, val);
  442.                 normSet = TRUE;
  443.                 synon = TRUE;
  444.                 canUse = TRUE;
  445.             }
  446.             break;
  447.                 
  448.             case KEY_NORM:
  449.             case KEY_VALID:
  450.             if (val != NULLCP && *val != '\0') {
  451.                 (void) strcpy(norm, val);
  452.                 normSet = TRUE;
  453.             }
  454.             canUse = TRUE;
  455.             break;
  456.  
  457.             case KEY_NORMKEY:
  458.             if (val != NULLCP && *val != '\0') {
  459.                 (void) strcpy(norm, val);
  460.                 (void) strcpy(chan, val);
  461.             } else {
  462.                 /* use lhs as key */
  463.                 char    *ix;
  464.                 if (lhs[0] == '*'
  465.                     && lhs[1] == '.')
  466.                     ix = &(lhs[2]);
  467.                 else
  468.                     ix = &(lhs[0]);
  469.                 (void) strcpy(norm, ix);
  470.                 (void) strcpy(chan, ix);
  471.             }
  472.             normSet = TRUE;
  473.             canUse = TRUE;
  474.             break;
  475.                 
  476.             case KEY_KEY:
  477.             if (val == NULLCP || *val == '\0') {
  478.                 /* use lhs as key */
  479.                 char    *ix;
  480.                 if (lhs[0] == '*'
  481.                     && lhs[1] == '.')
  482.                     ix = &(lhs[2]);
  483.                 else
  484.                     ix = &(lhs[0]);
  485.                 (void) strcpy(chan, ix);
  486.             } else 
  487.                 (void) strcpy(chan, val);
  488.             canUse = TRUE;
  489.             break;
  490.  
  491.             case KEY_LOCAL:
  492.             if (val == NULLCP || *val == '\0')
  493.                 *subdom = strdup("");
  494.             else
  495.                 *subdom = strdup(val);
  496.             canUse = TRUE;
  497.             break;
  498.                 
  499.             case KEY_MAX:
  500.             if (full == TRUE) {
  501.                 PP_LOG(LLOG_EXCEPTIONS,
  502.                        ("Illegal format in table '%s' for '%s': invalid specification of 'max' with exact match",
  503.                     table->tb_name, lhs));
  504.                 norm[0] = '\0';
  505.                 chan[0] = '\0';
  506.                 *subdom = NULLCP;
  507.                 return NOTOK;
  508.             }
  509.             if (val == NULLCP || *val == '\0')
  510.                 PP_LOG(LLOG_EXCEPTIONS,
  511.                        ("Illegal format in table '%s' for '%s': No value given with key '%s'",
  512.                     table->tb_name, lhs, vec[i]));
  513.             else {
  514.                 if (val[0] == '*')
  515.                     maxsub = NOTOK;
  516.                 else if (isdigit(val[0]))
  517.                     maxsub = atoi(val);
  518.                 else
  519.                     PP_LOG(LLOG_EXCEPTIONS,
  520.                            ("Illegal format in table '%s' for '%s': Non numeric string as value for key '%s'",
  521.                         table->tb_name, lhs, vec[i]));
  522.             }
  523.             break;
  524.  
  525.             case KEY_MIN:
  526.             if (full == TRUE) {
  527.                 PP_LOG(LLOG_EXCEPTIONS,
  528.                        ("Illegal format in table '%s' for '%s': invalid specification of 'min' with exact match",
  529.                     table->tb_name, lhs));
  530.                 norm[0] = '\0';
  531.                 chan[0] = '\0';
  532.                 *subdom = NULLCP;
  533.                 return NOTOK;
  534.             }
  535.             if (val == NULLCP || *val == '\0')
  536.                 PP_LOG(LLOG_EXCEPTIONS,
  537.                        ("Illegal format in table '%s' for '%s': No value given with key '%s'",
  538.                     table->tb_name, lhs, vec[i]));
  539.             else {
  540.                 if (val[0] == '*')
  541.                     minsub = NOTOK;
  542.                 else if (isdigit(val[0]))
  543.                     minsub = atoi(val);
  544.                 else
  545.                     PP_LOG(LLOG_EXCEPTIONS,
  546.                            ("Illegal format in table '%s' for '%s': Non numeric string as value for key '%s'",
  547.                         table->tb_name, lhs, vec[i]));
  548.             }
  549.             break;
  550.                 
  551.             default:
  552.             PP_LOG(LLOG_EXCEPTIONS,
  553.                    ("Unknown key '%s' in table '%s' for entry '%s'",
  554.                 vec[i], table->tb_name, lhs));
  555.             break;
  556.         }
  557.         
  558.     }
  559.     if (canUse == FALSE) 
  560.         PP_LOG(LLOG_EXCEPTIONS,
  561.                ("entry '%s' in table '%s' has no useful keys (mta,key,local)",
  562.             lhs, table->tb_name));
  563.     else if ((maxsub == NOTOK || maxsub >= nbits)
  564.          && (minsub == NOTOK || minsub <= nbits)) {
  565.  
  566.         if (normSet == FALSE) {
  567.             /* use lhs */
  568.             char    *ix;
  569.             if (lhs[0] == '*'
  570.                 && lhs[1] == '.')
  571.                 ix = &(lhs[2]);
  572.             else
  573.                 ix = &(lhs[0]);
  574.             (void) strcpy(norm, ix);
  575.         }
  576.         
  577.         if (synon == TRUE)
  578.             return DONE;
  579.         else
  580.             return OK;
  581.     }
  582.     return NOTOK;
  583. }
  584.  
  585. /*   */
  586.  
  587. static int fillin_results(innorm, inchan,
  588.               outnorm, outchan)
  589. char    *innorm, *inchan,
  590.     *outnorm, *outchan;
  591. {
  592.     if (outnorm) 
  593.         (void) strcpy(outnorm, innorm);
  594.     if (outchan) {
  595.         if (inchan[0] != '\0')
  596.             (void) strcpy(outchan, inchan);
  597.         else
  598.             /* no norm given */
  599.             outchan[0] = '\0';
  600.     }
  601. }
  602.  
  603. /*   */
  604.  
  605. /* reconstruct full domain by adding unmatched components to norm'd */
  606. reconstruct_dom(into, norm, hits, comps, numComps)
  607. char    *into;
  608. char    *norm;
  609. int    hits;
  610. char    *comps[];
  611. int    numComps;
  612. {
  613.     int    i;
  614.  
  615.     into[0] = '\0';
  616.  
  617.     for (i = 0; i < numComps - hits; i++) {
  618.         if (into[0] != '\0')
  619.             (void) strcat (into, ".");
  620.         (void) strcat (into, comps[i]);
  621.     }
  622.     if (into[0] != '\0')
  623.         (void) strcat (into, ".");
  624.     (void) strcat(into, norm);
  625. }
  626.  
  627.  
  628. /*   */
  629.  
  630. char *bits2str (ptr, sep)
  631. register char    **ptr;
  632. register char    sep;
  633. {
  634.     char        silly[2];
  635.     static char    tbuf[LINESIZE];
  636.  
  637.     PP_DBG (("Lib/table/bits2str ('%s')", *ptr));
  638.  
  639.     silly[0] = sep;
  640.     silly[1] = '\0';
  641.  
  642.     (void) strcpy (tbuf, *ptr++);
  643.  
  644.     while (*ptr != NULLCP) {
  645.         (void) strcat (tbuf, silly);
  646.         (void) strcat (tbuf, *ptr++);
  647.     }
  648.  
  649.     return (tbuf);
  650. }
  651.  
  652.  
  653. /*
  654. This routine takes a hostname in a string and returns two
  655. arrays of pointers into the string. The string is "chopped up"
  656. with '\0'. The number of elements in the hostname is returned.
  657. */
  658.  
  659. int str2bits (str, sep, forward, reverse)
  660. register char    *str;
  661. register char    sep;
  662. register char    **forward;
  663. register char    **reverse;
  664. {
  665.     register int    i,
  666.             j;
  667.  
  668.     PP_DBG (("Lib/table/str2bits(%s)", str));
  669.  
  670.     *forward++ = str;
  671.     for (i = 1; *str != '\0'; str++)
  672.         if (*str == sep) {
  673.             *str = '\0';
  674.             *forward++ = &str[1];
  675.             i++;
  676.         }
  677.  
  678.     *forward-- = NULLCP;
  679.  
  680.     if (reverse != NULL) {
  681.         for (j = 0; j < i; j++)
  682.             *reverse++ = *forward--;
  683.         *reverse = NULLCP;
  684.     }
  685.  
  686.     return (i);
  687. }
  688.