PHP Garbage Collector Debian alatt és általában [MEGOLDVA]

Üdv!

Röviden az alapsztori:

"...éppen most írom a szakdolgozatomat. A feladat egy olyan webes alapú rendszer készítése, amelyen keresztül hallgatókat lehet felvenni egy adatnázisba (mysql adatbázis). A rendszernek a lényege, hogy azokat a hallgatókat akik elkezdték/befejezték/leadták stb... a szakdolgozatukat tudjuk kezelni elektronikusan, illetve nyomon tudjuk követni ki hol tart, valamint statisztikát lehessen készíteni pl hány PTI-s hallgató írt szakdogát 2010 második félévében. A lényeg, hogy az egésznek az alapja php, de mivel ez egy admin felület lesz (értsd: localhoston megy és csak jelszóval lehet bármilyen funkcióját is elérni) ezért úgy érzem, hogy itt megengedhető a js..."

Most éppen ott akadtam el, hogy nem szeretném, hogy a session lejárjon egy bizonyos idő után. De mivel a php.ini-ben a session.gc_maxlifetime = 1440 ezért 24 perc elteltével minden session törlődik...
Na de én azt szeretném, hogy a saját session-öm megmaradjon, ergo míg nem kattintok a kilépésre nem lépjen ki a rendszer, bár amikor kilépek akkor törlődhet a session! A legegyszerűbb megoldás az lenne, ha a php.ini-ben a session.gc_mxlifetime-ot levenném 0-ra, de ez azért mégis csak elég "brutális" a többi domain-el szemben. Tehát valami olyan kéne, hogy csak az én session-jeimre vonatkozzon a beállítás. Itt jön a képbe ez. Amint látjuk az ini_set("session.gc_maxlifetime", 24 * 3600); parancs önmagában még nem megoldás mert a garbage collector a többi domain miatt ugyanugy lezúzza az én session-ömet is. Én az első megoldást választottam vagyis saját könyvtárba rakom a session-ömet. Na most itt jön a képbe az amit még nem mondtam, hogy Debian rendszeren fut a szerver.

Idézet a php.net fórumából: http://hu2.php.net/manual/en/function.session-save-path.php#98106

"Debian does not use the default garbage collector for sessions. Instead, it sets session.gc_probability to zero and it runs a cron job to clean up old session data in the default directory.

As a result, if your site sets a custom location with session_save_path() you also need to set a value for session.gc_probability, e.g.:

<?php
session_save_path('/home/example.com/sessions');
ini_set('session.gc_probability', 1);
?>

Otherwise, old files in '/home/example.com/sessions' will never get removed!"

Vagyis Debian alatt nem is a gc végzi a piszkos munkát hanem a crontab? Hogy is van ez akkor?

Ami eddig van:

public function setSession() {
strstr ( strtoupper ( substr ( PHP_OS, 0, 3 ) ), "WIN" ) ? $sep = "\\" : $sep = "/";
$sessDir = ini_get ( "session.save_path" ) . $sep . "my_sessions";
if (! is_dir ( $sessDir )) {
mkdir ( $sessDir, 0777 );
}
ini_set ( "session.save_path", $sessDir );
}

Szerintem ez azért nem lesz jó mert az almappákba (/var/lib/php5/my_sessions/) Ugyanúgy benéz a crontab és a gc is és letörli a session-öket. A másik, hogy van ez a setSession() függvény, minden php oldalam ugyebár a session_start() hívással kezdődik, akkor ezt minden oldal elejére be kell elé vágnom, vagy csak az index.php? Ez sem teljesen tiszta.

Összegzés:

- Szeretném, hogy soha ne léptesse ki a rendszer a userem, viszont nem szeretném, hogy egy idő után sok sok "szemét" session legyen a könyvtáramban.
- Nem szeretnék semmit se piszkálni a szerver configban. (Ha feltöltöm akármilyen szerverre a rendszerem működjön állítgatás nélkül)

