"Fossies" - the Fresh Open Source Software Archive

Member "freeradius-server-3.0.23/src/modules/rlm_sql_map/rlm_sql_map.c" (10 Jun 2021, 11253 Bytes) of package /linux/misc/freeradius-server-3.0.23.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "rlm_sql_map.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.0.22_vs_3.0.23.

    1 /*
    2  *   This program is is free software; you can redistribute it and/or modify
    3  *   it under the terms of the GNU General Public License as published by
    4  *   the Free Software Foundation; either version 2 of the License, or (at
    5  *   your option) any later version.
    6  *
    7  *   This program is distributed in the hope that it will be useful,
    8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10  *   GNU General Public License for more details.
   11  *
   12  *   You should have received a copy of the GNU General Public License
   13  *   along with this program; if not, write to the Free Software
   14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   15  */
   16 
   17 /**
   18  * $Id: 5443cf3c4f0ab21a9d0f6cc7c816adb0b1a46ed2 $
   19  * @file rlm_sql_map.c
   20  * @brief Tracks data usage and other counters using SQL.
   21  *
   22  * @copyright 2001,2006  The FreeRADIUS server project
   23  * @copyright 2001  Alan DeKok <aland@ox.org>
   24  */
   25 RCSID("$Id: 5443cf3c4f0ab21a9d0f6cc7c816adb0b1a46ed2 $")
   26 
   27 #include <freeradius-devel/radiusd.h>
   28 #include <freeradius-devel/modules.h>
   29 #include <freeradius-devel/rad_assert.h>
   30 
   31 #include <ctype.h>
   32 
   33 #include <rlm_sql.h>
   34 
   35 #define MAX_QUERY_LEN 2048
   36 
   37 typedef struct rlm_sql_map_t {
   38     char const  *sql_instance_name; //!< Instance of SQL module to use,
   39                         //!< usually just 'sql'.
   40     bool        multiple_rows;      //!< Process all rows creating an attr[*] array
   41 
   42     char const  *query;         //!< SQL query to retrieve current
   43 
   44     rlm_sql_t   *sql_inst;
   45 
   46     CONF_SECTION    *cs;
   47 
   48     /*
   49      *  SQL columns to RADIUS stuff
   50      */
   51     vp_map_t    *user_map;
   52 } rlm_sql_map_t;
   53 
   54 /*
   55  *  A mapping of configuration file names to internal variables.
   56  *
   57  *  Note that the string is dynamically allocated, so it MUST
   58  *  be freed.  When the configuration file parse re-reads the string,
   59  *  it free's the old one, and strdup's the new one, placing the pointer
   60  *  to the strdup'd string into 'config.string'.  This gets around
   61  *  buffer over-flows.
   62  */
   63 static const CONF_PARSER module_config[] = {
   64     { "sql_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sql_map_t, sql_instance_name), NULL },
   65     { "multiple_rows", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_map_t, multiple_rows), "no" },
   66     { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_REQUIRED | PW_TYPE_NOT_EMPTY, rlm_sql_map_t, query), NULL },
   67 
   68     CONF_PARSER_TERMINATOR
   69 };
   70 
   71 #define SQL_MAX_ATTRMAP (128)
   72 
   73 static int sql_map_verify(vp_map_t *map, UNUSED void *instance)
   74 {
   75     /*
   76      *  Destinations where we can put the VALUE_PAIRs we
   77      *  create using SQL values.
   78      */
   79     switch (map->lhs->type) {
   80     case TMPL_TYPE_ATTR:
   81         break;
   82 
   83     case TMPL_TYPE_ATTR_UNDEFINED:
   84         cf_log_err(map->ci, "Unknown attribute %s", map->lhs->tmpl_unknown_name);
   85         return -1;
   86 
   87     default:
   88         cf_log_err(map->ci, "Left hand side of map must be an attribute, not a %s",
   89                fr_int2str(tmpl_names, map->lhs->type, "<INVALID>"));
   90         return -1;
   91     }
   92 
   93     /*
   94      *  The RHS MUST be only a column number.
   95      */
   96     switch (map->rhs->type) {
   97     case TMPL_TYPE_LITERAL:
   98     case TMPL_TYPE_DATA:
   99         if (tmpl_cast_in_place(map->rhs, PW_TYPE_INTEGER, NULL) < 0) {
  100             cf_log_err(map->ci, "Failed parsing right hand side of map as an integer.");
  101             return -1;
  102         }
  103 
  104         if (map->rhs->tmpl_data_value.integer > SQL_MAX_ATTRMAP) {
  105             cf_log_err(map->ci, "Column number %u is larger than allowed maximum %u",
  106                 map->rhs->tmpl_data_value.integer, SQL_MAX_ATTRMAP);
  107             return -1;
  108         }
  109         break;
  110 
  111     default:
  112         cf_log_err(map->ci, "Right hand side of map must be a column number, not a %s",
  113                fr_int2str(tmpl_names, map->rhs->type, "<INVALID>"));
  114         return -1;
  115     }
  116 
  117     /*
  118      *  Only =, :=, += and -= operators are supported for SQL mappings.
  119      */
  120     switch (map->op) {
  121     case T_OP_SET:
  122     case T_OP_EQ:
  123     case T_OP_SUB:
  124     case T_OP_ADD:
  125         break;
  126 
  127     default:
  128         cf_log_err(map->ci, "Operator \"%s\" not allowed for SQL mappings",
  129                fr_int2str(fr_tokens, map->op, "<INVALID>"));
  130         return -1;
  131     }
  132 
  133     return 0;
  134 }
  135 
  136 typedef struct sql_map_row_s {
  137     int     num_columns;
  138     char        **row;
  139 } sql_map_row_t;
  140 
  141 
  142 /** Callback for map_to_request
  143  *
  144  * Performs exactly the same job as map_to_vp, but pulls attribute values from SQL entries
  145  *
  146  * @see map_to_vp
  147  */
  148 static int sql_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
  149 {
  150     VALUE_PAIR  *head = NULL, *vp;
  151     int     column;
  152     sql_map_row_t   *data = uctx;
  153     char        *value;
  154     vp_cursor_t cursor;
  155 
  156     *out = NULL;
  157     fr_cursor_init(&cursor, &head);
  158 
  159     switch (map->lhs->type) {
  160     /*
  161      *  Iterate over all the retrieved values,
  162      *  don't try and be clever about changing operators
  163      *  just use whatever was set in the attribute map.
  164      */
  165     case TMPL_TYPE_ATTR:
  166         fr_assert(map->rhs->type == TMPL_TYPE_DATA);
  167         fr_assert(map->rhs->tmpl_data_type == PW_TYPE_INTEGER);
  168 
  169         column = map->rhs->tmpl_data_value.integer;
  170         if (column >= data->num_columns) {
  171             RWDEBUG("Ignoring source column number %u, as it is larger than the number of returned columns %d",
  172                 column, data->num_columns);
  173             return 0;
  174         }
  175 
  176         if (!data->row[column]) {
  177             RWDEBUG("Ignoring source column number %u - it is empty", column);
  178             return 0;
  179         }
  180         
  181         value = data->row[column];
  182 
  183         vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
  184         rad_assert(vp);
  185 
  186         if (fr_pair_value_from_str(vp, value, -1) < 0) {
  187             char *escaped;
  188 
  189             escaped = fr_aprints(vp, value, -1, '"');
  190             RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped,
  191                 map->lhs->tmpl_da->name, fr_strerror());
  192             talloc_free(vp); /* also frees escaped */
  193             break;
  194         }
  195 
  196         vp->op = map->op;
  197         fr_cursor_insert(&cursor, vp);
  198         break;
  199 
  200     default:
  201         rad_assert(0);
  202     }
  203 
  204     *out = head;
  205 
  206     return 0;
  207 }
  208 
  209 
  210 /** Convert attribute map into valuepairs
  211  *
  212  * Use the attribute map built earlier to convert SQL values into valuepairs and insert them into whichever
  213  * list they need to go into.
  214  *
  215  * This is *NOT* atomic, but there's no condition for which we should error out...
  216  *
  217  * @param[in] inst module configuration.
  218  * @param[in] request Current request.
  219  * @param[in] handle associated with entry.
  220  * @return
  221  *  - Number of maps successfully applied.
  222  *  - -1 on failure.
  223  */
  224 static int sql_map_do(const rlm_sql_map_t *inst, REQUEST *request, rlm_sql_handle_t **handle)
  225 {
  226     vp_map_t const      *map;
  227     int         applied = 0;    /* How many maps have been applied to the current request */
  228     sql_map_row_t       ctx;
  229 
  230     /*
  231      *  Cache all of the rows in a simple array.
  232      */
  233     while ((inst->sql_inst->module->sql_fetch_row)(*handle, inst->sql_inst->config) == RLM_SQL_OK) {
  234 #ifdef __clang_analyzer__
  235         if (!*handle) return -1; /* only true when return code is not RLM_SQL_OK */
  236 #endif
  237 
  238         ctx.row = (*handle)->row;
  239         ctx.num_columns = (inst->sql_inst->module->sql_num_fields)(*handle, inst->sql_inst->config);
  240 
  241         if (applied >= 1 && !inst->multiple_rows) {
  242             RWDEBUG("Ignoring multiple rows. Enable the option 'multiple_rows' if you need multiple rows.");
  243             break;
  244         }
  245 
  246         for (map = inst->user_map; map != NULL; map = map->next) {
  247             /*
  248              *  If something bad happened, just skip, this is probably
  249              *  a case of the dst being incorrect for the current
  250              *  request context
  251              */
  252             if (map_to_request(request, map, sql_map_getvalue, &ctx) < 0) {
  253                 return -1;  /* Fail */
  254             }
  255         }
  256 
  257         applied++;
  258     }
  259 
  260     return applied;
  261 }
  262 
  263 /*
  264  *  Do any per-module initialization that is separate to each
  265  *  configured instance of the module.  e.g. set up connections
  266  *  to external databases, read configuration files, set up
  267  *  dictionary entries, etc.
  268  *
  269  *  If configuration information is given in the config section
  270  *  that must be referenced in later calls, store a handle to it
  271  *  in *instance otherwise put a null pointer there.
  272  */
  273 static int mod_instantiate(CONF_SECTION *conf, void *instance)
  274 {
  275     rlm_sql_map_t *inst = instance;
  276     module_instance_t *sql_inst;
  277     CONF_SECTION *update;
  278 
  279     sql_inst = module_instantiate(cf_section_find("modules"),
  280                     inst->sql_instance_name);
  281     if (!sql_inst) {
  282         cf_log_err_cs(conf, "Failed to find sql instance named %s",
  283                inst->sql_instance_name);
  284         return -1;
  285     }
  286     inst->sql_inst = (rlm_sql_t *)sql_inst->insthandle;
  287 
  288     inst->cs = conf;
  289 
  290     /*
  291      *  Build the attribute map
  292      */
  293     update = cf_section_sub_find(inst->cs, "update");
  294     if (!update) {
  295         cf_log_err_cs(conf, "Failed to find 'update' section");
  296         return -1;
  297     }
  298 
  299     if (map_afrom_cs(&inst->user_map, update,
  300              PAIR_LIST_REPLY, PAIR_LIST_REQUEST, sql_map_verify, inst,
  301              SQL_MAX_ATTRMAP) < 0) {
  302         return -1;
  303     }
  304 
  305     return 0;
  306 }
  307 
  308 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
  309 {
  310     rlm_sql_map_t *inst = instance;
  311     char const *p = inst->query;
  312 
  313     if (!p || !*p) {
  314         cf_log_err_cs(conf, "'query' cannot be empty");
  315         return -1;
  316     }
  317 
  318     while (isspace((int) *p)) p++;
  319 
  320     if (strncasecmp(p, "select", 6) != 0) {
  321         cf_log_err_cs(conf, "'query' MUST be 'SELECT ...', not 'INSERT' or 'UPDATE'");
  322         return -1;
  323     }
  324 
  325     return 0;
  326 }
  327 
  328 
  329 /** Detach from the SQL server and cleanup internal state.
  330  *
  331  */
  332 static int mod_detach(void *instance)
  333 {
  334     rlm_sql_map_t *inst = instance;
  335 
  336     talloc_free(inst->user_map);
  337 
  338     return 0;
  339 }
  340 
  341 
  342 /*
  343  *  Find the named user in this modules database.  Create the set
  344  *  of attribute-value pairs to check and reply with for this user
  345  *  from the database. The authentication code only needs to check
  346  *  the password, the rest is done here.
  347  */
  348 static rlm_rcode_t CC_HINT(nonnull) mod_map(void *instance, REQUEST *request)
  349 {
  350     int res;
  351     rlm_rcode_t rcode = RLM_MODULE_NOOP;
  352     char *query;
  353     rlm_sql_map_t *inst = instance;
  354     rlm_sql_handle_t *handle;
  355 
  356     handle = fr_connection_get(inst->sql_inst->pool);
  357     if (!handle) {
  358         REDEBUG("Failed reserving SQL connection");
  359         return RLM_MODULE_FAIL;
  360     }
  361 
  362     if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
  363         return RLM_MODULE_FAIL;
  364     }
  365 
  366     if (radius_axlat(&query, request, inst->query, inst->sql_inst->sql_escape_func, handle) < 0) {
  367         return RLM_MODULE_FAIL;
  368     }
  369 
  370     res = inst->sql_inst->sql_select_query(inst->sql_inst, request, &handle, query);
  371     talloc_free(query);
  372     if (res != RLM_SQL_OK) {
  373         if (handle) fr_connection_release(inst->sql_inst->pool, handle);
  374 
  375         return RLM_MODULE_FAIL;
  376     }
  377 
  378     fr_assert(handle != NULL);
  379 
  380     if (sql_map_do(inst, request, &handle) > 0) rcode = RLM_MODULE_UPDATED;
  381 
  382     if (handle) {
  383         (inst->sql_inst->module->sql_finish_query)(handle, inst->sql_inst->config);
  384 
  385         fr_connection_release(inst->sql_inst->pool, handle);
  386     }
  387 
  388     return rcode;
  389 }
  390 
  391 /*
  392  *  The module name should be the only globally exported symbol.
  393  *  That is, everything else should be 'static'.
  394  *
  395  *  If the module needs to temporarily modify it's instantiation
  396  *  data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
  397  *  The server will then take care of ensuring that the module
  398  *  is single-threaded.
  399  */
  400 extern module_t rlm_sql_map;
  401 module_t rlm_sql_map = {
  402     .magic      = RLM_MODULE_INIT,
  403     .name       = "sqlcounter",
  404     .type       = RLM_TYPE_THREAD_SAFE,
  405     .inst_size  = sizeof(rlm_sql_map_t),
  406     .config     = module_config,
  407     .bootstrap  = mod_bootstrap,
  408     .instantiate    = mod_instantiate,
  409     .detach     = mod_detach,
  410     .methods = {
  411         [MOD_AUTHENTICATE]  = mod_map,
  412         [MOD_AUTHORIZE]     = mod_map,
  413         [MOD_PREACCT]       = mod_map,
  414         [MOD_ACCOUNTING]    = mod_map,
  415         [MOD_PRE_PROXY]     = mod_map,
  416         [MOD_POST_PROXY]    = mod_map,
  417         [MOD_POST_AUTH]     = mod_map,
  418 #ifdef WITH_COA
  419         [MOD_RECV_COA]      = mod_map,
  420         [MOD_SEND_COA]      = mod_map
  421 #endif
  422     },
  423 };
  424