Begutachtung von Source

Dennis Wronka

Soulcollector
So, nachdem deepthroat mir schon dabei auf die Spruenge geholfen hat wie ich vernuenftig mit Strings in C umgehen kann hab ich meinen Virenscanner soweit laufen und wuerde gerne, da ich ja schon ewig nichts mehr mit C gemacht hab, und auch zuvor nie wirklich sehr viel damit gearbeitet hab, und somit vieles wieder vergessen hab darum bitten mir aufzuzeigen wo man den Source noch optimierien kann.
Ich hab schon die festgesetzten Grenzen der Char-Arrays beseitigt indem ich mit Pointern und malloc() und realloc() arbeite, da die Groesse der zu scannenden eMails ja nicht im Voraus bekannt ist.
Ich weiss, dass ich eine der strcat()-Anweisungen zusammenfassen koennte, dies habe ich aber bewusst nicht gemacht um diese aus Gruenden der Lesbarkeit nicht endlos lang werden zu lassen.
Aber zum Beispiel wuerde ich gern wissen ob ich diese 3 Anweisungen zusammenfassen kann.
Code:
strcat(email,"X-Virus-Checker: Scanned by mailscan ");
strcat(email,mailscanversion);
strcat(email," (No virus found)\r\n");
Stringverkettung mit . wie in PHP hat auf jeden Fall nicht geholfen, aber vielleicht kann man das in C auch irgendwie machen.

Naja, wie gesagt, ich wuerde mich freuen wenn mal jemand einen Blick auf den Source werfen koennte und mir Tipps zur Verbesserung geben koennte.

