static int analyzer_tftp_pkt_process(void *obj, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct analyzer_tftp_priv *priv = obj; struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_prev = &stack[stack_index - 1]; uint16_t opcode = *PTYPE_UINT16_GETVAL(s_prev->pkt_info->fields_value[proto_tftp_field_opcode]); // Get the session struct conntrack_session *session = conntrack_session_get(s_prev->ce); if (!session) return POM_ERR; struct analyzer_tftp_session_priv *spriv = conntrack_session_get_priv(session, obj); if (!spriv) { // Add session priv if it is not done yet spriv = malloc(sizeof(struct analyzer_tftp_session_priv)); if (!spriv) { pom_oom(sizeof(struct analyzer_tftp_session_priv)); goto err; } memset(spriv, 0, sizeof(struct analyzer_tftp_session_priv)); if (conntrack_session_add_priv(session, obj, spriv, analyzer_tftp_session_priv_cleanup) != POM_OK) { free(spriv); goto err; } } void *pload = s->pload; uint32_t plen = s->plen; switch (opcode) { case tftp_rrq: case tftp_wrq: { if (plen < 3) return POM_OK; // Invalid packet // Find the filename // The below should always be valid as proto_tftp already checked this char *filename = pload; char *mode = memchr(filename, 0, plen - 1) + 1; struct analyzer_tftp_file *fq = malloc(sizeof(struct analyzer_tftp_file)); if (!fq) { pom_oom(sizeof(struct analyzer_tftp_file)); goto err; } memset(fq, 0, sizeof(struct analyzer_tftp_file)); // Get the port on which we expect this file // No need to check the IP as we got the session biding struct proto_process_stack *s_l4 = &stack[stack_index - 2]; unsigned int i; for (i = 0; !fq->port ; i++) { struct proto_reg_info *pinfo = proto_get_info(s_l4->proto); char *name = pinfo->pkt_fields[i].name; if (!name) { pomlog(POMLOG_ERR "Source port not found in RRQ/WRQ packets"); goto err; } if (!strcmp(name, "sport")) { fq->port = *PTYPE_UINT16_GETVAL(s_l4->pkt_info->fields_value[i]); break; } } fq->evt = event_alloc(priv->evt_file); if (!fq->evt) { free(fq); goto err; } struct data *evt_data = event_get_data(fq->evt); PTYPE_STRING_SETVAL(evt_data[analyzer_tftp_file_filename].value, filename); data_set(evt_data[analyzer_tftp_file_filename]); PTYPE_STRING_SETVAL(evt_data[analyzer_tftp_file_mode].value, mode); data_set(evt_data[analyzer_tftp_file_mode]); PTYPE_BOOL_SETVAL(evt_data[analyzer_tftp_file_write].value, opcode == tftp_wrq); data_set(evt_data[analyzer_tftp_file_write]); fq->next = spriv->files; if (fq->next) fq->next->prev = fq; spriv->files = fq; conntrack_session_unlock(session); event_process_begin(fq->evt, stack, stack_index, p->ts); break; } case tftp_data: { if (plen < sizeof(uint16_t)) return POM_OK; // Invalid packet struct analyzer_tftp_file *f = conntrack_get_priv(s_prev->ce, obj); struct data *evt_data = NULL; if (!f) { // The file is not yet associated to this connection // Find it in the queue struct proto_process_stack *s_l4 = &stack[stack_index - 2]; unsigned int i; uint16_t sport = 0, dport = 0; for (i = 0; !sport || !dport ; i++) { struct proto_reg_info *pinfo = proto_get_info(s_l4->proto); char *name = pinfo->pkt_fields[i].name; if (!name) { pomlog(POMLOG_ERR "Source port not found in data packets"); goto err; } if (!strcmp(name, "sport")) sport = *PTYPE_UINT16_GETVAL(s_l4->pkt_info->fields_value[i]); if (!strcmp(name, "dport")) dport = *PTYPE_UINT16_GETVAL(s_l4->pkt_info->fields_value[i]); } // Find the file in the session list for (f = spriv->files; ; f = f->next) { evt_data = event_get_data(f->evt); if (*PTYPE_BOOL_GETVAL(evt_data[analyzer_tftp_file_write].value)) { if (f->port == sport) break; } else { if (f->port == dport) break; } } if (!f) { pomlog(POMLOG_DEBUG "File not found in queued file request."); conntrack_session_unlock(session); return POM_OK; } // Remove the file from the queue and assign it to the conntrack if (f->prev) f->prev->next = f->next; else spriv->files = f->next; if (f->next) f->next->prev = f->prev; f->prev = NULL; f->next = NULL; // Create the payload buffer f->pload = pload_alloc(f->evt, PLOAD_FLAG_NEED_MAGIC); if (!f->pload) goto err; conntrack_add_priv(s_prev->ce, obj, f, analyzer_tftp_conntrack_priv_cleanup); } else { evt_data = event_get_data(f->evt); } conntrack_session_unlock(session); if (!f->pload) { pomlog(POMLOG_DEBUG "Ignoring extra packet"); return POM_OK; } // Discard the block ID pload += sizeof(uint16_t); plen -= sizeof(uint16_t); if (pload_append(f->pload, pload, plen) != POM_OK) goto err; uint32_t *size = PTYPE_UINT32_GETVAL(evt_data[analyzer_tftp_file_size].value); *size += plen; if (plen < ANALYZER_TFTP_BLK_SIZE) { // Got last packet ! data_set(evt_data[analyzer_tftp_file_size]); int res = pload_end(f->pload); res += event_process_end(f->evt); f->evt = NULL; f->pload = NULL; if (res) goto err; } break; } case tftp_error: { conntrack_session_unlock(session); struct analyzer_tftp_file *f = conntrack_get_priv(s_prev->ce, obj); if (f && f->pload) { int res = pload_end(f->pload); res += event_process_end(f->evt); f->pload = NULL; f->evt = NULL; if (res) goto err; } break; } default: conntrack_session_unlock(session); break; } return POM_OK; err: conntrack_session_unlock(session); return POM_ERR; }
static int proto_tftp_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_prev = &stack[stack_index - 1]; struct proto_process_stack *s_next = &stack[stack_index + 1]; if (conntrack_get_unique_from_parent(stack, stack_index) != POM_OK) { pomlog(POMLOG_ERR "Could not get a conntrack entry"); return PROTO_ERR; } struct proto_tftp_conntrack_priv *priv = s->ce->priv; if (!priv) { priv = malloc(sizeof(struct proto_tftp_conntrack_priv)); if (!priv) { pom_oom(sizeof(struct proto_tftp_conntrack_priv)); conntrack_unlock(s->ce); return POM_ERR; } memset(priv, 0, sizeof(struct proto_tftp_conntrack_priv)); s->ce->priv = priv; } if (priv->flags & PROTO_TFTP_CONN_INVALID) { conntrack_unlock(s->ce); return PROTO_INVALID; } void *pload = s->pload; uint32_t plen = s->plen; // proto_tftp only process up to the opcode field // afterwards, it's up to the analyzer to parse the rest uint16_t opcode = ntohs(*((uint16_t*)pload)); PTYPE_UINT16_SETVAL(s->pkt_info->fields_value[proto_tftp_field_opcode], opcode); pload += sizeof(uint16_t); plen -= sizeof(uint16_t); s_next->pload = pload; s_next->plen = plen; switch (opcode) { case tftp_rrq: case tftp_wrq: { // Find the filename char *filename = pload; char *mode = memchr(filename, 0, plen - 1); if (!mode) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); debug_tftp("End of filename not found in read/write request"); return PROTO_INVALID; } mode++; ssize_t filename_len = mode - filename; char *end = memchr(mode, 0, plen - filename_len); if (!end) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); debug_tftp("End of mode not found in read/write request"); return PROTO_INVALID; } debug_tftp("Got read/write request for filename \"%s\" with mode \"%s\"", filename, mode); struct conntrack_session *session = conntrack_session_get(s->ce); if (!session) { conntrack_unlock(s->ce); return POM_ERR; } // We don't need to do anything with the session conntrack_session_unlock(session); struct proto_expectation *expt = proto_expectation_alloc_from_conntrack(s_prev->ce, proto_tftp, NULL); if (!expt) { conntrack_unlock(s->ce); return PROTO_ERR; } proto_expectation_set_field(expt, -1, NULL, POM_DIR_REV); if (proto_expectation_add(expt, session, PROTO_TFTP_EXPT_TIMER, p->ts) != POM_OK) { conntrack_unlock(s->ce); proto_expectation_cleanup(expt); return PROTO_ERR; } break; } case tftp_data: { if (plen < 2) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); return PROTO_INVALID; } uint16_t block_id = ntohs(*((uint16_t*)(pload))); int set_start_seq = 0; if (!priv->stream) { priv->stream = stream_alloc(PROTO_TFTP_STREAM_BUFF, s->ce, 0, proto_tftp_process_payload); if (!priv->stream) { conntrack_unlock(s->ce); return PROTO_ERR; } stream_set_timeout(priv->stream, PROTO_TFTP_PKT_TIMER); set_start_seq = 1; } conntrack_unlock(s->ce); if (set_start_seq) stream_set_start_seq(priv->stream, s->direction, PROTO_TFTP_BLK_SIZE + 2); int res = stream_process_packet(priv->stream, p, stack, stack_index + 1, block_id * (PROTO_TFTP_BLK_SIZE + 2), 0); return (res == PROTO_OK ? PROTO_STOP : res); } case tftp_ack: // Nothing to do break; case tftp_error: // An error occured, cleanup this conntrack soon conntrack_delayed_cleanup(s->ce, 1, p->ts); break; default: priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); return PROTO_INVALID; } conntrack_delayed_cleanup(s->ce, PROTO_TFTP_PKT_TIMER, p->ts); conntrack_unlock(s->ce); return PROTO_OK; }