10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * Copyright (c) 2000, 2001, 2003, 2004 Sendmail, Inc. and its suppliers. 30Sstevel@tonic-gate * All rights reserved. 40Sstevel@tonic-gate * 50Sstevel@tonic-gate * By using this file, you agree to the terms and conditions set 60Sstevel@tonic-gate * forth in the LICENSE file which can be found at the top level of 70Sstevel@tonic-gate * the sendmail distribution. 80Sstevel@tonic-gate */ 90Sstevel@tonic-gate 100Sstevel@tonic-gate #include <sm/gen.h> 11*11440SJohn.Beck@Sun.COM SM_RCSID("@(#)$Id: debug.c,v 1.32 2009/09/20 05:38:46 ca Exp $") 120Sstevel@tonic-gate 130Sstevel@tonic-gate /* 140Sstevel@tonic-gate ** libsm debugging and tracing 150Sstevel@tonic-gate ** For documentation, see debug.html. 160Sstevel@tonic-gate */ 170Sstevel@tonic-gate 180Sstevel@tonic-gate #include <ctype.h> 190Sstevel@tonic-gate #include <stdlib.h> 20*11440SJohn.Beck@Sun.COM #if _FFR_DEBUG_PID_TIME 21*11440SJohn.Beck@Sun.COM #include <unistd.h> 22*11440SJohn.Beck@Sun.COM #include <time.h> 23*11440SJohn.Beck@Sun.COM #endif /* _FFR_DEBUG_PID_TIME */ 240Sstevel@tonic-gate #include <setjmp.h> 250Sstevel@tonic-gate #include <sm/io.h> 260Sstevel@tonic-gate #include <sm/assert.h> 270Sstevel@tonic-gate #include <sm/conf.h> 280Sstevel@tonic-gate #include <sm/debug.h> 290Sstevel@tonic-gate #include <sm/string.h> 300Sstevel@tonic-gate #include <sm/varargs.h> 310Sstevel@tonic-gate #include <sm/heap.h> 320Sstevel@tonic-gate 330Sstevel@tonic-gate static void sm_debug_reset __P((void)); 340Sstevel@tonic-gate static const char *parse_named_setting_x __P((const char *)); 350Sstevel@tonic-gate 360Sstevel@tonic-gate /* 370Sstevel@tonic-gate ** Abstractions for printing trace messages. 380Sstevel@tonic-gate */ 390Sstevel@tonic-gate 400Sstevel@tonic-gate /* 410Sstevel@tonic-gate ** The output file to which trace output is directed. 420Sstevel@tonic-gate ** There is a controversy over whether this variable 430Sstevel@tonic-gate ** should be process global or thread local. 440Sstevel@tonic-gate ** To make the interface more abstract, we've hidden the 450Sstevel@tonic-gate ** variable behind access functions. 460Sstevel@tonic-gate */ 470Sstevel@tonic-gate 480Sstevel@tonic-gate static SM_FILE_T *SmDebugOutput = smioout; 490Sstevel@tonic-gate 500Sstevel@tonic-gate /* 510Sstevel@tonic-gate ** SM_DEBUG_FILE -- Returns current debug file pointer. 520Sstevel@tonic-gate ** 530Sstevel@tonic-gate ** Parameters: 540Sstevel@tonic-gate ** none. 550Sstevel@tonic-gate ** 560Sstevel@tonic-gate ** Returns: 570Sstevel@tonic-gate ** current debug file pointer. 580Sstevel@tonic-gate */ 590Sstevel@tonic-gate 600Sstevel@tonic-gate SM_FILE_T * 610Sstevel@tonic-gate sm_debug_file() 620Sstevel@tonic-gate { 630Sstevel@tonic-gate return SmDebugOutput; 640Sstevel@tonic-gate } 650Sstevel@tonic-gate 660Sstevel@tonic-gate /* 670Sstevel@tonic-gate ** SM_DEBUG_SETFILE -- Sets debug file pointer. 680Sstevel@tonic-gate ** 690Sstevel@tonic-gate ** Parameters: 700Sstevel@tonic-gate ** fp -- new debug file pointer. 710Sstevel@tonic-gate ** 720Sstevel@tonic-gate ** Returns: 730Sstevel@tonic-gate ** none. 740Sstevel@tonic-gate ** 750Sstevel@tonic-gate ** Side Effects: 760Sstevel@tonic-gate ** Sets SmDebugOutput. 770Sstevel@tonic-gate */ 780Sstevel@tonic-gate 790Sstevel@tonic-gate void 800Sstevel@tonic-gate sm_debug_setfile(fp) 810Sstevel@tonic-gate SM_FILE_T *fp; 820Sstevel@tonic-gate { 830Sstevel@tonic-gate SmDebugOutput = fp; 840Sstevel@tonic-gate } 850Sstevel@tonic-gate 860Sstevel@tonic-gate /* 870Sstevel@tonic-gate ** SM_DEBUG_CLOSE -- Close debug file pointer. 880Sstevel@tonic-gate ** 890Sstevel@tonic-gate ** Parameters: 900Sstevel@tonic-gate ** none. 910Sstevel@tonic-gate ** 920Sstevel@tonic-gate ** Returns: 930Sstevel@tonic-gate ** none. 940Sstevel@tonic-gate ** 950Sstevel@tonic-gate ** Side Effects: 960Sstevel@tonic-gate ** Closes SmDebugOutput. 970Sstevel@tonic-gate */ 980Sstevel@tonic-gate 990Sstevel@tonic-gate void 1000Sstevel@tonic-gate sm_debug_close() 1010Sstevel@tonic-gate { 1020Sstevel@tonic-gate if (SmDebugOutput != NULL && SmDebugOutput != smioout) 1030Sstevel@tonic-gate { 1040Sstevel@tonic-gate sm_io_close(SmDebugOutput, SM_TIME_DEFAULT); 1050Sstevel@tonic-gate SmDebugOutput = NULL; 1060Sstevel@tonic-gate } 1070Sstevel@tonic-gate } 1080Sstevel@tonic-gate 1090Sstevel@tonic-gate /* 1100Sstevel@tonic-gate ** SM_DPRINTF -- printf() for debug output. 1110Sstevel@tonic-gate ** 1120Sstevel@tonic-gate ** Parameters: 1130Sstevel@tonic-gate ** fmt -- format for printf() 1140Sstevel@tonic-gate ** 1150Sstevel@tonic-gate ** Returns: 1160Sstevel@tonic-gate ** none. 1170Sstevel@tonic-gate */ 1180Sstevel@tonic-gate 119*11440SJohn.Beck@Sun.COM #if _FFR_DEBUG_PID_TIME 120*11440SJohn.Beck@Sun.COM SM_DEBUG_T SmDBGPidTime = SM_DEBUG_INITIALIZER("sm_trace_pid_time", 121*11440SJohn.Beck@Sun.COM "@(#)$Debug: sm_trace_pid_time - print pid and time in debug $"); 122*11440SJohn.Beck@Sun.COM #endif /* _FFR_DEBUG_PID_TIME */ 123*11440SJohn.Beck@Sun.COM 1240Sstevel@tonic-gate void 1250Sstevel@tonic-gate #if SM_VA_STD 1260Sstevel@tonic-gate sm_dprintf(char *fmt, ...) 1270Sstevel@tonic-gate #else /* SM_VA_STD */ 1280Sstevel@tonic-gate sm_dprintf(fmt, va_alist) 1290Sstevel@tonic-gate char *fmt; 1300Sstevel@tonic-gate va_dcl 1310Sstevel@tonic-gate #endif /* SM_VA_STD */ 1320Sstevel@tonic-gate { 1330Sstevel@tonic-gate SM_VA_LOCAL_DECL 1340Sstevel@tonic-gate 1350Sstevel@tonic-gate if (SmDebugOutput == NULL) 1360Sstevel@tonic-gate return; 137*11440SJohn.Beck@Sun.COM #if _FFR_DEBUG_PID_TIME 138*11440SJohn.Beck@Sun.COM /* note: this is ugly if the output isn't a full line! */ 139*11440SJohn.Beck@Sun.COM if (sm_debug_active(&SmDBGPidTime, 1)) 140*11440SJohn.Beck@Sun.COM { 141*11440SJohn.Beck@Sun.COM static char str[32] = "[1900-00-00/00:00:00] "; 142*11440SJohn.Beck@Sun.COM struct tm *tmp; 143*11440SJohn.Beck@Sun.COM time_t currt; 144*11440SJohn.Beck@Sun.COM 145*11440SJohn.Beck@Sun.COM currt = time((time_t *)0); 146*11440SJohn.Beck@Sun.COM tmp = localtime(&currt); 147*11440SJohn.Beck@Sun.COM snprintf(str, sizeof(str), "[%d-%02d-%02d/%02d:%02d:%02d] ", 148*11440SJohn.Beck@Sun.COM 1900 + tmp->tm_year, /* HACK */ 149*11440SJohn.Beck@Sun.COM tmp->tm_mon + 1, 150*11440SJohn.Beck@Sun.COM tmp->tm_mday, 151*11440SJohn.Beck@Sun.COM tmp->tm_hour, tmp->tm_min, tmp->tm_sec); 152*11440SJohn.Beck@Sun.COM sm_io_fprintf(SmDebugOutput, SmDebugOutput->f_timeout, 153*11440SJohn.Beck@Sun.COM "%ld: %s ", (long) getpid(), str); 154*11440SJohn.Beck@Sun.COM } 155*11440SJohn.Beck@Sun.COM #endif /* _FFR_DEBUG_PID_TIME */ 156*11440SJohn.Beck@Sun.COM 1570Sstevel@tonic-gate SM_VA_START(ap, fmt); 1580Sstevel@tonic-gate sm_io_vfprintf(SmDebugOutput, SmDebugOutput->f_timeout, fmt, ap); 1590Sstevel@tonic-gate SM_VA_END(ap); 1600Sstevel@tonic-gate } 1610Sstevel@tonic-gate 1620Sstevel@tonic-gate /* 1630Sstevel@tonic-gate ** SM_DFLUSH -- Flush debug output. 1640Sstevel@tonic-gate ** 1650Sstevel@tonic-gate ** Parameters: 1660Sstevel@tonic-gate ** none. 1670Sstevel@tonic-gate ** 1680Sstevel@tonic-gate ** Returns: 1690Sstevel@tonic-gate ** none. 1700Sstevel@tonic-gate */ 1710Sstevel@tonic-gate 1720Sstevel@tonic-gate void 1730Sstevel@tonic-gate sm_dflush() 1740Sstevel@tonic-gate { 1750Sstevel@tonic-gate sm_io_flush(SmDebugOutput, SM_TIME_DEFAULT); 1760Sstevel@tonic-gate } 1770Sstevel@tonic-gate 1780Sstevel@tonic-gate /* 1790Sstevel@tonic-gate ** This is the internal database of debug settings. 1800Sstevel@tonic-gate ** The semantics of looking up a setting in the settings database 1810Sstevel@tonic-gate ** are that the *last* setting specified in a -d option on the sendmail 1820Sstevel@tonic-gate ** command line that matches a given SM_DEBUG structure is the one that is 1830Sstevel@tonic-gate ** used. That is necessary to conform to the existing semantics of 1840Sstevel@tonic-gate ** the sendmail -d option. We store the settings as a linked list in 1850Sstevel@tonic-gate ** reverse order, so when we do a lookup, we take the *first* entry 1860Sstevel@tonic-gate ** that matches. 1870Sstevel@tonic-gate */ 1880Sstevel@tonic-gate 1890Sstevel@tonic-gate typedef struct sm_debug_setting SM_DEBUG_SETTING_T; 1900Sstevel@tonic-gate struct sm_debug_setting 1910Sstevel@tonic-gate { 1920Sstevel@tonic-gate const char *ds_pattern; 1930Sstevel@tonic-gate unsigned int ds_level; 1940Sstevel@tonic-gate SM_DEBUG_SETTING_T *ds_next; 1950Sstevel@tonic-gate }; 1960Sstevel@tonic-gate SM_DEBUG_SETTING_T *SmDebugSettings = NULL; 1970Sstevel@tonic-gate 1980Sstevel@tonic-gate /* 1990Sstevel@tonic-gate ** We keep a linked list of SM_DEBUG structures that have been initialized, 2000Sstevel@tonic-gate ** for use by sm_debug_reset. 2010Sstevel@tonic-gate */ 2020Sstevel@tonic-gate 2030Sstevel@tonic-gate SM_DEBUG_T *SmDebugInitialized = NULL; 2040Sstevel@tonic-gate 2050Sstevel@tonic-gate const char SmDebugMagic[] = "sm_debug"; 2060Sstevel@tonic-gate 2070Sstevel@tonic-gate /* 2080Sstevel@tonic-gate ** SM_DEBUG_RESET -- Reset SM_DEBUG structures. 2090Sstevel@tonic-gate ** 2100Sstevel@tonic-gate ** Reset all SM_DEBUG structures back to the uninitialized state. 2110Sstevel@tonic-gate ** This is used by sm_debug_addsetting to ensure that references to 2120Sstevel@tonic-gate ** SM_DEBUG structures that occur before sendmail processes its -d flags 2130Sstevel@tonic-gate ** do not cause those structures to be permanently forced to level 0. 2140Sstevel@tonic-gate ** 2150Sstevel@tonic-gate ** Parameters: 2160Sstevel@tonic-gate ** none. 2170Sstevel@tonic-gate ** 2180Sstevel@tonic-gate ** Returns: 2190Sstevel@tonic-gate ** none. 2200Sstevel@tonic-gate */ 2210Sstevel@tonic-gate 2220Sstevel@tonic-gate static void 2230Sstevel@tonic-gate sm_debug_reset() 2240Sstevel@tonic-gate { 2250Sstevel@tonic-gate SM_DEBUG_T *debug; 2260Sstevel@tonic-gate 2270Sstevel@tonic-gate for (debug = SmDebugInitialized; 2280Sstevel@tonic-gate debug != NULL; 2290Sstevel@tonic-gate debug = debug->debug_next) 2300Sstevel@tonic-gate { 2310Sstevel@tonic-gate debug->debug_level = SM_DEBUG_UNKNOWN; 2320Sstevel@tonic-gate } 2330Sstevel@tonic-gate SmDebugInitialized = NULL; 2340Sstevel@tonic-gate } 2350Sstevel@tonic-gate 2360Sstevel@tonic-gate /* 2370Sstevel@tonic-gate ** SM_DEBUG_ADDSETTING_X -- add an entry to the database of debug settings 2380Sstevel@tonic-gate ** 2390Sstevel@tonic-gate ** Parameters: 2400Sstevel@tonic-gate ** pattern -- a shell-style glob pattern (see sm_match). 2410Sstevel@tonic-gate ** WARNING: the storage for 'pattern' will be owned by 2420Sstevel@tonic-gate ** the debug package, so it should either be a string 2430Sstevel@tonic-gate ** literal or the result of a call to sm_strdup_x. 2440Sstevel@tonic-gate ** level -- a non-negative integer. 2450Sstevel@tonic-gate ** 2460Sstevel@tonic-gate ** Returns: 2470Sstevel@tonic-gate ** none. 2480Sstevel@tonic-gate ** 2490Sstevel@tonic-gate ** Exceptions: 2500Sstevel@tonic-gate ** F:sm_heap -- out of memory 2510Sstevel@tonic-gate */ 2520Sstevel@tonic-gate 2530Sstevel@tonic-gate void 2540Sstevel@tonic-gate sm_debug_addsetting_x(pattern, level) 2550Sstevel@tonic-gate const char *pattern; 2560Sstevel@tonic-gate int level; 2570Sstevel@tonic-gate { 2580Sstevel@tonic-gate SM_DEBUG_SETTING_T *s; 2590Sstevel@tonic-gate 2600Sstevel@tonic-gate SM_REQUIRE(pattern != NULL); 2610Sstevel@tonic-gate SM_REQUIRE(level >= 0); 2620Sstevel@tonic-gate s = sm_malloc_x(sizeof(SM_DEBUG_SETTING_T)); 2630Sstevel@tonic-gate s->ds_pattern = pattern; 2640Sstevel@tonic-gate s->ds_level = (unsigned int) level; 2650Sstevel@tonic-gate s->ds_next = SmDebugSettings; 2660Sstevel@tonic-gate SmDebugSettings = s; 2670Sstevel@tonic-gate sm_debug_reset(); 2680Sstevel@tonic-gate } 2690Sstevel@tonic-gate 2700Sstevel@tonic-gate /* 2710Sstevel@tonic-gate ** PARSE_NAMED_SETTING_X -- process a symbolic debug setting 2720Sstevel@tonic-gate ** 2730Sstevel@tonic-gate ** Parameters: 2740Sstevel@tonic-gate ** s -- Points to a non-empty \0 or , terminated string, 2750Sstevel@tonic-gate ** of which the initial character is not a digit. 2760Sstevel@tonic-gate ** 2770Sstevel@tonic-gate ** Returns: 2780Sstevel@tonic-gate ** pointer to terminating \0 or , character. 2790Sstevel@tonic-gate ** 2800Sstevel@tonic-gate ** Exceptions: 2810Sstevel@tonic-gate ** F:sm.heap -- out of memory. 2820Sstevel@tonic-gate ** 2830Sstevel@tonic-gate ** Side Effects: 2840Sstevel@tonic-gate ** adds the setting to the database. 2850Sstevel@tonic-gate */ 2860Sstevel@tonic-gate 2870Sstevel@tonic-gate static const char * 2880Sstevel@tonic-gate parse_named_setting_x(s) 2890Sstevel@tonic-gate const char *s; 2900Sstevel@tonic-gate { 2910Sstevel@tonic-gate const char *pat, *endpat; 2920Sstevel@tonic-gate int level; 2930Sstevel@tonic-gate 2940Sstevel@tonic-gate pat = s; 2950Sstevel@tonic-gate while (*s != '\0' && *s != ',' && *s != '.') 2960Sstevel@tonic-gate ++s; 2970Sstevel@tonic-gate endpat = s; 2980Sstevel@tonic-gate if (*s == '.') 2990Sstevel@tonic-gate { 3000Sstevel@tonic-gate ++s; 3010Sstevel@tonic-gate level = 0; 3020Sstevel@tonic-gate while (isascii(*s) && isdigit(*s)) 3030Sstevel@tonic-gate { 3040Sstevel@tonic-gate level = level * 10 + (*s - '0'); 3050Sstevel@tonic-gate ++s; 3060Sstevel@tonic-gate } 3070Sstevel@tonic-gate if (level < 0) 3080Sstevel@tonic-gate level = 0; 3090Sstevel@tonic-gate } 3100Sstevel@tonic-gate else 3110Sstevel@tonic-gate level = 1; 3120Sstevel@tonic-gate 3130Sstevel@tonic-gate sm_debug_addsetting_x(sm_strndup_x(pat, endpat - pat), level); 3140Sstevel@tonic-gate 3150Sstevel@tonic-gate /* skip trailing junk */ 3160Sstevel@tonic-gate while (*s != '\0' && *s != ',') 3170Sstevel@tonic-gate ++s; 3180Sstevel@tonic-gate 3190Sstevel@tonic-gate return s; 3200Sstevel@tonic-gate } 3210Sstevel@tonic-gate 3220Sstevel@tonic-gate /* 3230Sstevel@tonic-gate ** SM_DEBUG_ADDSETTINGS_X -- process a list of debug options 3240Sstevel@tonic-gate ** 3250Sstevel@tonic-gate ** Parameters: 3260Sstevel@tonic-gate ** s -- a list of debug settings, eg the argument to the 3270Sstevel@tonic-gate ** sendmail -d option. 3280Sstevel@tonic-gate ** 3290Sstevel@tonic-gate ** The syntax of the string s is as follows: 3300Sstevel@tonic-gate ** 3310Sstevel@tonic-gate ** <settings> ::= <setting> | <settings> "," <setting> 3320Sstevel@tonic-gate ** <setting> ::= <categories> | <categories> "." <level> 3330Sstevel@tonic-gate ** <categories> ::= [a-zA-Z_*?][a-zA-Z0-9_*?]* 3340Sstevel@tonic-gate ** 3350Sstevel@tonic-gate ** However, note that we skip over anything we don't 3360Sstevel@tonic-gate ** understand, rather than report an error. 3370Sstevel@tonic-gate ** 3380Sstevel@tonic-gate ** Returns: 3390Sstevel@tonic-gate ** none. 3400Sstevel@tonic-gate ** 3410Sstevel@tonic-gate ** Exceptions: 3420Sstevel@tonic-gate ** F:sm.heap -- out of memory 3430Sstevel@tonic-gate ** 3440Sstevel@tonic-gate ** Side Effects: 3450Sstevel@tonic-gate ** updates the database of debug settings. 3460Sstevel@tonic-gate */ 3470Sstevel@tonic-gate 3480Sstevel@tonic-gate void 3490Sstevel@tonic-gate sm_debug_addsettings_x(s) 3500Sstevel@tonic-gate const char *s; 3510Sstevel@tonic-gate { 3520Sstevel@tonic-gate for (;;) 3530Sstevel@tonic-gate { 3540Sstevel@tonic-gate if (*s == '\0') 3550Sstevel@tonic-gate return; 3560Sstevel@tonic-gate if (*s == ',') 3570Sstevel@tonic-gate { 3580Sstevel@tonic-gate ++s; 3590Sstevel@tonic-gate continue; 3600Sstevel@tonic-gate } 3610Sstevel@tonic-gate s = parse_named_setting_x(s); 3620Sstevel@tonic-gate } 3630Sstevel@tonic-gate } 3640Sstevel@tonic-gate 3650Sstevel@tonic-gate /* 3660Sstevel@tonic-gate ** SM_DEBUG_LOADLEVEL -- Get activation level of the specified debug object. 3670Sstevel@tonic-gate ** 3680Sstevel@tonic-gate ** Parameters: 3690Sstevel@tonic-gate ** debug -- debug object. 3700Sstevel@tonic-gate ** 3710Sstevel@tonic-gate ** Returns: 3720Sstevel@tonic-gate ** Activation level of the specified debug object. 3730Sstevel@tonic-gate ** 3740Sstevel@tonic-gate ** Side Effects: 3750Sstevel@tonic-gate ** Ensures that the debug object is initialized. 3760Sstevel@tonic-gate */ 3770Sstevel@tonic-gate 3780Sstevel@tonic-gate int 3790Sstevel@tonic-gate sm_debug_loadlevel(debug) 3800Sstevel@tonic-gate SM_DEBUG_T *debug; 3810Sstevel@tonic-gate { 3820Sstevel@tonic-gate if (debug->debug_level == SM_DEBUG_UNKNOWN) 3830Sstevel@tonic-gate { 3840Sstevel@tonic-gate SM_DEBUG_SETTING_T *s; 3850Sstevel@tonic-gate 3860Sstevel@tonic-gate for (s = SmDebugSettings; s != NULL; s = s->ds_next) 3870Sstevel@tonic-gate { 3880Sstevel@tonic-gate if (sm_match(debug->debug_name, s->ds_pattern)) 3890Sstevel@tonic-gate { 3900Sstevel@tonic-gate debug->debug_level = s->ds_level; 3910Sstevel@tonic-gate goto initialized; 3920Sstevel@tonic-gate } 3930Sstevel@tonic-gate } 3940Sstevel@tonic-gate debug->debug_level = 0; 3950Sstevel@tonic-gate initialized: 3960Sstevel@tonic-gate debug->debug_next = SmDebugInitialized; 3970Sstevel@tonic-gate SmDebugInitialized = debug; 3980Sstevel@tonic-gate } 3990Sstevel@tonic-gate return (int) debug->debug_level; 4000Sstevel@tonic-gate } 4010Sstevel@tonic-gate 4020Sstevel@tonic-gate /* 4030Sstevel@tonic-gate ** SM_DEBUG_LOADACTIVE -- Activation level reached? 4040Sstevel@tonic-gate ** 4050Sstevel@tonic-gate ** Parameters: 4060Sstevel@tonic-gate ** debug -- debug object. 4070Sstevel@tonic-gate ** level -- level to check. 4080Sstevel@tonic-gate ** 4090Sstevel@tonic-gate ** Returns: 4100Sstevel@tonic-gate ** true iff the activation level of the specified debug 4110Sstevel@tonic-gate ** object >= level. 4120Sstevel@tonic-gate ** 4130Sstevel@tonic-gate ** Side Effects: 4140Sstevel@tonic-gate ** Ensures that the debug object is initialized. 4150Sstevel@tonic-gate */ 4160Sstevel@tonic-gate 4170Sstevel@tonic-gate bool 4180Sstevel@tonic-gate sm_debug_loadactive(debug, level) 4190Sstevel@tonic-gate SM_DEBUG_T *debug; 4200Sstevel@tonic-gate int level; 4210Sstevel@tonic-gate { 4220Sstevel@tonic-gate return sm_debug_loadlevel(debug) >= level; 4230Sstevel@tonic-gate } 424