blob: 47d338f741f79025bdded605049c694c11fb8f34 [file] [log] [blame]
/*
* snmptsmsm.c
*
* This code implements a security model that assumes the local user
* that executed the agent is the user who's attributes called
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/library/snmptsm.h>
#include <unistd.h>
static int tsm_session_init(netsnmp_session *);
static void tsm_free_state_ref(void *);
static int tsm_clone_pdu(netsnmp_pdu *, netsnmp_pdu *);
static void tsm_free_pdu(netsnmp_pdu *pdu);
u_int next_sess_id = 1;
/** Initialize the TSM security module */
void
init_tsm(void)
{
struct snmp_secmod_def *def;
int ret;
def = SNMP_MALLOC_STRUCT(snmp_secmod_def);
if (!def) {
snmp_log(LOG_ERR,
"Unable to malloc snmp_secmod struct, not registering TSM\n");
return;
}
def->encode_reverse = tsm_rgenerate_out_msg;
def->decode = tsm_process_in_msg;
def->session_open = tsm_session_init;
def->pdu_free_state_ref = tsm_free_state_ref;
def->pdu_clone = tsm_clone_pdu;
def->pdu_free = tsm_free_pdu;
def->probe_engineid = snmpv3_probe_contextEngineID_rfc5343;
DEBUGMSGTL(("tsm","registering ourselves\n"));
ret = register_sec_mod(NETSNMP_TSM_SECURITY_MODEL, "tsm", def);
DEBUGMSGTL(("tsm"," returned %d\n", ret));
netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "tsmUseTransportPrefix",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_TSM_USE_PREFIX);
}
/*
* Initialize specific session information (right now, just set up things to
* not do an engineID probe)
*/
static int
tsm_session_init(netsnmp_session * sess)
{
DEBUGMSGTL(("tsm",
"TSM: Reached our session initialization callback\n"));
sess->flags |= SNMP_FLAGS_DONT_PROBE;
/* XXX: likely needed for something: */
/*
tsmsession = sess->securityInfo =
if (!tsmsession)
return SNMPERR_GENERR;
*/
return SNMPERR_SUCCESS;
}
/** Free our state information (this is only done on the agent side) */
static void
tsm_free_state_ref(void *ptr)
{
netsnmp_tsmSecurityReference *tsmRef;
if (NULL == ptr)
return;
tsmRef = (netsnmp_tsmSecurityReference *) ptr;
/* the tmStateRef is always taken care of by the normal PDU, since this
is just a reference to that one */
/* DON'T DO: SNMP_FREE(tsmRef->tmStateRef); */
/* SNMP_FREE(tsmRef); ? */
}
static void
tsm_free_pdu(netsnmp_pdu *pdu)
{
/* free the security reference */
if (pdu->securityStateRef) {
tsm_free_state_ref(pdu->securityStateRef);
pdu->securityStateRef = NULL;
}
}
/** This is called when a PDU is cloned (to increase reference counts) */
static int
tsm_clone_pdu(netsnmp_pdu *pdu, netsnmp_pdu *pdu2)
{
netsnmp_tsmSecurityReference *oldref, *newref;
oldref = pdu->securityStateRef;
if (!oldref)
return SNMPERR_SUCCESS;
newref = SNMP_MALLOC_TYPEDEF(netsnmp_tsmSecurityReference);
fprintf(stderr, "cloned as pdu=%p, ref=%p (oldref=%p)\n",
pdu2, newref, pdu2->securityStateRef);
if (!newref)
return SNMPERR_GENERR;
memcpy(newref, oldref, sizeof(*oldref));
pdu2->securityStateRef = newref;
/* the tm state reference is just a link to the one in the pdu,
which was already copied by snmp_clone_pdu before handing it to
us. */
newref->tmStateRef = netsnmp_memdup(oldref->tmStateRef,
sizeof(*oldref->tmStateRef));
return SNMPERR_SUCCESS;
}
/* asn.1 easing definitions */
#define TSMBUILD_OR_ERR(fun, args, msg, desc) \
DEBUGDUMPHEADER("send", desc); \
rc = fun args; \
DEBUGINDENTLESS(); \
if (rc == 0) { \
DEBUGMSGTL(("tsm",msg)); \
retval = SNMPERR_TOO_LONG; \
goto outerr; \
}
/****************************************************************************
*
* tsm_generate_out_msg
*
* Parameters:
* (See list below...)
*
* Returns:
* SNMPERR_SUCCESS On success.
* ... and others
*
*
* Generate an outgoing message.
*
****************************************************************************/
int
tsm_rgenerate_out_msg(struct snmp_secmod_outgoing_params *parms)
{
u_char **wholeMsg = parms->wholeMsg;
size_t *offset = parms->wholeMsgOffset;
int rc;
size_t *wholeMsgLen = parms->wholeMsgLen;
netsnmp_tsmSecurityReference *tsmSecRef;
netsnmp_tmStateReference *tmStateRef;
int tmStateRefLocal = 0;
DEBUGMSGTL(("tsm", "Starting TSM processing\n"));
/* if we have this, this message is in response to something that
came in earlier */
tsmSecRef = parms->secStateRef;
if (tsmSecRef) {
/* section 4.2, step 1 */
if (tsmSecRef->tmStateRef)
tmStateRef = tsmSecRef->tmStateRef;
else
tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference);
if (NULL == tmStateRef) {
snmp_log(LOG_ERR, "failed to allocate a tmStateReference\n");
return SNMPERR_GENERR;
}
tmStateRef->sameSecurity = NETSNMP_TM_USE_SAME_SECURITY;
tmStateRef->requestedSecurityLevel = tsmSecRef->securityLevel;
/* XXX: this may be freed automatically later by the library? */
SNMP_FREE(parms->secStateRef);
} else {
/* section 4.2, step 2 */
tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference);
if (tmStateRef == NULL) {
return SNMPERR_GENERR;
}
tmStateRefLocal = 1;
tmStateRef->requestedSecurityLevel = parms->secLevel;
tmStateRef->sameSecurity = NETSNMP_TM_SAME_SECURITY_NOT_REQUIRED;
if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_LIB_TSM_USE_PREFIX)) {
/* XXX: probably shouldn't be a hard-coded list of
supported transports */
const char *prefix;
if (strncmp("ssh:",parms->session->peername,4) == 0)
prefix = "ssh:";
else if (strncmp("dtls:",parms->session->peername,4) == 0)
prefix = "dtls:";
else {
SNMP_FREE(tmStateRef);
return SNMPERR_GENERR;
}
/* a: - lookup the prefix */
/* - if DNE, snmpTsmUnknownPrefixes++ and bail */
if (!prefix) {
/* snmpTsmUnknownPrefixes++ */
SNMP_FREE(tmStateRef);
return SNMPERR_GENERR;
}
/* - If secName doesn't have the prefix (or any):
snmpTsmInvalidPrefixes++ and bail */
if (strchr(parms->secName, ':') == 0 ||
strlen(prefix)+1 >= parms->secNameLen ||
strncmp(parms->secName, prefix, strlen(prefix)) != 0 ||
parms->secName[strlen(prefix)] != ':') {
SNMP_FREE(tmStateRef);
/* snmpTsmInvalidPrefixes++ */
return SNMPERR_GENERR;
}
/* - Strip the prefix and trailing : */
/* set tmSecurityName to securityName minus stripped part */
memcpy(tmStateRef->securityName,
parms->secName + strlen(prefix) + 1,
parms->secNameLen - strlen(prefix) - 1);
tmStateRef->securityNameLen = parms->secNameLen - strlen(prefix) -1;
} else {
/* set tmSecurityName to securityName */
memcpy(tmStateRef->securityName, parms->secName,
parms->secNameLen);
tmStateRef->securityNameLen = parms->secNameLen;
}
}
tmStateRef->securityName[tmStateRef->securityNameLen] = '\0';
/* Section 4.2, Step 3:
* - Set securityParameters to a zero-length OCTET STRING ('0400')
*
* We define here what the security message section will look like:
* 04 00 -- null string
*/
DEBUGDUMPHEADER("send", "tsm security parameters");
rc = asn_realloc_rbuild_header(wholeMsg, wholeMsgLen, offset, 1,
(u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE
| ASN_OCTET_STR), 0);
DEBUGINDENTLESS();
if (rc == 0) {
DEBUGMSGTL(("tsm", "building msgSecurityParameters failed.\n"));
if (tmStateRefLocal)
SNMP_FREE(tmStateRef);
return SNMPERR_TOO_LONG;
}
/* Section 4.2, Step 4:
* Combine the message parts into a wholeMsg and calculate wholeMsgLen
*/
/*
* Copy in the msgGlobalData and msgVersion.
*/
while ((*wholeMsgLen - *offset) < parms->globalDataLen) {
if (!asn_realloc(wholeMsg, wholeMsgLen)) {
DEBUGMSGTL(("tsm", "building global data failed.\n"));
if (tmStateRefLocal)
SNMP_FREE(tmStateRef);
return SNMPERR_TOO_LONG;
}
}
*offset += parms->globalDataLen;
memcpy(*wholeMsg + *wholeMsgLen - *offset,
parms->globalData, parms->globalDataLen);
/*
* Total packet sequence.
*/
rc = asn_realloc_rbuild_sequence(wholeMsg, wholeMsgLen, offset, 1,
(u_char) (ASN_SEQUENCE |
ASN_CONSTRUCTOR), *offset);
if (rc == 0) {
DEBUGMSGTL(("tsm", "building master packet sequence failed.\n"));
if (tmStateRefLocal)
SNMP_FREE(tmStateRef);
return SNMPERR_TOO_LONG;
}
/* Section 4.2 Step 5: return everything */
if (parms->pdu->transport_data &&
parms->pdu->transport_data != tmStateRef) {
snmp_log(LOG_ERR, "tsm: needed to free transport data\n");
SNMP_FREE(parms->pdu->transport_data);
}
/* put the transport state reference into the PDU for the transport */
parms->pdu->transport_data = netsnmp_memdup(tmStateRef, sizeof(*tmStateRef));
if (!parms->pdu->transport_data)
snmp_log(LOG_ERR, "tsm: malloc failure\n");
parms->pdu->transport_data_length = sizeof(*tmStateRef);
if (tmStateRefLocal)
SNMP_FREE(tmStateRef);
DEBUGMSGTL(("tsm", "TSM processing completed.\n"));
return SNMPERR_SUCCESS;
}
/****************************************************************************
*
* tsm_process_in_msg
*
* Parameters:
* (See list below...)
*
* Returns:
* TSM_ERR_NO_ERROR On success.
* TSM_ERR_GENERIC_ERROR
* TSM_ERR_UNSUPPORTED_SECURITY_LEVEL
*
*
* Processes an incoming message.
*
****************************************************************************/
int
tsm_process_in_msg(struct snmp_secmod_incoming_params *parms)
{
u_char type_value;
size_t remaining;
u_char *data_ptr;
netsnmp_tmStateReference *tmStateRef;
netsnmp_tsmSecurityReference *tsmSecRef;
u_char ourEngineID[SNMP_MAX_ENG_SIZE];
static size_t ourEngineID_len = sizeof(ourEngineID);
/* Section 5.2, step 1 */
ourEngineID_len =
snmpv3_get_engineID((u_char*)ourEngineID, ourEngineID_len);
if (ourEngineID_len == 0 || ourEngineID_len > *parms->secEngineIDLen)
return SNMPERR_GENERR;
memcpy(parms->secEngineID, ourEngineID, *parms->secEngineIDLen);
/* Section 5.2, step 2 */
if (!parms->pdu->transport_data ||
sizeof(netsnmp_tmStateReference) !=
parms->pdu->transport_data_length) {
/* if we're not coming in over a proper transport; bail! */
DEBUGMSGTL(("tsm","improper transport data\n"));
return -1;
}
tmStateRef = (netsnmp_tmStateReference *) parms->pdu->transport_data;
parms->pdu->transport_data = NULL;
if (tmStateRef == NULL ||
/* not needed: tmStateRef->transportDomain == NULL || */
/* not needed: tmStateRef->transportAddress == NULL || */
tmStateRef->securityName[0] == '\0'
/* || seclevel is not valid */
) {
/* XXX: snmpTsmInvalidCaches++ */
return SNMPERR_GENERR;
}
/* Section 5.2, step 3 */
if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_LIB_TSM_USE_PREFIX)) {
/* Section 5.2, step 3a */
const char *prefix;
prefix = "ssh:"; /* XXX */
if (prefix == NULL) {
/* XXX: snmpTsmUnknownPrefixes++ */
return SNMPERR_GENERR;
}
if (strlen(prefix) < 1 || strlen(prefix) > 4) {
/* XXX: snmpTsmInvalidPrefixes++ */
return SNMPERR_GENERR;
}
snprintf(parms->secName, *parms->secNameLen,
"%s:%s", prefix, tmStateRef->securityName);
} else {
strncpy(parms->secName, tmStateRef->securityName, *parms->secNameLen);
}
*parms->secNameLen = strlen(parms->secName);
DEBUGMSGTL(("tsm", "user: %s/%d\n", parms->secName, *parms->secNameLen));
/* Section 5.2 Step 4 */
if (parms->secLevel > tmStateRef->transportSecurityLevel) {
/* XXX: snmpTsmInadequateSecurityLevels++ */
return SNMPERR_UNSUPPORTED_SEC_LEVEL;
}
/* Section 5.2 Step 5 */
if (NULL == *parms->secStateRef) {
tsmSecRef = SNMP_MALLOC_TYPEDEF(netsnmp_tsmSecurityReference);
} else {
tsmSecRef = *parms->secStateRef;
}
if (!tsmSecRef)
return SNMPERR_GENERR;
*parms->secStateRef = tsmSecRef;
tsmSecRef->tmStateRef = tmStateRef;
/* If this did not come through a tunneled connection, this
security model is inappropriate (and would be a HUGE security
hole to assume otherwise). This is functionally a double check
since the pdu wouldn't have transport data otherwise. But this
is safer though is functionally an extra step beyond the TSM
RFC. */
DEBUGMSGTL(("tsm","checking how we got here\n"));
if (!(parms->pdu->flags & UCD_MSG_FLAG_TUNNELED)) {
DEBUGMSGTL(("tsm"," pdu not tunneled\n"));
if (!(parms->sess->flags & NETSNMP_TRANSPORT_FLAG_TUNNELED)) {
DEBUGMSGTL(("tsm"," session not tunneled\n"));
return SNMPERR_USM_AUTHENTICATIONFAILURE;
}
DEBUGMSGTL(("tsm"," but session is tunneled\n"));
} else {
DEBUGMSGTL(("tsm"," tunneled\n"));
}
/* Section 5.2, Step 6 */
/*
* Eat the first octet header.
*/
remaining = parms->wholeMsgLen - (parms->secParams - parms->wholeMsg);
if ((data_ptr = asn_parse_sequence(parms->secParams, &remaining,
&type_value,
(ASN_UNIVERSAL | ASN_PRIMITIVE |
ASN_OCTET_STR),
"usm first octet")) == NULL) {
/*
* RETURN parse error
*/
return SNMPERR_GENERR;
}
*parms->scopedPdu = data_ptr;
*parms->scopedPduLen = parms->wholeMsgLen - (data_ptr - parms->wholeMsg);
/* Section 5.2, Step 7 */
*parms->maxSizeResponse = parms->maxMsgSize; /* XXX */
tsmSecRef->securityLevel = parms->secLevel;
return SNMPERR_SUCCESS;
}