Code:
/***************************************************************************
 *   Copyright (C) 2005 by Dennis Wronka                                   *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <clamav.h>

const char mailscanversion[4]="0.8";
const unsigned long initialmemsize=1024;
int ret,length;
unsigned int signo;
unsigned long memsize,memcount;
struct cl_node *node;
const char *virname;
char input[1],scantmpname[128];
char *data,*email,*part1,*part2;
FILE *scantmpfile;

int main(int argc, char *argv[])
{
	cl_loaddbdir(cl_retdbdir(),&node,&signo);
	cl_build(node);
	memsize=initialmemsize;
	data=malloc(memsize);
	data[0]=(char)NULL;
	memcount=0;
	while (scanf("%c",&input)!=EOF)
		{
			memcount++;
			if (memcount>=memsize)
				{
					memsize+=initialmemsize;
					data=realloc(data,memsize);
				}
			strcat(data,input);
		}
	email=malloc(memsize);
	email[0]=(char)NULL;
	sprintf(scantmpname,"/tmp/mailscan%u",rand());
	scantmpfile=fopen(scantmpname,"w");
	fwrite(data,sizeof(char),strlen(data),scantmpfile);
	fclose(scantmpfile);
	ret=cl_scanfile(scantmpname,&virname,NULL,node,NULL,CL_SCAN_MAIL);
	unlink(scantmpname);
	part1=strstr(data,"Subject:");
	part2=strstr(part1,"\n");
	strncpy(email,data,strlen(data)-strlen(part2)+2);
	email[strlen(data)-strlen(part2)+1]=(char)NULL;
	if (ret==CL_VIRUS)
		{
			strcat(email,"X-Virus-Status: Yes\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (Virus found: ");
			strcat(email,virname);
			strcat(email,")\r\n");
		}
	else if (ret==CL_CLEAN)
		{
			strcat(email,"X-Virus-Status: No\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (No virus found)\r\n");
		}
	else
		{
			strcat(email,"X-Virus-Status: Error\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (Scan error: ");
			strcat(email,cl_strerror(ret));
			strcat(email,")\r\n");
		}
	cl_free(node);
	strcat(email,part2+1);
	printf("%s",email);
	free(email);
	free(data);
	return EXIT_SUCCESS;
}
 
Hi.

Also ich würde sagen das sieht schonmal alles ganz gut aus. Allerdings ist das ein sehr optimistischer Code ;) D.h. du hast fast keine Fehlerprüfung oder Fehlerbehandlung gemacht. Z.B. gibt realloc einen NULL Pointer bei einem Fehler zurück.

Dann solltest du zur Generierung von temp. Dateinamen lieber die Funktion mkstemp benutzen.

Du solltest auch lieber strncat nehmen statt strcat um Buffer overflow Angriffen vorzubeugen. (auf der man Page ist auch ein Beispiel)

String Literale kannst du übrigens einfach hintereinander schreiben, die konkateniert der Compiler dann automatisch:
Code:
strcat(email, "blah\n"
              "neue Zeile\n"
              "noch ne Zeile..");

Außerdem werden die Header einer Mail durch eine leere Zeile beendet (nicht durch die Subject: Line). Am besten du scannst zeilenweise bis du auf eine Zeile triffst, die keine Header Line mehr sein kann: entweder eine leere Zeile - also wirklich "\n\n" oder eine Zeile die nicht mit einem Leerzeichen, Tab bzw. einem Header-Namen + Doppelpunkt anfängt.

Gruß
 
Ich fuege ja nur in die bestehende eMail meinen String ein. Daher kann mir der Aufbau der Mail an sich eigentlich egal sein, da ich nach dem Subject einfach nur 2 Zeilen einfuege.
Ich hatte zuerst probiert die Mail als Anhang zu kapseln, wie SpamAssassin es macht, das wollte aber irgendwie nicht, obwohl ich mich dabei ziemlich an meiner eMail-Klasse die ich in PHP geschrieben hab orientiert hat. Da kann ich ohne weiteres Dateien anhaengen.

Ich werde mich auf jeden Fall mit Deinen Anregungen beschaeftigen und versuchen zu implementieren.
Ich hatte zuerst etwas mehr Fehlerbehandlung drin, zum Beispiel fuer den Fall dass die Viren-Signaturen nicht geladen werden koennen, hab dies aber erstmal wieder rausgenommen um sicherzustellen, dass die eMail wieder an ProcMail zurueckgeht.
Ich werde da auf jeden Fall noch ein paar Checks einbauen und dann im Fehlerfall die Original-Mail ausgeben.

Auf jeden Fall schonmal vielen Dank fuer die Anregungen.
 
So, ich hab dem Code mal einiges seines Optimismus genommen und hier da und da Abfragen eingebaut ob was schief gelaufen ist.
Ausserdem hab ich mir mal die Funktionen mkstemp() und tmpfile() angesehen, habe aber da irgendwie das Problem, dass ich gerne wuesste wie denn das TempFile heisst, denn ich will es am Ende ja auch wieder loeschen.
Ich werd mir mal tmpnam() anschauen, die Funktion koennte mir weiterhelfen.

Und zum Abschluss hier die aktuelle Version meines Sources:
Code:
/***************************************************************************
 *   Copyright (C) 2005 by Dennis Wronka                                   *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <clamav.h>

const char mailscanversion[4]="0.8";
const unsigned long initialmemsize=1024;
int ret,length;
unsigned int signo;
unsigned long memsize,memcount;
struct cl_node *node;
const char *virname;
char input[1],scantmpname[128];
char *data,*email,*part1,*part2;
FILE *scantmpfile;

int main(int argc, char *argv[])
{
	ret=cl_loaddbdir(cl_retdbdir(),&node,&signo);
	if (ret)
		{
			return EXIT_FAILURE;
		}
	ret=cl_build(node);
	if (ret)
		{
			cl_free(node);
			return EXIT_FAILURE;
		}
	memsize=initialmemsize;
	data=malloc(memsize);
	if (data==NULL)
		{
			cl_free(node);
			return EXIT_FAILURE;
		}
	data[0]=(char)NULL;
	memcount=0;
	while ((scanf("%c",&input)!=EOF) && (data!=NULL))
		{
			memcount++;
			if (memcount>=memsize)
				{
					memsize+=initialmemsize;
					data=realloc(data,memsize);
				}
			strcat(data,input);
		}
	if (data==NULL)
		{
			cl_free(node);
			return EXIT_FAILURE;
		}
	email=malloc(memsize);
	email[0]=(char)NULL;
	sprintf(scantmpname,"/tmp/mailscan%u",rand());
	scantmpfile=fopen(scantmpname,"w");
	fwrite(data,sizeof(char),strlen(data),scantmpfile);
	fclose(scantmpfile);
	ret=cl_scanfile(scantmpname,&virname,NULL,node,NULL,CL_SCAN_MAIL);
	unlink(scantmpname);
	part1=strstr(data,"Subject:");
	if (part1==NULL)
		{
			printf("%s",data);
			cl_free(node);
			free(email);
			free(data);
			return EXIT_FAILURE;
		}
	part2=strstr(part1,"\n");
	strncpy(email,data,strlen(data)-strlen(part2)+2);
	email[strlen(data)-strlen(part2)+1]=(char)NULL;
	if (ret==CL_VIRUS)
		{
			strcat(email,"X-Virus-Status: Yes\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (Virus found: ");
			strcat(email,virname);
			strcat(email,")\r\n");
		}
	else if (ret==CL_CLEAN)
		{
			strcat(email,"X-Virus-Status: No\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (No virus found)\r\n");
		}
	else
		{
			strcat(email,"X-Virus-Status: Error\r\n");
			strcat(email,"X-Virus-Checker: Scanned by mailscan ");
			strcat(email,mailscanversion);
			strcat(email," (Scan error: ");
			strcat(email,cl_strerror(ret));
			strcat(email,")\r\n");
		}
	strcat(email,part2+1);
	printf("%s",email);
	cl_free(node);
	free(email);
	free(data);
	return EXIT_SUCCESS;
}

Nachtrag: Ich lasse den Namen des TempFiles nun mittels tmpnam() erzeugen.
 
Dennis Wronka hat gesagt.:
Ausserdem hab ich mir mal die Funktionen mkstemp() und tmpfile() angesehen, habe aber da irgendwie das Problem, dass ich gerne wuesste wie denn das TempFile heisst, denn ich will es am Ende ja auch wieder loeschen.
Das brauchst du doch gar nicht. Nimm lieber die Funktion
Code:
cl_scandesc(int desc, const char **virname, unsigned
	long int *scanned, const struct cl_node *root, const
	struct cl_limits *limits, unsigned int options);

Der Funktion kannst du als erstes Argument gleich ein File Handle (was zufällig genau von mkstemp zurückgegeben wird ;)) übergeben.

Die Datei kannst du übrigens sofort nach dem mkstemp Aufruf unlink(en) dann ist sie nicht mehr zugreifbar (für andere Programme) wird aber erst gelöscht wenn du das File Handle freigibst.
 
;-) Also die Funktion mkstemp modifiziert das übergebene Argument und ersetzt in situ die X Zeichen durch zufällige Zahlen [und Buchstaben].

Code:
char tmp_name = "/tmp/foo.XXXXXX";
int hndl;
if ((hndl = mkstemp (tmp_name)) != -1) {
  unlink (tmp_name);
  ...
}

/edit: Also, ich hab da ehrlich gesagt da jetzt auch erstmal nachschauen müssen. Da hab ich jetzt in der Doku von Sun gelesen, das die Funktion tmpfile noch besser ist - die unlinkt die Datei gleich automatisch.

Gruß
 
Zuletzt bearbeitet:
Dann werd ich gleich mal ein wenig mit tmpfile() rumprobieren und schauen was da so passiert.

Auf jeden Fall vielen, vielen Dank fuer die Hilfe.
Hab jetzt in den letzten Jahren so viel PHP gemacht und nie mit C gearbeitet, da muss man sich erstmal wieder dran gewoehnen. ;)

Nachtrag: Ich glaub tmpfile() ist doch nicht so ganz das richtige fuer mich.
Das TempFile scheint zwar angelegt zu werden, scantmpfile ist zumindest nicht NULL, aber entweder ist FILE* der falsche Datentyp, es wird immerhin INT erwartet, oder die Datei wird direkt nach fclose() geloescht, was sehr unguenstig waere.
Auf jeden Fall bekomme ich nun den folgenden Fehler:
LibClamAV Error: Can's fstat descriptor 142404504

Nachtrag 2: Ich hab's jetzt erstmal wieder mit tmpnam(), aber hab grad gesehen, dass GCC mir dabei sagt, dass tmpnam() gefaehrlich sei und ich besser mkstemp() nutzen sollte. Dieses gibt mir wenigstens einen INT zurueck, sodass ich dort dann wohl mit cl_scandesc() arbeiten koennte. Mal probieren...
 
Okay ich hab jetzt diesen Part
Code:
scantmpname=malloc(256);
strcpy(scantmpname,"/tmp/scanmailXXXXXX");
scantmp=mkstemp(scantmpname);
scantmpfile=fopen(scantmpname,"w");
fwrite(data,sizeof(char),strlen(data),scantmpfile);
fclose(scantmpfile);
ret=cl_scandesc(scantmp,&virname,NULL,node,NULL,CL_SCAN_MAIL);
unlink(scantmpname);
free(scantmpname);
durch diesen ersetzt
Code:
scantmpfile=tmpfile();
if (scantmpfile==NULL)
	{
		printf("%s",data);
		cl_free(node);
		free(email);
		free(data);
		return EXIT_FAILURE;
	}
fwrite(data,sizeof(char),strlen(data),scantmpfile);
fflush(scantmpfile);
ret=cl_scandesc(fileno(scantmpfile),&virname,NULL,node,NULL,CL_SCAN_MAIL);
fclose(scantmpfile);
scantmpfile wird offensichtlich geloescht sobald ich es mit fclose() schliesse, also hab ich mal kurz geschaut und fflush() gefunden womit ich den den mit fwrite() geschriebenen Puffer in das TempFile flushen kann.
Das scheint soweit auch ganz gut zu klappen.

So langsam werd ich mit C auch wieder was waermer, aber zum Thema Pointer werd ich mir noch ein paar Sachen durchlesen muessen.

Nachtrag: Ich bin jetzt mal dazu uebergegangen mit einer etwas groesseren Datei zu testen. Immerhin soll alles was durch Postfix geht auch durch meinen Scanner gejagt werden. Dementsprechend habe ich nun nicht den kleinen Eicar-Test-String geschickt, sondern ein nicht infiziertes Tar.Gz-Archiv mit schlappen 6MB Groesse.
Mit top kann ich sehen, dass mailscan sich waehrend es laeuft (was eine ganze Weile ist) mal schlappe 10MB Speicher an Land zieht. Was fuer mich im Grunde heisst, dass genuegend alloziert werden sollte um die eMail zu puffern.
Trotzdem wird mir die Mail am Ende ohne meinen zusaetzlichen Header zurueckgegeben. Es wurde also offensichtlich nicht gescannt.

Ich wuerde nun natuerlich schon gerne dafuer sorgen, dass auch grosse Dateien gescannt werden koennen, will aber nicht mit malloc() gleich zu Beginn die maximal akzeptable Groesse festlegen sondern wie es jetzt ist mit realloc() den allozierten Bereich vergroessern um dem ganzen etwas Flexibilitaet zu verleihen.

Nachtrag 2: Das Programm laeuft immer noch, es hat sich langsam aber sicher auf 13MB hochgearbeitet. Das ist jetzt zumindest der Speicher der gebraucht wird um die Mail 2 mal zu halten, was ja in der gerade laufenden Version noch der Fall ist (im aktuellen Source sind nur ein paar Zeilen zwischen dem malloc() der auszugebenden Mail und der free() der Original-Mail).

Nachtrag 3: mailscan arbeitet immer noch. Ich bin ja mal gespannt ob das heute noch was wird. Ich frag mich nur warum das so lange dauert. clamscan hat auf jeden Fall nicht so lang gebraucht um das selbe Archiv unter die Lupe zu nehmen.
Naja, falls das heute nicht fertig wird, morgen bin ich ja auch noch im Buero. ;)
Mittlerweile ist der Speicherbedarf uebrigens bei 15MB angekommen und ich bin zu der Ansicht gekommen, dass die Datei ungescannt ausgeliefert wurde weil es einfach zu lange gedauert hat und ProcMail die Schnauze voll davon hatte auf mailscan zu warten.

Nachtrag 4: Und es geht immer noch weiter, mittlerweile mit 17MB.
Ich hab jetzt gerade noch einen Teil des Codes ersetzt, wuerde aber gern mal Deine Meinung hoeren welcher Code besser ist:
alt:
Code:
while ((scanf("%c",&input)!=EOF) && (data!=NULL))
	{
		memcount++;
		if (memcount>=memsize)
			{
				memsize+=initialmemsize;
				data=realloc(data,memsize);
			}
		if (data!=NULL)
			{
				strncat(data,input,1);
			}
	}
neu:
Code:
while (((input[0]=getchar())!=EOF) && (data!=NULL))
	{
		memcount++;
		if (memcount>=memsize)
			{
				memsize+=initialmemsize;
				data=realloc(data,memsize);
			}
		if (data!=NULL)
			{
				strncat(data,input,1);
			}
	}
Also im Grunde geht es einfach um die Frage ob es besser ist scanf() oder getchar() zu nutzen.
Ich hatte es kurz mit gets() probiert, da wurde mir aber auch von GCC gemeldet, dass gets() wohl auch unsicher sei.

Und Nachtrag 5: Nach gut 4 Stunden 30 Minuten ist mailscan nun fertig eine 6MB "grosse" Mail zu scannen. Unglaublich, dass das so lange dauert. Aber das koennte daran liegen, dass ich die falsche Scan-Optionen genutzt habe. Denn ich hatte nicht aktiviert, dass Archive entpacket werden sollen.
Ich weiss jetzt aber nicht ob das Programm komplett gemacht hat was es soll, denn ich kann nicht die komplette Ausgabe sehen sodass der entscheidende Header fehlt. :( Ich haette die Ausgabe besser in eine Datei umleiten sollen.
Naja, ich lasse es jetzt nochmal laufen, mit dem geaenderten Code und schau mal was dabei rum kommt. Mit der Geschwindigkeit bin ich auf jeden Fall nicht zufrieden. Hast Du vielleicht eine Ahnung woran das liegt? Vielleicht an meinem realloc()?
 

Neue Beiträge

Zurück