Szóval ez ügyben kérném a segítségeteket.

MEGOLDÁS:

A megoldás végül az lett, hogy a session-t adatbázisban tároltam. Leírom részletesen az egészet, hátha valakinek még jó lesz (ha más nem nekem dokumentáció céljából :)) Az egészet rootkit leírása alapján csináltam (ezért köszönet neki) némi változtatással.

MySQL táblák felépítése:


mysql> desc users;
+---------+-------------+------+-----+---------+----------------+
| Field   | Type        | Null | Key | Default | Extra          |
+---------+-------------+------+-----+---------+----------------+
| user_id | int(11)     | NO   | PRI | NULL    | auto_increment | 
| logname | varchar(20) | YES  |     | NULL    |                | 
| logpass | varchar(32) | YES  |     | NULL    |                | 
+---------+-------------+------+-----+---------+----------------+

mysql> desc manage_session;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| sess_id   | varchar(32) | NO   | PRI | NULL    |       | 
| user_id   | int(11)     | YES  | MUL | NULL    |       | 
| lastlogin | datetime    | YES  |     | NULL    |       | 
+-----------+-------------+------+-----+---------+-------+

mysql> select * from users;
+---------+---------+----------------------------------+
| user_id | logname | logpass                          |
+---------+---------+----------------------------------+
|       2 | phppityu| 1337123456re4343szupertitkosmd5  | 
+---------+---------+----------------------------------+

mysql> select * from manage_session;
+----------------------------------+---------+---------------------+
| sess_id                          | user_id | lastlogin           |
+----------------------------------+---------+---------------------+
| 412732ewsdsd32ds323232ffds31dshr2|       2 | 2010-09-16 15:22:16 | 
+----------------------------------+---------+---------------------+ 

Itt érdemes megemlíteni a manage_session tábla létrehozását (adtam nevet a függőségnek a későbbi könnyebb módosítás édekében):

create table manage_session( sess_id varchar(32) not null, user_id
INTEGER, lastlogin DATETIME, PRIMARY KEY (sess_id), CONSTRAINT
fk_userid_id FOREIGN KEY(user_id) REFERENCES users(user_id));

Akkor most jöjjenek a php-s részek:

Az általam használt függvények:


public function Login() {
		if (! empty ( $_POST )) {
			$logname = $_POST ['logname'];
			$logpass = $_POST ['logpass'];
		}
		if (! empty ( $logname ) && ! empty ( $logpass )) {
			$logname = addslashes ( $logname );
			$logpass = md5 ( $logpass );
			
			$check = " SELECT user_id FROM users WHERE (logname='" . $logname . "' AND logpass='" . $logpass . "') ";
			try {
				$result = connectToDb::getInstance ()->query ( $check );
			} catch ( PDOException $e ) {
				echo $e->getMessage ();
			}
			if ($result->rowCount () !== 0) {
				foreach ( $result as $row ) { $user_id = $row ['user_id'];}
				$_SESSION['user_id'] = $user_id;				
				$sess_id = session_id ();
				$replace = "REPLACE INTO manage_session (sess_id,user_id,lastlogin) VALUES ('" . $sess_id . "','" . $user_id . "', NOW() )";
				$result = connectToDb::getInstance ()->query ( $replace );
				header ( "Location: /szakdoga/new/useriface.php" );
			}
		
		}
	}
	
	public function isLogged() {	
		session_set_cookie_params ( 3600 * 24 * 365 ); //egy év
		session_start ();
		if (empty ( $_SESSION ['user_id'] )) {
			$sess_id = session_id ();
			$check = "SELECT user_id FROM manage_session WHERE sess_id = '" . $sess_id . "' ";
			try {
				$result = connectToDb::getInstance ()->query ( $check );
			} catch ( PDOException $e ) {
				echo $e->getMessage ();
			}
			foreach ( $result as $row ) { $user_id = $row ['user_id'];	}
					
			if ( $user_id > 0) {
				$_SESSION['user_id'] = $user_id;
			} else {
				header ( 'Location: /szakdoga/new/index.php' );
				exit ();
			}
		}
	}
	
	public function delSess()
	{
		$sess_id = session_id ();
        $del_sess = "DELETE FROM manage_session WHERE sess_id = '".$sess_id."'";
 		try {
			$result = connectToDb::getInstance ()->query ( $del_sess );
		} catch ( PDOException $e ) {
			echo $e->getMessage ();
		}
		
		
	}

