unit popunit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, mystringlistunit, process; type tMarkenValiditaet = (mvKeine,mvGueltig,mvUngueltig); tBefehlAusfuehrenErgebnis = (baeOk,baeError,baeTimeout); tPopClient = class; tNachricht = class(tMyStringList) private _marke: tMarkenValiditaet; procedure pruefeMarke; function rMarke: tMarkenValiditaet; public id,groesze: longint; sdbName,gueltigerAdressat: string; constructor create; destructor destroy; override; procedure kopiereVon(n: tNachricht); function istGleich(n: tNachricht): boolean; property marke: tMarkenValiditaet read rMarke; end; tPopThread = class(tThread) private _beendet: boolean; _sshUser,_host,_user,_pass: string; _port: longint; _besitzer: tPopClient; _nachrichten: array of tNachricht; _lIds: array of longint; procedure datenRausGeben; procedure datenReinNehmen; procedure auszenWeltBenachrichtigung; procedure auszenWeltKontakt(var pr: tProcess); public beenden,esGibtArbeit: boolean; property beendet: boolean read _beendet; constructor create(besitzer: tPopClient); destructor destroy; override; procedure execute; override; end; tPopClient = class private _thread: tPopThread; _sdbName: string; _nachrichten: array of tNachricht; _lIds: array of longint; public sshUser,host,user,pass,gueltigerAdressat: string; port: longint; timeout: extended; neueNachrichten: tNotifyEvent; constructor create; destructor destroy; override; function nAnz: longint; function betreff(n: longint): string; function von(n: longint): string; function zeit(n: longint): string; function marke(n: longint): tMarkenValiditaet; procedure threadAnhalten; procedure aufThreadWarten; procedure loesche(n: longint); procedure loeschen; end; function readString(p: tProcess): string; procedure writeString(p: tProcess; s: string); function befehlAusfuehren(var pr: tProcess; bef: string; timeout: extended): tBefehlAusfuehrenErgebnis; overload; inline; function befehlAusfuehren(var pr: tProcess; bef: string; timeout: extended; out ausg: string): tBefehlAusfuehrenErgebnis; overload; inline; function befehlAusfuehren(var pr: tProcess; bef,ende: string; timeout: extended; out ausg: string): tBefehlAusfuehrenErgebnis; overload; procedure _app(var f: text); inline; implementation uses lowlevelunit, systemunit; const extraLen = 1024; function readString(p: tProcess): string; var len: longint; begin setlength(result,p.output.numBytesAvailable); if length(result)>0 then begin len:=p.output.read(result[1],length(result)); setlength(result,len); end else sleep(10); end; procedure writeString(p: tProcess; s: string); var i: longint; begin i:=1; while p.running and (i<=length(s)) do i:=i+p.input.write(s[i],length(s)-i+1); end; function befehlAusfuehren(var pr: tProcess; bef: string; timeout: extended): tBefehlAusfuehrenErgebnis; var dummy: string; begin result:=befehlAusfuehren(pr,bef,timeout,dummy); end; function befehlAusfuehren(var pr: tProcess; bef: string; timeout: extended; out ausg: string): tBefehlAusfuehrenErgebnis; begin result:=befehlAusfuehren(pr,bef,'+OK',timeout,ausg); end; function befehlAusfuehren(var pr: tProcess; bef,ende: string; timeout: extended; out ausg: string): tBefehlAusfuehrenErgebnis; var zeit: extended; begin writeString(pr,bef+#10); zeit:=now+timeout/24/60/60; ausg:=''; while (zeit>now) and (pos(ende,ausg)=0) and (pos('-ERR',ausg)=0) do ausg:=ausg+readString(pr); if pos(ende,ausg)>0 then begin result:=baeOk; exit; end; if pos('-ERR',ausg)>0 then begin writeln(stderr,bef+' fehlgeschlagen (Antwort: '''+ausg+''')!'); result:=baeError; exit; end; writeln(stderr,bef+' fehlgeschlagen (timeout erreicht)!'); pr.free; pr:=nil; result:=baeTimeout; end; // tNachricht ****************************************************************** constructor tNachricht.create; begin inherited create; id:=-1; groesze:=-1; _marke:=mvKeine; fillchar(gueltigerAdressat,sizeof(gueltigerAdressat),#0); gueltigerAdressat:=''; fillchar(sdbName,sizeof(sdbName),#0); sdbName:=''; end; destructor tNachricht.destroy; begin gueltigerAdressat:=''; sdbName:=''; inherited destroy; end; procedure tNachricht.pruefeMarke; var pr: tProcess; f: textfile; begin if grepFirst('^[Xx]-[Hh]ash[Cc]ash:\s')='' then begin _marke:=mvKeine; exit; end; if _marke<>mvKeine then exit; if sdbName='' then raise exception.create('sdbName noch nicht gesetzt!'); pr:=tProcess.create(nil); pr.executable:='hashcash'; pr.parameters.add('-c'); // Prüfen pr.parameters.add('-X'); // Header-Format pr.parameters.add('-b'); // Bits pr.parameters.add('20'); pr.parameters.add('-d'); // mit Datenbank pr.parameters.add('-f'); // an folgendem Ort: pr.parameters.add(sdbName); pr.parameters.add('-E'); // mit Regex pr.parameters.add('-r'); // gültige Rezipienten pr.parameters.add(gueltigerAdressat); pr.options:=[poUsePipes]; pr.execute; writeString(pr,text); pr.closeInput; while pr.running or (pr.output.numBytesAvailable>0) do readString(pr); pr.closeOutput; case pr.exitStatus of 0: _marke:=mvGueltig; else _marke:=mvUngueltig; end; pr.free; assignfile(f,'/tmp/email-log'); if fileexists('/tmp/email-log') then _app(f) else rewrite(f); writeln(f,datetimetostr(now)); writeln(f,grepFirst('^[Xx]-[Hh]ash[Cc]ash:\s')); writeln(f,_marke); closefile(f); end; function tNachricht.rMarke: tMarkenValiditaet; begin pruefeMarke; result:=_marke; end; procedure tNachricht.kopiereVon(n: tNachricht); begin text:=n.text; id:=n.id; groesze:=n.groesze; _marke:=n.marke; gueltigerAdressat:=n.gueltigerAdressat; sdbName:=n.sdbName; end; function tNachricht.istGleich(n: tNachricht): boolean; begin result:=(text=n.text) and (id=n.id) and (groesze=n.groesze); end; // tPopThread ****************************************************************** constructor tPopThread.create(besitzer: tPopClient); begin inherited create(true); _besitzer:=besitzer; beenden:=false; esGibtArbeit:=false; _beendet:=false; _sshUser:=''; _user:=''; _pass:=''; _host:=''; setlength(_nachrichten,0); fillchar(_lIds,sizeof(_lIds),#0); setlength(_lIds,0); suspended:=false; end; destructor tPopThread.destroy; var i: longint; begin beenden:=true; while not beendet do sleep(10); for i:=0 to length(_nachrichten)-1 do _nachrichten[i].free; setlength(_nachrichten,0); setlength(_lIds,0); inherited destroy; end; procedure tPopThread.datenRausGeben; var neu: boolean; i: longint; begin if beenden then exit; neu:=length(_besitzer._nachrichten)<>length(_nachrichten); if not neu then for i:=0 to length(_nachrichten)-1 do neu:=neu or not _nachrichten[i].istGleich(_besitzer._nachrichten[i]); if neu then begin for i:=0 to length(_besitzer._nachrichten)-1 do _besitzer._nachrichten[i].free; setlength(_besitzer._nachrichten,length(_nachrichten)); for i:=0 to length(_besitzer._nachrichten)-1 do begin _besitzer._nachrichten[i]:=tNachricht.create; _besitzer._nachrichten[i].kopiereVon(_nachrichten[i]); end; end; end; procedure tPopThread.auszenWeltBenachrichtigung; begin if assigned(_besitzer.neueNachrichten) then _besitzer.neueNachrichten(_besitzer); end; procedure tPopThread.datenReinNehmen; begin if beenden then exit; _sshUser:=_besitzer.sshUser; _user:=_besitzer.user; _pass:=_besitzer.pass; _host:=_besitzer.host; _port:=_besitzer.port; setlength(_lIds,length(_besitzer._lIds)); if length(_lIds)>0 then move(_besitzer._lIds[0],_lIds[0],sizeof(_lIds[0])*length(_lIds)); setlength(_besitzer._lIds,0); end; procedure tPopThread.auszenWeltKontakt(var pr: tProcess); var i,j,k: longint; begin synchronize(@datenReinNehmen); if assigned(pr) then begin for i:=0 to length(_lIds)-1 do begin case befehlAusfuehren(pr,'DELE '+intToStr(_lIds[i]),_besitzer.timeout) of baeTimeout: exit; baeError: begin if beenden then exit; continue; end; end{of case}; if beenden then exit; for j:=length(_nachrichten)-1 downto 0 do if _nachrichten[j].id=_lIds[i] then begin _nachrichten[j].free; for k:=j+1 to length(_nachrichten)-1 do _nachrichten[k-1]:=_nachrichten[k]; setlength(_nachrichten,length(_nachrichten)-1); break; end else if j=0 then begin pr.free; pr:=nil; writeln(stderr,'DELE '+intToStr(_lIds[i])+' fehlgeschlagen - ich habe die Nachricht nicht bei mir gefunden!'); exit; end; end; setlength(_lIds,0); esGibtArbeit:=false; end; synchronize(@datenRausGeben) end; procedure tPopThread.execute; var pr: tProcess; buf,s: string; zeit: extended; i,j,len,id,groesze: longint; begin pr:=nil; for i:=0 to length(_nachrichten)-1 do _nachrichten[i].free; setlength(_nachrichten,0); while not beenden do begin auszenWeltKontakt(pr); if (_user<>'') and (_host<>'') and (_pass<>'') and (_port<>0) then begin if assigned(pr) then begin if befehlAusfuehren(pr,'QUIT',_besitzer.timeout)=baeOk then pr.WaitOnExit; pr.free; pr:=nil; if beenden then break; end; pr:=tProcess.create(nil); if _sshUser<>'' then begin pr.executable:='ssh'; pr.parameters.add(_sshUser+'@'+_host); pr.parameters.add('openssl s_client -connect 127.0.0.1:'+inttostr(_port)+' -quiet'); end else begin pr.executable:='openssl'; pr.parameters.add('s_client'); pr.parameters.add('-connect'); pr.parameters.add(_host+':'+inttostr(_port)); pr.parameters.add('-quiet'); end; pr.options:=[poUsePipes]; pr.execute; zeit:=now+_besitzer.timeout/24/60/60; buf:=''; while (zeit>now) and (pos('+OK',buf)=0) do buf:=buf+readString(pr); if pos('+OK',buf)=0 then begin pr.free; pr:=nil; writeln(stderr,'Keine Willkommensnachricht!'); continue; end; if beenden then break; if befehlAusfuehren(pr,'USER '+_user,_besitzer.timeout)<>baeOk then continue; if beenden then break; if befehlAusfuehren(pr,'PASS '+_pass,_besitzer.timeout)<>baeOk then continue; if beenden then break; if befehlAusfuehren(pr,'LIST',#13#10'.'#13#10,_besitzer.timeout,buf)<>baeOk then continue; if beenden then break; buf:=erstesArgument(buf,#13#10'.'#13#10); erstesArgument(buf,'.'); len:=0; while (not beenden) and (buf<>'') do begin s:=erstesArgument(buf,#13#10); id:=strToInt(erstesArgument(s)); groesze:=strToInt(s); if (len=length(_nachrichten) then begin setlength(_nachrichten,len+extraLen); for i:=len to length(_nachrichten)-1 do _nachrichten[i]:=nil; end; _nachrichten[len]:=tNachricht.create; _nachrichten[len].id:=id; _nachrichten[len].groesze:=groesze; _nachrichten[len].gueltigerAdressat:=_besitzer.gueltigerAdressat; _nachrichten[len].sdbName:=_besitzer._sdbName; inc(len); end; for i:=len to length(_nachrichten)-1 do _nachrichten[i].free; setlength(_nachrichten,len); if beenden then break; for i:=0 to length(_nachrichten)-1 do if _nachrichten[i].count=0 then begin if befehlAusfuehren(pr,'TOP '+intToStr(_nachrichten[i].id)+' 0',#13#10'.'#13#10,_besitzer.timeout,buf)<>baeOk then break; if beenden then break; buf:=erstesArgument(buf,#13#10'.'#13#10); erstesArgument(buf,'.'); _nachrichten[i].text:=buf; for j:=_nachrichten[i].count-1 downto 1 do if (length(_nachrichten[i][j])>0) and (_nachrichten[i][j][1] in [' ',#9]) then begin _nachrichten[i][j-1]:=trimRight(_nachrichten[i][j-1])+trimLeft(_nachrichten[i][j]); _nachrichten[i].delete(j); end; _nachrichten[i].pruefeMarke; end; if beenden or not assigned(pr) then continue; auszenWeltKontakt(pr); synchronize(@auszenWeltBenachrichtigung); if beenden or not assigned(pr) or esGibtArbeit then continue; zeit:=now; for i:=0 to 5 do begin zeit:=now+1/24/60/6; if befehlAusfuehren(pr,'NOOP',10)<>baeOk then break; while (not beenden) and (zeit>now) and (not esGibtArbeit) do sleep(100); if beenden or esGibtArbeit then break; end; end else sleep(10); end; if assigned(pr) then begin if befehlAusfuehren(pr,'QUIT',_besitzer.timeout)=baeOk then pr.waitOnExit; pr.free; pr:=nil; end; _beendet:=true; end; // tPopClient ****************************************************************** constructor tPopClient.create; begin inherited create; sshUser:=''; user:=''; pass:=''; host:=''; _sdbName:=mkTemp('/tmp/Emails-hashcash.sdb.XXXXXX'); gueltigerAdressat:=''; port:=0; timeout:=10; setlength(_nachrichten,0); neueNachrichten:=nil; fillchar(_lIds,sizeof(_lIds),#0); setlength(_lIds,0); _thread:=tPopThread.create(self); end; destructor tPopClient.destroy; var i: longint; begin _thread.free; for i:=0 to length(_nachrichten)-1 do _nachrichten[i].free; setlength(_nachrichten,0); setlength(_lIds,0); if fileexists(_sdbName) then deleteFile(_sdbName); inherited destroy; end; function tPopClient.nAnz: longint; begin result:=length(_nachrichten); end; function tPopClient.betreff(n: longint): string; begin result:=_nachrichten[n].grepFirst('^Subject:\s'); erstesArgument(result,':'); end; function tPopClient.von(n: longint): string; begin result:=_nachrichten[n].grepFirst('^From:\s'); erstesArgument(result,':'); end; function tPopClient.zeit(n: longint): string; begin result:=_nachrichten[n].grepFirst('^Date:\s'); erstesArgument(result,':'); end; function tPopClient.marke(n: longint): tMarkenValiditaet; begin result:=_nachrichten[n].marke; end; procedure tPopClient.threadAnhalten; begin _thread.beenden:=true; end; procedure tPopClient.aufThreadWarten; begin while not _thread.beendet do sleep(10); end; procedure tPopClient.loesche(n: longint); var i: longint; begin setlength(_lIds,length(_lIds)+1); _lIds[length(_lIds)-1]:=_nachrichten[n].id; _nachrichten[n].free; for i:=n+1 to length(_nachrichten)-1 do _nachrichten[i-1]:=_nachrichten[i]; setlength(_nachrichten,length(_nachrichten)-1); _thread.esGibtArbeit:=true; if assigned(neueNachrichten) then neueNachrichten(self); end; procedure tPopClient.loeschen; var i: longint; begin setlength(_lIds,length(_lIds)+length(_nachrichten)); for i:=0 to length(_nachrichten)-1 do begin _lIds[length(_lIds)-1-i]:=_nachrichten[i].id; _nachrichten[i].free; end; setlength(_nachrichten,0); _thread.esGibtArbeit:=true; if assigned(neueNachrichten) then neueNachrichten(self); end; procedure _app(var f: text); inline; begin append(f); end; end.