Snmpset not working on an agent generated by mib2c - c

I generated code from MIB file with mib2c. When I try to set object with read-write access, it returns Error in packet. Reason: notWritable (That object does not support modification.
I tried to run my subagent with few debug flags. I found out that not a single function generated code is called on snmpset request, only on snmpget. smnpget on exactly same OID will return valid value. I have user with RW access everywhere. I can set value to sysName.0 with same user. I tried removing MIB file and use exact oid but had same result.
Because It's not even reaching code, I don't know much what to do.
I tried it with 2 tables generated same way.
One table has index as IMPLIED DisplayString and second table has INDEX as combination of 2 INTEGERs.
EDIT:
I found out that it created .conf file in /var/lib/snmp/ for each my agent. I tried to add create_user with same name & password but it disappeared after agent was started again.
EDIT2:
Code was generetad using mib2c.mfd.conf . I tried mib2c.iterate.conf and it called function from generated code. It's not working with mib2c.mfd.conf but looks like it will work with mib2c.iterate.conf . I would like to be able make it works with mib2c.mfd.conf so I wouldn't need to change all subagents.
Output from my subagent where 3.fw is index:
agentx/subagent: checking status of session 0x44150
agentx_build: packet built okay
agentx/subagent: synching input, op 0x01
agentx/subagent: session 0x44150 responded to ping
agentx/subagent: handling AgentX request (req=0x1f9,trans=0x1f8,sess=0x21)
agentx/subagent: -> testset
snmp_agent: agent_sesion 0xc4a08 created
snmp_agent: add_vb_to_cache( 0xc4a08, 1, MSE-CONFIGURATION-MIB::mseDpuConfigActivationAdminStatus.3.fw, 0x3d3d0)
snmp_agent: tp->start MSE-CONFIGURATION-MIB::mseDpuConfigActivationTable, tp->end MSE-CONFIGURATION-MIB::mseDpuConfigActivation.3,
agent_set: doing set mode = 0 (SET_RESERVE1)
agent_set: did set mode = 0, status = 17
results: request results (status = 17):
results: MSE-CONFIGURATION-MIB::mseDpuConfigActivationAdminStatus.3.fw = INTEGER: prepare(1)
snmp_agent: REMOVE session == 0xc4a08
snmp_agent: agent_session 0xc4a08 released
snmp_agent: end of handle_snmp_packet, asp = 0xc4a08
agentx/subagent: handling agentx subagent set response (mode=162,req=0x1f9,trans=0x1f8,sess=0x21)
agentx_build: packet built okay
agentx/subagent: FINISHED
agentx/subagent: handling AgentX request (req=0x1fa,trans=0x1f8,sess=0x21)
agentx/subagent: -> cleanupset
snmp_agent: agent_sesion 0xc7640 created
agent_set: doing set mode = 4 (SET_FREE)
agent_set: did set mode = 4, status = 17
results: request results (status = 17):
results: MSE-CONFIGURATION-MIB::mseDpuConfigActivationAdminStatus.3.fw = INTEGER: prepare(1)
snmp_agent: REMOVE session == 0xc7640
snmp_agent: agent_session 0xc7640 released
snmp_agent: end of handle_snmp_packet, asp = 0xc7640
agentx/subagent: handling agentx subagent set response (mode=162,req=0x1fa,trans=0x1f8,sess=0x21)
agentx_build: packet built okay
agentx/subagent: FINISHED
agentx/subagent: checking status of session 0x44150
agentx_build: packet built okay
agentx/subagent: synching input, op 0x01
agentx/subagent: session 0x44150 responded to ping
Values/config used for generating code:
## defaults
#eval $m2c_context_reg = "netsnmp_data_list"#
#eval $m2c_data_allocate = 0#
#eval $m2c_data_cache = 1#
#eval $m2c_data_context = "generated"# [generated|NAME]
#eval $m2c_data_init = 1#
#eval $m2c_data_transient = 0#
#eval $m2c_include_examples = 1#
#eval $m2c_irreversible_commit = 0#
#eval $m2c_table_access = "container-cached"#
#eval $m2c_table_dependencies = 0#
#eval $m2c_table_persistent = 0#
#eval $m2c_table_row_creation = 0#
#eval $m2c_table_settable = 1#
#eval $m2c_table_skip_mapping = 1#
#eval $m2c_table_sparse = 1#
#eval $mfd_generate_makefile = 1#
#eval $mfd_generate_subagent = 1#
SNMPd version:
# snmpd --version
NET-SNMP version: 5.9
Web: http://www.net-snmp.org/
Email: net-snmp-coders#lists.sourceforge.net

I found out that in generated file *_interface.c from mib2c.mfd.conf template, there is inverted check.
#if !(defined(NETSNMP_NO_WRITE_SUPPORT) || defined(NETSNMP_DISABLE_SET_SUPPORT))
HANDLER_CAN_RONLY
#else
HANDLER_CAN_RWRITE
#endif /* NETSNMP_NO_WRITE_SUPPORT || NETSNMP_DISABLE_SET_SUPPORT */
I removed ! from condition and it stared working. Both defines are undefined so it should use HANDLER_CAN_RWRITE but because of wrong check it used HANDLER_CAN_RONLY.

Related

Python 3, extract info from file problems

And again, asking for help. But, before I start, here will be a lot of text, so please sorry for that.
I have about 500~ IP addresses with devices 2x categories in .xlsx book
I want:
telnet to device. Check device (by authentication prompt) type 1 or type 2.
If device is type 1 - get it firmware version in 2x partitions
write in excel file:
column 1 - IP address
column 2 - device type
column 3 - firmware version
column 4 - firmware version in reserve partition.
If type 2 - write in excel file:
column 1 - IP address
column 2 - device type
If device is down, or device type 3(unknown) - write in excel file:
column 1 - IP address
column 2 - result (EOF, TIMEOUT)
What I have done: I'm able to telnet to device, check device type, write in excel with 2 columns (in 1 column IP addresses, in 2 column is device type, or EOF/TIMEOUT results)
And, I'm writing full logs from session to files in format IP_ADDRESS.txt to future diagnosis.
What I can't understand to do? I can't understand how to get firmware version, and put it on 3,4 columns.
I can't understand how to work with current log session in real time, so I've decided to copy logs from main file (IP_ADDRESS.txt) to temp.txt to work with it.
I can't understand how to extract information I needed.
The file output example:
Trying 10.40.81.167...
Connected to 10.40.81.167.
Escape character is '^]'.
####################################
# #
# RADIUS authorization disabled #
# Enter local login/password #
# #
####################################
bt6000 login: admin
Password:
Please, fill controller information at first time (Ctrl+C to abort):
^C
Controller information filling canceled.
^Cadmin#bt6000# firmware info
Active boot partition: 1
Partition 0 (reserved):
Firmware: Energomera-2.3.1
Version: 10117
Partition 1 (active):
Firmware: Energomera-2.3.1_01.04.15c
Version: 10404M
Kernel version: 2.6.38.8 #2 Mon Mar 2 20:41:26 MSK 2015
STM32:
Version: bt6000 10083
Part Number: BT6024
Updated: 27.04.2015 16:43:50
admin#bt6000#
I need values - after "Energomera" words, like 2.3.1 for reserved partition, and 2.3.1_01.04.15c for active partition.
I've tried to work with string numbers and excract string, but there was not any kind of good result at all.
Full code of my script below.
import pexpect
import pxssh
import sys #hz module
import re #Parser module
import os #hz module
import getopt
import glob #hz module
import xlrd #Excel read module
import xlwt #Excel write module
import telnetlib #telnet module
import shutil
#open excel book
rb = xlrd.open_workbook('/samba/allaccess/Energomera_Eltek_list.xlsx')
#select work sheet
sheet = rb.sheet_by_name('IPs')
#rows number in sheet
num_rows = sheet.nrows
#cols number in sheet
num_cols = sheet.ncols
#creating massive with IP addresses inside
ip_addr_list = [sheet.row_values(rawnum)[0] for rawnum in range(sheet.nrows)]
#create excel workbook with write permissions (xlwt module)
wb = xlwt.Workbook()
#create sheet IP LIST with cell overwrite rights
ws = wb.add_sheet('IP LIST', cell_overwrite_ok=True)
#create counter
i = 0
#authorization details
port = "23" #telnet port
user = "admin" #telnet username
password = "12345" #telnet password
#firmware ask function
def fw_info():
print('asking for firmware')
px.sendline('firmware info')
px.expect('bt6000#')
#firmware update function
def fw_send():
print('sending firmware')
px.sendline('tftp server 172.27.2.21')
px.expect('bt6000')
px.sendline('firmware download tftp firmware.ext2')
px.expect('Updating')
px.sendline('y')
px.send(chr(13))
ws.write(i, 0, host)
ws.write(i, 1, 'Energomera')
#if eltek found - skip, write result in book
def eltek_found():
print(host, "is Eltek. Skipping")
ws.write(i, 0, host)
ws.write(i, 1, 'Eltek')
#if 23 port telnet conn. refused - skip, write result in book
def conn_refuse():
print(host, "connection refused")
ws.write(i, 0, host)
ws.write(i, 1, 'Connection refused')
#auth function
def auth():
print(host, "is up! Energomera found. Starting auth process")
px.sendline(user)
px.expect('assword')
px.sendline(password)
#start working with ip addresses in ip_addr_list massive
for host in ip_addr_list:
#spawn pexpect connection
px = pexpect.spawn('telnet ' + host)
px.timeout = 35
#create log file with in IP.txt format (10.1.1.1.txt, for example)
fout = open('/samba/allaccess/Energomera_Eltek/{0}.txt'.format(host),"wb")
#push pexpect logfile_read output to log file
px.logfile_read = fout
try:
index = px.expect (['bt6000', 'sername', 'refused'])
#if device tell us bt6000 - authorize
if index == 0:
auth()
index1 = px.expect(['#', 'lease'])
#if "#" - ask fw version immediatly
if index1 == 0:
print('seems to controller ID already set')
fw_info()
#if "Please" - press 2 times Ctrl+C, then ask fw version
elif index1 == 1:
print('trying control C controller ID')
px.send(chr(3))
px.send(chr(3))
px.expect('bt6000')
fw_info()
#firmware update start (temporarily off)
# fw_send()
#Eltek found - func start
elif index == 1:
eltek_found()
#Conn refused - func start
elif index == 2:
conn_refuse()
#print output to console (test purposes)
print(px.before)
px.send(chr(13))
#Copy from current log file to temp.txt for editing
shutil.copy2('/samba/allaccess/Energomera_Eltek/{0}.txt'.format(host), '/home/bark/expect/temp.txt')
#EOF result - skip host, write result to excel
except pexpect.EOF:
print(host, "EOF")
ws.write(i, 0, host)
ws.write(i, 1, 'EOF')
#print output to console (test purposes)
print(px.before)
#Timeout result - skip host, write result to excel
except pexpect.TIMEOUT:
print(host, "TIMEOUT")
ws.write(i, 0, host)
ws.write(i, 1, 'TIMEOUT')
#print output to console (test purposes)
print(px.before)
#Copy from current log file to temp.txt for editing
shutil.copy2('/samba/allaccess/Energomera_Eltek/{0}.txt'.format(host), '/home/bark/expect/temp.txt')
#count +1 to correct output for Excel
i += 1
#workbook save
wb.save('/samba/allaccess/Energomera_Eltek_result.xls')
Have you have any suggestions or ideas, guys, how I can do this?
Any help is greatly appreciated.
You can use regular expressions
example:
>>> import re
>>>
>>> str = """
... Trying 10.40.81.167...
...
... Connected to 10.40.81.167.
...
... Escape character is '^]'.
...
...
...
... ####################################
... # #
... # RADIUS authorization disabled #
... # Enter local login/password #
... # #
... ####################################
... bt6000 login: admin
... Password:
... Please, fill controller information at first time (Ctrl+C to abort):
... ^C
... Controller information filling canceled.
... ^Cadmin#bt6000# firmware info
... Active boot partition: 1
... Partition 0 (reserved):
... Firmware: Energomera-2.3.1
... Version: 10117
... Partition 1 (active):
... Firmware: Energomera-2.3.1_01.04.15c
... Version: 10404M
... Kernel version: 2.6.38.8 #2 Mon Mar 2 20:41:26 MSK 2015
... STM32:
... Version: bt6000 10083
... Part Number: BT6024
... Updated: 27.04.2015 16:43:50
... admin#bt6000#
... """
>>> re.findall(r"Firmware:.*?([0-9].*)\s", str)
['2.3.1', '2.3.1_01.04.15c']
>>> reserved_firmware = re.search(r"reserved.*\s*Firmware:.*?([0-9].*)\s", str).group(1)
>>> reserved_firmware
'2.3.1'
>>> active_firmware = re.search(r"active.*\s*Firmware:.*?([0-9].*)\s", str).group(1)
>>> active_firmware
'2.3.1_01.04.15c'
>>>

dbWriteTable in RMySQL error in name pasting

i have many data.frames() that i am trying to send to MySQL database via RMySQL().
# Sends data frame to database without a problem
dbWriteTable(con3, name="SPY", value=SPY , append=T)
# stock1 contains a character vector of stock names...
stock1 <- c("SPY.A")
But when I try to loop it:
i= 1
while(i <= length(stock1)){
# converts "SPY.A" into SPY
name <- print(paste0(str_sub(stock1, start = 1, end = -3))[i], quote=F)
# sends data.frame to database
dbWriteTable(con3,paste0(str_sub(stock1, start = 1, end = -3))[i], value=name, append=T)
i <- 1+i
}
The following warning is returned & nothing was sent to database
In addition: Warning message:
In file(fn, open = "r") :
cannot open file './SPY': No such file or directory
However, I believe that the problem is with pasting value onto dbWriteTable() since writing dbWriteTable(con3, "SPY", SPY, append=T) works but dbWriteTable(con3, "SPY", name, append=T) will not...
You are probably using a non-base package for str_sub and I'm guessing you get the same behavior with substr. Does this succeed?
dbWriteTable(con3, substr( stock1, 1,3) , get(stock1), append=T)

In Django how can I run a custom clean function on fixture data during import and validation?

In a ModelForm I can write a clean_<field_name> member function to automatically validate and clean up data entered by a user, but what can I do about dirty json or csv files (fixtures) during a manage.py loaddata?
Fixtures loaded with loaddata are assumed to contain clean data that doen't need validation (usually as an inverse operation to a prior dumpdata), so the short answer is that loaddata isn't the approach you want if you need to clean your inputs.
However, you probably can use some of the underpinnings of loaddata while implementing your custom data cleaning code--I'm sure you can easily script something using the Django serialization libs to read your existing data files them in and the save the resulting objects normally after the data has been cleaned up.
In case others want to do something similar, I defined a model method to do the cleaning (so it can be called from ModelForms)
MAX_ZIPCODE_DIGITS = 9
MIN_ZIPCODE_DIGITS = 5
def clean_zip_code(self, s=None):
#s = str(s or self.zip_code)
if not s: return None
s = re.sub("\D","",s)
if len(s)>self.MAX_ZIPCODE_DIGITS:
s = s[:self.MAX_ZIPCODE_DIGITS]
if len(s) in (self.MIN_ZIPCODE_DIGITS-1,self.MAX_ZIPCODE_DIGITS-1):
s = '0'+s # FIXME: deal with other intermediate lengths
if len(s)>=self.MAX_ZIPCODE_DIGITS:
s = s[:self.MIN_ZIPCODE_DIGITS]+'-'+s[self.MIN_ZIPCODE_DIGITS:]
return s
Then wrote a standalone python script to clean up my legacy json files using any clean_ methods found among the models.
import os, json
def clean_json(app = 'XYZapp', model='Entity', fields='zip_code', cleaner_prefix='clean_'):
# Set the DJANGO_SETTINGS_MODULE environment variable.
os.environ['DJANGO_SETTINGS_MODULE'] = app+".settings"
settings = __import__(app+'.settings').settings
models = __import__(app+'.models').models
fpath = os.path.join( settings.SITE_PROJECT_PATH, 'fixtures', model+'.json')
if isinstance(fields,(str,unicode)):
fields = [fields]
Ns = []
for field in fields:
try:
instance = getattr(models,model)()
except AttributeError:
print 'No model named %s could be found'%(model,)
continue
try:
cleaner = getattr(instance, cleaner_prefix+field)
except AttributeError:
print 'No cleaner method named %s.%s could be found'%(model,cleaner_prefix+field)
continue
print 'Cleaning %s using %s.%s...'%(fpath,model,cleaner.__name__)
fin = open(fpath,'r')
if fin:
l = json.load(fin)
before = len(l)
cleans = 0
for i in range(len(l)):
if 'fields' in l[i] and field in l[i]['fields']:
l[i]['fields'][field]=cleaner(l[i]['fields'][field]) # cleaner returns None to delete records
cleans += 1
fin.close()
after = len(l)
assert after>.5*before
Ns += [(before, after,cleans)]
print 'Writing %d/%d (new/old) records after %d cleanups...'%Ns[-1]
with open(fpath,'w') as fout:
fout.write(json.dumps(l,indent=2,sort_keys=True))
return Ns
if __name__ == '__main__':
clean_json()

Ldap error code 32

I'm trying to synchronize OpenLDAP and Active directory together. To do so I'm using a program called LSC-Project which is specified to do this sort of thing.
I have configured the program the best I can however I can't find a way to shake off the following error:
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-
031001CD,
problem 2001 (NO_OBJECT), data 0, best match of:
'DC=domname,DC=com'
]; remaining name
'uid=user1,ou=Users'
May 09 15:19:25 - ERROR - Error while synchronizing ID uid=user1,ou=Users:
java.lang.Exception:
Technical problem while applying modifications to directory
dn: uid=user1,ou=Users,dc=domname,dc=com
changetype: add
userPassword: 3+kU2th/WMo/v553A24a3SBw2kU=
objectClass: uid
This is the configuration file that the program runs on:
###############################
Destination LDAP directory #
##############################
dst.java.naming.provider.url = ldap://192.168.1.3:389/dc=Windows,dc=com
dst.java.naming.security.authentication = simple
dst.java.naming.security.principal = cn=Administrator,cn=Users,dc=Windows,dc=com
dst.java.naming.security.credentials = 11111
dst.java.naming.referral = ignore
dst.java.naming.ldap.derefAliases = never
dst.java.naming.factory.initial = com.sun.jndi.ldap.LdapCtxFactory
dst.java.naming.ldap.version = 3
dst.java.naming.ldap.pageSize = 1000
#########################
Source LDAP directory
#########################
src.java.naming.provider.url = ldap://192.168.1.2:389/dc=Linux,dc=com
src.java.naming.security.authentication = simple
src.java.naming.security.principal = uid=root,ou=users,dc=Linux,dc=com
src.java.naming.security.credentials = 11111
src.java.naming.referral = ignore
src.java.naming.ldap.derefAliases = never
src.java.naming.factory.initial = com.sun.jndi.ldap.LdapCtxFactory
src.java.naming.ldap.version = 3
#######################
Tasks configuration
#######################
lsc.tasks = Administrator
lsc.tasks.Administrator.srcService = org.lsc.jndi.SimpleJndiSrcService
lsc.tasks.Administrator.srcService.baseDn = ou=users
lsc.tasks.Administrator.srcService.filterAll = (&(objectClass=person))
lsc.tasks.Administrator.srcService.pivotAttrs = uid
lsc.tasks.Administrator.srcService.filterId = (&(objectClass=person)(uid={uid}))
lsc.tasks.Administrator.srcService.attrs = description uid userPassword
lsc.tasks.Administrator.dstService = org.lsc.jndi.SimpleJndiDstService
lsc.tasks.Administrator.dstService.baseDn = cn=Users
lsc.tasks.Administrator.dstService.filterAll = (&(cn=*)(objectClass=organizationalPerson))
lsc.tasks.Administrator.dstService.pivotAttrs = cn, top, person, user, organizationalPerson
lsc.tasks.Administrator.dstService.filterId = (&(objectClass=user) (sAMAccountName={cn}))
lsc.tasks.Administrator.dstService.attrs = description cn userPassword objectClass
lsc.tasks.Administrator.bean = org.lsc.beans.SimpleBean
lsc.tasks.Administrator.dn = "uid=" + srcBean.getAttributeValueById("uid") + ",ou=Users"
dn.real_root = dc=Domname,dc=com
#############################
Syncoptions configuration
#############################
lsc.syncoptions.Administrator = org.lsc.beans.syncoptions.PropertiesBasedSyncOptions
lsc.syncoptions.Administrator.default.action = M
lsc.syncoptions.Administrator.objectClass.action = M
lsc.syncoptions.Administrator.objectClass.force_value = srcBean.getAttributeValueById("cn").toUpperCase()
lsc.syncoptions.Administrator.userPassword.default_value = SecurityUtils.hash(SecurityUtils.HASH_SHA1, "defaultPassword")
lsc.syncoptions.Administrator.default.delimiter=;
lsc.syncoptions.Administrator.objectClass.force_value = "top";"user";"person";"organizationalPerson"
lsc.syncoptions.Administrator.userPrincipalName.force_value = srcBean.getAttributeValueById("uid") + "#Domname.com"
lsc.syncoptions.Administrator.userAccountControl.create_value = AD.userAccountControlSet ( "0", [AD.UAC_SET_NORMAL_ACCOUNT])
I'm suspecting that it has something to do with the baseDn of the Task configuration in the part of the source configuration.
The OSs is ubuntu 10.04 and Windows2K3
Someone suggested to me to make a manual sync between them but I have not found any guides to do so. And this program is pretty much the only thing that says that is does this kind of job without costs.
The baseDn should be the distinguished name of the base object of the search, for example, ou=users,dc=domname,dc=com.
see also
LDAP: Mastering Search Filters
LDAP: Search best practices
LDAP: Programming practices
The main reason for NameNotFoundException is that the object which you're searching doesn't exist or the container in which you are searching is not correct.
In case of Spring-ldap, we used to get this error when we specify the baseDn in the context file(LdapContextSource bean) and also in createUser code to build userDn.we need not specify the dc again in the buildUserDn()
protected Name buildUserDn(String userName) {
DistinguishedName dn = new DistinguishedName();
//only cn is required as the base dn is already specified in context file
dn.add("cn", userName);
return dn;
}
In Active Directory: Users catalog is container class, not OrganizationalUnit, so you should use: cn=users,dc=domname,dc=com

