// SPDX: Apache-2.0
// Karl Palsson <karlp@etactica.com> 2021
'use strict';
'require form';
'require uci';
'require fs';
'require rpc';
'require ui';
'require view';

var testlibsslv3 = function() {
	return fs.exec('/usr/bin/ldd', [ '/usr/lib/libnetsnmp.so.40' ]).then(function (res) {
		return res.stdout.includes('libssl');
	});
};

return L.view.extend({
	load: function() {
		return Promise.all([
			testlibsslv3(),
			uci.load('snmpd'),
		]);
	},

	__init__: function() {
		this.super('__init__', arguments);

		this.ro_community = null;
		this.ro_community_src = null;
		this.rw_community = null;
		this.rw_community_src = null;
		this.oid = null;
		this.ip_protocol = null;
		this.snmp_version = null;
	},

	testPassLength: function(section_id, value) {
			if (value.length < 8)
				return _('Password must be at least 8 characters');

			return true;
		},

	populateGlobalSettings: function(tab, s, havesslv3) {
		let go, g, o;

		o = s.taboption('general', form.SectionValue, '__general__',
			form.TypedSection, 'snmpd', null,
			_('Here you can configure agent settings'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = false;

		go = g.option(form.Flag, 'enabled', _('Enable SNMP'),
			_('Enable to use SNMP'));
		go.default = '0';
		go.rmempty = false;

		this.ip_protocol = g.option(form.ListValue, 'ip_protocol', _('IP version'));
		this.ip_protocol.value('ipv4', _('Only IPv4'));
		this.ip_protocol.value('ipv6', _('Only IPv6'));
		this.ip_protocol.value('ipv4/ipv6', _('IPv4 and IPv6'));
		this.ip_protocol.optional = false;
		this.ip_protocol.forcewrite = true;
		this.ip_protocol.default = 'ipv4';
		this.ip_protocol.rmempty = false;

		this.ip_protocol.cfgvalue = function(section_id) {
			let ip_protocol = uci.get('snmpd', section_id, 'ip_protocol');

			if (!ip_protocol) {
				const s = uci.get_first('snmpd', 'agent');
				if (!s)
					return null;

				const rawAddr = uci.get('snmpd', s['.name'], 'agentaddress');
				if (!rawAddr)
					return null;
				const addr = rawAddr.toUpperCase();
				const p = [];

				if (addr.match(/UDP:\d+/g))
					p.push('ipv4');

				if (addr.match(/UDP6:\d+/g))
					p.push('ipv6');

				ip_protocol = p.join('/');
			}

			return ip_protocol;
		};

		go = g.option(form.Value, 'snmp_port', _('Port'));
		go.rmempty = false;
		go.default = '161';
		go.datatype = 'port';
		go.forcewrite = true;
		go.cfgvalue = function(section_id) {
			const port = uci.get('snmpd', section_id, 'snmp_port');
			if (!port)
				return port;

			const s = uci.get_first('snmpd', 'agent');
			if (!s) return null;
			const rawAddr = uci.get('snmpd', s['.name'], 'agentaddress');
			if (!rawAddr)
				return null;

			const addr = rawAddr.toUpperCase();
			const match = addr.match(/UDP6?:(\d+)/i);

			return Array.isArray(match) && match.length > 1 ?
				match[1] : null;
		};

		go.write = L.bind(function(protocol, section_id, value) {
			const port = parseInt(value);
			const ip_protocol = protocol.formvalue(section_id);

			const addr = [];

			if (ip_protocol.match(/ipv4/g))
				addr.push('UDP:%d'.format(port));

			if (ip_protocol.match(/ipv6/g))
				addr.push('UDP6:%d'.format(port));

			if (addr.length > 0) {
				const s = uci.get_first('snmpd', 'agent');
				if (s)
					uci.set('snmpd', s['.name'], 'agentaddress', addr.join(','));
			}

			return form.Value.prototype.write.apply(this, [section_id, value]);
		}, go, this.ip_protocol);

		this.snmp_version = g.option(form.ListValue, 'snmp_version',
			_('SNMP version'),
			_('SNMP version used to monitor and control the device'));
		this.snmp_version.default = 'v1/v2c';
		this.snmp_version.rmempty = false;
		this.snmp_version.forcewrite = true;
		this.snmp_version.value('v1/v2c', _('SNMPv1 and SNMPv2c'));
		if (havesslv3) {
			this.snmp_version.value('v1/v2c/v3', _('SNMPv1, SNMPv2c and SNMPv3'));
			this.snmp_version.value('v3', _('Only SNMPv3'));
		}

		go = g.option(form.Value, '__agentxsocket', _('AgentX socket path'),
			_('Empty for disable AgentX'));
		go.rmempty = true;
		go.forcewrite = true;
		go.cfgvalue = function(section_id) {
			const s = uci.get_first('snmpd', 'agentx');
			let socket = uci.get('snmpd', s['.name'], 'agentxsocket');
			if (!socket)
				socket = this.default;
			return socket;
		};

		go.remove = function(section_id) {
			const s = uci.get_first('snmpd', 'agentx');
			if (s)
				uci.remove('snmpd', s['.name']);
		};

		go.write = function(section_id, value) {
			const s = uci.get_first('snmpd', 'agentx');
			var sid = s ? s['.name'] : uci.add('snmpd', 'agentx');
			return uci.set('snmpd', sid, 'agentxsocket', value);
		};
	},

	populateSystemSettings: function(tab, s, havesslv3) {
		let g, go, o;

		o = s.taboption('general', form.SectionValue, '__general__',
			form.TypedSection, 'system', null,
			_('Here you can configure system settings'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = false;

		function snmpd_sys_cfgvalue(section) {
			const s = uci.get_first('snmpd', 'system');
			return s && uci.get('snmpd', s['.name'], this.option || '');
		};

		function snmpd_sys_remove(section) {
			const s = uci.get_first('snmpd', 'system');
			if (s)
				uci.unset('snmpd', s['.name'], this.option);
		};

		function snmpd_sys_write(section, value) {
			const s = uci.get_first('snmpd', 'system');
			const sid = s ? s['.name'] : uci.add('snmpd', 'system');
			uci.set('snmpd', sid, this.option, value);
		};

		go = g.option(form.Value, 'sysName', _('Name'),
			_('System Name'));
		go.cfgvalue = snmpd_sys_cfgvalue;
		go.write = snmpd_sys_write;
		go.remove = snmpd_sys_remove;

		go = g.option(form.Value, 'sysContact', _('Contact'),
			_('System contact'));
		go.cfgvalue = snmpd_sys_cfgvalue;
		go.write = snmpd_sys_write;
		go.remove = snmpd_sys_remove;

		go = g.option(form.Value, 'sysLocation', _('Location'),
			_('System location'));
		go.cfgvalue = snmpd_sys_cfgvalue;
		go.write = snmpd_sys_write;
		go.remove = snmpd_sys_remove;
	},

	populateAdvancedSettings(tab, s, havesslv3) {
		let o, g, go;

		o = s.taboption('advanced', form.SectionValue, '__advanced__',
			form.GridSection, 'com2sec', null,
			_('Here you can configure com2sec options'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = 'com2sec Settings';

		go = g.option(form.Value, 'secname', _('Secname'),
			_('Arbitrary label for use in group settings'));
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.Value, 'source', _('Source'),
			_('Source describes a host or network'));
		go.rmempty = false;

		go = g.option(form.Value, 'community', _('Community'),
			_('The community name that is used'));
		go.optional = false;
		go.rmempty = false;

		o = s.taboption('advanced', form.SectionValue, '__advanced__',
			form.GridSection, 'group', null,
			_('Here you can configure group options'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = 'Group Settings';

		go = g.option(form.Value, 'group', _('Group'),
			_('A group maps com2sec names to access names'));
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.ListValue, 'version', _('Version'),
			_('The used version for the group'));
		go.value('v1', _('SNMPv1'));
		go.value('v2c', _('SNMPv2c'));
		if (havesslv3) {
			go.value('usm', _('SNMPv3'));
		}
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.Value, 'secname', _('Secname'),
			_('Here you define which secname is mapped to the group'));
		go.optional = false;
		go.rmempty = false;

		o = s.taboption('advanced', form.SectionValue, '__advanced__',
			form.GridSection, 'access', null,
			_('Here you can configure access options'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = 'Access Settings';

		go = g.option(form.Value, 'group', _('Group'),
			_('The group that is mapped to the views (Read, Write, Notify)'));
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.Value, 'context', _('Context'),
			_('The context of the request'));
		go.modalonly = true;

		go = g.option(form.ListValue, 'version', _('Version'),
			_('The used version for access configuration'));
		go.value('any', _('Any version'));
		go.value('v1', _('SNMPv1'));
		go.value('v2c', _('SNMPv2c'));
		if (havesslv3) {
			go.value('usm', _('SNMPv3'));
		}
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.ListValue, 'level', _('Level'),
			_('Level of security'));
		go.value('noauth', _('No authentication'));
		go.value('auth', _('Authentication'));
		go.value('priv', _('Authentication and encryption'));
		go.default = 'noauth';
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.ListValue, 'prefix', _('Prefix'),
			_('Specification how context of requests is matched to context'));
		go.value('exact', _('Exact'));
		go.value('prefix', _('Prefix'));
		go.optional = false;
		go.default = 'exact';
		go.rmempty = false;

		go = g.option(form.ListValue, 'read', _('Read'),
			_('Read access modification for groups'));
		go.value('all', _('All'));
		go.value('none', _('None'));
		go.default = 'none';
		go.rmempty = false;
		go.modalonly = true;
		go.optional = false;

		go = g.option(form.ListValue, 'write', _('Write'),
			_('Write access modification for groups'));
		go.value('all', _('All'));
		go.value('none', _('None'));
		go.default = 'none';
		go.rmempty = false;
		go.modalonly = true;
		go.optional = false;

		go = g.option(form.ListValue, 'notify', _('Notify'),
			_('Notify access modification for groups'));
		go.value('all', _('All'));
		go.value('none', _('None'));
		go.default = 'none';
		go.rmempty = false;
		go.modalonly = true;
		go.optional = false;
	},

	populateV1V2CSettings: function(subsection, desc, access, s) {
		let g, go, o, community, community_src, mode, mask;

		o = s.taboption('v1/v2c', form.SectionValue, '__v1/v2c__',
			form.GridSection, subsection, null, desc);

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = desc;

		mode = g.option(form.ListValue, 'Mode', _('Access Control'),
			_('Access restriction to readonly or Read/Write'));
		mode.value('rwcommunity', _('Read/Write'));
		mode.value('rocommunity', _('Readonly'));

		community = g.option(form.Value, 'CommunityName',
			_('Community Name'),
			_('Community that is used for SNMP'));
		community.datatype = 'string';
		community.default = '';
		community.optional = false;
		community.rmempty = false;
		if(access == null) {
			if (uci.get('snmpd', 'access_default', 'Mode') === 'rwcommunity') {
				this.rw_community_src = 'default';
			} else {
				this.ro_community_src = 'default';
			}
		}

		if (access !== null) {
			community_src = g.option(form.Value, access,
				_('Community source'),
				_('Trusted source for SNMP read community access (hostname or IP)'));
			community_src.value('default', _('any (default)'));
			community_src.value('localhost', _('localhost'));
			community_src.default = 'default';
			community_src.optional = false;
			community_src.rmempty = false;
			community_src.datatype = 'host(0)';

			if (access == 'HostIP') {
				mask = g.option(form.Value, 'IPMask',
					_('IPMask'),
					_('Prefix'));
				mask.rmempty = false;
				mask.datatype = 'and(ip6prefix, ip4prefix)';
				mask.size = 2;
			}
		}

		go = g.option(form.ListValue, 'RestrictOID',
			_('OID-Restriction'),
			_('Restriction to specific OID'));
		go.value('no', _('No'));
		go.value('yes', _('Yes'));
		go.default = 'no';
		go.optional = false;
		go.rmempty = false;

		this.oid = g.option(form.Value,
			'RestrictedOID',
			_('OID'),
			_('Restrict to the following OID node/branch'));
		this.oid.datatype = 'string';
		this.oid.depends('RestrictOID', 'yes');

		if (uci.get('snmpd', subsection, 'Mode') === 'rocommunity') {
			this.ro_community = community;
			this.ro_community_src = community_src;
		} else {
			this.rw_community = community;
			this.rw_community_src = community_src;
		}
	},

	populateV3Settings: function(tab, s){
		let g, go, o;

		o = s.taboption(tab, form.SectionValue, '__v3__',
			form.GridSection, 'v3',
			null, _('Here you can configure SNMPv3 settings'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = 'SNMPv3';

		go = g.option(form.Value, 'username',
			_('username'),
			_('Set username to access SNMP'));
		go.rmempty = false;
		go.optional = false;
		go.modalonly = true;

		go = g.option(form.Flag, 'allow_write',
			_('Allow write'));
		go.rmempty = false;
		go.default = '0';

		go = g.option(form.ListValue, 'auth_type',
			_('SNMPv3 authentication type'));
		go.value('', _('none'));
		go.value('SHA', _('SHA'));
		go.value('MD5', _('MD5'));
		go.rmempty = true;
		go.default = 'SHA';

		// SNMPv3 auth pass
		go = g.option(form.Value, 'auth_pass',
			_('SNMPv3 authentication passphrase'));
		go.password = true;
		go.rmempty = true;
		go.modalonly = true;
		go.optional = false;
		go.validate = this.testPassLength;
		go.depends({'auth_type': '', '!reverse': true});

		// SNMPv3 privacy/encryption type
		go = g.option(form.ListValue, 'privacy_type',
			_('SNMPv3 encryption type'));
		go.value('', _('none'));
		go.value('AES', _('AES'));
		go.value('DES', _('DES'));
		go.rmempty = true;
		go.default = 'AES';

		// SNMPv3 privacy/encryption pass
		go = g.option(form.Value, 'privacy_pass',
			_('SNMPv3 encryption passphrase'));
		go.password = true;
		go.rmempty = true;
		go.modalonly = true;
		go.optional = false;
		go.validate = this.testPassLength;
		go.depends({'privacy_type': '', '!reverse': true});

		go = g.option(form.ListValue, 'RestrictOID',
			_('OID-Restriction'));
		go.value('no', _('No'));
		go.value('yes', _('Yes'));
		go.default = 'no';
		go.optional = false;
		go.rmempty = false;

		this.oid = g.option(form.Value,
			'RestrictedOID',
			_('OID'));
		this.oid.datatype = 'string';
		this.oid.depends('RestrictOID', 'yes');
	},

	populateTrapsSettings: function(subsection, desc, s, type) {
		let g, go, o;

		o = s.taboption('traps', form.SectionValue, '__traps__',
			form.GridSection, subsection, null, desc);

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;
		g.modaltitle = desc;

		go = g.option(form.Value,
			type,
			_(type));
		if (type == 'HostIP'){
			go.datatype = 'ipaddr';
		}else{
			go.datatype = 'hostname';
		}
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.Value,
			'Port',
			_('Port'));
		go.datatype = 'port'
		go.default = '162'
		go.optional = true;
		go.rmempty = false;

		go = g.option(form.Value,
			'Community',
			_('Community'));
		go.datatype = 'string';
		go.optional = false;
		go.rmempty = false;

		go = g.option(form.ListValue,
			'Type',
			_('Type'));
		go.value('trapsink', _('SNMPv1 Trap Receiver'));
		go.value('trap2sink', _('SNMPv2c Trap Receiver'));
		go.value('informsink', _('SNMPv2c Inform Receiver'));
		go.default = 'trapsink';
		go.optional = false;
		go.rmempty = false;
	},

	populateLogSettings: function(tab, s) {
		let g, go, o;

		o = s.taboption(tab, form.SectionValue, '__log__',
			form.GridSection, tab, null,
			_('Here you can configure Logging settings'));

		g = o.subsection;
		g.anonymous = true;
		g.addremove = true;
		g.nodescriptions = true;

		go = g.option(form.Flag, 'file',
			_('Enable logging to file'));
		go.default = '0';
		go.rmempty = false;
		go.optional = false;

		go = g.option(form.Value, 'log_file',
			_('Path to log file'));
		go.default = '/var/log/snmpd.log';
		go.rmempty = false;
		go.placeholder = '/var/log/snmpd.log';
		go.depends('file', '1');

		go = g.option(form.ListValue, 'log_file_priority',
			_('Priority for file logging'),
			_('Will log messages of selected priority and above.'));
		go.default = 'i';
		go.value('!', 'LOG_EMERG');
		go.value('a', 'LOG_ALERT');
		go.value('c', 'LOG_CRIT');
		go.value('e', 'LOG_ERR');
		go.value('w', 'LOG_WARNING');
		go.value('n', 'LOG_NOTICE');
		go.value('i', 'LOG_INFO');
		go.value('d', 'LOG_DEBUG');
		go.depends('file', '1');

		go = g.option(form.Flag, 'log_syslog',
			_('Enable logging to syslog'));
		go.default = '0';
		go.rmempty = false;
		go.optional = false;

		go = g.option(form.ListValue, 'log_syslog_facility',
			_('Syslog facility'));
		go.default = 'd';
		go.value('d', 'LOG_DAEMON');
		go.value('u', 'LOG_USER');
		go.value('0', 'LOG_LOCAL0');
		go.value('1', 'LOG_LOCAL1');
		go.value('2', 'LOG_LOCAL2');
		go.value('3', 'LOG_LOCAL3');
		go.value('4', 'LOG_LOCAL4');
		go.value('5', 'LOG_LOCAL5');
		go.value('6', 'LOG_LOCAL6');
		go.value('7', 'LOG_LOCAL7');
		go.depends('log_syslog', '1');
	},

	render: function([havesslv3]) {
		let m, s, o, g, go;

		m = new form.Map('snmpd',
			_('SNMP Settings'),
			_('On this page you may configure SNMP settings'));

		s = m.section(form.TypedSection, 'snmpd');
		s.anonymous = true;
		s.addremove = false;

		s.tab('general', _('General'));
		this.populateSystemSettings('general', s, havesslv3);
		this.populateGlobalSettings('general', s, havesslv3);

		s.tab('advanced', _('Advanced Settings'));
		this.populateAdvancedSettings('advanced', s, havesslv3);

		s.tab('v1/v2c', _('SNMPv1/SNMPv2c'));
		this.populateV1V2CSettings('access_default',
			_('Communities for any hosts'), null, s);
		this.populateV1V2CSettings('access_HostName',
			_('Communities via hostname'), 'HostName', s);
		this.populateV1V2CSettings('access_HostIP',
			_('Communities via IP-Address range'), 'HostIP', s);

		if (havesslv3) {
			s.tab('v3', _('SNMPv3'));
			this.populateV3Settings('v3', s);
		}

		s.tab('traps', _('Traps', 'SNMP'));
		this.populateTrapsSettings('trap_HostName', 'Traps via Hostname',
			s, 'HostName');
		this.populateTrapsSettings('trap_HostIP', 'Traps via IP-Address',
			s, 'HostIP');

		s.tab('log', _('Logging'));
		this.populateLogSettings('log', s);

		return m.render();
	}
});
