/* * mysql.c Functions to access the MYSQL database. * * Much of the Mysql connection and accounting code was taken from * Wim Bonis's (bonis@kiss.de) accounting patch to livingston radius * 2.01. His patch can be found at: * * ftp://ftp.kiss.de/pub/unix/livingston/mysql-patches.tgz * * Version: @(#)mysql.c 1.10 25-Sep-1998 oyarzun@wilmington.net * * mysqlRadius OpenISP Project modifications: * * $Id: mysql.c 1 2006-03-06 22:05:41Z root $ * sql.log turned off * via define SQL_LOG * * 12/2002 ggw SMTP_RELAY option. Has compile time login info for mysqlSendmail db! * uSource=3,72 hrs. uSource=2,30mins for qualcomm qpopper mysqlSendmail version * * 8/2004 ggw Got rid of nasty strcpy security and error generating data * */ //No please... //#define SQL_LOG // //Use with mysqlSendmail.tAccess subsystem //#define SMTP_RELAY //Usually you can run in production mode without this //After verification //#define LOGFILE_DEBUG #ifdef USEMYSQL #include #include #include #include #include #include #include #include #include #include #include #include #include "radiusd.h" #define MYSQL_AUTH 1 #define MYSQL_ACCT 2 MYSQL *mysql_Reconnect(int); int my_mysql_query(const char *,MYSQL *,int); MYSQL real_mysql_auth, *mysql_auth = NULL; MYSQL real_mysql_acct, *mysql_acct = NULL; char mysql_server[40]; char mysql_login[20]; char mysql_password[20]; char acct_db[20]; char auth_db[20]; char auth_query[MAX_AUTH_QUERY_LEN]; char mysql_auth_table[20]; char mysql_acct_table[20]; int mysql_keepopen; int use_mysql_acct; int use_mysql_auth; #ifdef SMTP_RELAY //See modified qualcomm qpopper 4.0.4 mysqlSendmail.c MYSQL mysql; static char query[2048]; //local in order of appearance in file void ConnectDb(); void mysqlSendmail_Relay(char *cDomainIP); void mysqlSendmail_RemoveRelay(char *cDomainIP); void ScheduleJob(unsigned uUser, unsigned uServer, char *cJobName, char *cJobData); char *ClassC(char *cInput); #endif //Reads /sqlserver file int mysql_rad_init() { FILE *sqlfd; char dummystr[64]; char namestr[64]; int line_no; char buffer[256]; char sqlfile[256]; //These are not a security or overflow issue. Clean up later. strcpy(mysql_server,""); strcpy(mysql_login,""); strcpy(mysql_password,""); strcpy(acct_db,""); strcpy(auth_db,""); strcpy(mysql_auth_table,"passwd"); strcpy(mysql_acct_table,"usertime"); mysql_keepopen = 0; use_mysql_acct = 0; use_mysql_auth = 0; sprintf(sqlfile, "%s/%s", radius_dir, "sqlserver"); if((sqlfd = fopen(sqlfile, "r")) == (FILE *)NULL) { log(L_ERR,"could not read sqlserver file %s",sqlfile); return(-1); } line_no = 0; while(fgets(buffer, sizeof(buffer), sqlfd) != (char *)NULL) { line_no++; // Skip empty space if(*buffer == '#' || *buffer == '\0' || *buffer == '\n') continue; if(strncasecmp(buffer, "server", 6) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"Invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); use_mysql_acct = 0; use_mysql_auth = 0; } else { sprintf(mysql_server,"%.39s",namestr); } } if(strncasecmp(buffer, "login", 5) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); } else { sprintf(mysql_login,"%.19s",namestr); } } if(strncasecmp(buffer, "password", 8) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); } else { sprintf(mysql_password,"%.19s",namestr); } } if(strncasecmp(buffer, "acct_db", 7) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); use_mysql_acct = 0; } else { sprintf(acct_db,"%.19s",namestr); } } if(strncasecmp(buffer, "acct_table", 10) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); use_mysql_acct = 0; } else { sprintf(mysql_acct_table,"%.19s",namestr); } } if(strncasecmp(buffer, "keepopen", 8) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); } else { if(strncasecmp(namestr, "yes", 3) == 0) mysql_keepopen = 1; else mysql_keepopen = 0; } } if(strncasecmp(buffer, "doacct", 6) == 0) { if(sscanf(buffer, "%s%s", dummystr, namestr) != 2) { log(L_ERR,"invalid attribute on line %d of sqlserver file %s", line_no,sqlfile); } else { if(strncasecmp(namestr, "yes", 3) == 0) use_mysql_acct = 1; else use_mysql_acct = 0; } } } fclose(sqlfd); log(L_INFO,"mysql_rad_init(): using: %s,%s,%s,%s,%d,%d", mysql_server, mysql_login, acct_db, mysql_acct_table, mysql_keepopen, use_mysql_acct); return 0; }//int mysql_rad_init () /* typedef struct mysql_rec { char cSessionID[33]; char cLogin[101]; char cNAS[20]; char cIP[20]; unsigned uStatus; unsigned uPort; unsigned uPortType; unsigned uProtocol; unsigned long uDelay; unsigned long uConnectTime; unsigned uService; unsigned long uInBytes; unsigned long uOutBytes; unsigned uTermCause; char cLine[20]; char cCallerID[20]; char cInfo[20]; } MYSQL_RECORD; */ //Builds accounting record for MySQL void build_mysql_record(VALUE_PAIR * pair, MYSQL_RECORD * rec) { char buffer[100]; char *str; if (use_mysql_acct == 0) return; switch (pair->attribute) { case PW_ACCT_SESSION_ID: strncpy(rec->cSessionID, pair->strvalue,32); break; case PW_USER_NAME: if (pair->strvalue[0] <= 'Z') { /* Strip first Upper case Letter --why? (ggw)*/ str = pair->strvalue + 1; strncpy(rec->cLogin,str,100); } else { strncpy(rec->cLogin,pair->strvalue,100); } break; case PW_ACCT_STATUS_TYPE: rec->uStatus= pair->lvalue; break; case PW_NAS_IP_ADDRESS: ipaddr2str(buffer, pair->lvalue); sprintf(rec->cNAS,"%.19s",buffer); break; case PW_FRAMED_IP_ADDRESS: ipaddr2str(buffer, pair->lvalue); sprintf(rec->cIP,"%.19s",buffer); break; case PW_NAS_PORT: rec->uPort = pair->lvalue; break; case PW_SERVICE_TYPE: rec->uService= pair->lvalue; break; case PW_FRAMED_PROTOCOL: rec->uProtocol = pair->lvalue; break; case PW_LOGIN_SERVICE: rec->uProtocol = pair->lvalue + 100; break; case PW_ACCT_SESSION_TIME: rec->uConnectTime = pair->lvalue; break; case PW_ACCT_AUTHENTIC: //rec->uAuth = pair->lvalue; break; case PW_ACCT_OUTPUT_OCTETS: rec->uOutBytes = pair->lvalue; break; case PW_ACCT_INPUT_OCTETS: rec->uInBytes = pair->lvalue; break; case PW_LOGIN_IP_HOST: if(!rec->cIP[0]) { ipaddr2str(buffer, pair->lvalue); sprintf(rec->cIP,"%.19s",buffer); } break; case PW_ACCT_DELAY_TIME: rec->uDelay = pair->lvalue; break; case PW_FRAMED_COMPRESSION: break; case PW_ACCT_INPUT_PACKETS: break; case PW_ACCT_OUTPUT_PACKETS: break; case PW_NAS_PORT_TYPE: rec->uPortType = pair->lvalue; break; case PW_CONNECT_INFO: sprintf(rec->cInfo,"%.19s",pair->strvalue); break; default: if(!strcasecmp(pair->name, "called-station-id")) { sprintf(rec->cLine,"%.19s",pair->strvalue); } else if(!strcasecmp(pair->name, "calling-station-id")) { register int count = 0; register int i = 0; str = pair->strvalue; while ( (*str != '\0') && (i < 3) ) { if ((*str >= '\200') && (*str <= '\205')) { count++; } str++; i++; } sprintf(rec->cCallerID,"%.19s",pair->strvalue+count); } else if(!strcasecmp(pair->name, "Acct-Terminate-Cause")) { rec->uTermCause = pair->lvalue; } #ifdef LOGFILE_DEBUG else if(!strcasecmp(pair->name, "NAS_Identifier")) { /*elminate error below. Show up w/ a USR TC?*/ } else if(!strcasecmp(pair->name, "Client-IP-Address")) { /*Elminate error below. For Cisco*/ } else if(!strcasecmp(pair->name, "Acct-Multi-Session-Id")) { /*Elminate error below.*/ } //All Ascend/Lucent pairs no matter what full name else if(!strncasecmp(pair->name, "Ascend",6)) { /*Elminate error below. For Ascend*/ } else if(!strcasecmp(pair->name, "Vendor-Specific")) { /*Elminate error below. For Ascend*/ } else if(!strcasecmp(pair->name, "Acct-Link-Count")) { /*Elminate error below.*/ } else if(!strcasecmp(pair->name, "Login-TCP-Port")) { /*Elminate error below.*/ } else if(!strcasecmp(pair->name, "Realm")) { /*Elminate error below.*/ } else { log(L_ERR,"Unknown Pair: %s", pair->name); } #endif }//switch() }//void build_mysql_record(VALUE_PAIR * pair, MYSQL_RECORD * rec) //FIXME: There is probably a better way of implenting this function to // connect to two db's. // Changed for mySQL 4 support @ openisp . net MYSQL *mysql_Reconnect(int type) { int i=0; MYSQL * mysql = NULL; //This should handle the looped calls this crazy patch guy makes. if(type == MYSQL_AUTH) { //if(!mysql_ping(&real_mysql_auth)) // return &real_mysql_auth; mysql_init(&real_mysql_auth); } else { //if(!mysql_ping(&real_mysql_acct)) // return &real_mysql_acct; mysql_init(&real_mysql_acct); } if (type == MYSQL_AUTH) { if(!(mysql_real_connect(&real_mysql_auth,mysql_server,mysql_login,mysql_password,acct_db,0,NULL,0))) { log(L_ERR,"MYSQL AUTH: Cannot Connect to %s.%s as %s",mysql_server,mysql_login); mysql = NULL; } else { log(L_INFO,"MYSQL AUTH Connected to %s",mysql_server); mysql = &real_mysql_auth; } } else { if (!(mysql_real_connect(&real_mysql_acct,mysql_server,mysql_login,mysql_password,acct_db,0,NULL,0))) { log(L_ERR,"MYSQL ACCT: Cannot Connect to %s.%s as %s/%s",mysql_server,acct_db,mysql_login,mysql_password); mysql = NULL; } else { log(L_INFO,"MYSQL ACCT Connected to %s",mysql_server); mysql = &real_mysql_acct; } } return mysql; }//MYSQL *mysql_Reconnect(int type) //Purpose: Query MySQL database int my_mysql_query(const char *q, MYSQL *mysql, int type) { int ret; int i; #ifdef STOP_SIGNALS sigset_t set,old_set; #endif i = 0; while(i<10) { // Try it 10 Times if (mysql == NULL) mysql = mysql_Reconnect(type); #ifdef STOP_SIGNALS /* Try to not catch any signals during mysql_query * We need this if mysql is less than 3.20.25 or 3.21.3 */ sigemptyset(&set); sigaddset(&set,SIGALRM); sigprocmask(SIG_BLOCK,&set,&old_set); #endif ret = mysql_query(mysql,q); #ifdef STOP_SIGNALS sigprocmask(SIG_SETMASK,&old_set,NULL); #endif if(!ret) { DEBUG("MYSQL query OK"); return ret; } if (!strcasecmp(mysql_error(mysql),"mysql server has gone away")) { log(L_ERR,"MYSQL Error (retrying %d): Cannot Query:%s",ret,q); log(L_ERR,"MYSQL error: %s",mysql_error(mysql)); mysql_close(mysql); mysql = NULL; } else { log(L_ERR,"MYSQL Error (%d): Cannot Query:%s",ret,q); log(L_ERR,"MYSQL error: %s",mysql_error(mysql)); return ret; } i++; } log(L_ERR,"MYSQL Error (giving up): Cannot Query:%s",q); return ret; }//int my_mysql_query(const char *q, MYSQL *mysql, int type) //Purpose: Saves Account record to MySQL database void save_mysql_record(MYSQL_RECORD * rec) { char buf[500]; int err; int num; unsigned long time_on; int i = 0; time_t curtime; #ifdef SQL_LOG FILE *sqloutfd; #endif MYSQL_RES *result; MYSQL_ROW row; if(use_mysql_acct == 0) return; #ifdef SQL_LOG sprintf(buf, "%s/sql.log", radacct_dir); if((sqloutfd = fopen(buf, "a")) == (FILE *) NULL) log(L_ERR,"accounting: could not append to file %s", buf); #endif if(mysql_acct == NULL) mysql_acct = mysql_Reconnect(MYSQL_ACCT); curtime = time(0) - rec->uDelay; //Special case when NAS was rebooted if(rec->uStatus == PW_STATUS_ACCOUNTING_ON || rec->uStatus == PW_STATUS_ACCOUNTING_OFF) { //The Terminal server informed us that it was rebooted // -> delete all records (ggw: ? update all you mean) if (mysql_acct != NULL) { sprintf(buf, "SELECT cSessionID,cLogin,uStartTime FROM %s WHERE cNAS='%s' AND uStopTime=0 AND uConnectTime=0", mysql_acct_table, rec->cNAS); DEBUG("%s",buf); (void) my_mysql_query(buf, mysql_acct, MYSQL_ACCT); if(!(result = mysql_store_result(mysql_acct))) { log(L_ERR,"MYSQL Error: Cannot get result"); log(L_ERR,"MYSQL error: %s",mysql_error(mysql_acct)); mysql_close(mysql_acct); mysql_acct = NULL; } else { num = mysql_num_rows(result); while(i < num) { row = mysql_fetch_row(result); time_on = difftime(curtime, ((time_t) atoi(row[2]))); sprintf(buf, "UPDATE %s SET uStopTime=%lu,uConnectTime=%lu,uTermCause=7 WHERE uConnectTime=0 AND uStopTime=0 AND cNAS='%s' AND cSessionID='%s' AND cLogin='%s'", mysql_acct_table,curtime,time_on,rec->cNAS,row[0],row[1]); my_mysql_query(buf, mysql_acct, MYSQL_ACCT); #ifdef SQL_LOG if(sqloutfd) { fputs(buf, sqloutfd); fputs(";\n", sqloutfd); fflush(sqloutfd); } #endif i++; } mysql_free_result(result); } return; } log(L_ERR,"MYSQL Error: Cannot update after %s rebooted",rec->cNAS); return; }//Special NAS Rebbot case //Normal case if(mysql_acct != NULL) { sprintf(buf, "SELECT cSessionID FROM %s WHERE cSessionID='%s' AND cNAS='%s' AND cLogin='%s'", mysql_acct_table, rec->cSessionID, rec->cNAS, rec->cLogin); DEBUG("%s",buf); my_mysql_query(buf, mysql_acct, MYSQL_ACCT); if(!(result = mysql_store_result(mysql_acct))) { log(L_ERR,"MYSQL Error: Cannot get result"); log(L_ERR,"MYSQL error: %s",mysql_error(mysql_acct)); mysql_close(mysql_acct); mysql_acct = NULL; } else { num = mysql_num_rows(result); mysql_free_result(result); } } //Only insert it if its not there if((mysql_acct != NULL) && (num == 0)) { //unsigned long mysql_real_escape_string(MYSQL *mysql, char *to, const char *from, unsigned long length) char cLogin[256]; mysql_real_escape_string(mysql_acct,cLogin,rec->cLogin,strlen(rec->cLogin)); sprintf(buf, "INSERT INTO %s SET cSessionId='%s', uConnectTime=%lu, cLogin='%s', cNAS='%s', uPort=%u, uPortType=%u, uService=%u, uProtocol=%u, cIP='%s', cLine='%s', cCallerID='%s', uInBytes=%lu, uOutBytes=%lu, uTermCause=%u", mysql_acct_table, rec->cSessionID, rec->uConnectTime, cLogin, rec->cNAS, rec->uPort, rec->uPortType, rec->uService, rec->uProtocol, rec->cIP, rec->cLine, rec->cCallerID, rec->uInBytes, rec->uOutBytes, rec->uTermCause); DEBUG("%s",buf); #ifdef SQL_LOG if(sqloutfd) { fputs(buf, sqloutfd); fputs(";\n", sqloutfd); fflush(sqloutfd); } #endif err = my_mysql_query(buf, mysql_acct, MYSQL_ACCT); if((rec->uStatus == 1) && err) log(L_ERR,"MYSQL Error: Cannot insert"); } //Start record? if(rec->uStatus == 1) { sprintf(buf, "UPDATE %s SET uStartTime=%lu,cInfo='%s' WHERE cSessionID='%s' AND cNAS='%s' AND cLogin='%s'", mysql_acct_table, curtime, rec->cInfo, rec->cSessionID, rec->cNAS, rec->cLogin); #ifdef SMTP_RELAY //Add async entry to access file for sendmail relay access //See modified qualcomm qpopper 4.0.4 if(rec->cIP[0]) mysqlSendmail_Relay(rec->cIP); #endif } //Alive record, Stop, etc. else { sprintf(buf,"UPDATE %s SET uConnectTime=%lu,uStopTime=%lu,uInBytes=%lu,uOutBytes=%lu,uTermCause=%u,cIP='%s' WHERE cSessionID='%s' AND cNAS='%s' AND cLogin='%s'", mysql_acct_table, rec->uConnectTime, curtime, rec->uInBytes, rec->uOutBytes, rec->uTermCause, rec->cIP, rec->cSessionID, rec->cNAS, rec->cLogin); #ifdef SMTP_RELAY //Stop record only if(rec->uStatus == 2 && rec->cIP[0]) mysqlSendmail_RemoveRelay(rec->cIP); #endif } DEBUG("%s",buf); #ifdef SQL_LOG if(sqloutfd) { if( (rec->uStatus != 1) && (num == 0) ) fprintf(sqloutfd,"-- Record is missing\n"); fputs(buf, sqloutfd); fputs(";\n", sqloutfd); fflush(sqloutfd); } #endif if(mysql_acct != NULL) { if(my_mysql_query(buf, mysql_acct, MYSQL_ACCT)) { } else { if (mysql_affected_rows(mysql_acct) > 1) { log(L_ERR,"MYSQL %s (%s) %d rows changed",rec->cLogin, rec->cSessionID,mysql_affected_rows(mysql_acct)); } } } #ifdef SQL_LOG if(sqloutfd) fclose(sqloutfd); #endif if(mysql_keepopen == 0) { //Close the connection after each record if (mysql_acct != NULL) mysql_close(mysql_acct); mysql_acct = NULL; } }//void save_mysql_record(MYSQL_RECORD * rec) #ifdef SMTP_RELAY void ConnectDb() { char cDbIp[256]={""}; char *cEffectiveDbIp=NULL; char cDbName[256]={"mysqlsendmail"}; char cDbPwd[256]={"wsxedc"}; char cDbLogin[256]={"mysqlsendmail"}; //Already connected? Connect only once if(!mysql_ping(&mysql)) return; if(cDbIp[0]) cEffectiveDbIp=cDbIp; mysql_init(&mysql); if (!mysql_real_connect(&mysql,cEffectiveDbIp, cDbLogin,cDbPwd,cDbName,0,NULL,0)) { sprintf(query,"ConnectDb %s.%s",cDbIp,cDbName); log(L_ERR,"[SMTP_RELAY] (%s) %s",mysql_error(&mysql),query); return; } log(L_INFO, "[SMTP_RELAY] connected to mysqlsendmail"); }//end of void ConnectDb(void) void mysqlSendmail_RemoveRelay(char *cDomainIP) { ConnectDb();//Connects only once. Check sprintf(query,"DELETE FROM tAccess WHERE cDomainIP='%s' AND cRelayAttr='RELAY' AND uSource=3 AND uOwner=1 AND uCreatedBy=1 AND uServer=1",cDomainIP); mysql_query(&mysql,query); if(mysql_errno(&mysql)) log(L_ERR,"[SMTP_RELAY] %s",mysql_error(&mysql)); //Create job for deletion if(mysql_affected_rows(&mysql)) ScheduleJob(1,1,"DelAccess",cDomainIP); }//void mysqlSendmail_RemoveRelay(char *cDomainIP) //Add entry to tAccess: Expire in 72 hrs void mysqlSendmail_Relay(char *cDomainIP) { MYSQL_RES *res; char cClasscIP[33]={""}; time_t ulClock,ulExpire; ConnectDb();//Connects only once. Check //Check against tAccess to see if needed. Check class c and complete IP //Note that other entries with cRelayAttr are also noted. so even //blocks are counted. sprintf(query,"SELECT uAccess FROM tAccess WHERE cDomainIP='%s'",cDomainIP); mysql_query(&mysql,query); if(mysql_errno(&mysql)) { log(L_ERR,"[SMTP_RELAY] %s",mysql_error(&mysql)); return; } res=mysql_store_result(&mysql); if(mysql_num_rows(res)>0) { mysql_free_result(res); return; } mysql_free_result(res); sprintf(cClasscIP,"%32s",cDomainIP); sprintf(query,"SELECT uAccess FROM tAccess WHERE cDomainIP='%s'",ClassC(cClasscIP)); mysql_query(&mysql,query); if(mysql_errno(&mysql)) { log(L_ERR,"[SMTP_RELAY] %s",mysql_error(&mysql)); return; } res=mysql_store_result(&mysql); if(mysql_num_rows(res)>0) { mysql_free_result(res); return; } mysql_free_result(res); time(&ulClock); ulExpire=ulClock+259200;//72 hrs sprintf(query,"REPLACE INTO tAccess SET cDomainIP='%s',cRelayAttr='RELAY',uSource=3,uExpireDate=%lu,uOwner=1,uCreatedBy=1,uCreatedDate=%lu,uServer=1", cDomainIP, ulExpire, ulClock); mysql_query(&mysql,query); if(mysql_errno(&mysql)) log(L_ERR,"[SMTP_RELAY] %s",mysql_error(&mysql)); //Create job for addition ScheduleJob(1,1,"NewAccess",cDomainIP); }//void mysqlSendmail_Relay() void ScheduleJob(unsigned uUser, unsigned uServer, char *cJobName, char *cJobData) { time_t clock; time(&clock); sprintf(query,"INSERT INTO tJob (cServer,cLabel,cJobData,cJobName,uUser,uJobDate,uJobStatus,uOwner,uCreatedBy,uCreatedDate) SELECT cLabel,'%s','%s','%s',%u,%lu,%u,%u,%u,%lu FROM tServer WHERE uServer=%u", "Priority=Normal", cJobData, cJobName, uUser, (unsigned long)clock, 1,//uJobStatus 1=waiting 1, 1, (unsigned long)clock, uServer); mysql_query(&mysql,query); if(mysql_errno(&mysql)) log(L_ERR,"[SMTP_RELAY] %s",mysql_error(&mysql)); }//void ScheduleJob() char *ClassC(char *cInput) { unsigned a=0,b=0,c=0,d=0; sscanf(cInput,"%u.%u.%u.%u",&a,&b,&c,&d); sprintf(cInput,"%u.%u.%u",a,b,c); return(cInput); }//char *ClassC(char *cInput) #endif //SMTP_RELAY #endif //USEMYSQL