/* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2021-2022 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #include "portable.h" #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include "slap.h" #include "ldif.h" #include "slap-config.h" #include "slap-cfglog.h" static int config_syslog, active_syslog; static char logfile_suffix[sizeof(".xx.gz")]; static char logfile_path[MAXPATHLEN - sizeof(logfile_suffix) -1]; static long logfile_fslimit; static int logfile_age, logfile_only, logfile_max; static char *syslog_prefix; static int splen; typedef enum { LFMT_DEFAULT, LFMT_DEBUG, LFMT_SYSLOG_UTC, LFMT_SYSLOG_LOCAL } LogFormat; static LogFormat logfile_format; static slap_verbmasks logformat_key[] = { { BER_BVC("default"), LFMT_DEFAULT }, { BER_BVC("debug"), LFMT_DEBUG }, { BER_BVC("syslog-utc"), LFMT_SYSLOG_UTC }, { BER_BVC("syslog-localtime"), LFMT_SYSLOG_LOCAL }, { BER_BVNULL, 0 } }; char *serverName; int slap_debug_orig; ldap_pvt_thread_mutex_t logfile_mutex; static off_t logfile_fsize; static time_t logfile_fcreated; static int logfile_fd = -1; static char logpaths[2][MAXPATHLEN]; static int logpathlen; #define SYSLOG_STAMP "Mmm dd hh:mm:ss" void slap_debug_print( const char *data ) { #ifdef _WIN32 char msgbuf[4096]; int prefixlen, poffset = 0, datalen; #else char prefix[sizeof("ssssssssssssssss.ffffffff 0xtttttttttttttttt ")]; struct iovec iov[2]; #endif int rotate = 0; #ifdef HAVE_CLOCK_GETTIME struct timespec tv; #define TS "%08x" #define Tfrac tv.tv_nsec #define gettime(tv) clock_gettime( CLOCK_REALTIME, tv ) #else struct timeval tv; #define TS "%05x" #define Tfrac tv.tv_usec #define gettime(tv) gettimeofday( tv, NULL ) #endif char *ptr; int len; gettime( &tv ); #ifdef _WIN32 ptr = msgbuf; prefixlen = sprintf( ptr, "%lx." TS " %p ", (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() ); if ( prefixlen < splen ) { poffset = splen - prefixlen; AC_MEMCPY( ptr+poffset, ptr, prefixlen ); } ptr = lutil_strncopy( ptr+poffset+prefixlen, data, sizeof(msgbuf) - prefixlen); len = ptr - msgbuf - poffset; datalen = len - prefixlen; if ( !logfile_only ) (void)!write( 2, msgbuf+poffset, len ); ptr = msgbuf; #else iov[0].iov_base = prefix; iov[0].iov_len = sprintf( prefix, "%lx." TS " %p ", (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() ); iov[1].iov_base = (void *)data; iov[1].iov_len = strlen( data ); len = iov[0].iov_len + iov[1].iov_len; if ( !logfile_only ) (void)!writev( 2, iov, 2 ); #endif if ( logfile_fd >= 0 ) { if ( logfile_fslimit || logfile_age ) { ldap_pvt_thread_mutex_lock( &logfile_mutex ); if ( logfile_fslimit && logfile_fsize + len > logfile_fslimit ) rotate = 1; if ( logfile_age && tv.tv_sec - logfile_fcreated >= logfile_age ) rotate |= 2; if ( rotate ) { close( logfile_fd ); logfile_fd = -1; strcpy( logpaths[0]+logpathlen, ".tmp" ); rename( logfile_path, logpaths[0] ); logfile_open( logfile_path ); } } if ( logfile_format > LFMT_DEBUG ) { struct tm tm; if ( logfile_format == LFMT_SYSLOG_UTC ) ldap_pvt_gmtime( &tv.tv_sec, &tm ); else ldap_pvt_localtime( &tv.tv_sec, &tm ); #ifdef _WIN32 if ( splen < prefixlen ) ptr += prefixlen - splen; memcpy( ptr, syslog_prefix, splen ); #else ptr = syslog_prefix; #endif strftime( ptr, sizeof( SYSLOG_STAMP ), "%b %d %H:%M:%S", &tm ); ptr[ sizeof( SYSLOG_STAMP )-1 ] = ' '; #ifdef _WIN32 len = datalen + splen; #else iov[0].iov_base = syslog_prefix; iov[0].iov_len = splen; #endif } #ifdef _WIN32 if ( logfile_format <= LFMT_DEBUG ) ptr += poffset; /* only nonzero if logfile-format was explicitly set */ len = write( logfile_fd, ptr, len ); #else len = writev( logfile_fd, iov, 2 ); #endif if ( len > 0 ) logfile_fsize += len; if ( logfile_fslimit || logfile_age ) ldap_pvt_thread_mutex_unlock( &logfile_mutex ); } if ( rotate ) { int i; for (i=logfile_max; i > 1; i--) { sprintf( logpaths[0]+logpathlen, ".%02d", i ); sprintf( logpaths[1]+logpathlen, ".%02d", i-1 ); rename( logpaths[1], logpaths[0] ); } sprintf( logpaths[0]+logpathlen, ".tmp" ); rename( logpaths[0], logpaths[1] ); } } void logfile_close() { if ( logfile_fd >= 0 ) { close( logfile_fd ); logfile_fd = -1; } logfile_path[0] = '\0'; } int logfile_open( const char *path ) { struct stat st; int fd, saved_errno; /* the logfile is for slapd only, not tools */ if ( !( slapMode & SLAP_SERVER_MODE )) return 0; fd = open( path, O_CREAT|O_WRONLY, 0640 ); if ( fd < 0 ) { saved_errno = errno; fail: logfile_only = 0; /* make sure something gets output */ return saved_errno; } if ( fstat( fd, &st ) ) { saved_errno = errno; close( fd ); goto fail; } if ( !logfile_path[0] ) { logpathlen = strlen( path ); if ( logpathlen >= sizeof(logfile_path) ) { saved_errno = ENAMETOOLONG; goto fail; } strcpy( logfile_path, path ); strcpy( logpaths[0], path ); strcpy( logpaths[1], path ); } logfile_fsize = st.st_size; logfile_fcreated = st.st_ctime; /* not strictly true but close enough */ logfile_fd = fd; lseek( fd, 0, SEEK_END ); return 0; } const char * logfile_name() { return logfile_path[0] ? logfile_path : NULL; } #if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG) #ifdef LOG_LOCAL4 int slap_parse_syslog_user( const char *arg, int *syslogUser ) { static slap_verbmasks syslogUsers[] = { { BER_BVC( "LOCAL0" ), LOG_LOCAL0 }, { BER_BVC( "LOCAL1" ), LOG_LOCAL1 }, { BER_BVC( "LOCAL2" ), LOG_LOCAL2 }, { BER_BVC( "LOCAL3" ), LOG_LOCAL3 }, { BER_BVC( "LOCAL4" ), LOG_LOCAL4 }, { BER_BVC( "LOCAL5" ), LOG_LOCAL5 }, { BER_BVC( "LOCAL6" ), LOG_LOCAL6 }, { BER_BVC( "LOCAL7" ), LOG_LOCAL7 }, #ifdef LOG_USER { BER_BVC( "USER" ), LOG_USER }, #endif /* LOG_USER */ #ifdef LOG_DAEMON { BER_BVC( "DAEMON" ), LOG_DAEMON }, #endif /* LOG_DAEMON */ { BER_BVNULL, 0 } }; int i = verb_to_mask( arg, syslogUsers ); if ( BER_BVISNULL( &syslogUsers[ i ].word ) ) { Debug( LDAP_DEBUG_ANY, "unrecognized syslog user \"%s\".\n", arg ); return 1; } *syslogUser = syslogUsers[ i ].mask; return 0; } #endif /* LOG_LOCAL4 */ int slap_parse_syslog_level( const char *arg, int *levelp ) { static slap_verbmasks str2syslog_level[] = { { BER_BVC( "EMERG" ), LOG_EMERG }, { BER_BVC( "ALERT" ), LOG_ALERT }, { BER_BVC( "CRIT" ), LOG_CRIT }, { BER_BVC( "ERR" ), LOG_ERR }, { BER_BVC( "WARNING" ), LOG_WARNING }, { BER_BVC( "NOTICE" ), LOG_NOTICE }, { BER_BVC( "INFO" ), LOG_INFO }, { BER_BVC( "DEBUG" ), LOG_DEBUG }, { BER_BVNULL, 0 } }; int i = verb_to_mask( arg, str2syslog_level ); if ( BER_BVISNULL( &str2syslog_level[ i ].word ) ) { Debug( LDAP_DEBUG_ANY, "unknown syslog level \"%s\".\n", arg ); return 1; } *levelp = str2syslog_level[ i ].mask; return 0; } #endif /* LDAP_DEBUG && LDAP_SYSLOG */ static char **debug_unknowns; static char **syslog_unknowns; static int parse_debug_unknowns( char **unknowns, int *levelp ) { int i, level, rc = 0; for ( i = 0; unknowns[ i ] != NULL; i++ ) { level = 0; if ( str2loglevel( unknowns[ i ], &level )) { fprintf( stderr, "unrecognized log level \"%s\"\n", unknowns[ i ] ); rc = 1; } else { *levelp |= level; } } return rc; } int slap_parse_debug_level( const char *arg, int *levelp, int which ) { int level; if ( arg && arg[ 0 ] != '-' && !isdigit( (unsigned char) arg[ 0 ] ) ) { int i; char **levels; char ***unknowns = which ? &syslog_unknowns : &debug_unknowns; levels = ldap_str2charray( arg, "," ); for ( i = 0; levels[ i ] != NULL; i++ ) { level = 0; if ( str2loglevel( levels[ i ], &level ) ) { /* remember this for later */ ldap_charray_add( unknowns, levels[ i ] ); fprintf( stderr, "unrecognized log level \"%s\" (deferred)\n", levels[ i ] ); } else { *levelp |= level; } } ldap_charray_free( levels ); } else { int rc; if ( arg[0] == '-' ) { rc = lutil_atoix( &level, arg, 0 ); } else { unsigned ulevel; rc = lutil_atoux( &ulevel, arg, 0 ); level = (int)ulevel; } if ( rc ) { fprintf( stderr, "unrecognized log level " "\"%s\"\n", arg ); return 1; } if ( level == 0 ) { *levelp = 0; } else { *levelp |= level; } } return 0; } int slap_parse_debug_unknowns() { int rc = 0; if ( debug_unknowns ) { rc = parse_debug_unknowns( debug_unknowns, &slap_debug ); ldap_charray_free( debug_unknowns ); debug_unknowns = NULL; if ( rc ) goto leave; ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug ); ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug ); } if ( syslog_unknowns ) { rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog ); ldap_charray_free( syslog_unknowns ); syslog_unknowns = NULL; } leave: return rc; } void slap_check_unknown_level( char *levelstr, int level ) { int i; if ( debug_unknowns ) { for ( i = 0; debug_unknowns[ i ]; i++ ) { if ( !strcasecmp( debug_unknowns[ i ], levelstr )) { slap_debug |= level; break; } } } if ( syslog_unknowns ) { for ( i = 0; syslog_unknowns[ i ]; i++ ) { if ( !strcasecmp( syslog_unknowns[ i ], levelstr )) { ldap_syslog |= level; break; } } } } static slap_verbmasks *loglevel_ops; static int loglevel_init( void ) { slap_verbmasks lo[] = { { BER_BVC("Any"), (slap_mask_t) LDAP_DEBUG_ANY }, { BER_BVC("Trace"), LDAP_DEBUG_TRACE }, { BER_BVC("Packets"), LDAP_DEBUG_PACKETS }, { BER_BVC("Args"), LDAP_DEBUG_ARGS }, { BER_BVC("Conns"), LDAP_DEBUG_CONNS }, { BER_BVC("BER"), LDAP_DEBUG_BER }, { BER_BVC("Filter"), LDAP_DEBUG_FILTER }, { BER_BVC("Config"), LDAP_DEBUG_CONFIG }, { BER_BVC("ACL"), LDAP_DEBUG_ACL }, { BER_BVC("Stats"), LDAP_DEBUG_STATS }, { BER_BVC("Stats2"), LDAP_DEBUG_STATS2 }, { BER_BVC("Shell"), LDAP_DEBUG_SHELL }, { BER_BVC("Parse"), LDAP_DEBUG_PARSE }, #if 0 /* no longer used (nor supported) */ { BER_BVC("Cache"), LDAP_DEBUG_CACHE }, { BER_BVC("Index"), LDAP_DEBUG_INDEX }, #endif { BER_BVC("Sync"), LDAP_DEBUG_SYNC }, { BER_BVC("None"), LDAP_DEBUG_NONE }, { BER_BVNULL, 0 } }; return slap_verbmasks_init( &loglevel_ops, lo ); } void slap_loglevel_destroy( void ) { if ( loglevel_ops ) { (void)slap_verbmasks_destroy( loglevel_ops ); } loglevel_ops = NULL; } static slap_mask_t loglevel_ignore[] = { -1, 0 }; int slap_loglevel_get( struct berval *s, int *l ) { int rc; slap_mask_t m, i; if ( loglevel_ops == NULL ) { loglevel_init(); } for ( m = 0, i = 1; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) { m |= loglevel_ops[ i ].mask; } for ( i = 1; m & i; i <<= 1 ) ; if ( i == 0 ) { return -1; } rc = slap_verbmasks_append( &loglevel_ops, i, s, loglevel_ignore ); if ( rc != 0 ) { Debug( LDAP_DEBUG_ANY, "slap_loglevel_get(%lu, \"%s\") failed\n", i, s->bv_val ); } else { *l = i; slap_check_unknown_level( s->bv_val, i ); } return rc; } int slap_syslog_get() { return active_syslog; } void slap_syslog_set( int l ) { active_syslog = l; if ( logfile_only ) { slap_debug |= active_syslog; ldap_syslog = 0; } else { ldap_syslog = active_syslog; } } int slap_debug_get() { return slap_debug_orig; } void slap_debug_set( int l ) { slap_debug_orig = l; if ( logfile_only ) slap_debug = slap_debug_orig | active_syslog; else slap_debug = slap_debug_orig; ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug); ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug); ldif_debug = slap_debug; } int str2loglevel( const char *s, int *l ) { int i; if ( loglevel_ops == NULL ) { loglevel_init(); } i = verb_to_mask( s, loglevel_ops ); if ( BER_BVISNULL( &loglevel_ops[ i ].word ) ) { return -1; } *l = loglevel_ops[ i ].mask; return 0; } const char * loglevel2str( int l ) { struct berval bv = BER_BVNULL; loglevel2bv( l, &bv ); return bv.bv_val; } int loglevel2bv( int l, struct berval *bv ) { if ( loglevel_ops == NULL ) { loglevel_init(); } BER_BVZERO( bv ); return enum_to_verb( loglevel_ops, l, bv ) == -1; } int loglevel2bvarray( int l, BerVarray *bva ) { if ( loglevel_ops == NULL ) { loglevel_init(); } if ( l == 0 ) { struct berval bv = BER_BVC("0"); return value_add_one( bva, &bv ); } return mask_to_verbs( loglevel_ops, l, bva ); } int loglevel_print( FILE *out ) { int i; if ( loglevel_ops == NULL ) { loglevel_init(); } fprintf( out, "Installed log subsystems:\n\n" ); for ( i = 0; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) { unsigned mask = loglevel_ops[ i ].mask & 0xffffffffUL; fprintf( out, (mask == ((slap_mask_t) -1 & 0xffffffffUL) ? "\t%-30s (-1, 0xffffffff)\n" : "\t%-30s (%u, 0x%x)\n"), loglevel_ops[ i ].word.bv_val, mask, mask ); } fprintf( out, "\nNOTE: custom log subsystems may be later installed " "by specific code\n\n" ); return 0; } int config_logging(ConfigArgs *c) { int i, rc = 0; if ( loglevel_ops == NULL ) { loglevel_init(); } if (c->op == SLAP_CONFIG_EMIT) { switch(c->type) { case CFG_LOGLEVEL: /* Get default or commandline slapd setting */ if ( ldap_syslog && !config_syslog ) config_syslog = ldap_syslog; rc = loglevel2bvarray( config_syslog, &c->rvalue_vals ); break; case CFG_LOGFILE: { const char *logfileName = logfile_name(); if ( logfileName && *logfileName ) c->value_string = ch_strdup( logfileName ); else rc = 1; } break; case CFG_LOGFILE_FORMAT: if ( logfile_format ) { value_add_one( &c->rvalue_vals, &logformat_key[logfile_format].word ); } else { rc = 1; } break; case CFG_LOGFILE_ONLY: c->value_int = logfile_only; break; case CFG_LOGFILE_ROTATE: rc = 1; if ( logfile_max ) { char buf[64]; struct berval bv; bv.bv_len = snprintf( buf, sizeof(buf), "%d %ld %ld", logfile_max, (long) logfile_fslimit / 1048576, (long) logfile_age / 3600 ); if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { bv.bv_val = buf; value_add_one( &c->rvalue_vals, &bv ); rc = 0; } } break; default: rc = 1; } return rc; } else if ( c->op == LDAP_MOD_DELETE ) { switch(c->type) { case CFG_LOGLEVEL: if ( !c->line ) { config_syslog = 0; } else { i = verb_to_mask( c->line, loglevel_ops ); config_syslog &= ~loglevel_ops[i].mask; } goto reset; case CFG_LOGFILE: logfile_close(); break; case CFG_LOGFILE_FORMAT: logfile_format = 0; ch_free( syslog_prefix ); syslog_prefix = NULL; break; case CFG_LOGFILE_ONLY: /* remove loglevel from debuglevel */ slap_debug = slap_debug_orig; ldap_syslog = config_syslog; break; case CFG_LOGFILE_ROTATE: logfile_max = logfile_fslimit = logfile_age = 0; break; default: rc = 1; } return rc; } switch(c->type) { case CFG_LOGLEVEL: for( i=1; i < c->argc; i++ ) { int level; if ( isdigit((unsigned char)c->argv[i][0]) || c->argv[i][0] == '-' ) { if( lutil_atoix( &level, c->argv[i], 0 ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse level", c->argv[0] ); Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", c->log, c->cr_msg, c->argv[i]); return( 1 ); } } else { if ( str2loglevel( c->argv[i], &level ) ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown level", c->argv[0] ); Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", c->log, c->cr_msg, c->argv[i]); return( 1 ); } } /* Explicitly setting a zero clears all the levels */ if ( level ) config_syslog |= level; else config_syslog = 0; } reset: slap_debug = slap_debug_orig; active_syslog = config_syslog; if ( slapMode & SLAP_SERVER_MODE ) { if ( logfile_only ) { slap_debug |= config_syslog; ldap_syslog = 0; } else { ldap_syslog = config_syslog; } } rc = 0; break; case CFG_LOGFILE: rc = logfile_open( c->value_string ); ch_free( c->value_string ); break; case CFG_LOGFILE_FORMAT: { int len; i = verb_to_mask( c->argv[1], logformat_key ); if ( BER_BVISNULL( &logformat_key[ i ].word ) ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown format", c->argv[0] ); Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", c->log, c->cr_msg, c->argv[1]); return( 1 ); } if ( syslog_prefix ) ch_free( syslog_prefix ); len = strlen( global_host ) + 1 + strlen( serverName ) + 1 + sizeof("[123456789]:") + sizeof( SYSLOG_STAMP ); syslog_prefix = ch_malloc( len ); splen = sprintf( syslog_prefix, SYSLOG_STAMP " %s %s[%d]: ", global_host, serverName, getpid() ); logfile_format = logformat_key[i].mask; } break; case CFG_LOGFILE_ONLY: logfile_only = c->value_int; goto reset; case CFG_LOGFILE_ROTATE: { unsigned lf_max, lf_mbyte, lf_hour; if ( lutil_atoux( &lf_max, c->argv[1], 0 ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " "invalid max value \"%s\"", c->argv[0], c->argv[1] ); Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( !lf_max || lf_max > 99 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " "invalid max value \"%s\" must be 1-99", c->argv[0], c->argv[1] ); Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( lutil_atoux( &lf_mbyte, c->argv[2], 0 ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " "invalid Mbyte value \"%s\"", c->argv[0], c->argv[2] ); Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( lutil_atoux( &lf_hour, c->argv[3], 0 ) != 0 ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " "invalid hours value \"%s\"", c->argv[0], c->argv[3] ); Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); return 1; } if ( !lf_mbyte && !lf_hour ) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " "Mbyte and hours cannot both be zero", c->argv[0] ); Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); return 1; } logfile_max = lf_max; logfile_fslimit = lf_mbyte * 1048576; /* Megabytes to bytes */ logfile_age = lf_hour * 3600; /* hours to seconds */ } break; default: rc = 1; } return rc; }