When users log in I have created a new Session in Perl CGI, and stored the session ID in a cookie (CGISESSID). This cookie has then been sent the next page via a redirect, as shown below:
my $session = CGI::Session->new("driver:File", undef, {Directory=>"/tmp"});
my $sid = $session->id();
$session->param("username", $username);
$session->expire('+15m');
print redirect ( -cookie => cookie(CGISESSID => $session->id), -uri => 'x.cgi');
On 'x.cgi' (and all other pages of the site) I have:
my $sid = cookie ('CGISESSID') || param('CGISESSID') || undef;
my $session = CGI::Session->load(undef, $sid, {Directory=>'/tmp'});
$session->expire('+15m');
my $username = $session->param("username");
if (!defined ($username)) {
print redirect ("login.cgi");
}
However, after a random amount of time/clicks (well before the 15m mark, anywhere from the 1st click to a click 2-3 minutes later), it redirects to login.cgi.
Debugging has shown me that although it redirects me (and $username is not defined), the code still obtains the correct session ID originally created (the session seems to be still intact), and the cookie remains.
I don't pass CGISESSID as a parameter on any page requests/links (as I assume a cookie saves me from doing this)
Any idea what could be causing $username to be undefined after a random amount of time?
Given that your description is correct and your code works for some time, and after that stops working, I would suggest checking that noone is cleaning up your /tmp directory and, in particular, the session file there.
When you create a session using CGI::Session module with file driver it just creates a text file in the given directory (/tmp in your code). It's a text file with some Perl code, you can cat it and see what's inside:
$ cat cgisess_126b3cd2c4b9ac6eaac0185afbc46d34 && echo
$D = {'_SESSION_ID' => '126b3cd2c4b9ac6eaac0185afbc46d34','_SESSION_ATIME' => 1413493418,'_SESSION_REMOTE_ADDR' => '','_SESSION_CTIME' => 1413493418};;$D
For file driver the filename it uses for the session can be obtained by
my $filename = sprintf $CGI::Session::Driver::file::FileName, $session->id;
Check that it is created and exists for at least 15 minutes. If it disappears, blame some cron job, some other script or your hosting provider.
Related
I'm currently developing a script in Perl as want to recover a specific cookie that Facebook is currently storing in my browser.
The cookie name is Datr and i've tried multiple Perl modules such as HTTP::Cookies, CGI::Cookie and so forth, without success.
What i wanna do is simple and i need to do it via Perl: storing the Datr value (which changes dynamically) into a new variable in my Perl script.
I decided to set a test cookie and try to read its value, but neither the script returns something nor the browser (Mozilla) seems to store my cookie.
Here is the code i used:
#!bin/perl
use CGI;
$query = new CGI;
##setting a new cookie into the browser
$cookie = $query->cookie(-name=>'MY_COOKIE',
-value=>'HelloWorld',
-domain=>'facebook.com',
-expires=>'+4h');
##retrieving cookie value
$theCookie = $query->cookie('MY_COOKIE');
Please help me with this as i'm going crazy!
thanks
First of all i would like to thanks all of you for the prompt reply.
To Dave: thanks for your very good answer on this. Yes i'm aware that Firefox can read the Datr value via GUI, but actually i need to read it with Perl code, because my original script features a specific SSL request which actually needs the Datr value. Facebook has arealdy answered on Datr ''understanding'' (http://www.adweek.com/digital/datr-cookie-belgium/) and seems an interesting side-topic. Getting back to my original request, i'm quite sure that Firefox stores all cookies value locally in some sort of .sqlite db called cookie.sqlite, and i can get there! If a try to read it ''manually'', i can see the Datr string, but that's not going to fix anything as i do not want to update my PL script each and every time i want to perform any sort of SSL request to FB!! that's the point.
My original question could be reformulated as follows: is there any way to query the .sqlite cookie db created by Firefox in order to retreive the Datr cookie and store it in a Perl variable? Please bear in mind that i don't want to set an absolute ''path to file'' in my PL script, as it is supposed to be executed either in Linux or any other OS (Windows, OSx..).
Thanks in advace for any further reply on this subject.
There are two steps to reading a cookie.
Accessing the value of the cookie.
Understanding that value.
Modules like the ones you mention, only deal with the first step. They allow you to write cookie headers into your web application's responses and read cookie headers from requests that come into your web application. But they will only read or write cookies for the domain that your web application is running on. It's very unlikely that your application is running on facebook.com, therefore these modules are going to be useless to you.
However, all is not lost. Firefox will give you access to any cookies that are stored in the browser. I assume you already know that (as you know the name of the cookie you're interested in) but in case you don't - choose "preferences" from the hamburger menu and then "privacy"; that page has a "remove individual cookies" link.
So you can see the contents of the datr cookie. I'm looking at mine right now. It's string of 24 random-looking characters.
And that's the next problem. How do you interpret that string? Only Facebook can answer that. It's possible that it is a hash containing all sorts of interesting data. But it will be almost impossible to prove that. More likely (because this is, I think, best practice) it's just a random string of characters which is a key into some data store that is held somewhere within Facebook's system.
So it's either a well-encrypted secret or a random string. Either way it's useless to you.
Getting the value of a cookie is easy. Understanding that value is (usually) impossible.
Update: So actually, now you've redefined the question completely. It's not about cookies at all. It's about reading data from an SQLite database. And for that you should look at DBI and DBD::SQLite. If you have any more specific questions about how to do this, then please ask a new question.
This is what i've figured out. It works well. I didn't use absolute path (at least, directly) therefore, in theory, it should work also in Windows (having Perl on it) and Mac OS, but i didn't test it. It simply search the .sqlite db into the system and once found it returns the value of Datr stored by Firefox.
#!/bin/perl
use File::Find::Rule;
use DBI;
use strict;
my #files = File::Find::Rule->file()
->name('firefox', 'cookies.sqlite')
->in( '/home' );
my #matches = grep { /firefox/i && /mozilla/i } #files;
print "DB PATH: #matches\n\n";
####### connect to .sqlite database
my $database = #matches[0];
my $dbfile = "$database";
my $dsn = "dbi:SQLite:dbname=$dbfile"; #set path to db
my $user = "";
my $password = "";
my $dbh = DBI->connect($dsn, $user, $password, {
PrintError => 0,
RaiseError => 1,
});
print "Database opened successfully\n\n";
my $stmt = "SELECT VALUE FROM moz_cookies WHERE NAME='datr'";
my $sth = $dbh->prepare( $stmt );
my $rv = $sth->execute() or die $DBI::errstr;
if($rv < 0) {
print $DBI::errstr;
}
my #row=$sth->fetchrow_array(); #fetching data of sth into a new array
print "COOKIE VALUE: #row"; #print data
print "\n\nOperation done successfully\n";
I need to move our perl apps to a new server, but I don't want everyone to have to re-authenticate when the move is made. I'd like to have our "Home" script redirect over to the new server, pre-plant a cookie, with a corresponding session file in /tmp, for each user before I make the DNS change.
Thought it would be simple but it's turning out not to be. That or I'm just missing something right in front of my face.
Here is the code I put in the "Home" script on the current server..
my $has_session = $cgi->param("session") || "";
if ($has_session eq "") {
my $url = "http://111.222.333.444/cgi-bin/SetNewSession.cgi?back=http://" . "$ENV{SERVER_NAME}" ."$ENV{SCRIPT_NAME}";
print "Location: $url\n\n";
}
And here is the code in a script on the new server...
use strict;
use warnings;
use CGI;
use CGI::Session;
use CGI::Carp qw/fatalsToBrowser warningsToBrowser/;
my $cgi = new CGI;
my $userid = $cgi->param("userid");
my $redir_back = $cgi->param("back");
my $session = CGI::Session->load() or die $!;
my $session_userid = $session->param("userid");
if (! defined $session_userid) {
$session->expire("9y");
$session->param("userid",$userid);
$session->flush();
}
my $url = $redir_back . "?session=1";
print $session->header(-location=>$url);
exit;
No cookie. No session file. Nothing.
P.S. Please, no flak for the 9y expiration. Management is "above" having to login. :)
Here is the code I put in the "Home" script on the current server..
That part doesn't seem to transmit userid, it also doesn't use url(qw/ -full 1 -rewrite 1 /) to retrieve the url value for back=
You also might want to Crypt::CBC userid (and even back) using rjindel like Session::Storage::Secure does for cookies (UTSL)
And here is the code in a script on the new server... No cookie. No session file. Nothing.
The way I read your program, it should have at least throw an error because load won't create a session, its called load() its not called new. new will create a session.
I am using CGI.pm to write out cookies. Now during the course of the user using my site, other cookies are added to the "test.com" cookie set, (as shown in the broswer history)
But now I want to log the user out, and "clean" the PC. Since I don't know what scripts the user has used, I can't foresee what cookies would be on the PC.
In short, it there a way to read all the cookies for "test.com" back into a script so I can then print them out again with a 1s duration, (effectively 'deleting' them) ** I know you can read the cookie back in with $xyz=cookie('$name') ... but how can I create the array holding the $name variable so I can loop through it? The script will also run on "test.com", so the cross site policy is not an issue
+++++
brian d foy added a partial answer below. So this how I envisage the code might be strung together.
use CGI::Cookie;
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
$del_cookie.="cookie(-NAME=>'$cookies[$_]',-PATH=>'/',-EXPIRES=>'+1s');";
}
print header(-cookie=>[$del_cookie]);
I wondered how the script would recognise the domain. Appears the script is intelligent enough to only load the cookies for the domain for which the script is being executed on. (Now I've just got to find out why Firefox doesn't delete expired cookies!! Just found some listed that expired 29th - 31st Jan within my test domain, and at first wondered why they didn't appear in my cookie list!)
If you are trying to do this from your CGI script, you'll only have access to the cookies for that domain. You can get that list and reset them by giving them a time in the past.
It sounds like you aren't asking a cookie question at all. You're asking how to make an array. The CGI::Cookies (which comes with CGI.pm) has an example to deal with all the cookies you have access to under that domain:
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
do_something($cookies{$_});
}
This is what I ended up with:
use CGI::Cookies;
%cookies = CGI::Cookie->fetch;
#cookie = keys %cookies;
for($x=0; $x<#cookie; $x++){
my $c = CGI::Cookie->new(-name => $cookie[$x],-value => '-',-expires => '+1s');
print "Set-Cookie: $c\n";
}
print "content-type: text/html\n\n";
Firefox still leaves the cookies intact, (apparently that's a "design issue" and not a bug!!) but they are reset to a void value, and set to expire / become redundant in 1 second. Plus, quite why the "print" statement being sent before the "content-type" header doesn't cause a server error I don't know. OK, so purists will probably find a simpler system, and use "foreach" rather than for/next loop ... but I understand how the latter works!
I want the user to non have to login even if the browser was closed. My cookies are set to expire after a month.
when the user logs in sucessfully
$session = CGI::Session->new (undef, undef, {Directory=>'tmp/'})
or die CGI::Session->errstr;
$session->param('username', $username);
$session->expire('+1M');
$cookie = $cgi->cookie( -name=>$session->name, -value=>$session->id );
print $cgi->header(-cookie=>$cookie );
They are then redirected to another page that they can access as long as they don't close the browser. This is the code in the second page:
my $cookie = $cgi->cookie('CGISESSID');
if ($cookie){
print $cgi->header(-cookie => $cookie);
else{
//ask them to relog in
}
I can see the sessions created in tmp/. How do I load an existing cookie after the browser is closed. How do I know which session to load based on the user/browser?
As long as you set a future expiration date on your cookies, they should persist even after a user restarts their browser (as long as they restart before that date, of course). To load the cookie, do exactly what you're doing:
my $cookie = $cgi->cookie('CGISESSID');
To try to load an existing session using the cookie you can simply pass your CGI object to the new method of CGI::Session:
my $session = new CGI::Session(undef, $cgi, {Directory=>"/tmp"});
This will attempt to initialize an existing session using the cookie passed in with the CGI request; if one doesn't exist, it will create a new session. Note that this assumes the cookie name is CGISESSID. To use another name, run:
CGI::Session->name("MY_SID");
# or
$session->name("MY_SID");
$session = new CGI::Session(undef, $cgi, {Directory=>'/tmp'});
If you haven't already, I would recommend reading through the CGI::Session tutorial.
EDIT: The session was set to expire in one month
$session->expire('+1M');
but the cookie was not. If you don't set an expiration on a cookie, the browser will store it in memory but not on disk; as soon as you close the browser, the cookie disappears. To set the cookie expiration, do something like
$cookie = $cgi->cookie( -name=>$session->name, -value=>$session->id, -expires=>'+1M' );
I'm writing a web application that needs to make use of module CGI::Session ver 4.35. Upon receiving request from the client with an SESSIONID string
$sid = $cgi->cookie("CGISESSID") || $cgi->param("CGISESSID") || undef;
it tries to recreate the session by passing the $sid as an argument
$session = new CGI::Session($sid) or ($logger->error(CGI::Session->errstr) and die);
If there was an session created with that sid, $session->id and $sid are suppose to be the same, but the truth is it's NOT.
This is the statement where I create a completely new session
$session = new CGI::Session("id:md5", undef, {Directory=>$SESSION_DIR})
or ($logger->error(CGI::Session->errstr) and die);
What went wrong here? How am I supposed to use the module CGI::Session correctly?
I'm the maintainer of CGI::Session. I recommend creating the session the same way in all cases, like this:
$session = CGI::Session->new("id:md5", $cgi, {Directory=>$SESSION_DIR});
This follows the recommended syntax in the docs for new(). I also recommend making sure that you call flush() explicitly near the end of the script. The reason for that is explained more here:
http://metacpan.org/pod/CGI::Session#A-Warning-about-Auto-flushing
there really is no need for you to grab the session cookie yourself. If you pass a CGI object instance to CGI::Session it does it for you. So, basically, the above code by jfd can be re-written like this:
my $session = CGI::Session->new( $query );
$self->header_props(-cookie => $session->cookie);
And $query->cookie() and if/else blocks are all redundant, because they already exist in CGI::Session's logic!
So the above code checks for client's cookie named CGI::Session->name (which defaults to CGISESSID). If it doesn't exist, looks for query parameter in the URL or request's body named CGI::Session->name (which also defaults to CGISESSID). If it can get claimed session id it tries to load its data into the session. If the session id cannot be validated (either expired, or forged) it ignores it, and creates a brand new, empty session.
If the session id cannot be found in either cookie nor in URL parameters it creates a new session.
most examples of session management I see out there try to re-invent the session logic inside the code while using CGI::Session. I'm just here to tell you that all that code is completely redundant!!!
Enjoy using CGI::Session!
So, I'm not clear why you are creating the session twice? You want to first try and get the sid, and then create the session with it, whether it exists or not. If it doesn't exist, set the cookie. It's been awhile, but I pulled this from an old piece of code...
my $sid = $query->cookie('CGISESSID') || undef;
# grab the session obj, if one already exists otherwise create one
my $session = new CGI::Session( "id:md5", $sid, { Directory => $SESSION_DIR } );
# If there is no user cookie, or it's non existent, we give them a new one
if ( !$sid or $sid ne $session->id ) {
my $cookie = $query->cookie(
-name => 'CGISESSID',
-value => $session->id,
-expires => EXPIRE_TIME
);
$self->header_props( -cookie => $cookie );
}