📄 rlm_sqlcounter.c
字号:
/* * rlm_sqlcounter.c * * Version: $Id: rlm_sqlcounter.c,v 1.39 2008/04/20 14:19:46 aland Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * Copyright 2001,2006 The FreeRADIUS server project * Copyright 2001 Alan DeKok <aland@ox.org> *//* This module is based directly on the rlm_counter module */#include <freeradius-devel/ident.h>RCSID("$Id: rlm_sqlcounter.c,v 1.39 2008/04/20 14:19:46 aland Exp $")#include <freeradius-devel/radiusd.h>#include <freeradius-devel/modules.h>#include <ctype.h>#define MAX_QUERY_LEN 1024static int sqlcounter_detach(void *instance);/* * Note: When your counter spans more than 1 period (ie 3 months * or 2 weeks), this module probably does NOT do what you want! It * calculates the range of dates to count across by first calculating * the End of the Current period and then subtracting the number of * periods you specify from that to determine the beginning of the * range. * * For example, if you specify a 3 month counter and today is June 15th, * the end of the current period is June 30. Subtracting 3 months from * that gives April 1st. So, the counter will sum radacct entries from * April 1st to June 30. Then, next month, it will sum entries from * May 1st to July 31st. * * To fix this behavior, we need to add some way of storing the Next * Reset Time. *//* * Define a structure for our module configuration. * * These variables do not need to be in a structure, but it's * a lot cleaner to do so, and a pointer to the structure can * be used as the instance handle. */typedef struct rlm_sqlcounter_t { char *counter_name; /* Daily-Session-Time */ char *check_name; /* Max-Daily-Session */ char *reply_name; /* Session-Timeout */ char *key_name; /* User-Name */ char *sqlmod_inst; /* instance of SQL module to use, usually just 'sql' */ char *query; /* SQL query to retrieve current session time */ char *reset; /* daily, weekly, monthly, never or user defined */ char *allowed_chars; /* safe characters list for SQL queries */ time_t reset_time; time_t last_reset; int key_attr; /* attribute number for key field */ int dict_attr; /* attribute number for the counter. */ int reply_attr; /* attribute number for the reply */} rlm_sqlcounter_t;/* * A mapping of configuration file names to internal variables. * * Note that the string is dynamically allocated, so it MUST * be freed. When the configuration file parse re-reads the string, * it free's the old one, and strdup's the new one, placing the pointer * to the strdup'd string into 'config.string'. This gets around * buffer over-flows. */static const CONF_PARSER module_config[] = { { "counter-name", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,counter_name), NULL, NULL }, { "check-name", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,check_name), NULL, NULL }, { "reply-name", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,reply_name), NULL, NULL }, { "key", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,key_name), NULL, NULL }, { "sqlmod-inst", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,sqlmod_inst), NULL, NULL }, { "query", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,query), NULL, NULL }, { "reset", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,reset), NULL, NULL }, { "safe-characters", PW_TYPE_STRING_PTR, offsetof(rlm_sqlcounter_t,allowed_chars), NULL, "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"}, { NULL, -1, 0, NULL, NULL }};static char *allowed_chars = NULL;/* * Translate the SQL queries. */static size_t sql_escape_func(char *out, size_t outlen, const char *in){ int len = 0; while (in[0]) { /* * Non-printable characters get replaced with their * mime-encoded equivalents. */ if ((in[0] < 32) || strchr(allowed_chars, *in) == NULL) { /* * Only 3 or less bytes available. */ if (outlen <= 3) { break; } snprintf(out, outlen, "=%02X", (unsigned char) in[0]); in++; out += 3; outlen -= 3; len += 3; continue; } /* * Only one byte left. */ if (outlen <= 1) { break; } /* * Allowed character. */ *out = *in; out++; in++; outlen--; len++; } *out = '\0'; return len;}static int find_next_reset(rlm_sqlcounter_t *data, time_t timeval){ int ret = 0; size_t len; unsigned int num = 1; char last = '\0'; struct tm *tm, s_tm; char sCurrentTime[40], sNextTime[40]; tm = localtime_r(&timeval, &s_tm); len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm); if (len == 0) *sCurrentTime = '\0'; tm->tm_sec = tm->tm_min = 0; if (data->reset == NULL) return -1; if (isdigit((int) data->reset[0])){ len = strlen(data->reset); if (len == 0) return -1; last = data->reset[len - 1]; if (!isalpha((int) last)) last = 'd'; num = atoi(data->reset); DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last); } if (strcmp(data->reset, "hourly") == 0 || last == 'h') { /* * Round up to the next nearest hour. */ tm->tm_hour += num; data->reset_time = mktime(tm); } else if (strcmp(data->reset, "daily") == 0 || last == 'd') { /* * Round up to the next nearest day. */ tm->tm_hour = 0; tm->tm_mday += num; data->reset_time = mktime(tm); } else if (strcmp(data->reset, "weekly") == 0 || last == 'w') { /* * Round up to the next nearest week. */ tm->tm_hour = 0; tm->tm_mday += (7 - tm->tm_wday) +(7*(num-1)); data->reset_time = mktime(tm); } else if (strcmp(data->reset, "monthly") == 0 || last == 'm') { tm->tm_hour = 0; tm->tm_mday = 1; tm->tm_mon += num; data->reset_time = mktime(tm); } else if (strcmp(data->reset, "never") == 0) { data->reset_time = 0; } else { radlog(L_ERR, "rlm_sqlcounter: Unknown reset timer \"%s\"", data->reset); return -1; } len = strftime(sNextTime, sizeof(sNextTime),"%Y-%m-%d %H:%M:%S",tm); if (len == 0) *sNextTime = '\0'; DEBUG2("rlm_sqlcounter: Current Time: %li [%s], Next reset %li [%s]", timeval, sCurrentTime, data->reset_time, sNextTime); return ret;}/* I don't believe that this routine handles Daylight Saving Time adjustments properly. Any suggestions?*/static int find_prev_reset(rlm_sqlcounter_t *data, time_t timeval){ int ret = 0; size_t len; unsigned int num = 1; char last = '\0'; struct tm *tm, s_tm; char sCurrentTime[40], sPrevTime[40]; tm = localtime_r(&timeval, &s_tm); len = strftime(sCurrentTime, sizeof(sCurrentTime), "%Y-%m-%d %H:%M:%S", tm); if (len == 0) *sCurrentTime = '\0'; tm->tm_sec = tm->tm_min = 0; if (data->reset == NULL) return -1; if (isdigit((int) data->reset[0])){ len = strlen(data->reset); if (len == 0) return -1; last = data->reset[len - 1]; if (!isalpha((int) last)) last = 'd'; num = atoi(data->reset); DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last); } if (strcmp(data->reset, "hourly") == 0 || last == 'h') { /* * Round down to the prev nearest hour. */ tm->tm_hour -= num - 1; data->last_reset = mktime(tm); } else if (strcmp(data->reset, "daily") == 0 || last == 'd') { /* * Round down to the prev nearest day. */ tm->tm_hour = 0; tm->tm_mday -= num - 1; data->last_reset = mktime(tm); } else if (strcmp(data->reset, "weekly") == 0 || last == 'w') { /* * Round down to the prev nearest week. */ tm->tm_hour = 0; tm->tm_mday -= (7 - tm->tm_wday) +(7*(num-1)); data->last_reset = mktime(tm); } else if (strcmp(data->reset, "monthly") == 0 || last == 'm') { tm->tm_hour = 0; tm->tm_mday = 1; tm->tm_mon -= num - 1; data->last_reset = mktime(tm); } else if (strcmp(data->reset, "never") == 0) { data->reset_time = 0; } else { radlog(L_ERR, "rlm_sqlcounter: Unknown reset timer \"%s\"", data->reset); return -1; } len = strftime(sPrevTime, sizeof(sPrevTime), "%Y-%m-%d %H:%M:%S", tm); if (len == 0) *sPrevTime = '\0'; DEBUG2("rlm_sqlcounter: Current Time: %li [%s], Prev reset %li [%s]", timeval, sCurrentTime, data->last_reset, sPrevTime); return ret;}/* * Replace %<whatever> in a string. * * %b last_reset * %e reset_time * %k key_name * %S sqlmod_inst * */static int sqlcounter_expand(char *out, int outlen, const char *fmt, void *instance){ rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance; int c,freespace; const char *p; char *q; char tmpdt[40]; /* For temporary storing of dates */ int openbraces=0; q = out; for (p = fmt; *p ; p++) { /* Calculate freespace in output */ freespace = outlen - (q - out); if (freespace <= 1) break; c = *p; if ((c != '%') && (c != '$') && (c != '\\')) { /* * We check if we're inside an open brace. If we are * then we assume this brace is NOT literal, but is * a closing brace and apply it */ if((c == '}') && openbraces) { openbraces--; continue; } *q++ = *p; continue; } if (*++p == '\0') break; if (c == '\\') switch(*p) { case '\\': *q++ = *p; break; case 't': *q++ = '\t'; break; case 'n': *q++ = '\n'; break; default: *q++ = c; *q++ = *p; break; } else if (c == '%') switch(*p) { case '%': *q++ = *p; case 'b': /* last_reset */ snprintf(tmpdt, sizeof(tmpdt), "%lu", data->last_reset); strlcpy(q, tmpdt, freespace); q += strlen(q); break; case 'e': /* reset_time */ snprintf(tmpdt, sizeof(tmpdt), "%lu", data->reset_time); strlcpy(q, tmpdt, freespace); q += strlen(q); break; case 'k': /* Key Name */ strlcpy(q, data->key_name, freespace); q += strlen(q); break; case 'S': /* SQL module instance */ strlcpy(q, data->sqlmod_inst, freespace); q += strlen(q); break; default: *q++ = '%'; *q++ = *p; break; } } *q = '\0'; DEBUG2("sqlcounter_expand: '%s'", out); return strlen(out);}/* * See if the counter matches. */static int sqlcounter_cmp(void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs){ rlm_sqlcounter_t *data = (rlm_sqlcounter_t *) instance; int counter; char querystr[MAX_QUERY_LEN]; char responsestr[MAX_QUERY_LEN]; check_pairs = check_pairs; /* shut the compiler up */ reply_pairs = reply_pairs; /* first, expand %k, %b and %e in query */ sqlcounter_expand(querystr, MAX_QUERY_LEN, data->query, instance); /* second, xlat any request attribs in query */ radius_xlat(responsestr, MAX_QUERY_LEN, querystr, req, sql_escape_func); /* third, wrap query with sql module call & expand */ snprintf(querystr, sizeof(querystr), "%%{%%S:%s}", responsestr); sqlcounter_expand(responsestr, MAX_QUERY_LEN, querystr, instance); /* Finally, xlat resulting SQL query */ radius_xlat(querystr, MAX_QUERY_LEN, responsestr, req, sql_escape_func); counter = atoi(querystr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -