static int radsnmp_send_recv(radsnmp_conf_t *conf, int fd) { fr_strerror(); #define NEXT_LINE(_line, _buffer) \ { \ size_t _len; \ if (stop) return 0; \ errno = 0;\ _line = fgets(_buffer, sizeof(_buffer), stdin); \ if (_line) { \ _len = strlen(_line); \ if ((_len > 0) && (_line[_len - 1] == '\n')) _line[_len - 1] = '\0'; \ DEBUG2("read: %s", _line); \ } \ } /* * Read commands from pass_persist */ while (!stop) { radsnmp_command_t command; char buffer[256]; char *line; ssize_t slen; fr_cursor_t cursor; VALUE_PAIR *vp; RADIUS_PACKET *request; /* * Alloc a new request so we can start adding * new pairs to it. */ request = radsnmp_alloc(conf, fd); if (!request) { ERROR("Failed allocating request"); return EXIT_FAILURE; } fr_cursor_init(&cursor, &request->vps); NEXT_LINE(line, buffer); /* * Determine the type of SNMP operation */ command = fr_str2int(radsnmp_command_str, line, RADSNMP_UNKNOWN); switch (command) { case RADSNMP_EXIT: DEBUG("Empty command, exiting"); return 0; case RADSNMP_PING: RESPOND_STATIC("PONG"); continue; case RADSNMP_SET: { char value_buff[254]; /* RADIUS attribute length + 1 */ char *value; char type_str[64]; char *p; fr_dict_enum_t *type; NEXT_LINE(line, buffer); /* Should be the OID */ NEXT_LINE(value, value_buff); /* Should be the value */ p = strchr(value, ' '); if (!p) { ERROR("No SNMP type specified (or type/value string was malformed)"); RESPOND_STATIC("NONE"); continue; } if ((size_t)(p - value) >= sizeof(type_str)) { ERROR("SNMP Type string too long"); RESPOND_STATIC("NONE"); continue; } strlcpy(type_str, value, (p - value) + 1); type = fr_dict_enum_by_alias(attr_freeradius_snmp_type, type_str, -1); if (!type) { ERROR("Unknown type \"%s\"", type_str); RESPOND_STATIC("NONE"); continue; } slen = radsnmp_pair_from_oid(conf, conf, &cursor, line, type->value->vb_uint32, p + 1); } break; case RADSNMP_GET: case RADSNMP_GETNEXT: NEXT_LINE(line, buffer); /* Should be the OID */ slen = radsnmp_pair_from_oid(conf, conf, &cursor, line, 0, NULL); break; default: ERROR("Unknown command \"%s\"", line); RESPOND_STATIC("NONE"); talloc_free(request); continue; } /* * Deal with any errors from the GET/GETNEXT/SET command */ if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(conf, &spaces, &text, slen, line); ERROR("Failed evaluating OID:"); ERROR("%s", text); ERROR("%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); talloc_free(request); RESPOND_STATIC("NONE"); continue; } /* * Now add an attribute indicating what the * SNMP operation was */ vp = fr_pair_afrom_da(request, attr_freeradius_snmp_operation); if (!vp) { ERROR("Failed allocating SNMP operation attribute"); return EXIT_FAILURE; } vp->vp_uint32 = (unsigned int)command; /* Commands must match dictionary */ fr_cursor_append(&cursor, vp); /* * Add message authenticator or the stats * request will be rejected. */ MEM(vp = fr_pair_afrom_da(request, attr_message_authenticator)); fr_pair_value_memcpy(vp, (uint8_t const *)"\0", 1, true); fr_cursor_append(&cursor, vp); /* * Send the packet */ { RADIUS_PACKET *reply = NULL; ssize_t rcode; fd_set set; unsigned int ret; unsigned int i; if (fr_radius_packet_encode(request, NULL, conf->secret) < 0) { ERROR("Failed encoding request: %s", fr_strerror()); return EXIT_FAILURE; } if (fr_radius_packet_sign(request, NULL, conf->secret) < 0) { ERROR("Failed signing request: %s", fr_strerror()); return EXIT_FAILURE; } /* * Print the attributes we're about to send */ if (fr_log_fp) fr_packet_header_print(fr_log_fp, request, false); if (fr_debug_lvl > 0) fr_pair_list_fprint(fr_log_fp, request->vps); #ifndef NDEBUG if (fr_log_fp && (fr_debug_lvl > 3)) fr_radius_packet_print_hex(request); #endif FD_ZERO(&set); /* clear the set */ FD_SET(fd, &set); /* * Any connection issues cause us to exit, so * the connection can be re-initialised on the * next call. */ for (i = 0; i < conf->retries; i++) { rcode = write(request->sockfd, request->data, request->data_len); if (rcode < 0) { ERROR("Failed sending: %s", fr_syserror(errno)); return EXIT_FAILURE; } rcode = select(fd + 1, &set, NULL, NULL, &conf->timeout); switch (rcode) { case -1: ERROR("Select failed: %s", fr_syserror(errno)); return EXIT_FAILURE; case 0: DEBUG("Response timeout. Retrying %d/%u...", i + 1, conf->retries); continue; /* Timeout */ case 1: reply = fr_radius_packet_recv(request, request->sockfd, UDP_FLAGS_NONE, RADIUS_MAX_ATTRIBUTES, false); if (!reply) { ERROR("Failed receiving reply: %s", fr_strerror()); recv_error: RESPOND_STATIC("NONE"); talloc_free(request); continue; } if (fr_radius_packet_decode(reply, request, RADIUS_MAX_ATTRIBUTES, false, conf->secret) < 0) { ERROR("Failed decoding reply: %s", fr_strerror()); goto recv_error; } break; default: DEBUG("Invalid select() return value %zd", rcode); return EXIT_FAILURE; } break; } if (!reply) { ERROR("Server didn't respond"); return EXIT_FAILURE; } /* * Print the attributes we received in response */ if (fr_log_fp) fr_packet_header_print(fr_log_fp, reply, true); if (fr_debug_lvl > 0) fr_pair_list_fprint(fr_log_fp, reply->vps); #ifndef NDEBUG if (fr_log_fp && (fr_debug_lvl > 3)) fr_radius_packet_print_hex(reply); #endif switch (command) { case RADSNMP_GET: case RADSNMP_GETNEXT: ret = radsnmp_get_response(STDOUT_FILENO, conf->snmp_oid_root, attr_freeradius_snmp_type, reply->vps); switch (ret) { case -1: ERROR("Failed converting pairs to varbind response: %s", fr_strerror()); return EXIT_FAILURE; case 0: DEBUG("Empty response"); break; default: DEBUG("Returned %u varbind responses", ret); break; } break; case RADSNMP_SET: if (radsnmp_set_response(STDOUT_FILENO, attr_freeradius_snmp_failure, reply->vps) < 0) { ERROR("Failed writing SET response: %s", fr_strerror()); return EXIT_FAILURE; } break; default: assert(0); return EXIT_FAILURE; } talloc_free(request); } } return EXIT_SUCCESS; }
/** Perform basic parsing of multiple types of messages, checking for error conditions * * @note Error messages should be retrieved with fr_strerror() and fr_strerror_pop() * * @param[out] ctrls Server ctrls returned to the client. May be NULL if not required. * Must be freed with ldap_free_ctrls. * @param[in] conn the message was received on. * @param[in] msg we're parsing. * @param[in] dn if processing the result from a search request. * @return One of the LDAP_PROC_* (#fr_ldap_rcode_t) values. */ fr_ldap_rcode_t fr_ldap_error_check(LDAPControl ***ctrls, fr_ldap_connection_t const *conn, LDAPMessage *msg, char const *dn) { fr_ldap_rcode_t status = LDAP_PROC_SUCCESS; int msg_type; int lib_errno = LDAP_SUCCESS; /* errno returned by the library */ int srv_errno = LDAP_SUCCESS; /* errno in the result message */ char *part_dn = NULL; /* Partial DN match */ char *srv_err = NULL; /* Server's extended error message */ ssize_t len; if (ctrls) *ctrls = NULL; if (!msg) { ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno); if (lib_errno != LDAP_SUCCESS) goto process_error; fr_strerror_printf("No result available"); return LDAP_PROC_NO_RESULT; } msg_type = ldap_msgtype(msg); switch (msg_type) { /* * Parse the result and check for errors sent by the server */ case LDAP_RES_SEARCH_RESULT: /* The result of a search */ case LDAP_RES_BIND: /* The result of a bind operation */ case LDAP_RES_EXTENDED: lib_errno = ldap_parse_result(conn->handle, msg, &srv_errno, &part_dn, &srv_err, NULL, ctrls, 0); break; /* * These are messages containing objects so unless they're * malformed they can't contain errors. */ case LDAP_RES_SEARCH_ENTRY: if (ctrls) lib_errno = ldap_get_entry_controls(conn->handle, msg, ctrls); break; /* * An intermediate message updating us on the result of an operation */ case LDAP_RES_INTERMEDIATE: lib_errno = ldap_parse_intermediate(conn->handle, msg, NULL, NULL, ctrls, 0); break; /* * Can't extract any more useful information. */ default: return LDAP_PROC_SUCCESS; } /* * Stupid messy API */ if (lib_errno != LDAP_SUCCESS) { rad_assert(!ctrls || !*ctrls); ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno); } process_error: if ((lib_errno == LDAP_SUCCESS) && (srv_errno != LDAP_SUCCESS)) { lib_errno = srv_errno; } else if ((lib_errno != LDAP_SUCCESS) && (srv_errno == LDAP_SUCCESS)) { srv_errno = lib_errno; } switch (lib_errno) { case LDAP_SUCCESS: fr_strerror_printf("Success"); break; case LDAP_SASL_BIND_IN_PROGRESS: fr_strerror_printf("Continuing"); status = LDAP_PROC_CONTINUE; break; case LDAP_NO_SUCH_OBJECT: fr_strerror_printf("The specified DN wasn't found"); status = LDAP_PROC_BAD_DN; /* * Build our own internal diagnostic string */ if (dn && part_dn) { char *spaces; char *text; len = fr_ldap_common_dn(dn, part_dn); if (len < 0) break; fr_canonicalize_error(NULL, &spaces, &text, -len, dn); fr_strerror_printf_push("%s", text); fr_strerror_printf_push("%s^ %s", spaces, "match stopped here"); talloc_free(spaces); talloc_free(text); } goto error_string; case LDAP_INSUFFICIENT_ACCESS: fr_strerror_printf("Insufficient access. Check the identity and password configuration directives"); status = LDAP_PROC_NOT_PERMITTED; break; case LDAP_UNWILLING_TO_PERFORM: fr_strerror_printf("Server was unwilling to perform"); status = LDAP_PROC_NOT_PERMITTED; break; case LDAP_FILTER_ERROR: fr_strerror_printf("Bad search filter"); status = LDAP_PROC_ERROR; break; case LDAP_TIMEOUT: fr_strerror_printf("Timed out while waiting for server to respond"); status = LDAP_PROC_TIMEOUT; break; case LDAP_TIMELIMIT_EXCEEDED: fr_strerror_printf("Time limit exceeded"); status = LDAP_PROC_TIMEOUT; break; case LDAP_BUSY: case LDAP_UNAVAILABLE: case LDAP_SERVER_DOWN: status = LDAP_PROC_BAD_CONN; goto error_string; case LDAP_INVALID_CREDENTIALS: case LDAP_CONSTRAINT_VIOLATION: status = LDAP_PROC_REJECT; goto error_string; case LDAP_OPERATIONS_ERROR: fr_strerror_printf("Please set 'chase_referrals=yes' and 'rebind=yes'. " "See the ldap module configuration for details"); /* FALL-THROUGH */ default: status = LDAP_PROC_ERROR; error_string: if (lib_errno == srv_errno) { fr_strerror_printf("lib error: %s (%u)", ldap_err2string(lib_errno), lib_errno); } else { fr_strerror_printf("lib error: %s (%u), srv error: %s (%u)", ldap_err2string(lib_errno), lib_errno, ldap_err2string(srv_errno), srv_errno); } if (srv_err) fr_strerror_printf_push("Server said: %s", srv_err); break; } /* * Cleanup memory */ if (srv_err) ldap_memfree(srv_err); if (part_dn) ldap_memfree(part_dn); return status; }
/** Convert an 'update' config section into an attribute map. * * Uses 'name2' of section to set default request and lists. * Copied from map_afrom_cs, except that list assignments can have the RHS * be a bare word. * * @param[in] cs the update section * @param[out] out Where to store the head of the map. * @param[in] dst_list_def The default destination list, usually dictated by * the section the module is being called in. * @param[in] src_list_def The default source list, usually dictated by the * section the module is being called in. * @param[in] max number of mappings to process. * @return -1 on error, else 0. */ static int ldap_map_afrom_cs(value_pair_map_t **out, CONF_SECTION *cs, pair_lists_t dst_list_def, pair_lists_t src_list_def, unsigned int max) { char const *cs_list, *p; request_refs_t request_def = REQUEST_CURRENT; CONF_ITEM *ci; unsigned int total = 0; value_pair_map_t **tail, *map; TALLOC_CTX *ctx; *out = NULL; tail = out; if (!cs) return 0; /* * The first map has cs as the parent. * The rest have the previous map as the parent. */ ctx = cs; ci = cf_sectiontoitem(cs); cs_list = p = cf_section_name2(cs); if (cs_list) { request_def = radius_request_name(&p, REQUEST_CURRENT); if (request_def == REQUEST_UNKNOWN) { cf_log_err(ci, "Default request specified " "in mapping section is invalid"); return -1; } dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN); if (dst_list_def == PAIR_LIST_UNKNOWN) { cf_log_err(ci, "Default list \"%s\" specified " "in mapping section is invalid", p); return -1; } } for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { char const *attr; FR_TOKEN type; CONF_PAIR *cp; cp = cf_itemtopair(ci); type = cf_pair_value_type(cp); if (total++ == max) { cf_log_err(ci, "Map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { cf_log_err(ci, "Entry is not in \"attribute = value\" format"); goto error; } cp = cf_itemtopair(ci); /* * Look for "list: OP BARE_WORD". If it exists, * we can make the RHS a bare word. Otherwise, * just call map_afrom_cp() * * Otherwise, the map functions check the RHS of * list assignments, and complain that the RHS * isn't another list. */ attr = cf_pair_attr(cp); p = strrchr(attr, ':'); if (!p || (p[1] != '\0') || (type == T_DOUBLE_QUOTED_STRING)) { if (map_afrom_cp(ctx, &map, cp, request_def, dst_list_def, REQUEST_CURRENT, src_list_def) < 0) { goto error; } } else { ssize_t slen; char const *value; map = talloc_zero(ctx, value_pair_map_t); map->op = cf_pair_operator(cp); map->ci = cf_pairtoitem(cp); slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, request_def, dst_list_def); if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(ctx, &spaces, &text, slen, attr); cf_log_err(ci, "Failed parsing list reference"); cf_log_err(ci, "%s", text); cf_log_err(ci, "%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); goto error; } if (map->lhs->type != TMPL_TYPE_LIST) { cf_log_err(map->ci, "Invalid list name"); goto error; } if (map->op != T_OP_ADD) { cf_log_err(map->ci, "Only '+=' operator is permitted for valuepair to list mapping"); goto error; } value = cf_pair_value(cp); if (!value) { cf_log_err(map->ci, "No value specified for list assignment"); goto error; } /* * the RHS type is a bare word or single * quoted string. We don't want it being * interpreted as a list or attribute * reference, so we force the RHS to be a * literal. */ slen = tmpl_afrom_str(ctx, &map->rhs, value, T_SINGLE_QUOTED_STRING, request_def, dst_list_def); if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(ctx, &spaces, &text, slen, value); cf_log_err(ci, "Failed parsing string"); cf_log_err(ci, "%s", text); cf_log_err(ci, "%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); goto error; } /* * And unlike map_afrom_cp(), we do NOT * try to parse the RHS as a list * reference. It's a literal, and we * leave it as a literal. */ rad_assert(map->rhs->type == TMPL_TYPE_LITERAL); } ctx = *tail = map; tail = &(map->next); } return 0; error: TALLOC_FREE(*out); return -1; }