A Login() fgv-t az index.php-ban hívom meg, az isLogged()-et minden védett oldal legelején, a delSess()-t pedig a logut.php-ban.

Ha van benne valami hiba, backdoor vagy bug akkor pls szóljatok.

Köszi mégegyszer rootkit-nek!

szerk: Azért nem raktam a kódot fel valamilyen pastebin oldalra. Mert azt tapasztaltam, hogy mikor olvasok mondjuk egy 2 éves postot akkor az esetek 99%-ban, az ott lévő linkek már halottak lesznek. Így meg legalább megmarad, úgyse zavar senkit.

Hozzászólások

Lehet akkor nem a jó eszközt használtam a probléma megoldására. Tehát a cookie ugye kliens oldalon tárolódik most így néz ki a rendszerem: http://pastebin.com/i0KiaJbD

Akkor a 'login' változót nem sessionbe hanem a cookiba kéne tárolni? De akkor nem lehetséges az, hogy fogja a user és a cookie-ban a login-t átírja 1 re és meg van oldva, vagy nem manipulálható? Akkor mire jó a session? Mind a kettőnek értem a működését, csak még azt nem látom, hogy melyik milyen funkciót lát el. Esetleg tudnál te vagy valaki más példát írni, hogy mégis hogy használjuk egyszerre a kettőt?

szerk:
Most van a cookie-m amiben benne van a session_id. Tehát amit a szerveren tárolok session-öket abból megnézi melyik az enyém és megnézi hogy abban mi van. Ha az én session-öm tartalma login:1 akkor beenged ha nem akkor nem. Ez így tökéletes, nem azzal van a baj hogy a cookie tűnik el mert az megmarad a gépen, hanem hogy a session jár le.

Gondolom dbben tarolod a user adatokat. Keszitesz meg 1 tablat remember_me neven, amiben lerakod a useridjat, az ip cimet, meg valami kulcsot, amit utana leraksz sutiben es kesz. Ha ip es a kulcs egyezik, a usert be lehet leptetni, amugy meg nem, ha megfejeled meg 1 timestamppel, akkor lehet ra siman expiret is rakni, oszt csokolom.
Bar teny, hogy megoldhato session handler izelasssal is.

Ellenőrizd, hogy a session-cookie-k expire-ra nagyon hosszú.
Tárold adatbázisban a session adatokat.

Egyébként igen. Alapból egy cron folyamat takarítja a session-fájlokat mtime alapján.

Azért nem lesz egy idő után nagyon sok szemét, mert én az adott felhasználóhoz tárolnám le a session-t (meg annak azonosítóját is). Azaz nem lehetne több session-adat, mint felhasználó. Így felhasználó munkafolyamata csak 1 gépen "tárolódna" egyszerre és egy gépen csak 1 felhasználó munkafolyamata "tárolódna".
Remélem érthető volt.

Ezt kifejtenéd egy kicsit bővebben mert nem volt teljesen világos. A következő képen néz ki most a users táblám (akik beléphetnek):

id | logname | logpass (md5 el kodolva)

1 phppisti pityu23

