/*
- * AUTHOR: Pedro Lineu Orso orso@penguintech.com.br
- * 1998, 2005
* SARG Squid Analysis Report Generator http://sarg.sourceforge.net
+ * 1998, 2015
*
* SARG donations:
* please look at http://sarg.sourceforge.net/donations.php
+ * Support:
+ * http://sourceforge.net/projects/sarg/forums/forum/363374
* ---------------------------------------------------------------------
*
* This program is free software; you can redistribute it and/or modify
*/
#include "include/conf.h"
+#include "include/defs.h"
+#include "include/filelist.h"
+#include "include/readlog.h"
-int getdata(char *, FILE *);
-void datashow(char *);
-void getlog();
-void header();
+//! Maximum length of the scheme plus host name from the url.
+#define MAX_URL_HOST_LEN 260
-char dat[128];
-char tim[128];
-char typ[128];
-char ouser[MAXLEN]="";
-char ourl[MAXLEN]="";
-
-void
-realtime(int argc, char *argv[])
+/*!
+\brief Data read from an input log file.
+*/
+struct RealtimeReadLogStruct
{
-
- getlog();
-
+ //! The time corresponding to the entry.
+ struct tm EntryTime;
+ //! The IP address connecting to internet.
+ char Ip[48];
+ //! The user's name.
+ char User[MAX_USER_LEN];
+ /*!
+ The URL of the visited site.
+
+ The pointer may be NULL if the URL doesn't exists in the log file.
+ */
+ char Url[MAX_URL_HOST_LEN];
+ //! HTTP method or NULL if the information is not stored in the log.
+ char HttpMethod[32];
+};
+
+extern FileListObject AccessLog;
+
+static bool GetLatestModified(char *file_name,int file_name_size)
+{
+ FileListIterator FIter;
+ const char *file;
+ bool found=false;
+ struct stat st;
+ time_t latest;
+
+ FIter=FileListIter_Open(AccessLog);
+ while ((file=FileListIter_Next(FIter))!=NULL)
+ {
+ if (stat(file,&st)==-1) {
+ debuga(__FILE__,__LINE__,_("Cannot stat \"%s\": %s\n"),file,strerror(errno));
+ }
+ if (!found)
+ {
+ found=true;
+ latest=st.st_mtime;
+ safe_strcpy(file_name,file,file_name_size);
+ }
+ else if (st.st_mtime>latest)
+ {
+ latest=st.st_mtime;
+ safe_strcpy(file_name,file,file_name_size);
+ }
+ }
+ FileListIter_Close(FIter);
+ return(found);
}
-void getlog()
+/*!
+ * \brief Store a log entry.
+ *
+ * \param Dest A pointer to the list entry where to store the entry.
+ * \param Entry The entry to store.
+ */
+static void StoreLogEntry(struct RealtimeReadLogStruct *Dest,struct ReadLogStruct *Entry)
{
- FILE *tmp, *fp, *fp_usr;
- char template1[255]="/var/tmp/sargtpl1.XXXXXX";
- char template2[255]="/var/tmp/sargtpl2.XXXXXX";
- char cmd[512];
- char buf[512];
- int fd1,fd2,nreg;
- int cstatus;
-
- if(UserTabFile[0] != '\0') {
- if(debug) {
- sprintf(msg,"%s: %s",text[86],UserTabFile);
- debuga(msg);
- }
- if((fp_usr=fopen(UserTabFile,"r"))==NULL) {
- fprintf(stderr, "SARG: (realtime) %s: %s - %s\n",text[45],UserTabFile,strerror(errno));
- exit(1);
- }
- nreg = lseek(fileno(fp_usr), 0, SEEK_END);
- lseek(fileno(fp_usr), 0, 0);
- if((userfile=(char *) malloc(nreg+100))==NULL){
- fprintf(stderr, "SARG ERROR: %s",text[87]);
- exit(1);
- }
- bzero(userfile,nreg+100);
- strncat(userfile,":",1);
- z1=0;
- z2=1;
- while(fgets(buf,MAXLEN,fp_usr)!=NULL){
- buf[strlen(buf)-1]='\0';
- if(strstr(buf,"\r") != 0) buf[strlen(buf)-1]='\0';
- if (getword(bufy,sizeof(bufy),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",UserTabFile);
- exit(1);
- }
- for(z1=0; z1<=strlen(bufy); z1++) {
- userfile[z2]=bufy[z1];
- z2++;
- }
- strncat(userfile,":",1);
- for(z1=0; z1<=strlen(buf); z1++) {
- userfile[z2]=buf[z1];
- z2++;
- }
- strncat(userfile,":",1);
- }
- fclose(fp_usr);
- }
-
- fd1 = mkstemp(template1);
- fd2 = mkstemp(template2);
-
- if((fd1 == -1 ) || ((tmp = fdopen (fd1, "w+" )) == NULL) ) { /* failure, bail out */
- fprintf(stderr, "SARG: (realtime) mkstemp error - %s\n",strerror(errno));
- exit(1);
- }
-
- sprintf(cmd,"tail -%d %s",realtime_access_log_lines,AccessLog);
- fp = popen(cmd, "r");
- while(fgets(buf,sizeof(buf),fp) != NULL )
- if (getdata(buf,tmp)<0) {
- printf("SARG: Maybe a broken record or garbage was returned by %s.\n",cmd);
- exit(1);
- }
- pclose(fp);
- fclose(tmp);
-
- sprintf(cmd,"sort -r -k 1,1 -k 2,2 -o %s %s",template2,template1);
- cstatus=system(cmd);
- if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus)) {
- fprintf(stderr, "SARG: sort command return status %d\n",WEXITSTATUS(cstatus));
- fprintf(stderr, "SARG: sort command: %s\n",cmd);
- exit(1);
- }
- unlink(template1);
- datashow(template2);
+ memcpy(&Dest->EntryTime,&Entry->EntryTime,sizeof(Dest->EntryTime));
+ safe_strcpy(Dest->Ip,Entry->Ip,sizeof(Dest->Ip));
+ if (Entry->Url)
+ {
+ int i;
+ const char *url=Entry->Url;
+
+ // skip the scheme
+ for (i=0 ; i<8 && url[i] && (isalnum(url[i]) || url[i]=='+' || url[i]=='-' || url[i]=='.') ; i++);
+ if (url[i]==':' && url[i+1]=='/' && url[i+2]=='/')
+ {
+ url+=i+3;
+ for (i=0 ; url[i] && url[i]!='/' ; i++);
+ }
+ if (i>=sizeof(Dest->Url)) i=sizeof(Dest->Url)-1;
+ strncpy(Dest->Url,url,i);
+ Dest->Url[i]='\0';
+ }
+ safe_strcpy(Dest->User,Entry->User,sizeof(Dest->User));
+ safe_strcpy(Dest->HttpMethod,Entry->HttpMethod,sizeof(Dest->HttpMethod));
}
-int getdata(char *rec, FILE *ftmp)
+static void header(void)
{
- time_t tt;
- struct tm *t;
-
- if (getword3(dat,sizeof(dat),rec,' ')<0) {
- return(-1);
- }
- if (getword3(warea,sizeof(warea),rec,' ')<0) {
- return(-1);
- }
- while(strcmp(warea,"") == 0 && strlen(rec) > 0)
- if (getword3(warea,sizeof(warea),rec,' ')<0) {
- return(-1);
- }
- if (getword3(ip,sizeof(ip),rec,' ')<0) {
- return(-1);
- }
- if (getword3(warea,sizeof(warea),rec,' ')<0) {
- return(-1);
- }
- if (getword3(warea,sizeof(warea),rec,' ')<0) {
- return(-1);
- }
- if (getword3(typ,sizeof(typ),rec,' ')<0) {
- return(-1);
- }
- if(strncmp(typ,"CONNECT",7) == 0) {
- if (getword3(url,sizeof(url),rec,' ')<0) {
- return(-1);
- }
- if (getword3(user,sizeof(user),rec,' ')<0) {
- return(-1);
- }
- }else {
- if (getword3(url,sizeof(url),rec,'/')<0) {
- return(-1);
- }
- if (getword3(url,sizeof(url),rec,'/')<0) {
- return(-1);
- }
- if (getword3(url,sizeof(url),rec,'/')<0) {
- return(-1);
- }
- if (getword3(user,sizeof(user),rec,' ')<0) {
- return(-1);
- }
- if (getword3(user,sizeof(user),rec,' ')<0) {
- return(-1);
- }
- }
-
- if(strncmp(user,"-",1) == 0 && strcmp(RealtimeUnauthRec,"ignore") == 0)
- return(0);
-
- tt=atoi(dat);
- t=localtime(&tt);
- if(strncmp(DateFormat,"u",1) == 0)
- strftime(tbuf, 127, "%Y-%m-%d %H:%M", t);
- else if(strncmp(DateFormat,"e",1) == 0)
- strftime(tbuf, 127, "%d-%m-%Y %H:%M", t);
-
- sprintf(warea,"%s %s %s %s %s\n",tbuf,ip,user,url,typ);
- fputs(warea,ftmp);
- return(0);
+ puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"");
+ puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
+ puts("<html>\n");
+ puts("<head>\n");
+ if(realtime_refresh)
+ printf(" <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet);
+ else
+ printf(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">\n",CharSet);
+ css(stdout);
+ puts("</head>\n");
+ printf("<body style=\"font-family:%s;font-size:%s;background-color:%s;background-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage);
+ puts("<div align=\"center\"><table cellpadding=\"1\" cellspacing=\"1\">\n");
+ printf("<tr><th class=\"title_l\" colspan=\"10\">SARG %s</th></tr>\n",_("Realtime"));
+ printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",_("Auto refresh"),realtime_refresh);
+ printf("<tr><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_l\">%s</th></tr>\n",_("DATE/TIME"),_("IP/NAME"),_("USERID"),_("TYPE"),_("ACCESSED SITE"));
}
-void datashow(char *tmp)
+static void datashow(struct RealtimeReadLogStruct *List,int Index,int Size)
{
- FILE *fin;
- char buf[MAXLEN];
-
- if((fin=fopen(tmp,"r"))==NULL) {
- fprintf(stderr, "SARG: (realtime) open error %s - %s\n",tmp,strerror(errno));
- exit(1);
- }
-
- header();
-
- while(fgets(buf, MAXLEN, fin)) {
- buf[strlen(buf)-1]='\0';
- if (getword3(dat,sizeof(dat),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if (getword3(tim,sizeof(tim),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if (getword3(ip,sizeof(ip),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if (getword3(user,sizeof(user),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if(strlen(dat) < 3 || strlen(user) < 1) continue;
- if (getword3(url,sizeof(url),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if (getword3(typ,sizeof(typ),buf,' ')<0) {
- printf("SARG: Maybe you have a broken record or garbage in your %s file.\n",tmp);
- exit(1);
- }
- if(strstr(RealtimeTypes,typ) == 0)
- continue;
-
- if(strcmp(ouser,user) == 0 && strcmp(ourl,url) == 0)
- continue;
-
- strcpy(u2,user);
- if(strcmp(Ip2Name,"yes") == 0)
- ip2name(u2,sizeof(u2));
- if(UserTabFile[0] != '\0') {
- sprintf(warea,":%s:",u2);
- if((str=(char *) strstr(userfile,warea)) != (char *) NULL ) {
- z1=0;
- str2=(char *) strstr(str+1,":");
- str2++;
- bzero(name, MAXLEN);
- while(str2[z1] != ':') {
- name[z1]=str2[z1];
- z1++;
- }
- } else strcpy(name,u2);
- } else strcpy(name,u2);
-
- if(dotinuser && strstr(name,"_")) {
- str2=(char *)subs(name,"_",".");
- strcpy(name,str2);
- }
-
- printf("<tr><td class=\"data\">%s %s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a href=\"http://%s\">%s</td></tr>\n",dat,tim,ip,name,typ,url,url);
- strcpy(ouser,user);
- strcpy(ourl,url);
- }
-
- puts("</table>\n</html>\n");
- fclose(fin);
- unlink(tmp);
- fflush(NULL);
-
+ char tbuf[128];
+ char user[MAX_USER_LEN];
+ char name[MAX_USER_LEN];
+ int i;
+ struct RealtimeReadLogStruct *entry;
+
+ header();
+ for (i=0 ; i<realtime_access_log_lines ; i++)
+ {
+ entry=List+Index;
+ Index--;
+ if (Index<0) Index=Size-1;
+
+ if (UserIp)
+ strcpy(user,entry->Ip);
+ else
+ strcpy(user,entry->User);
+ if(Ip2Name)
+ ip2name(user,sizeof(user));
+ user_find(name, sizeof(name), user);
+
+ if (df=='u')
+ strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", &entry->EntryTime);
+ else if (df=='e')
+ strftime(tbuf, sizeof(tbuf), "%d-%m-%Y %H:%M", &entry->EntryTime);
+
+ printf("<tr><td class=\"data\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a href=\"http://%s\">%s</td></tr>\n",
+ tbuf,entry->Ip,name,entry->HttpMethod,entry->Url,entry->Url);
+ }
+
+ puts("</table>\n</div>\n</body>\n</html>\n");
+ fflush(NULL);
}
-void header()
+void realtime(void)
{
- puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"");
- puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
- puts("<html>\n");
- puts("<head>\n");
- if(realtime_refresh)
- printf(" <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet);
- else
- printf(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">\n",CharSet);
- css(stdout);
- puts("</head>\n");
- printf(buf,"<body style=\"font-family:%s;font-size:%s;background-color:%s;background-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage);
- puts("<center><table cellpadding=\"1\" cellspacing=\"1\">\n");
- printf("<tr><th class=\"title2\" colspan=\"10\">SARG %s</th></tr>\n",text[134]);
- printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",text[136],realtime_refresh);
- printf("<tr><th class=\"header3\">%s</th><th class=\"header3\">%s</th><th class=\"header3\">%s</th><th class=\"header3\">%s</th><th class=\"header\">%s</th></tr>\n",text[110],text[111],text[98],text[135],text[91]);
+ FILE *fp;
+ char file_name[2048];
+ char *buf;
+ longline line;
+ struct ReadLogStruct log_entry;
+ enum ReadLogReturnCodeEnum log_entry_status;
+ struct LogLineStruct log_line;
+ struct RealtimeReadLogStruct *StoredLogEntries;
+ int StoreIndex=0;
+ int StoreSize=0;
+ int NextIndex=1;
+
+ init_usertab(UserTabFile);
+ LogLine_Init(&log_line);
+
+ /*
+ * Store one more entry to prepare the memory structure in place and reject it if
+ * it is about the same user and url as the last stored one.
+ */
+ StoredLogEntries=calloc(realtime_access_log_lines+1,sizeof(struct RealtimeReadLogStruct));
+ if (!StoredLogEntries)
+ {
+ debuga(__FILE__,__LINE__,_("Not enough memory to store %d records"),realtime_access_log_lines);
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Clear the url and user strings so that strcmp on the user and url are not
+ * satisfied and the first entry can be stored.
+ */
+ memset(StoredLogEntries,0,sizeof(struct RealtimeReadLogStruct));
+
+ if (!GetLatestModified(file_name,sizeof(file_name)))
+ {
+ debuga(__FILE__,__LINE__,_("No log file to read the last %d lines from\n"),realtime_access_log_lines);
+ exit(EXIT_FAILURE);
+ }
+ fp = fopen(file_name, "r");
+ if (!fp) {
+ debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),file_name,strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if ((line=longline_create())==NULL) {
+ debuga(__FILE__,__LINE__,_("Not enough memory to read file \"%s\"\n"),file_name);
+ exit(EXIT_FAILURE);
+ }
+
+ while((buf=longline_read(fp,line)) != NULL )
+ {
+ log_entry_status=LogLine_Parse(&log_line,&log_entry,buf);
+ if (log_entry_status==RLRC_Unknown)
+ {
+ continue;
+ }
+ if (log_entry_status==RLRC_Ignore)
+ {
+ continue;
+ }
+ if (log_entry.HttpMethod && strstr(RealtimeTypes,log_entry.HttpMethod)==0)
+ continue;
+ if (RealtimeUnauthRec==REALTIME_UNAUTH_REC_IGNORE && log_entry.User[0]=='-' && log_entry.User[1]=='\0')
+ continue;
+ StoreLogEntry(StoredLogEntries+NextIndex,&log_entry);
+ if (strcmp(StoredLogEntries[StoreIndex].User,StoredLogEntries[NextIndex].User)==0 && strcmp(StoredLogEntries[StoreIndex].Url,StoredLogEntries[NextIndex].Url)==0)
+ continue;
+
+ StoreIndex=NextIndex;
+ NextIndex++;
+ if (NextIndex>StoreSize) StoreSize=NextIndex;
+ if (NextIndex>realtime_access_log_lines) NextIndex=0;
+ }
+ if (fclose(fp)==EOF) {
+ debuga(__FILE__,__LINE__,_("Read error in \"%s\": %s\n"),file_name,strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ longline_destroy(&line);
+
+ datashow(StoredLogEntries,StoreIndex,StoreSize);
+ free(StoredLogEntries);
}