/* * Log the query to a file. */ void rlm_sql_query_log(SQL_INST *inst, REQUEST *request, rlm_sql_config_section_t *section, char *query) { int fd; const char *filename = NULL; char buffer[8192]; if (section) filename = section->logfile; if (!filename) filename = inst->config->logfile; if (!filename) return; if (!radius_xlat(buffer, sizeof(buffer), filename, request, NULL, NULL)) { radlog(L_ERR, "rlm_sql (%s): xlat failed.", inst->config->xlat_name); return; } fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd < 0) { radlog(L_ERR, "rlm_sql (%s): Couldn't open logfile '%s': %s", inst->config->xlat_name, buffer, strerror(errno)); return; } rad_lockfd(fd, MAX_QUERY_LEN); if ((write(fd, query, strlen(query) < 0) || (write(fd, ";\n", 2) < 0))) radlog(L_ERR, "rlm_sql (%s): Failed writing to logfile '%s': %s", inst->config->xlat_name, buffer, strerror(errno)); close(fd); /* and release the lock */ }
void query_log(REQUEST *request, SQL_INST *inst, char *querystr) { FILE *sqlfile = NULL; if (inst->config->sqltrace) { char buffer[8192]; if (!radius_xlat(buffer, sizeof(buffer), inst->config->tracefile, request, NULL)) { radlog(L_ERR, "rlm_sql (%s): xlat failed.", inst->config->xlat_name); return; } if ((sqlfile = fopen(buffer, "a")) == (FILE *) NULL) { radlog(L_ERR, "rlm_sql (%s): Couldn't open file %s", inst->config->xlat_name, buffer); } else { int fd = fileno(sqlfile); rad_lockfd(fd, MAX_QUERY_LEN); fputs(querystr, sqlfile); fputs(";\n", sqlfile); fclose(sqlfile); /* and release the lock */ } } }
static int radutmp_lookup(struct radutmp *u, uint32_t nasaddr, uint32_t port, const char *user) { int fd; if ((fd = open(radutmpconfig.radutmp_fn, O_RDONLY|O_CREAT, 0644)) >= 0) { /* * Lock the utmp file. */ rad_lockfd(fd, LOCK_LEN); /* * Find the entry for this NAS / portno combination. */ while (read(fd, u, sizeof(*u)) == sizeof(*u)) { if ((nasaddr != 0 && nasaddr != u->nas_address) || (port != u->nas_port) || (user != NULL && strncmp(u->login, user, sizeof u->login) != 0) || u->type != P_LOGIN) continue; /* * Match. Zap it. */ close(fd); return 1; } close(fd); } return 0; }
/* * Zap all users on a NAS from the radutmp file. */ static rlm_rcode_t radutmp_zap(REQUEST *request, char const *filename, uint32_t nasaddr, time_t t) { struct radutmp u; int fd; if (t == 0) time(&t); fd = open(filename, O_RDWR); if (fd < 0) { REDEBUG("Error accessing file %s: %s", filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * Lock the utmp file, prefer lockf() over flock(). */ if (rad_lockfd(fd, LOCK_LEN) < 0) { REDEBUG("Failed to acquire lock on file %s: %s", filename, strerror(errno)); close(fd); return RLM_MODULE_FAIL; } /* * Find the entry for this NAS / portno combination. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if ((nasaddr != 0 && nasaddr != u.nas_address) || u.type != P_LOGIN) { continue; } /* * Match. Zap it. */ if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { REDEBUG("radutmp_zap: negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); } u.type = P_IDLE; u.time = t; if (write(fd, &u, sizeof(u)) < 0) { REDEBUG("Failed writing: %s", strerror(errno)); close(fd); return RLM_MODULE_FAIL; } } close(fd); /* and implicitely release the locks */ return RLM_MODULE_OK; }
/* * Zap all users on a NAS from the radutmp file. */ static int radutmp_zap(UNUSED rlm_radutmp_t *inst, const char *filename, uint32_t nasaddr, time_t t) { struct radutmp u; int fd; if (t == 0) time(&t); fd = open(filename, O_RDWR); if (fd < 0) { radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s", filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * Find the entry for this NAS / portno combination. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if ((nasaddr != 0 && nasaddr != u.nas_address) || u.type != P_LOGIN) continue; /* * Match. Zap it. */ if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { radlog(L_ERR, "rlm_radutmp: radutmp_zap: negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); } u.type = P_IDLE; u.time = t; write(fd, &u, sizeof(u)); } close(fd); /* and implicitely release the locks */ return 0; }
/* * Log the query to a file. */ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section, char const *query) { int fd; char const *filename = NULL; char *expanded = NULL; if (section) { filename = section->logfile; } else { filename = inst->config->logfile; } if (!filename) { return; } if (radius_axlat(&expanded, request, filename, NULL, NULL) < 0) { return; } fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd < 0) { ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); talloc_free(expanded); return; } if ((rad_lockfd(fd, MAX_QUERY_LEN) < 0) || (write(fd, query, strlen(query)) < 0) || (write(fd, ";\n", 2) < 0)) { ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); } talloc_free(expanded); close(fd); /* and release the lock */ }
/* * See if a user is already logged in. Sets request->simul_count to the * current session count for this user and sets request->simul_mpp to 2 * if it looks like a multilink attempt based on the requested IP * address, otherwise leaves request->simul_mpp alone. * * Check twice. If on the first pass the user exceeds his * max. number of logins, do a second pass and validate all * logins by querying the terminal server (using eg. SNMP). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; struct radutmp u; int fd = -1; VALUE_PAIR *vp; uint32_t ipno = 0; char const *call_num = NULL; rlm_radutmp_t *inst = instance; char *expanded = NULL; ssize_t len; /* * Get the filename, via xlat. */ if (radius_axlat(&expanded, request, inst->filename, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } fd = open(expanded, O_RDWR); if (fd < 0) { /* * If the file doesn't exist, then no users * are logged in. */ if (errno == ENOENT) { request->simul_count=0; return RLM_MODULE_OK; } /* * Error accessing the file. */ ERROR("rlm_radumtp: Error accessing file %s: %s", expanded, strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } TALLOC_FREE(expanded); len = radius_axlat(&expanded, request, inst->username, NULL, NULL); if (len < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!len) { rcode = RLM_MODULE_NOOP; goto finish; } /* * WTF? This is probably wrong... we probably want to * be able to check users across multiple session accounting * methods. */ request->simul_count = 0; /* * Loop over utmp, counting how many people MAY be logged in. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { ++request->simul_count; } } /* * The number of users logged in is OK, * OR, we've been told to not check the NAS. */ if ((request->simul_count < request->simul_max) || !inst->check_nas) { rcode = RLM_MODULE_OK; goto finish; } lseek(fd, (off_t)0, SEEK_SET); /* * Setup some stuff, like for MPP detection. */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { ipno = vp->vp_ipaddr; } if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { call_num = vp->vp_strvalue; } /* * lock the file while reading/writing. */ rad_lockfd(fd, LOCK_LEN); /* * FIXME: If we get a 'Start' for a user/nas/port which is * listed, but for which we did NOT get a 'Stop', then * it's not a duplicate session. This happens with * static IP's like DSL. */ request->simul_count = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { char session_id[sizeof(u.session_id) + 1]; char utmp_login[sizeof(u.login) + 1]; strlcpy(session_id, u.session_id, sizeof(session_id)); /* * The login name MAY fill the whole field, * and thus won't be zero-filled. * * Note that we take the user name from * the utmp file, as that's the canonical * form. The 'login' variable may contain * a string which is an upper/lowercase * version of u.login. When we call the * routine to check the terminal server, * the NAS may be case sensitive. * * e.g. We ask if "bob" is using a port, * and the NAS says "no", because "BOB" * is using the port. */ memset(utmp_login, 0, sizeof(utmp_login)); memcpy(utmp_login, u.login, sizeof(u.login)); /* * rad_check_ts may take seconds * to return, and we don't want * to block everyone else while * that's happening. */ rad_unlockfd(fd, LOCK_LEN); rcode = rad_check_ts(u.nas_address, u.nas_port, utmp_login, session_id); rad_lockfd(fd, LOCK_LEN); if (rcode == 0) { /* * Stale record - zap it. */ session_zap(request, u.nas_address, u.nas_port, expanded, session_id, u.framed_address, u.proto, 0); } else if (rcode == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (strchr("SCPA", u.proto) && ipno && u.framed_address == ipno) { request->simul_mpp = 2; } else if (strchr("SCPA", u.proto) && call_num && !strncmp(u.caller_id, call_num,16)) { request->simul_mpp = 2; } } else { RWDEBUG("Failed to check the terminal server for user '%s'.", utmp_login); rcode = RLM_MODULE_FAIL; goto finish; } } } finish: talloc_free(expanded); if (fd > -1) { close(fd); /* and implicitely release the locks */ } return rcode; }
/* * Store logins in the RADIUS utmp file. */ static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; struct radutmp ut, u; vp_cursor_t cursor; VALUE_PAIR *vp; int status = -1; int protocol = -1; time_t t; int fd = -1; int port_seen = 0; int off; rlm_radutmp_t *inst = instance; char ip_name[32]; /* 255.255.255.255 */ char const *nas; NAS_PORT *cache; int r; char *filename = NULL; char *expanded = NULL; if (request->packet->src_ipaddr.af != AF_INET) { DEBUG("rlm_radutmp: IPv6 not supported!"); return RLM_MODULE_NOOP; } /* * Which type is this. */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { RDEBUG("No Accounting-Status-Type record."); return RLM_MODULE_NOOP; } status = vp->vp_integer; /* * Look for weird reboot packets. * * ComOS (up to and including 3.5.1b20) does not send * standard PW_STATUS_ACCOUNTING_XXX messages. * * Check for: o no Acct-Session-Time, or time of 0 * o Acct-Session-Id of "00000000". * * We could also check for NAS-Port, that attribute * should NOT be present (but we don't right now). */ if ((status != PW_STATUS_ACCOUNTING_ON) && (status != PW_STATUS_ACCOUNTING_OFF)) do { int check1 = 0; int check2 = 0; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) == NULL || vp->vp_date == 0) check1 = 1; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY)) != NULL && vp->length == 8 && memcmp(vp->vp_strvalue, "00000000", 8) == 0) check2 = 1; if (check1 == 0 || check2 == 0) { break; } INFO("rlm_radutmp: converting reboot records."); if (status == PW_STATUS_STOP) status = PW_STATUS_ACCOUNTING_OFF; if (status == PW_STATUS_START) status = PW_STATUS_ACCOUNTING_ON; } while(0); time(&t); memset(&ut, 0, sizeof(ut)); ut.porttype = 'A'; ut.nas_address = htonl(INADDR_NONE); /* * First, find the interesting attributes. */ for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor)) { if (!vp->da->vendor) switch (vp->da->attr) { case PW_LOGIN_IP_HOST: case PW_FRAMED_IP_ADDRESS: ut.framed_address = vp->vp_ipaddr; break; case PW_FRAMED_PROTOCOL: protocol = vp->vp_integer; break; case PW_NAS_IP_ADDRESS: ut.nas_address = vp->vp_ipaddr; break; case PW_NAS_PORT: ut.nas_port = vp->vp_integer; port_seen = 1; break; case PW_ACCT_DELAY_TIME: ut.delay = vp->vp_integer; break; case PW_ACCT_SESSION_ID: /* * If length > 8, only store the * last 8 bytes. */ off = vp->length - sizeof(ut.session_id); /* * Ascend is br0ken - it adds a \0 * to the end of any string. * Compensate. */ if (vp->length > 0 && vp->vp_strvalue[vp->length - 1] == 0) off--; if (off < 0) off = 0; memcpy(ut.session_id, vp->vp_strvalue + off, sizeof(ut.session_id)); break; case PW_NAS_PORT_TYPE: if (vp->vp_integer <= 4) ut.porttype = porttypes[vp->vp_integer]; break; case PW_CALLING_STATION_ID: if(inst->caller_id_ok) strlcpy(ut.caller_id, vp->vp_strvalue, sizeof(ut.caller_id)); break; } } /* * If we didn't find out the NAS address, use the * originator's IP address. */ if (ut.nas_address == htonl(INADDR_NONE)) { ut.nas_address = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr; nas = request->client->shortname; } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == ut.nas_address) { /* might be a client, might not be. */ nas = request->client->shortname; } else { /* * The NAS isn't a client, it's behind * a proxy server. In that case, just * get the IP address. */ nas = ip_ntoa(ip_name, ut.nas_address); } /* * Set the protocol field. */ if (protocol == PW_PPP) { ut.proto = 'P'; } else if (protocol == PW_SLIP) { ut.proto = 'S'; } else { ut.proto = 'T'; } ut.time = t - ut.delay; /* * Get the utmp filename, via xlat. */ filename = NULL; if (radius_axlat(&filename, request, inst->filename, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } /* * See if this was a reboot. * * Hmm... we may not want to zap all of the users when the NAS comes up, because of issues with receiving * UDP packets out of order. */ if (status == PW_STATUS_ACCOUNTING_ON && (ut.nas_address != htonl(INADDR_NONE))) { RIDEBUG("NAS %s restarted (Accounting-On packet seen)", nas); rcode = radutmp_zap(request, filename, ut.nas_address, ut.time); goto finish; } if (status == PW_STATUS_ACCOUNTING_OFF && (ut.nas_address != htonl(INADDR_NONE))) { RIDEBUG("NAS %s rebooted (Accounting-Off packet seen)", nas); rcode = radutmp_zap(request, filename, ut.nas_address, ut.time); goto finish; } /* * If we don't know this type of entry pretend we succeeded. */ if (status != PW_STATUS_START && status != PW_STATUS_STOP && status != PW_STATUS_ALIVE) { REDEBUG("NAS %s port %u unknown packet type %d)", nas, ut.nas_port, status); rcode = RLM_MODULE_NOOP; goto finish; } /* * Translate the User-Name attribute, or whatever else they told us to use. */ if (radius_axlat(&expanded, request, inst->username, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } strlcpy(ut.login, expanded, RUT_NAMESIZE); TALLOC_FREE(expanded); /* * Perhaps we don't want to store this record into * radutmp. We skip records: * * - without a NAS-Port (telnet / tcp access) * - with the username "!root" (console admin login) */ if (!port_seen) { RWDEBUG2("No NAS-Port seen. Cannot do anything. Checkrad will probably not work!"); rcode = RLM_MODULE_NOOP; goto finish; } if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) { RDEBUG2("Not recording administrative user"); rcode = RLM_MODULE_NOOP; goto finish; } /* * Enter into the radutmp file. */ fd = open(filename, O_RDWR|O_CREAT, inst->permission); if (fd < 0) { REDEBUG("Error accessing file %s: %s", filename, strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * Find the entry for this NAS / portno combination. */ if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address, ut.nas_port)) != NULL) { lseek(fd, (off_t)cache->offset, SEEK_SET); } r = 0; off = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { off += sizeof(u); if ((u.nas_address != ut.nas_address) || (u.nas_port != ut.nas_port)) { continue; } /* * Don't compare stop records to unused entries. */ if (status == PW_STATUS_STOP && u.type == P_IDLE) { continue; } if ((status == PW_STATUS_STOP) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) != 0) { /* * Don't complain if this is not a * login record (some clients can * send _only_ logout records). */ if (u.type == P_LOGIN) { RWDEBUG("Logout entry for NAS %s port %u has wrong ID", nas, u.nas_port); } r = -1; break; } if ((status == PW_STATUS_START) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.time >= ut.time) { if (u.type == P_LOGIN) { INFO("rlm_radutmp: Login entry for NAS %s port %u duplicate", nas, u.nas_port); r = -1; break; } RWDEBUG("Login entry for NAS %s port %u wrong order", nas, u.nas_port); r = -1; break; } /* * FIXME: the ALIVE record could need some more checking, but anyway I'd * rather rewrite this mess -- miquels. */ if ((status == PW_STATUS_ALIVE) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.type == P_LOGIN) { /* * Keep the original login time. */ ut.time = u.time; } if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { RWDEBUG("negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); off = 0; } else { off -= sizeof(u); } r = 1; break; } /* read the file until we find a match */ /* * Found the entry, do start/update it with * the information from the packet. */ if ((r >= 0) && (status == PW_STATUS_START || status == PW_STATUS_ALIVE)) { /* * Remember where the entry was, because it's * easier than searching through the entire file. */ if (!cache) { cache = talloc_zero(inst, NAS_PORT); if (cache) { cache->nasaddr = ut.nas_address; cache->port = ut.nas_port; cache->offset = off; cache->next = inst->nas_port_list; inst->nas_port_list = cache; } } ut.type = P_LOGIN; if (write(fd, &ut, sizeof(u)) < 0) { REDEBUG("Failed writing: %s", strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } } /* * The user has logged off, delete the entry by * re-writing it in place. */ if (status == PW_STATUS_STOP) { if (r > 0) { u.type = P_IDLE; u.time = ut.time; u.delay = ut.delay; if (write(fd, &u, sizeof(u)) < 0) { REDEBUG("Failed writing: %s", strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } } else if (r == 0) { RWDEBUG("Logout for NAS %s port %u, but no Login record", nas, ut.nas_port); } } finish: talloc_free(filename); if (fd > -1) { close(fd); /* and implicitely release the locks */ } return rcode; }
/** Open a new log file, or maybe an existing one. * * When multithreaded, the FD is locked via a mutex. This way we're * sure that no other thread is writing to the file. * * @param lf The logfile context returned from fr_logfile_init(). * @param filename the file to open. * @param permissions to use. * @return an FD used to write to the file, or -1 on error. */ int fr_logfile_open(fr_logfile_t *lf, char const *filename, mode_t permissions) { int i; uint32_t hash; time_t now = time(NULL); struct stat st; if (!lf || !filename) return -1; hash = fr_hash_string(filename); PTHREAD_MUTEX_LOCK(&lf->mutex); /* * Clean up old entries. */ for (i = 0; i < lf->max_entries; i++) { if (!lf->entries[i].filename) continue; /* * FIXME: make this configurable? */ if ((lf->entries[i].last_used + 30) < now) { /* * This will block forever if a thread is * doing something stupid. */ TALLOC_FREE(lf->entries[i].filename); close(lf->entries[i].fd); } } /* * Find the matching entry. */ for (i = 0; i < lf->max_entries; i++) { if (!lf->entries[i].filename) continue; if (lf->entries[i].hash == hash) { /* * Same hash but different filename. Give up. */ if (strcmp(lf->entries[i].filename, filename) != 0) { PTHREAD_MUTEX_UNLOCK(&lf->mutex); return -1; } /* * Someone else failed to create the entry. */ if (!lf->entries[i].filename) { PTHREAD_MUTEX_UNLOCK(&lf->mutex); return -1; } goto do_return; } } /* * Find an unused entry */ for (i = 0; i < lf->max_entries; i++) { if (!lf->entries[i].filename) break; } if (i >= lf->max_entries) { fr_strerror_printf("Too many different filenames"); PTHREAD_MUTEX_UNLOCK(&(lf->mutex)); return -1; } /* * Create a new entry. */ lf->entries[i].hash = hash; lf->entries[i].filename = talloc_strdup(lf->entries, filename); lf->entries[i].fd = -1; lf->entries[i].fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, permissions); if (lf->entries[i].fd < 0) { mode_t dirperm; char *p, *dir; /* * Maybe the directory doesn't exist. Try to * create it. */ dir = talloc_strdup(lf, filename); if (!dir) goto error; p = strrchr(dir, FR_DIR_SEP); if (!p) goto error; *p = '\0'; /* * Ensure that the 'x' bit is set, so that we can * read the directory. */ dirperm = permissions; if ((dirperm & 0600) != 0) dirperm |= 0100; if ((dirperm & 0060) != 0) dirperm |= 0010; if ((dirperm & 0006) != 0) dirperm |= 0001; if (rad_mkdir(dir, dirperm) < 0) { talloc_free(dir); goto error; } talloc_free(dir); lf->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions); if (lf->entries[i].fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); goto error; } /* else fall through to creating the rest of the entry */ } /* else the file was already opened */ do_return: /* * Lock from the start of the file. */ if (lseek(lf->entries[i].fd, 0, SEEK_SET) < 0) { fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno)); error: lf->entries[i].hash = 0; TALLOC_FREE(lf->entries[i].filename); close(lf->entries[i].fd); lf->entries[i].fd = -1; PTHREAD_MUTEX_UNLOCK(&(lf->mutex)); return -1; } if (rad_lockfd(lf->entries[i].fd, 0) < 0) { fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno)); goto error; } /* * Maybe someone deleted the file while we were waiting * for the lock. If so, re-open it. */ if (fstat(lf->entries[i].fd, &st) < 0) { fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno)); goto error; } if (st.st_nlink == 0) { close(lf->entries[i].fd); lf->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions); if (lf->entries[i].fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); goto error; } } /* * Seek to the end of the file before returning the FD to * the caller. */ lseek(lf->entries[i].fd, 0, SEEK_END); /* * Return holding the mutex for the entry. */ lf->entries[i].last_used = now; lf->entries[i].dup = dup(lf->entries[i].fd); if (lf->entries[i].dup < 0) goto error; return lf->entries[i].dup; }
/* * Store logins in the RADIUS utmp file. */ static rlm_rcode_t radutmp_accounting(void *instance, REQUEST *request) { rlm_radutmp_t *inst = instance; struct radutmp utmp, u; VALUE_PAIR *vp; int status = -1; uint32_t nas_address = 0; uint32_t framed_address = 0; int protocol = -1; int fd; int port_seen = 0; char buffer[256]; char filename[1024]; char ip_name[32]; /* 255.255.255.255 */ const char *nas; NAS_PORT *nas_port, myPort; radutmp_cache_t *cache; int read_size; rbnode_t *node; /* * Which type is this. */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { radlog(L_ERR, "rlm_radutmp: No Accounting-Status-Type record."); return RLM_MODULE_NOOP; } status = vp->vp_integer; /* * Look for weird reboot packets. * * ComOS (up to and including 3.5.1b20) does not send * standard PW_STATUS_ACCOUNTING_* messages. * * Check for: o no Acct-Session-Time, or time of 0 * o Acct-Session-Id of "00000000". * * We could also check for NAS-Port, that attribute * should NOT be present (but we don't right now). */ if ((status != PW_STATUS_ACCOUNTING_ON) && (status != PW_STATUS_ACCOUNTING_OFF)) do { int check1 = 0; int check2 = 0; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) == NULL || vp->vp_date == 0) check1 = 1; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY)) != NULL && vp->length == 8 && memcmp(vp->vp_strvalue, "00000000", 8) == 0) check2 = 1; if (check1 == 0 || check2 == 0) { #if 0 /* Cisco sometimes sends START records without username. */ radlog(L_ERR, "rlm_radutmp: no username in record"); return RLM_MODULE_FAIL; #else break; #endif } radlog(L_INFO, "rlm_radutmp: converting reboot records."); if (status == PW_STATUS_STOP) status = PW_STATUS_ACCOUNTING_OFF; if (status == PW_STATUS_START) status = PW_STATUS_ACCOUNTING_ON; } while(0); memset(&utmp, 0, sizeof(utmp)); utmp.porttype = 'A'; /* * First, find the interesting attributes. */ for (vp = request->packet->vps; vp; vp = vp->next) { switch (vp->da->attribute) { case PW_LOGIN_IP_HOST: case PW_FRAMED_IP_ADDRESS: framed_address = vp->vp_ipaddr; utmp.framed_address = vp->vp_ipaddr; break; case PW_FRAMED_PROTOCOL: protocol = vp->vp_integer; break; case PW_NAS_IP_ADDRESS: nas_address = vp->vp_ipaddr; utmp.nas_address = vp->vp_ipaddr; break; case PW_NAS_PORT: utmp.nas_port = vp->vp_integer; port_seen = 1; break; case PW_ACCT_DELAY_TIME: utmp.delay = vp->vp_integer; break; case PW_ACCT_SESSION_ID: /* * If it's too big, only use the * last bit. */ if (vp->length > sizeof(utmp.session_id)) { int length = vp->length - sizeof(utmp.session_id); /* * Ascend is br0ken - it * adds a \0 to the end * of any string. * Compensate. */ if (vp->vp_strvalue[vp->length - 1] == 0) { length--; } memcpy(utmp.session_id, vp->vp_strvalue + length, sizeof(utmp.session_id)); } else { memset(utmp.session_id, 0, sizeof(utmp.session_id)); memcpy(utmp.session_id, vp->vp_strvalue, vp->length); } break; case PW_NAS_PORT_TYPE: if (vp->vp_integer <= 4) utmp.porttype = porttypes[vp->vp_integer]; break; case PW_CALLING_STATION_ID: if(inst->callerid_ok) strlcpy(utmp.caller_id, (char *)vp->vp_strvalue, sizeof(utmp.caller_id)); break; } } /* * If we didn't find out the NAS address, use the * originator's IP address. */ if (nas_address == 0) { nas_address = request->packet->src_ipaddr; utmp.nas_address = nas_address; nas = request->client->shortname; } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == nas_address) { /* might be a client, might not be. */ nas = request->client->shortname; } else { /* * The NAS isn't a client, it's behind * a proxy server. In that case, just * get the IP address. */ nas = ip_ntoa(ip_name, nas_address); } /* * Set the protocol field. */ if (protocol == PW_PPP) utmp.proto = 'P'; else if (protocol == PW_SLIP) utmp.proto = 'S'; else utmp.proto = 'T'; utmp.time = request->timestamp - utmp.delay; /* * Get the utmp filename, via xlat. */ radius_xlat(filename, sizeof(filename), inst->filename, request, NULL); /* * Future: look up filename in filename tree, to get * radutmp_cache_t pointer */ cache = &inst->cache; /* * For now, double-check the filename, to be sure it isn't * changing. */ if (!cache->filename) { cache->filename = strdup(filename); rad_assert(cache->filename != NULL); } else if (strcmp(cache->filename, filename) != 0) { radlog(L_ERR, "rlm_radutmp: We do not support dynamically named files."); return RLM_MODULE_FAIL; } /* * If the lookup failed, create a new one, and add it * to the filename tree, and cache the file, as below. */ /* * For aging, in the future. */ cache->last_used = request->timestamp; /* * If we haven't already read the file, then read the * entire file, in order to cache its entries. */ if (!cache->cached_file) { cache_file(inst, cache); } /* * See if this was a reboot. * * Hmm... we may not want to zap all of the users when * the NAS comes up, because of issues with receiving * UDP packets out of order. */ if (status == PW_STATUS_ACCOUNTING_ON && nas_address) { radlog(L_INFO, "rlm_radutmp: NAS %s restarted (Accounting-On packet seen)", nas); if (!radutmp_zap(inst, cache, nas_address, utmp.time)) { rad_assert(0 == 1); } return RLM_MODULE_OK; } if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) { radlog(L_INFO, "rlm_radutmp: NAS %s rebooted (Accounting-Off packet seen)", nas); if (!radutmp_zap(inst, cache, nas_address, utmp.time)) { rad_assert(0 == 1); } return RLM_MODULE_OK; } /* * If we don't know this type of entry, then pretend we * succeeded. */ if (status != PW_STATUS_START && status != PW_STATUS_STOP && status != PW_STATUS_ALIVE) { radlog(L_ERR, "rlm_radutmp: NAS %s port %u unknown packet type %d, ignoring it.", nas, utmp.nas_port, status); return RLM_MODULE_NOOP; } /* * Perhaps we don't want to store this record into * radutmp. We skip records: * * - without a NAS-Port (telnet / tcp access) * - with the username "!root" (console admin login) */ if (!port_seen) { DEBUG2(" rlm_radutmp: No NAS-Port in the packet. Cannot do anything."); DEBUG2(" rlm_radumtp: WARNING: checkrad will probably not work!"); return RLM_MODULE_NOOP; } /* * Translate the User-Name attribute, or whatever else * they told us to use. */ *buffer = '\0'; radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL); /* * Don't log certain things... */ if (strcmp(buffer, "!root") == 0) { DEBUG2(" rlm_radutmp: Not recording administrative user"); return RLM_MODULE_NOOP; } strlcpy(utmp.login, buffer, RUT_NAMESIZE); /* * First, try to open the file. If it doesn't exist, * nuke the existing caches, and try to create it. * * FIXME: Create any intermediate directories, as * appropriate. See rlm_detail. */ fd = open(cache->filename, O_RDWR, inst->permission); if (fd < 0) { if (errno == ENOENT) { DEBUG2(" rlm_radutmp: File %s doesn't exist, creating it.", cache->filename); if (!cache_reset(inst, cache)) return RLM_MODULE_FAIL; /* * Try to create the file. */ fd = open(cache->filename, O_RDWR | O_CREAT, inst->permission); } } else { /* exists, but may be empty */ struct stat buf; /* * If the file is empty, reset the cache. */ if ((stat(cache->filename, &buf) == 0) && (buf.st_size == 0) && (!cache_reset(inst, cache))) { return RLM_MODULE_FAIL; } DEBUG2(" rlm_radutmp: File %s was truncated. Resetting cache.", cache->filename); } /* * Error from creation, or error other than ENOENT: die. */ if (fd < 0) { radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s", cache->filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * OK. Now that we've prepared everything we want to do, * let's see if we've cached the entry. */ myPort.nas_address = utmp.nas_address; myPort.nas_port = utmp.nas_port; pthread_mutex_lock(&cache->mutex); node = rbtree_find(cache->nas_ports, &myPort); pthread_mutex_unlock(&cache->mutex); if (node) { nas_port = rbtree_node2data(cache->nas_ports, node); #if 0 /* * stat the file, and get excited if it's been * truncated. * * i.e wipe out the cache, and re-read the file. */ /* * Now find the new entry. */ pthread_mutex_lock(&cache->mutex); node = rbtree_find(cache->nas_ports, &myPort); pthread_mutex_unlock(&cache->mutex); #endif } if (!node) { radutmp_simul_t *user; /* * Not found in the cache, and we're trying to * delete an existing record: ignore it. */ if (status == PW_STATUS_STOP) { DEBUG2(" rlm_radumtp: Logout entry for NAS %s port %u with no Login: ignoring it.", nas, utmp.nas_port); return RLM_MODULE_NOOP; } pthread_mutex_lock(&cache->mutex); /* * It's a START or ALIVE. Try to find a free * offset where we can store the new entry, or * create one, if one doesn't already exist. */ if (!cache->free_offsets) { cache->free_offsets = rad_malloc(sizeof(NAS_PORT)); memset(cache->free_offsets, 0, sizeof(*(cache->free_offsets))); cache->free_offsets->offset = cache->max_offset; cache->max_offset += sizeof(u); } /* * Grab the offset, and put it into the various * caches. */ nas_port = cache->free_offsets; cache->free_offsets = nas_port->next; nas_port->nas_address = nas_address; nas_port->nas_port = utmp.nas_port; if (!rbtree_insert(cache->nas_ports, nas_port)) { rad_assert(0 == 1); } /* * Allocate new entry, and add it * to the tree. */ user = rad_malloc(sizeof(user)); strlcpy(user->login, utmp.login, sizeof(user->login)); user->simul_count = 1; if (!rbtree_insert(inst->user_tree, user)) { rad_assert(0 == 1); } pthread_mutex_unlock(&cache->mutex); } /* * Entry was found, or newly created in the cache. * Seek to the place in the file. */ lseek(fd, nas_port->offset, SEEK_SET); /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * If it WAS found in the cache, double-check it against * what is in the file. */ if (node) { /* * If we didn't read anything, then this entry * doesn't exist. * * Similarly, if the entry in the file doesn't * match what we recall, then nuke the cache * entry. */ read_size = read(fd, &u, sizeof(u)); if ((read_size < 0) || ((read_size > 0) && (read_size != sizeof(u)))) { /* * Bad read, or bad record. */ radlog(L_ERR, "rlm_radutmp: Badly formed file %s", cache->filename); close(fd); return RLM_MODULE_FAIL; } rad_assert(read_size != 0); /* * We've read a record, go poke at it. */ if (read_size > 0) { /* * If these aren't true, then * * a) we have cached a "logout" entry, * which we don't do. * * b) we have cached the wrong NAS address * * c) we have cached the wrong NAS port. */ rad_assert(u.type == P_LOGIN); rad_assert(u.nas_address == utmp.nas_address); rad_assert(u.nas_port == utmp.nas_port); /* * An update for the same session. */ if (strncmp(utmp.session_id, u.session_id, sizeof(u.session_id)) == 0) { /* * It's a duplicate start, so we * don't bother writing it. */ if (status == PW_STATUS_START) { DEBUG2(" rlm_radutmp: Login entry for NAS %s port %u duplicate, ignoring it.", nas, u.nas_port); close(fd); return RLM_MODULE_OK; /* * ALIVE for this session, keep the * original login time. */ } else if (status == PW_STATUS_ALIVE) { utmp.time = u.time; /* * Stop: delete it from our cache. */ } else if (status == PW_STATUS_STOP) { radutmp_simul_t *user, myUser; pthread_mutex_lock(&cache->mutex); rbtree_deletebydata(cache->nas_ports, nas_port); strlcpy(myUser.login, u.login, sizeof(myUser.login)); user = rbtree_finddata(inst->user_tree, &myUser); rad_assert(user != NULL); rad_assert(user->simul_count > 0); user->simul_count--; if (user->simul_count == 0) { rbtree_deletebydata(inst->user_tree, user); } pthread_mutex_unlock(&cache->mutex); } else { /* * We don't know how to * handle this. */ rad_assert(0 == 1); } } else { /* session ID doesn't match */ /* * STOP for the right NAS & port, * but the Acct-Session-Id is * different. This means that * we missed the original "stop", * and a new "start". */ if (status == PW_STATUS_STOP) { radlog(L_ERR, "rlm_radutmp: Logout entry for NAS %s port %u has old Acct-Session-ID, ignoring it.", nas, u.nas_port); close(fd); return RLM_MODULE_OK; } } /* checked session ID's */ } /* else we haven't read anything from the file. */ } /* else the entry wasn't cached, but could have been inserted */ /* * Hmm... we may have received a start or alive packet * AFTER a stop or nas-down, in that case, we want to * discard the new packet. However, the original code * could over-write an idle record with a new login * record for another NAS && port, so we won't worry * about this case too much. */ /* * Seek to where the entry is, and write it blindly. */ lseek(fd, nas_port->offset, SEEK_SET); /* FIXME: err */ if (status != PW_STATUS_STOP) { utmp.type = P_LOGIN; rad_assert(nas_port != NULL); /* it WAS cached */ } else { /* FIXME: maybe assert that the entry was deleted... */ memcpy(&utmp, &u, sizeof(utmp)); utmp.type = P_IDLE; } write(fd, &utmp, sizeof(utmp)); /* FIXME: err */ close(fd); /* and implicitly release the locks */ return RLM_MODULE_OK; }
/* * Zap all users on a NAS from the radutmp file. */ static int radutmp_zap(rlm_radutmp_t *inst, radutmp_cache_t *cache, uint32_t nas_address, time_t now) { int rcode; rbtree_t *offset_tree; offset_walk_t walk; rad_assert(now != 0); /* * If there's nothing in the file, do nothing, * but truncate the file, just to be safe. */ if (rbtree_num_elements(cache->nas_ports) == 0) { truncate(cache->filename, (off_t) 0); DEBUG2(" rlm_radutmp: No entries in file. Quenching zap."); return 1; } /* * Create the offset tree, as we want to delete utmp * entries starting from the start of the file, and we * can't delete nodes from an rbtree while we're walking * it. */ offset_tree = rbtree_create(offset_cmp, NULL, 0); if (!offset_tree) { radlog(L_ERR, "rlm_radutmp: Out of memory"); return 0; } pthread_mutex_lock(&cache->mutex); /* * Walk through the cache, finding entries for this NAS, * and add those entries to the offset tree. */ memset(&walk, 0, sizeof(walk)); walk.inst = inst; walk.offset_tree = offset_tree; walk.nas_address = nas_address; rcode = rbtree_walk(cache->nas_ports, PreOrder, nas_port_walk, &walk); if (rcode != 0) { pthread_mutex_unlock(&cache->mutex); rbtree_free(offset_tree); radlog(L_ERR, "rlm_radutmp: Failed walking the cache."); return 0; } /* * If both trees have the same number of elements, then * don't do anything special, as UDP packets may be * received out of order, by several seconds. The * "offset_walk" routine MAY NOT delete the entries, if * it sees that the entries in the file are newer than * the reboot packet. */ /* * If there's nothing to do, don't do anything. */ if (rbtree_num_elements(offset_tree) == 0) { DEBUG2(" rlm_radutmp: NAS IP %08x has no users recorded in file %s.", htonl(nas_address), cache->filename); pthread_mutex_unlock(&cache->mutex); rbtree_free(offset_tree); return 1; } /* * Open the file, to re-write only a few of the entries. */ walk.fd = open(cache->filename, O_RDWR); if (walk.fd < 0) { pthread_mutex_unlock(&cache->mutex); rbtree_free(offset_tree); radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s", cache->filename, strerror(errno)); return 0; } /* * Lock the utmp file, prefer lockf() over flock(). * * FIXME: maybe we want to lock per-record? */ rad_lockfd(walk.fd, LOCK_LEN); /* * Walk through the offset tree, from start to finish, * deleting entries from the NAS tree, adding them to * the "free offset" cache, and lseek'ing to that offset * in the file, and clearing out the data. */ walk.cache = cache; walk.now = now; rcode = rbtree_walk(offset_tree, InOrder, offset_walk, &walk); rbtree_free(offset_tree); if (rcode != 0) { radlog(L_ERR, "rlm_radutmp: Failed walking the offsets."); return 0; } close(walk.fd); /* and implicitly release the locks */ /* * Just to clean up the file. If it's empty, * nuke everything. */ if (rbtree_num_elements(cache->nas_ports) == 0) { NAS_PORT *this, *next; /* too many copies of code */ for (this = inst->cache.free_offsets; this != NULL; this = next) { next = this->next; free(this); } truncate(cache->filename, 0); rad_assert(rbtree_num_elements(inst->user_tree) == 0); } pthread_mutex_unlock(&cache->mutex); return 1; }
/* * See if a user is already logged in. Sets request->simul_count * to the current session count for this user and sets * request->simul_mpp to 2 if it looks like a multilink attempt * based on the requested IP address, otherwise leaves * request->simul_mpp alone. * * Check twice. If on the first pass the user exceeds his * max. number of logins, do a second pass and validate all * logins by querying the terminal server (using eg. SNMP). */ static rlm_rcode_t radutmp_checksimul(void *instance, REQUEST *request) { struct radutmp u; int fd; VALUE_PAIR *vp; uint32_t ipno = 0; char *call_num = NULL; int rcode; rlm_radutmp_t *inst = instance; char login[256]; char filename[1024]; radutmp_cache_t *cache; radutmp_simul_t *user, myUser; /* * Get the filename, via xlat. */ radius_xlat(filename, sizeof(filename), inst->filename, request, NULL); /* * Future: look up filename in filename tree, to get * radutmp_cache_t pointer */ cache = &inst->cache; /* * For now, double-check the filename, to be sure it isn't * changing. */ if (!cache->filename) { cache->filename = strdup(filename); rad_assert(cache->filename != NULL); } else if (strcmp(cache->filename, filename) != 0) { radlog(L_ERR, "rlm_radutmp: We do not support dynamically named files."); return RLM_MODULE_FAIL; } *login = '******'; radius_xlat(login, sizeof(login), inst->username, request, NULL); if (!*login) { return RLM_MODULE_NOOP; } /* * WTF? This is probably wrong... we probably want to * be able to check users across multiple session accounting * methods. */ request->simul_count = 0; strlcpy(myUser.login, login, sizeof(myUser.login)); pthread_mutex_lock(&inst->cache.mutex); user = rbtree_finddata(inst->user_tree, &myUser); if (user) request->simul_count = user->simul_count; user = NULL; /* someone else may delete it */ pthread_mutex_unlock(&inst->cache.mutex); /* * The number of users logged in is OK, * OR, we've been told to not check the NAS. */ if ((request->simul_count < request->simul_max) || !inst->check_nas) { return RLM_MODULE_OK; } /* * The user is logged in at least N times, and * we're told to check the NAS. In that case, * we've got to read the file, and check each * NAS port by hand. */ if ((fd = open(cache->filename, O_RDWR)) < 0) { /* * If the file doesn't exist, then no users * are logged in. */ if (errno == ENOENT) { request->simul_count = 0; return RLM_MODULE_OK; } /* * Error accessing the file. */ radlog(L_ERR, "rlm_radumtp: Error accessing file %s: %s", cache->filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * Setup some stuff, like for MPP detection. */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) ipno = vp->vp_ipaddr; if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) call_num = vp->vp_strvalue; /* * lock the file while reading/writing. */ rad_lockfd(fd, LOCK_LEN); /* * FIXME: If we get a 'Start' for a user/nas/port which is * listed, but for which we did NOT get a 'Stop', then * it's not a duplicate session. This happens with * static IP's like DSL. */ request->simul_count = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(login, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(login, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { char session_id[sizeof(u.session_id) + 1]; char utmp_login[sizeof(u.login) + 1]; strlcpy(session_id, u.session_id, sizeof(session_id)); /* * The login name MAY fill the whole field, * and thus won't be zero-filled. * * Note that we take the user name from * the utmp file, as that's the canonical * form. The 'login' variable may contain * a string which is an upper/lowercase * version of u.login. When we call the * routine to check the terminal server, * the NAS may be case sensitive. * * e.g. We ask if "bob" is using a port, * and the NAS says "no", because "BOB" * is using the port. */ strlcpy(utmp_login, u.login, sizeof(u.login)); /* * rad_check_ts may take seconds * to return, and we don't want * to block everyone else while * that's happening. */ rad_unlockfd(fd, LOCK_LEN); rcode = rad_check_ts(u.nas_address, u.nas_port, utmp_login, session_id); rad_lockfd(fd, LOCK_LEN); if (rcode == 0) { /* * Stale record - zap it. * * Hmm... this ends up calling * the accounting section * recursively... */ session_zap(request, u.nas_address, u.nas_port, login, session_id, u.framed_address, u.proto,0); } else if (rcode == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (strchr("SCPA", u.proto) && ipno && u.framed_address == ipno) request->simul_mpp = 2; else if (strchr("SCPA", u.proto) && call_num && !strncmp(u.caller_id,call_num,16)) request->simul_mpp = 2; } else { /* * Failed to check the terminal * server for duplicate logins: * Return an error. */ close(fd); radlog(L_ERR, "rlm_radutmp: Failed to check the terminal server for user '%s'.", utmp_login); return RLM_MODULE_FAIL; } } } close(fd); /* and implicitly release the locks */ return RLM_MODULE_OK; }