]> git.sur5r.net Git - openldap/blob - servers/ldapd/main.c
Enable browsing info in MSVC debugging configurations.
[openldap] / servers / ldapd / main.c
1 /*
2  * Copyright (c) 1990-1996 Regents of the University of Michigan.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that this notice is preserved and that due credit is given
7  * to the University of Michigan at Ann Arbor. The name of the University
8  * may not be used to endorse or promote products derived from this
9  * software without specific prior written permission. This software
10  * is provided ``as is'' without express or implied warranty.
11  */
12 /*
13  * Some code fragments to run from inetd stolen from the University
14  * of Minnesota gopher distribution, which had this copyright on it:
15  *
16  * Part of the Internet Gopher program, copyright (C) 1991
17  * University of Minnesota Microcomputer Workstation and Networks Center
18  */
19
20 #include "portable.h"
21
22 #include <stdio.h>
23
24 #include <ac/signal.h>
25 #include <ac/socket.h>
26 #include <ac/string.h>
27 #include <ac/syslog.h>
28 #include <ac/time.h>
29 #include <ac/unistd.h>
30 #include <ac/wait.h>
31
32 #ifdef LDAP_PROCTITLE
33 #include <ac/setproctitle.h>
34 #endif
35
36 #include <quipu/commonarg.h>
37 #include <quipu/ds_error.h>
38
39 #include "lber.h"
40 #include "../../libraries/liblber/lber-int.h"   /* get struct sockbuf */
41 #include "ldap.h"
42 #include "common.h"
43 #include "lutil.h"              /* Get lutil_detach() */
44
45 #ifdef HAVE_TCPD
46 #include <tcpd.h>
47
48 int allow_severity = LOG_INFO;
49 int deny_severity = LOG_NOTICE;
50 #endif /* TCP_WRAPPERS */
51
52 static int      set_socket( int port, int udp );
53 static void     do_queries( int clientsock, int udp );
54 static RETSIGTYPE wait4child( int sig );
55 #ifdef LDAP_CONNECTIONLESS
56 static int      udp_init( int port, int createsocket );
57 #endif
58
59 #ifdef LDAP_DEBUG
60 int     ldap_debug;
61 #endif
62 int     version;
63 #ifdef LDAP_COMPAT
64 int     ldap_compat;
65 #endif
66 int     dosyslog;
67 int     do_tcp = 1;
68 #ifdef LDAP_CONNECTIONLESS
69 int     do_udp = 0;
70 #endif
71 int     idletime = DEFAULT_TIMEOUT;
72 int     referral_connection_timeout = DEFAULT_REFERRAL_TIMEOUT;
73 struct timeval  conn_start_tv;
74 #ifdef HAVE_KERBEROS
75 char    *krb_ldap_service = "ldapserver";
76 char    *krb_x500_service = "x500dsa";
77 char    *krb_x500_instance;
78 char    *krb_x500_nonce;
79 char    *kerberos_keyfile;
80 #endif
81
82 int     dtblsize;
83 int     RunFromInetd = 0;
84
85 static void
86 usage( char *name )
87 {
88         fprintf( stderr, "usage: %s [-d debuglvl] [-p port] [-l] [-c dsa] [-r referraltimeout]", name );
89 #ifdef LDAP_CONNECTIONLESS
90         fprintf( stderr, " [ -U | -t timeout ]" );
91 #else
92         fprintf( stderr, " [ -t timeout ]" );
93 #endif
94         fprintf( stderr, " [-I]" );
95 #ifdef HAVE_KERBEROS
96         fprintf( stderr, " [-i dsainstance]" );
97 #endif
98         fprintf( stderr, "\n" );
99 }
100
101 int
102 main( int argc, char **argv )
103 {
104         int                     tcps, ns;
105 #ifdef LDAP_CONNECTIONLESS
106         int                     udps;
107 #endif
108         int                     myport = LDAP_PORT;
109         int                     i, pid, socktype;
110         char                    *myname;
111         fd_set                  readfds;
112         struct hostent          *hp;
113         struct sockaddr_in      from;
114         int                     len;
115         int                     dsapargc;
116         char                    **dsapargv;
117 #ifdef LDAP_PROCTITLE
118         char                    title[80];
119 #endif
120
121 #ifdef VMS
122         /* Pick up socket from inetd-type server on VMS */
123         if ( (ns = socket_from_server( NULL )) > 0 )
124                 RunFromInetd = 1;
125 #else
126         /* Socket from inetd is usually 0 */
127         ns = 0;
128 #endif
129
130         /* for dsap_init */
131         if ( (dsapargv = (char **) malloc( 4 * sizeof(char *) )) == NULL ) {
132                 perror( "malloc" );
133                 exit( 1 );
134         }
135         dsapargv[0] = argv[0];
136         dsapargv[1] = 0;
137         dsapargv[2] = 0;
138         dsapargv[3] = 0;
139         dsapargc = 1;
140 #ifdef HAVE_KERBEROS
141         kerberos_keyfile = "";
142 #endif
143
144         /* process command line arguments */
145         while ( (i = getopt( argc, argv, "d:lp:f:i:c:r:t:IuU" )) != EOF ) {
146                 switch ( i ) {
147                 case 'c':       /* specify dsa to contact */
148                         dsapargv[1] = "-call";
149                         dsapargv[2] = strdup( optarg );
150                         dsapargc = 3;
151                         break;
152
153                 case 'd':       /* turn on debugging */
154 #ifdef LDAP_DEBUG
155                         ldap_debug = atoi( optarg );
156                         if ( ldap_debug & LDAP_DEBUG_PACKETS )
157                                 lber_int_debug = ldap_debug;
158 #else
159                         fprintf( stderr, "Not compiled with -DLDAP_DEBUG!\n" );
160 #endif
161                         break;
162
163                 case 'l':       /* do syslogging */
164                         dosyslog = 1;
165                         break;
166
167                 case 'p':       /* specify port number */
168                         myport = atoi( optarg );
169                         break;
170
171                 case 'r':       /* timeout for referral connections */
172                         referral_connection_timeout = atoi( optarg );
173                         break;
174
175                 case 't':       /* timeout for idle connections */
176                         idletime = atoi( optarg );
177                         break;
178
179 #ifdef HAVE_KERBEROS
180                 case 'f':       /* kerberos key file */
181                         kerberos_keyfile = strdup( optarg );
182                         break;
183
184                 case 'i':       /* x500 dsa kerberos instance */
185                         if ( krb_x500_instance != NULL )
186                                 free( krb_x500_instance );
187                         krb_x500_instance = strdup( optarg );
188                         break;
189 #endif
190
191                 case 'I':       /* Run from inetd */
192                         RunFromInetd = 1;
193                         break;
194
195 #ifdef LDAP_CONNECTIONLESS
196                 case 'U':       /* UDP only (no TCP) */
197                         do_tcp = 0;
198                         do_udp = 1;
199                         break;
200
201 #ifdef NOTYET
202                 case 'u':       /* allow UDP requests (CLDAP) */
203                         do_udp = 1;
204                         break;
205 #endif /* NOTYET */
206
207 #endif /* LDAP_CONNECTIONLESS */
208
209                 default:
210                         usage( argv[0] );
211                         exit( 1 );
212                 }
213         }
214
215         if ( optind < argc ) {
216                 usage( argv[ 0 ] );
217                 exit( 1 );
218         }
219
220 #ifdef LDAP_CONNECTIONLESS
221         if ( do_udp && !do_tcp && idletime != DEFAULT_TIMEOUT ) {
222                 usage( argv[ 0 ] );
223                 exit( 1 );
224         }
225 #endif
226
227         Debug( LDAP_DEBUG_TRACE, "%s", Versionstr, 0, 0 );
228
229 #ifdef HAVE_SYSCONF
230         dtblsize = sysconf( _SC_OPEN_MAX );
231 #elif HAVE_GETDTABLESIZE
232         dtblsize = getdtablesize();
233 #else
234         dtblsize = FD_SETSIZE;
235 #endif
236
237 #ifdef FD_SETSIZE
238         if( dtblsize > FD_SETSIZE ) {
239                 dtblsize = FD_SETSIZE;
240         }
241 #endif /* FD_SETSIZE */
242
243 #if defined(LDAP_PROCTITLE) && !defined( HAVE_SETPROCTITLE )
244         /* for setproctitle */
245         Argv = argv;
246         Argc = argc;
247 #endif
248
249         if ( (myname = strrchr( argv[0], '/' )) == NULL )
250                 myname = strdup( argv[0] );
251         else
252                 myname = strdup( myname + 1 );
253
254         /* 
255          * detach from the terminal if stderr is redirected or no
256          * debugging is wanted, and then arrange to reap children
257          * that have exited
258          */
259         if (!RunFromInetd) {
260 #ifdef LDAP_PROCTITLE
261                 setproctitle( "initializing" );
262 #endif
263 #ifndef VMS
264 #  ifdef LDAP_DEBUG
265                 lutil_detach( ldap_debug, 1 );
266 #  else
267                 lutil_detach( 0, 1 );
268 #  endif
269 #endif
270                 (void) SIGNAL( SIGCHLD, wait4child );
271                 (void) SIGNAL( SIGINT, log_and_exit );
272         }
273
274         /* 
275          * set up syslogging (if desired)
276          */
277         if ( dosyslog ) {
278 #ifdef LOG_LOCAL4
279                 openlog( myname, OPENLOG_OPTIONS, LOG_LOCAL4 );
280 #else
281                 openlog( myname, OPENLOG_OPTIONS );
282 #endif
283         }
284
285         /* 
286          * load the syntax handlers, oidtables, and initialize some stuff,
287          * then start listening
288          */
289
290         (void) quipu_syntaxes();
291 #ifdef LDAP_USE_PP
292         (void) pp_quipu_init( argv[0] );
293 #endif
294 #if ISODEPACKAGE == IC
295 #if ICRELEASE > 2
296         dsa_operation_syntaxes();
297 #endif
298 #endif
299         (void) dsap_init( &dsapargc, &dsapargv );
300         (void) get_syntaxes();
301         if (RunFromInetd) {
302                 len = sizeof( socktype );
303                 getsockopt( ns, SOL_SOCKET, SO_TYPE, (char *)&socktype, &len );
304                 if ( socktype == SOCK_DGRAM ) {
305 #ifdef LDAP_CONNECTIONLESS
306                         Debug( LDAP_DEBUG_ARGS,
307                             "CLDAP request from unknown (%s)\n",
308                             inet_ntoa( from.sin_addr ), 0, 0 );
309                         conn_start_tv.tv_sec = 0;
310                         udp_init( 0, 0 );
311                         do_queries( ns, 1 );
312 #else /* LDAP_CONNECTIONLESS */
313                         Debug( LDAP_DEBUG_ARGS,
314                             "Compile with -DLDAP_CONNECTIONLESS for UDP support\n",0,0,0 );
315 #endif /* LDAP_CONNECTIONLESS */
316                         exit( 0 );
317                 }
318
319                 len = sizeof(from);
320                 if ( getpeername( ns, (struct sockaddr *) &from, &len )
321                     == 0 ) {
322                         hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
323                         sizeof(from.sin_addr.s_addr), AF_INET );
324                         Debug( LDAP_DEBUG_ARGS, "connection from %s (%s)\n",
325                             (hp == NULL) ? "unknown" : hp->h_name,
326                             inet_ntoa( from.sin_addr ), 0 );
327
328                         if ( dosyslog ) {
329                                 syslog( LOG_INFO, "connection from %s (%s)",
330                                     (hp == NULL) ? "unknown" : hp->h_name,
331                                     inet_ntoa( from.sin_addr ) );
332                         }
333
334 #ifdef LDAP_PROCTITLE
335                         sprintf( title, "%s %d\n", hp == NULL ?
336                             inet_ntoa( from.sin_addr ) : hp->h_name, myport );
337                         setproctitle( title );
338 #endif
339                 }
340                 gettimeofday( &conn_start_tv, (struct timezone *) NULL );
341                 do_queries( ns, 0 );
342
343                 exit( 0 );
344         }
345
346         if ( do_tcp )
347             tcps = set_socket( myport, 0 );
348
349 #ifdef LDAP_CONNECTIONLESS
350         if ( do_udp )
351                 udps = udp_init( myport, 1 );
352 #endif
353
354         /*
355          * loop, wait for a connection, then fork off a child to handle it
356          * if we are doing CLDAP as well, handle those requests on the fly
357          */
358
359 #ifdef LDAP_PROCTITLE
360 #ifdef LDAP_CONNECTIONLESS
361         sprintf( title, "listening %s/%s %d", do_tcp ? "tcp" : "",
362             do_udp ? "udp" : "", myport );
363 #else
364         sprintf( title, "listening %s %d", do_tcp ? "tcp" : "", myport );
365 #endif
366         setproctitle( title );
367 #endif
368
369         for ( ;; ) {
370                 FD_ZERO( &readfds );
371                 if ( do_tcp )
372                         FD_SET( tcps, &readfds );
373 #ifdef LDAP_CONNECTIONLESS
374                 if ( do_udp )
375                         FD_SET( udps, &readfds );
376 #endif
377
378                 if ( select( dtblsize, &readfds, 0, 0, 0 ) < 1 ) {
379 #ifdef LDAP_DEBUG
380                         if ( ldap_debug ) perror( "main select" );
381 #endif
382                         continue;
383                 }
384
385 #ifdef LDAP_CONNECTIONLESS
386                 if ( do_udp && FD_ISSET( udps, &readfds ) ) {
387                         do_queries( udps, 1 );
388                 }
389 #endif
390
391                 if ( !do_tcp || ! FD_ISSET( tcps, &readfds ) ) {
392                         continue;
393                 }
394
395                 len = sizeof(from);
396                 if ( (ns = accept( tcps, (struct sockaddr *) &from, &len ))
397                     == -1 ) {
398 #ifdef LDAP_DEBUG
399                         if ( ldap_debug ) perror( "accept" );
400 #endif
401                         continue;
402                 }
403
404                 hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
405                     sizeof(from.sin_addr.s_addr), AF_INET );
406
407 #ifdef HAVE_TCPD
408                 if ( !hosts_ctl("ldapd", (hp == NULL) ? "unknown" : hp->h_name,
409                         inet_ntoa( from.sin_addr ), STRING_UNKNOWN ) {
410
411                         Debug( LDAP_DEBUG_ARGS, "connection from %s (%s) denied.\n",
412                                 (hp == NULL) ? "unknown" : hp->h_name,
413                                 inet_ntoa( from.sin_addr ), 0 );
414
415                         if ( dosyslog ) {
416                                 syslog( LOG_NOTICE, "connection from %s (%s) denied.",
417                                     (hp == NULL) ? "unknown" : hp->h_name,
418                                     inet_ntoa( from.sin_addr ) );
419                         }
420
421                         tcp_close(ns);
422                         continue;
423                 }
424 #endif /* TCP_WRAPPERS */
425
426                 Debug( LDAP_DEBUG_ARGS, "connection from %s (%s)\n",
427                     (hp == NULL) ? "unknown" : hp->h_name,
428                     inet_ntoa( from.sin_addr ), 0 );
429
430
431                 if ( dosyslog ) {
432                         syslog( LOG_INFO, "connection from %s (%s)",
433                             (hp == NULL) ? "unknown" : hp->h_name,
434                             inet_ntoa( from.sin_addr ) );
435                 }
436
437 #ifdef VMS
438                 /* This is for debug on terminal on VMS */
439                 tcp_close( tcps );
440 #ifdef LDAP_PROCTITLE
441                 setproctitle( hp == NULL ? inet_ntoa( from.sin_addr ) :
442                     hp->h_name );
443 #endif
444                 gettimeofday( &conn_start_tv, (struct timezone *) NULL );
445                 (void) SIGNAL( SIGPIPE, log_and_exit );
446
447                 do_queries( ns, 0 );
448                 /* NOT REACHED */
449 #endif
450
451                 switch( pid = fork() ) {
452                 case 0:         /* child */
453                         tcp_close( tcps );
454 #ifdef LDAP_PROCTITLE
455                         sprintf( title, "%s (%d)\n", hp == NULL ?
456                                 inet_ntoa( from.sin_addr ) : hp->h_name,
457                                 myport );
458                         setproctitle( title );
459 #endif
460                         gettimeofday( &conn_start_tv, (struct timezone *) NULL );
461                         (void) SIGNAL( SIGPIPE, log_and_exit );
462
463                         do_queries( ns, 0 );
464                         break;
465
466                 case -1:        /* failed */
467 #ifdef LDAP_DEBUG
468                         if ( ldap_debug ) perror( "fork" );
469 #endif
470                         tcp_close( ns );
471                         syslog( LOG_ERR, "fork failed %m" );
472                         /* let things cool off */
473                         sleep( 15 );
474                         break;
475
476                 default:        /* parent */
477                         tcp_close( ns );
478                         Debug( LDAP_DEBUG_TRACE, "forked child %d\n", pid, 0,
479                             0 );
480                         break;
481                 }
482         }
483         /* NOT REACHED */
484 }
485
486 static void
487 do_queries(
488     int clientsock,
489     int udp             /* is this a UDP (CLDAP) request? */
490 )
491 {
492         fd_set          readfds;
493         int             rc;
494         struct timeval  timeout;
495         Sockbuf         sb;
496 #ifdef LDAP_CONNECTIONLESS
497         struct sockaddr saddr, faddr;
498         struct sockaddr *saddrlist[ 1 ];
499 #endif /* LDAP_CONNECTIONLESS */
500
501         Debug( LDAP_DEBUG_TRACE, "do_queries%s\n",
502             udp ? " udp" : "", 0, 0 );
503
504         /*
505          * Loop, wait for a request from the client or a response from
506          * a dsa, then handle it.  Dsap_ad is always a connection to the
507          * "default" dsa.  Other connections can be made as a result of
508          * a referral being chased down.  These association descriptors
509          * are kept track of with the message that caused the referral.
510          * The set_dsa_fds() routine traverses the list of outstanding
511          * messages, setting the appropriate bits in readfds.
512          */
513
514         if ( !udp ) {
515                 conn_init();
516         }
517
518         ber_pvt_sb_init( &sb );
519         ber_pvt_sb_set_desc( &sb, clientsock );
520         ber_pvt_sb_set_io( &sb, (udp) ? &ber_pvt_sb_io_udp :
521                                         &ber_pvt_sb_io_tcp, NULL );
522         timeout.tv_sec = idletime;
523         timeout.tv_usec = 0;
524         for ( ;; ) {
525                 struct conn             *dsaconn;
526
527                 FD_ZERO( &readfds );
528                 FD_SET( clientsock, &readfds );
529                 conn_setfds( &readfds );
530
531 #ifdef LDAP_DEBUG
532                 if ( ldap_debug & LDAP_DEBUG_CONNS ) {
533                         int i;
534                         Debug( LDAP_DEBUG_CONNS, "FDLIST:", 0, 0, 0 );
535                         for ( i = 0; i < dtblsize; i++ ) {
536                                 if ( FD_ISSET( i, &readfds ) ) {
537                                         Debug( LDAP_DEBUG_CONNS, " %d", i, 0,
538                                             0);
539                                 }
540                         }
541                         Debug( LDAP_DEBUG_CONNS, "\n", 0, 0, 0 );
542                 }
543 #endif
544
545                 /* 
546                  * hack - because of lber buffering, there might be stuff
547                  * already waiting for us on the client sock.
548                  */
549
550                 if ( ! ber_pvt_sb_data_ready( &sb ) ) {
551                         if ( (rc = select( dtblsize, &readfds, 0, 0,
552                             udp ? 0 : &timeout )) < 1 ) {
553 #ifdef LDAP_DEBUG
554                                 if ( ldap_debug ) perror( "do_queries select" );
555 #endif
556                                 if ( rc == 0 )
557                                         log_and_exit( 0 ); /* idle timeout */
558
559                                 Debug( LDAP_DEBUG_ANY, "select returns %d!\n",
560                                     rc, 0, 0 );
561
562                                 /* client gone away - we can too */
563                                 if ( isclosed( clientsock ) )
564                                         log_and_exit( 0 );
565
566                                 /*
567                                  * check if a dsa conn has gone away -
568                                  * mark it bad if so
569                                  */
570                                 conn_badfds();
571
572                                 continue;
573                         }
574                 }
575
576                 if ( ber_pvt_sb_data_ready( &sb ) ||
577                     FD_ISSET( clientsock, &readfds ) ) {
578                         client_request( &sb, conns, udp );
579                 } else {
580                         if ( (dsaconn = conn_getfd( &readfds )) == NULL ) {
581                                 Debug( LDAP_DEBUG_ANY, "No DSA activity!\n",
582                                     0, 0, 0 );
583                                 continue;
584                         }
585
586                         dsa_response( dsaconn, &sb );
587                 }
588         }
589         /* NOT REACHED */
590 }
591
592 static int
593 set_socket(
594     int port,
595     int udp     /* UDP port? */
596 )
597 {
598         int                     s, i;
599         struct sockaddr_in      addr;
600
601         if ( (s = socket( AF_INET, udp ? SOCK_DGRAM:SOCK_STREAM, 0 )) == -1 ) {
602                 perror( "socket" );
603                 exit( 1 );
604         }
605
606         /* set option so clients can't keep us from coming back up */
607         i = 1;
608         if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, (void *) &i, sizeof(i) )
609             < 0 ) {
610                 perror( "setsockopt" );
611                 exit( 1 );
612         }
613
614         /* bind to a name */
615         (void)memset( (void *)&addr, '\0', sizeof( addr ));
616         addr.sin_family = AF_INET;
617         addr.sin_addr.s_addr = INADDR_ANY;
618         addr.sin_port = htons( port );
619         if ( bind( s, (struct sockaddr *) &addr, sizeof(addr) ) ) {
620                 perror( "bind" );
621                 exit( 1 );
622         }
623
624         if ( !udp ) {
625                 /* listen for connections */
626                 if ( listen( s, 5 ) == -1 ) {
627                         perror( "listen" );
628                         exit( 1 );
629                 }
630         }
631  
632         Debug( LDAP_DEBUG_TRACE, "listening on %s port %d\n",
633                 udp ? "udp" : "tcp", port, 0 );
634
635         return( s );
636 }
637
638 static RETSIGTYPE
639 wait4child( int sig )
640 {
641 #ifndef HAVE_WAITPID
642         WAITSTATUSTYPE     status;
643 #endif
644
645         Debug( LDAP_DEBUG_TRACE, "parent: catching child status\n", 0, 0, 0 );
646
647 #ifdef HAVE_WAITPID
648         while( waitpid( (pid_t) -1, (int *) NULL, WAIT_FLAGS ) > 0 )
649                 ;       /* NULL */
650 #else
651         while ( wait4( (pid_t) -1, &status, WAIT_FLAGS, 0 ) > 0 )
652                 ;       /* NULL */
653 #endif
654
655         (void) SIGNAL( SIGCHLD, wait4child );
656 }
657
658
659 RETSIGTYPE
660 log_and_exit( int exitcode )
661 {
662         struct timeval  tv;
663
664         if ( dosyslog ) {
665                 if ( conn_start_tv.tv_sec == 0 ) {
666                         syslog( LOG_INFO, "UDP exit(%d)", exitcode );
667                 } else {
668                         gettimeofday( &tv, (struct timezone *)NULL );
669                         syslog( LOG_INFO, "TCP closed %d seconds,  exit(%d)",
670                             tv.tv_sec - conn_start_tv.tv_sec, exitcode );
671                 }
672         }
673
674         exit( exitcode );
675 }
676
677
678 #ifdef LDAP_CONNECTIONLESS
679 static int
680 udp_init(
681     int port,
682     int createsocket
683 )
684 {
685         int     s, bound;
686         char    *matched;
687
688         if ( createsocket )
689                 s = set_socket( port, 1 );
690
691         conn_init();
692         conns->c_dn = strdup("");
693         conns->c_cred = strdup("");
694         conns->c_credlen = 0;
695         conns->c_method = LDAP_AUTH_SIMPLE;
696
697        if ( dsa_address == NULL || (conns->c_paddr = str2paddr( dsa_address ))
698             == NULLPA ) {
699                 fprintf(stderr, "Bad DSA address (%s)\n", dsa_address ?
700                     dsa_address : "NULL" );
701                 exit( 1 );
702         } else {
703                 conns->c_paddr = psap_cpy(conns->c_paddr);
704         }
705
706         if ( do_bind_real(conns, &bound, &matched) != LDAP_SUCCESS) {
707                 fprintf(stderr, "Cannot bind to directory\n");
708                 exit( 1 );
709         }
710         if ( matched != NULL )
711                 free( matched );
712
713         return( createsocket ? s : 0 );
714 }
715 #endif