home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / gnunet10.zip / source / libtelnet / auth.c < prev    next >
C/C++ Source or Header  |  1996-02-12  |  15KB  |  672 lines

  1. /*-
  2.  * Copyright (c) 1991, 1993
  3.  *    The Regents of the University of California.  All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice, this list of conditions and the following disclaimer in the
  12.  *    documentation and/or other materials provided with the distribution.
  13.  * 3. All advertising materials mentioning features or use of this software
  14.  *    must display the following acknowledgement:
  15.  *    This product includes software developed by the University of
  16.  *    California, Berkeley and its contributors.
  17.  * 4. Neither the name of the University nor the names of its contributors
  18.  *    may be used to endorse or promote products derived from this software
  19.  *    without specific prior written permission.
  20.  *
  21.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  22.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  25.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  27.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  28.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  30.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31.  * SUCH DAMAGE.
  32.  */
  33.  
  34. #ifndef lint
  35. static char sccsid[] = "@(#)auth.c    8.3 (Berkeley) 5/30/95";
  36. #endif /* not lint */
  37.  
  38. /*
  39.  * Copyright (C) 1990 by the Massachusetts Institute of Technology
  40.  *
  41.  * Export of this software from the United States of America is assumed
  42.  * to require a specific license from the United States Government.
  43.  * It is the responsibility of any person or organization contemplating
  44.  * export to obtain such a license before exporting.
  45.  *
  46.  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
  47.  * distribute this software and its documentation for any purpose and
  48.  * without fee is hereby granted, provided that the above copyright
  49.  * notice appear in all copies and that both that copyright notice and
  50.  * this permission notice appear in supporting documentation, and that
  51.  * the name of M.I.T. not be used in advertising or publicity pertaining
  52.  * to distribution of the software without specific, written prior
  53.  * permission.  M.I.T. makes no representations about the suitability of
  54.  * this software for any purpose.  It is provided "as is" without express
  55.  * or implied warranty.
  56.  */
  57.  
  58.  
  59. #if    defined(AUTHENTICATION)
  60. #include <stdio.h>
  61. #include <sys/types.h>
  62. #include <signal.h>
  63. #define    AUTH_NAMES
  64. #include <arpa/telnet.h>
  65. #ifdef    __STDC__
  66. #include <stdlib.h>
  67. #endif
  68. #ifdef    NO_STRING_H
  69. #include <strings.h>
  70. #else
  71. #include <string.h>
  72. #endif
  73.  
  74. #include "encrypt.h"
  75. #include "auth.h"
  76. #include "misc-proto.h"
  77. #include "auth-proto.h"
  78.  
  79. #define    typemask(x)        (1<<((x)-1))
  80.  
  81. #ifdef    KRB4_ENCPWD
  82. extern krb4encpwd_init();
  83. extern krb4encpwd_send();
  84. extern krb4encpwd_is();
  85. extern krb4encpwd_reply();
  86. extern krb4encpwd_status();
  87. extern krb4encpwd_printsub();
  88. #endif
  89.  
  90. #ifdef    RSA_ENCPWD
  91. extern rsaencpwd_init();
  92. extern rsaencpwd_send();
  93. extern rsaencpwd_is();
  94. extern rsaencpwd_reply();
  95. extern rsaencpwd_status();
  96. extern rsaencpwd_printsub();
  97. #endif
  98.  
  99. int auth_debug_mode = 0;
  100. static     char    *Name = "Noname";
  101. static    int    Server = 0;
  102. static    Authenticator    *authenticated = 0;
  103. static    int    authenticating = 0;
  104. static    int    validuser = 0;
  105. static    unsigned char    _auth_send_data[256];
  106. static    unsigned char    *auth_send_data;
  107. static    int    auth_send_cnt = 0;
  108.  
  109. /*
  110.  * Authentication types supported.  Plese note that these are stored
  111.  * in priority order, i.e. try the first one first.
  112.  */
  113. Authenticator authenticators[] = {
  114. #ifdef    SPX
  115.     { AUTHTYPE_SPX, AUTH_WHO_CLIENT|AUTH_HOW_MUTUAL,
  116.                 spx_init,
  117.                 spx_send,
  118.                 spx_is,
  119.                 spx_reply,
  120.                 spx_status,
  121.                 spx_printsub },
  122.     { AUTHTYPE_SPX, AUTH_WHO_CLIENT|AUTH_HOW_ONE_WAY,
  123.                 spx_init,
  124.                 spx_send,
  125.                 spx_is,
  126.                 spx_reply,
  127.                 spx_status,
  128.                 spx_printsub },
  129. #endif
  130. #ifdef    KRB5
  131. # ifdef    ENCRYPTION
  132.     { AUTHTYPE_KERBEROS_V5, AUTH_WHO_CLIENT|AUTH_HOW_MUTUAL,
  133.                 kerberos5_init,
  134.                 kerberos5_send,
  135.                 kerberos5_is,
  136.                 kerberos5_reply,
  137.                 kerberos5_status,
  138.                 kerberos5_printsub },
  139. # endif    /* ENCRYPTION */
  140.     { AUTHTYPE_KERBEROS_V5, AUTH_WHO_CLIENT|AUTH_HOW_ONE_WAY,
  141.                 kerberos5_init,
  142.                 kerberos5_send,
  143.                 kerberos5_is,
  144.                 kerberos5_reply,
  145.                 kerberos5_status,
  146.                 kerberos5_printsub },
  147. #endif
  148. #ifdef    KRB4
  149. # ifdef ENCRYPTION
  150.     { AUTHTYPE_KERBEROS_V4, AUTH_WHO_CLIENT|AUTH_HOW_MUTUAL,
  151.                 kerberos4_init,
  152.                 kerberos4_send,
  153.                 kerberos4_is,
  154.                 kerberos4_reply,
  155.                 kerberos4_status,
  156.                 kerberos4_printsub },
  157. # endif    /* ENCRYPTION */
  158.     { AUTHTYPE_KERBEROS_V4, AUTH_WHO_CLIENT|AUTH_HOW_ONE_WAY,
  159.                 kerberos4_init,
  160.                 kerberos4_send,
  161.                 kerberos4_is,
  162.                 kerberos4_reply,
  163.                 kerberos4_status,
  164.                 kerberos4_printsub },
  165. #endif
  166. #ifdef    KRB4_ENCPWD
  167.     { AUTHTYPE_KRB4_ENCPWD, AUTH_WHO_CLIENT|AUTH_HOW_MUTUAL,
  168.                 krb4encpwd_init,
  169.                 krb4encpwd_send,
  170.                 krb4encpwd_is,
  171.                 krb4encpwd_reply,
  172.                 krb4encpwd_status,
  173.                 krb4encpwd_printsub },
  174. #endif
  175. #ifdef    RSA_ENCPWD
  176.     { AUTHTYPE_RSA_ENCPWD, AUTH_WHO_CLIENT|AUTH_HOW_ONE_WAY,
  177.                 rsaencpwd_init,
  178.                 rsaencpwd_send,
  179.                 rsaencpwd_is,
  180.                 rsaencpwd_reply,
  181.                 rsaencpwd_status,
  182.                 rsaencpwd_printsub },
  183. #endif
  184.     { 0, },
  185. };
  186.  
  187. static Authenticator NoAuth = { 0 };
  188.  
  189. static int    i_support = 0;
  190. static int    i_wont_support = 0;
  191.  
  192.     Authenticator *
  193. findauthenticator(type, way)
  194.     int type;
  195.     int way;
  196. {
  197.     Authenticator *ap = authenticators;
  198.  
  199.     while (ap->type && (ap->type != type || ap->way != way))
  200.         ++ap;
  201.     return(ap->type ? ap : 0);
  202. }
  203.  
  204.     void
  205. auth_init(name, server)
  206.     char *name;
  207.     int server;
  208. {
  209.     Authenticator *ap = authenticators;
  210.  
  211.     Server = server;
  212.     Name = name;
  213.  
  214.     i_support = 0;
  215.     authenticated = 0;
  216.     authenticating = 0;
  217.     while (ap->type) {
  218.         if (!ap->init || (*ap->init)(ap, server)) {
  219.             i_support |= typemask(ap->type);
  220.             if (auth_debug_mode)
  221.                 printf(">>>%s: I support auth type %d %d\r\n",
  222.                     Name,
  223.                     ap->type, ap->way);
  224.         }
  225.         else if (auth_debug_mode)
  226.             printf(">>>%s: Init failed: auth type %d %d\r\n",
  227.                 Name, ap->type, ap->way);
  228.         ++ap;
  229.     }
  230. }
  231.  
  232.     void
  233. auth_disable_name(name)
  234.     char *name;
  235. {
  236.     int x;
  237.     for (x = 0; x < AUTHTYPE_CNT; ++x) {
  238.         if (!strcasecmp(name, AUTHTYPE_NAME(x))) {
  239.             i_wont_support |= typemask(x);
  240.             break;
  241.         }
  242.     }
  243. }
  244.  
  245.     int
  246. getauthmask(type, maskp)
  247.     char *type;
  248.     int *maskp;
  249. {
  250.     register int x;
  251.  
  252.     if (!strcasecmp(type, AUTHTYPE_NAME(0))) {
  253.         *maskp = -1;
  254.         return(1);
  255.     }
  256.  
  257.     for (x = 1; x < AUTHTYPE_CNT; ++x) {
  258.         if (!strcasecmp(type, AUTHTYPE_NAME(x))) {
  259.             *maskp = typemask(x);
  260.             return(1);
  261.         }
  262.     }
  263.     return(0);
  264. }
  265.  
  266.     int
  267. auth_enable(type)
  268.     char *type;
  269. {
  270.     return(auth_onoff(type, 1));
  271. }
  272.  
  273.     int
  274. auth_disable(type)
  275.     char *type;
  276. {
  277.     return(auth_onoff(type, 0));
  278. }
  279.  
  280.     int
  281. auth_onoff(type, on)
  282.     char *type;
  283.     int on;
  284. {
  285.     int i, mask = -1;
  286.     Authenticator *ap;
  287.  
  288.     if (!strcasecmp(type, "?") || !strcasecmp(type, "help")) {
  289.                 printf("auth %s 'type'\n", on ? "enable" : "disable");
  290.         printf("Where 'type' is one of:\n");
  291.         printf("\t%s\n", AUTHTYPE_NAME(0));
  292.         mask = 0;
  293.         for (ap = authenticators; ap->type; ap++) {
  294.             if ((mask & (i = typemask(ap->type))) != 0)
  295.                 continue;
  296.             mask |= i;
  297.             printf("\t%s\n", AUTHTYPE_NAME(ap->type));
  298.         }
  299.         return(0);
  300.     }
  301.  
  302.     if (!getauthmask(type, &mask)) {
  303.         printf("%s: invalid authentication type\n", type);
  304.         return(0);
  305.     }
  306.     if (on)
  307.         i_wont_support &= ~mask;
  308.     else
  309.         i_wont_support |= mask;
  310.     return(1);
  311. }
  312.  
  313.     int
  314. auth_togdebug(on)
  315.     int on;
  316. {
  317.     if (on < 0)
  318.         auth_debug_mode ^= 1;
  319.     else
  320.         auth_debug_mode = on;
  321.     printf("auth debugging %s\n", auth_debug_mode ? "enabled" : "disabled");
  322.     return(1);
  323. }
  324.  
  325.     int
  326. auth_status()
  327. {
  328.     Authenticator *ap;
  329.     int i, mask;
  330.  
  331.     if (i_wont_support == -1)
  332.         printf("Authentication disabled\n");
  333.     else
  334.         printf("Authentication enabled\n");
  335.  
  336.     mask = 0;
  337.     for (ap = authenticators; ap->type; ap++) {
  338.         if ((mask & (i = typemask(ap->type))) != 0)
  339.             continue;
  340.         mask |= i;
  341.         printf("%s: %s\n", AUTHTYPE_NAME(ap->type),
  342.             (i_wont_support & typemask(ap->type)) ?
  343.                     "disabled" : "enabled");
  344.     }
  345.     return(1);
  346. }
  347.  
  348. /*
  349.  * This routine is called by the server to start authentication
  350.  * negotiation.
  351.  */
  352.     void
  353. auth_request()
  354. {
  355.     static unsigned char str_request[64] = { IAC, SB,
  356.                          TELOPT_AUTHENTICATION,
  357.                          TELQUAL_SEND, };
  358.     Authenticator *ap = authenticators;
  359.     unsigned char *e = str_request + 4;
  360.  
  361.     if (!authenticating) {
  362.         authenticating = 1;
  363.         while (ap->type) {
  364.             if (i_support & ~i_wont_support & typemask(ap->type)) {
  365.                 if (auth_debug_mode) {
  366.                     printf(">>>%s: Sending type %d %d\r\n",
  367.                         Name, ap->type, ap->way);
  368.                 }
  369.                 *e++ = ap->type;
  370.                 *e++ = ap->way;
  371.             }
  372.             ++ap;
  373.         }
  374.         *e++ = IAC;
  375.         *e++ = SE;
  376.         net_write(str_request, e - str_request);
  377.         printsub('>', &str_request[2], e - str_request - 2);
  378.     }
  379. }
  380.  
  381. /*
  382.  * This is called when an AUTH SEND is received.
  383.  * It should never arrive on the server side (as only the server can
  384.  * send an AUTH SEND).
  385.  * You should probably respond to it if you can...
  386.  *
  387.  * If you want to respond to the types out of order (i.e. even
  388.  * if he sends  LOGIN KERBEROS and you support both, you respond
  389.  * with KERBEROS instead of LOGIN (which is against what the
  390.  * protocol says)) you will have to hack this code...
  391.  */
  392.     void
  393. auth_send(data, cnt)
  394.     unsigned char *data;
  395.     int cnt;
  396. {
  397.     Authenticator *ap;
  398.     static unsigned char str_none[] = { IAC, SB, TELOPT_AUTHENTICATION,
  399.                         TELQUAL_IS, AUTHTYPE_NULL, 0,
  400.                         IAC, SE };
  401.     if (Server) {
  402.         if (auth_debug_mode) {
  403.             printf(">>>%s: auth_send called!\r\n", Name);
  404.         }
  405.         return;
  406.     }
  407.  
  408.     if (auth_debug_mode) {
  409.         printf(">>>%s: auth_send got:", Name);
  410.         printd(data, cnt); printf("\r\n");
  411.     }
  412.  
  413.     /*
  414.      * Save the data, if it is new, so that we can continue looking
  415.      * at it if the authorization we try doesn't work
  416.      */
  417.     if (data < _auth_send_data ||
  418.         data > _auth_send_data + sizeof(_auth_send_data)) {
  419.         auth_send_cnt = cnt > sizeof(_auth_send_data)
  420.                     ? sizeof(_auth_send_data)
  421.                     : cnt;
  422.         memmove((void *)_auth_send_data, (void *)data, auth_send_cnt);
  423.         auth_send_data = _auth_send_data;
  424.     } else {
  425.         /*
  426.          * This is probably a no-op, but we just make sure
  427.          */
  428.         auth_send_data = data;
  429.         auth_send_cnt = cnt;
  430.     }
  431.     while ((auth_send_cnt -= 2) >= 0) {
  432.         if (auth_debug_mode)
  433.             printf(">>>%s: He supports %d\r\n",
  434.                 Name, *auth_send_data);
  435.         if ((i_support & ~i_wont_support) & typemask(*auth_send_data)) {
  436.             ap = findauthenticator(auth_send_data[0],
  437.                            auth_send_data[1]);
  438.             if (ap && ap->send) {
  439.                 if (auth_debug_mode)
  440.                     printf(">>>%s: Trying %d %d\r\n",
  441.                         Name, auth_send_data[0],
  442.                             auth_send_data[1]);
  443.                 if ((*ap->send)(ap)) {
  444.                     /*
  445.                      * Okay, we found one we like
  446.                      * and did it.
  447.                      * we can go home now.
  448.                      */
  449.                     if (auth_debug_mode)
  450.                         printf(">>>%s: Using type %d\r\n",
  451.                             Name, *auth_send_data);
  452.                     auth_send_data += 2;
  453.                     return;
  454.                 }
  455.             }
  456.             /* else
  457.              *    just continue on and look for the
  458.              *    next one if we didn't do anything.
  459.              */
  460.         }
  461.         auth_send_data += 2;
  462.     }
  463.     net_write(str_none, sizeof(str_none));
  464.     printsub('>', &str_none[2], sizeof(str_none) - 2);
  465.     if (auth_debug_mode)
  466.         printf(">>>%s: Sent failure message\r\n", Name);
  467.     auth_finished(0, AUTH_REJECT);
  468. #ifdef KANNAN
  469.     /*
  470.      *  We requested strong authentication, however no mechanisms worked.
  471.      *  Therefore, exit on client end.
  472.      */
  473.     printf("Unable to securely authenticate user ... exit\n"); 
  474.     exit(0);
  475. #endif /* KANNAN */
  476. }
  477.  
  478.     void
  479. auth_send_retry()
  480. {
  481.     /*
  482.      * if auth_send_cnt <= 0 then auth_send will end up rejecting
  483.      * the authentication and informing the other side of this.
  484.      */
  485.     auth_send(auth_send_data, auth_send_cnt);
  486. }
  487.  
  488.     void
  489. auth_is(data, cnt)
  490.     unsigned char *data;
  491.     int cnt;
  492. {
  493.     Authenticator *ap;
  494.  
  495.     if (cnt < 2)
  496.         return;
  497.  
  498.     if (data[0] == AUTHTYPE_NULL) {
  499.         auth_finished(0, AUTH_REJECT);
  500.         return;
  501.     }
  502.  
  503.     if (ap = findauthenticator(data[0], data[1])) {
  504.         if (ap->is)
  505.             (*ap->is)(ap, data+2, cnt-2);
  506.     } else if (auth_debug_mode)
  507.         printf(">>>%s: Invalid authentication in IS: %d\r\n",
  508.             Name, *data);
  509. }
  510.  
  511.     void
  512. auth_reply(data, cnt)
  513.     unsigned char *data;
  514.     int cnt;
  515. {
  516.     Authenticator *ap;
  517.  
  518.     if (cnt < 2)
  519.         return;
  520.  
  521.     if (ap = findauthenticator(data[0], data[1])) {
  522.         if (ap->reply)
  523.             (*ap->reply)(ap, data+2, cnt-2);
  524.     } else if (auth_debug_mode)
  525.         printf(">>>%s: Invalid authentication in SEND: %d\r\n",
  526.             Name, *data);
  527. }
  528.  
  529.     void
  530. auth_name(data, cnt)
  531.     unsigned char *data;
  532.     int cnt;
  533. {
  534.     Authenticator *ap;
  535.     unsigned char savename[256];
  536.  
  537.     if (cnt < 1) {
  538.         if (auth_debug_mode)
  539.             printf(">>>%s: Empty name in NAME\r\n", Name);
  540.         return;
  541.     }
  542.     if (cnt > sizeof(savename) - 1) {
  543.         if (auth_debug_mode)
  544.             printf(">>>%s: Name in NAME (%d) exceeds %d length\r\n",
  545.                     Name, cnt, sizeof(savename)-1);
  546.         return;
  547.     }
  548.     memmove((void *)savename, (void *)data, cnt);
  549.     savename[cnt] = '\0';    /* Null terminate */
  550.     if (auth_debug_mode)
  551.         printf(">>>%s: Got NAME [%s]\r\n", Name, savename);
  552.     auth_encrypt_user(savename);
  553. }
  554.  
  555.     int
  556. auth_sendname(cp, len)
  557.     unsigned char *cp;
  558.     int len;
  559. {
  560.     static unsigned char str_request[256+6]
  561.             = { IAC, SB, TELOPT_AUTHENTICATION, TELQUAL_NAME, };
  562.     register unsigned char *e = str_request + 4;
  563.     register unsigned char *ee = &str_request[sizeof(str_request)-2];
  564.  
  565.     while (--len >= 0) {
  566.         if ((*e++ = *cp++) == IAC)
  567.             *e++ = IAC;
  568.         if (e >= ee)
  569.             return(0);
  570.     }
  571.     *e++ = IAC;
  572.     *e++ = SE;
  573.     net_write(str_request, e - str_request);
  574.     printsub('>', &str_request[2], e - &str_request[2]);
  575.     return(1);
  576. }
  577.  
  578.     void
  579. auth_finished(ap, result)
  580.     Authenticator *ap;
  581.     int result;
  582. {
  583.     if (!(authenticated = ap))
  584.         authenticated = &NoAuth;
  585.     validuser = result;
  586. }
  587.  
  588.     /* ARGSUSED */
  589.     static void
  590. auth_intr(sig)
  591.     int sig;
  592. {
  593.     auth_finished(0, AUTH_REJECT);
  594. }
  595.  
  596.     int
  597. auth_wait(name)
  598.     char *name;
  599. {
  600.     if (auth_debug_mode)
  601.         printf(">>>%s: in auth_wait.\r\n", Name);
  602.  
  603.     if (Server && !authenticating)
  604.         return(0);
  605.  
  606.     (void) signal(SIGALRM, auth_intr);
  607.     alarm(30);
  608.     while (!authenticated)
  609.         if (telnet_spin())
  610.             break;
  611.     alarm(0);
  612.     (void) signal(SIGALRM, SIG_DFL);
  613.  
  614.     /*
  615.      * Now check to see if the user is valid or not
  616.      */
  617.     if (!authenticated || authenticated == &NoAuth)
  618.         return(AUTH_REJECT);
  619.  
  620.     if (validuser == AUTH_VALID)
  621.         validuser = AUTH_USER;
  622.  
  623.     if (authenticated->status)
  624.         validuser = (*authenticated->status)(authenticated,
  625.                              name, validuser);
  626.     return(validuser);
  627. }
  628.  
  629.     void
  630. auth_debug(mode)
  631.     int mode;
  632. {
  633.     auth_debug_mode = mode;
  634. }
  635.  
  636.     void
  637. auth_printsub(data, cnt, buf, buflen)
  638.     unsigned char *data, *buf;
  639.     int cnt, buflen;
  640. {
  641.     Authenticator *ap;
  642.  
  643.     if ((ap = findauthenticator(data[1], data[2])) && ap->printsub)
  644.         (*ap->printsub)(data, cnt, buf, buflen);
  645.     else
  646.         auth_gen_printsub(data, cnt, buf, buflen);
  647. }
  648.  
  649.     void
  650. auth_gen_printsub(data, cnt, buf, buflen)
  651.     unsigned char *data, *buf;
  652.     int cnt, buflen;
  653. {
  654.     register unsigned char *cp;
  655.     unsigned char tbuf[16];
  656.  
  657.     cnt -= 3;
  658.     data += 3;
  659.     buf[buflen-1] = '\0';
  660.     buf[buflen-2] = '*';
  661.     buflen -= 2;
  662.     for (; cnt > 0; cnt--, data++) {
  663.         sprintf((char *)tbuf, " %d", *data);
  664.         for (cp = tbuf; *cp && buflen > 0; --buflen)
  665.             *buf++ = *cp++;
  666.         if (buflen <= 0)
  667.             return;
  668.     }
  669.     *buf = '\0';
  670. }
  671. #endif
  672.