Nézzük akkor gyakorlatban. Van a session_start() hívás ami fogja legenerál egy sess_123454532 file-t, ha nem létezik. Ezt berakja a cookie-ba, mármint csak ezt a file nevet. Nálam így néz ki a cookie: PHPSESSID a süti neve és a tartalma sess_123454532. Ezzel párhuzamban fogja és a szerveren a /var/lib/php5 alá létrehozza a sess_12345453 file-t. Ez kezdetben üres, majd mikor bejelentkezek akkor a tartalma login=1 re vált. Mindig amikor egy jelszóval védett oldalt akarok lekérni megnézi a sütiben tárolt sess_id-t, majd benéz a /var/lib/php5 alá, hogy van e ilyen file, ha van megnézi a tartalmát, ha login=1 megnézhetem az oldalt. Kilépéskor ennek a file-nak a tartalmát módosítom login=0 ra.

Kérdés: Hogyan nézzen ez ki MySQL es megoldással? Legyen plusz 2 oszlop a users-be, egy sess_id meg egy login_state? És akkor mindig mikor be akarok lépni, vagy meg akarok nézni egy jelszóval védett oldalt kérjem le a users tábla login_state állapotát, majd mikor kilépek upateeljem 0 ra? A sess_id statikus legyen (pl sess_phppisti) és cookie ba ezt rakjam be? Majd beléptetésnél:

SELECT login_state from uses WHERE sess_id=$_COOKIE[sess_id];

Így gondoltad vagy félre értettem? Ez nekem így elég macerásnak tűnik, szerintem nem így gondoltad és én értettem félre.

szerk: Nem is rossz ötlet, ezt így megoldani... :D Most már tényleg kíváncsi vagyok a te megoldásodra is. Esetleg konkrét példával, táblával. :)

szerk2: Mire jó akkor a $_SESSION? Pár oldalon át hordozni néhány adatot, vagy...?

Nézd meg ezt: http://php.net/manual/en/function.session-set-cookie-params.php
Állítsd a cookie-t jó hosszúra, mondjuk 5 évre.

SEMMIT SEM ELLENŐRIZTEM, SZÓVAL LEHET BENNE HIBA!!! Az SQL-es részek nincsenek mindenhol kifejve PHP-ben.

Ahogy a kolléga már leírta:

CREATE TABLE remember_me
(
sessid CHAR(32) NOT NULL,
userid INTEGER,
lastlogin DATETIME,
PRIMARY KEY (sessid),
KEY (userid)
)

Sikeres login esetében:
$_SESSION['userid'] = [a-beloginolt-júzer-ájdíja];
$sess_uid = (int) $_SESSION['userid'];
"REPLACE INTO remember_me SET sessid = '".mysql_escape_string(session_id())."', userid = $sess_uid, lastlogin=NOW()"

Időnként lehet takarítani a lastlogin alapján, ha nem lépett be már hónapok óta, akkor mehet a levesbe a remember_me táblából.

Ha kilépést nyom:
"DELETE FROM remember_me WHERE sessid = '".mysql_escape_string(session_id())."'"

Ha törölsz egy felhasználót:
"DELETE FROM remember_me WHERE userid = $sess_uid"

csinálsz egy session.inc fájlt, amit minden oldal elején behúzol (kivéve login.php !!!) és beleírod ezt:
<?php
session_set_cookie_params([ide-kell-valami-jó-nagy-expire]);
session_start();
$sess_uid = (int) @$_SESSION['userid'];
if ($sess_uid <= 0)
{
"SELECT userid FROM remember_me WHERE sessid = '".mysql_escape_string(session_id())."'"

if ($row->userid > 0)
{
$sess_uid = (int) $row->userid;
$_SESSION['userid'] = $sess_uid;
}
else
{
header('Location: login.php');
exit;
}
}
?>

Vagy csinálsz egy ajax requestert, ami pl. 10 (vagy legyen akár 15) percenkét csinál egy semmi php aktivitást: a session_start() legyen benne, és annyi.

Ha már "mersz" JS-t használni.

Persze ezt csak akkor, ha mást nem akarsz megcsinálni, mert ez kicsit barkács. :)