waf - build works, custom build targets fail

The waf command waf build shows compiler errors (if there are any) while waf debug or waf release does not and always fails, utilizing the following wscript file (or maybe the wscript file has some other shortcomings I am currently not aware of):
APPNAME = 'waftest'
VERSION = '0.0.1'
def configure(ctx):
ctx.load('compiler_c')
ctx.define('VERSION', VERSION)
ctx.define('GETTEXT_PACKAGE', APPNAME)
ctx.check_cfg(atleast_pkgconfig_version='0.1.1')
ctx.check_cfg(package='glib-2.0', uselib_store='GLIB', args=['--cflags', '--libs'], mandatory=True)
ctx.check_cfg(package='gobject-2.0', uselib_store='GOBJECT', args=['--cflags', '--libs'], mandatory=True)
ctx.check_cfg(package='gtk+-3.0', uselib_store='GTK3', args=['--cflags', '--libs'], mandatory=True)
ctx.check_cfg(package='libxml-2.0', uselib_store='XML', args=['--cflags', '--libs'], mandatory=True)
ctx.check_large_file(mandatory=False)
ctx.check_endianness(mandatory=False)
ctx.check_inline(mandatory=False)
ctx.setenv('debug')
ctx.env.CFLAGS = ['-g', '-Wall']
ctx.define('DEBUG',1)
ctx.setenv('release')
ctx.env.CFLAGS = ['-O2', '-Wall']
ctx.define('RELEASE',1)
def pre(ctx):
print ('Building [[[' + ctx.variant + ']]] ...')
def post(ctx):
print ('Building is complete.')
def build(ctx):
ctx.add_pre_fun(pre)
ctx.add_post_fun(post)
# if not ctx.variant:
# ctx.fatal('Do "waf debug" or "waf release"')
exe = ctx.program(
features = ['c', 'cprogram'],
target = APPNAME+'.bin',
source = ctx.path.ant_glob(['src/*.c']),
includes = ['src/'],
export_includes = ['src/'],
uselib = 'GOBJECT GLIB GTK3 XML'
)
# for item in exe.includes:
# print(item)
from waflib.Build import BuildContext
class release(BuildContext):
cmd = 'release'
variant = 'release'
class debug(BuildContext):
cmd = 'debug'
variant = 'debug'
Error resulting from waf debug :
Build failed
-> task in 'waftest.bin' failed (exit status -1):
{task 46697488: c qqq.c -> qqq.c.1.o}
[useless filepaths]
I had a look at the waf demos, read the wafbook at section 6.2.2 but those did not supply me with valuable information in order to fix this issue.
What's wrong, and how do I fix it?
You need to do at least the following:
def configure(ctx):
...
ctx.setenv('debug')
ctx.load('compiler_c')
...
Since the cfg.setenv function resets whole previous environment. If you want to save previous environment, you can do cfg.setenv('debug', env=cfg.env.derive()).
Also, you don't need to explicitly specify the features = ['c', 'cprogram'], since, it's redundant, when you call bld.program(...).
P.S. Don't forget to reconfigure after modifying wscript file.

Resources