Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
mongoose
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
esp
mongoose
Commits
b33755c9
Commit
b33755c9
authored
May 04, 2010
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
sessions implemented (simplistically). security expert review would be nice to have.
parent
4b0424d0
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
108 additions
and
33 deletions
+108
-33
chat.c
examples/chat.c
+108
-33
No files found.
examples/chat.c
View file @
b33755c9
/*
/*
* This file is part of the Mongoose project, http://code.google.com/p/mongoose
* This file is part of the Mongoose project, http://code.google.com/p/mongoose
* It implements an online chat server. For more details,
* It implements an online chat server. For more details,
* see the documentation on
project pag
e.
* see the documentation on
the project web sit
e.
* To
start the server
,
* To
test the application
,
*
a)
type "make" in the directory where this file lives
*
1.
type "make" in the directory where this file lives
*
b)
point your browser to http://127.0.0.1:8081
*
2.
point your browser to http://127.0.0.1:8081
*
*
* NOTE(lsm): this file follows Google style, not BSD style as the rest of
* NOTE(lsm): this file follows Google style, not BSD style as the rest of
* Mongoose code.
* Mongoose code.
*
* $Id: chat.c 513 2010-05-03 11:06:08Z valenok $
*/
*/
#include <stdio.h>
#include <stdio.h>
...
@@ -25,13 +23,13 @@
...
@@ -25,13 +23,13 @@
#define MAX_MESSAGE_LEN 100
#define MAX_MESSAGE_LEN 100
#define MAX_MESSAGES 5
#define MAX_MESSAGES 5
#define MAX_SESSIONS 2
#define MAX_SESSIONS 2
#define SESSION_TTL 120
static
const
char
*
login_url
=
"/login.html"
;
static
const
char
*
login_url
=
"/login.html"
;
static
const
char
*
authorize_url
=
"/authorize"
;
static
const
char
*
authorize_url
=
"/authorize"
;
static
const
char
*
web_root
=
"./html"
;
static
const
char
*
web_root
=
"./html"
;
static
const
char
*
http_ports
=
"8081,8082s"
;
static
const
char
*
http_ports
=
"8081,8082s"
;
static
const
char
*
ssl_certificate
=
"ssl_cert.pem"
;
static
const
char
*
ssl_certificate
=
"ssl_cert.pem"
;
static
const
char
*
ajax_reply_start
=
static
const
char
*
ajax_reply_start
=
"HTTP/1.1 200 OK
\r\n
"
"HTTP/1.1 200 OK
\r\n
"
"Cache: no-cache
\r\n
"
"Cache: no-cache
\r\n
"
...
@@ -41,17 +39,18 @@ static const char *ajax_reply_start =
...
@@ -41,17 +39,18 @@ static const char *ajax_reply_start =
// Describes single message sent to a chat. If user is empty (0 length),
// Describes single message sent to a chat. If user is empty (0 length),
// the message is then originated from the server itself.
// the message is then originated from the server itself.
struct
message
{
struct
message
{
long
id
;
long
id
;
// Message ID
char
user
[
MAX_USER_LEN
];
char
user
[
MAX_USER_LEN
];
// User that have sent the message
char
text
[
MAX_MESSAGE_LEN
];
char
text
[
MAX_MESSAGE_LEN
];
// Message text
time_t
utc_timestamp
;
time_t
timestamp
;
// Message timestamp, UTC
};
};
// Describes web session.
// Describes web session.
struct
session
{
struct
session
{
char
session_id
[
33
];
char
session_id
[
33
];
// Session ID, must be unique
char
authenticated_user
[
MAX_USER_LEN
];
char
random
[
20
];
// Random data used for extra user validation
time_t
expiration_timestamp_utc
;
char
user
[
MAX_USER_LEN
];
// Authenticated user
time_t
expire
;
// Expiration timestamp, UTC
};
};
static
struct
message
messages
[
MAX_MESSAGES
];
// Ringbuffer for messages
static
struct
message
messages
[
MAX_MESSAGES
];
// Ringbuffer for messages
...
@@ -80,7 +79,7 @@ static char *messages_to_json(long last_id) {
...
@@ -80,7 +79,7 @@ static char *messages_to_json(long last_id) {
}
}
for
(;
last_id
<
last_message_id
;
last_id
++
)
{
for
(;
last_id
<
last_message_id
;
last_id
++
)
{
message
=
&
messages
[
last_id
%
max_msgs
];
message
=
&
messages
[
last_id
%
max_msgs
];
if
(
message
->
utc_
timestamp
==
0
)
{
if
(
message
->
timestamp
==
0
)
{
break
;
break
;
}
}
// buf is allocated on stack and hopefully is large enough to hold all
// buf is allocated on stack and hopefully is large enough to hold all
...
@@ -88,7 +87,7 @@ static char *messages_to_json(long last_id) {
...
@@ -88,7 +87,7 @@ static char *messages_to_json(long last_id) {
// messages are large. in this case asserts will trigger).
// messages are large. in this case asserts will trigger).
len
+=
snprintf
(
buf
+
len
,
sizeof
(
buf
)
-
len
,
len
+=
snprintf
(
buf
+
len
,
sizeof
(
buf
)
-
len
,
"{user: '%s', text: '%s', timestamp: %lu, id: %lu},"
,
"{user: '%s', text: '%s', timestamp: %lu, id: %lu},"
,
message
->
user
,
message
->
text
,
message
->
utc_
timestamp
,
message
->
id
);
message
->
user
,
message
->
text
,
message
->
timestamp
,
message
->
id
);
assert
(
len
>
0
);
assert
(
len
>
0
);
assert
((
size_t
)
len
<
sizeof
(
buf
));
assert
((
size_t
)
len
<
sizeof
(
buf
));
}
}
...
@@ -153,7 +152,7 @@ static void ajax_send_message(struct mg_connection *conn,
...
@@ -153,7 +152,7 @@ static void ajax_send_message(struct mg_connection *conn,
// TODO(lsm): JSON-encode all text strings
// TODO(lsm): JSON-encode all text strings
strncpy
(
message
->
text
,
text
,
sizeof
(
text
));
strncpy
(
message
->
text
,
text
,
sizeof
(
text
));
strncpy
(
message
->
user
,
"joe"
,
sizeof
(
message
->
user
));
strncpy
(
message
->
user
,
"joe"
,
sizeof
(
message
->
user
));
message
->
utc_
timestamp
=
time
(
0
);
message
->
timestamp
=
time
(
0
);
message
->
id
=
last_message_id
++
;
message
->
id
=
last_message_id
++
;
pthread_rwlock_unlock
(
&
rwlock
);
pthread_rwlock_unlock
(
&
rwlock
);
}
}
...
@@ -169,43 +168,90 @@ static void ajax_send_message(struct mg_connection *conn,
...
@@ -169,43 +168,90 @@ static void ajax_send_message(struct mg_connection *conn,
// we came from, so that after the authorization we could redirect back.
// we came from, so that after the authorization we could redirect back.
static
void
redirect_to_login
(
struct
mg_connection
*
conn
,
static
void
redirect_to_login
(
struct
mg_connection
*
conn
,
const
struct
mg_request_info
*
request_info
)
{
const
struct
mg_request_info
*
request_info
)
{
const
char
*
host
;
host
=
mg_get_header
(
conn
,
"Host"
);
mg_printf
(
conn
,
"HTTP/1.1 302 Found
\r\n
"
mg_printf
(
conn
,
"HTTP/1.1 302 Found
\r\n
"
"Set-Cookie: original_url=%s
\r\n
"
"Set-Cookie: original_url=%s://%s%s
\r\n
"
"Location: %s
\r\n\r\n
"
,
request_info
->
uri
,
login_url
);
"Location: %s
\r\n\r\n
"
,
request_info
->
is_ssl
?
"https"
:
"http"
,
host
?
host
:
"127.0.0.1"
,
request_info
->
uri
,
login_url
);
}
}
// Return 1 if username/password is allowed, 0 otherwise.
// Return 1 if username/password is allowed, 0 otherwise.
static
int
check_password
(
const
char
*
user
,
const
char
*
password
)
{
static
int
check_password
(
const
char
*
user
,
const
char
*
password
)
{
// In production environment we should ask an authentication system
// In production environment we should ask an authentication system
// to authenticate the user.
// to authenticate the user.
// Here however we do trivial check: if username == password, allow.
// Here however we do trivial check that user and password are not empty
return
(
strcmp
(
user
,
password
)
==
0
?
1
:
0
);
return
(
user
[
0
]
&&
password
[
0
]);
}
// Allocate new session object
static
struct
session
*
new_session
(
void
)
{
int
i
;
time_t
now
=
time
(
NULL
);
pthread_rwlock_wrlock
(
&
rwlock
);
for
(
i
=
0
;
i
<
MAX_SESSIONS
;
i
++
)
{
if
(
sessions
[
i
].
expire
==
0
||
sessions
[
i
].
expire
<
now
)
{
sessions
[
i
].
expire
=
time
(
0
)
+
SESSION_TTL
;
break
;
}
}
pthread_rwlock_unlock
(
&
rwlock
);
return
i
==
MAX_SESSIONS
?
NULL
:
&
sessions
[
i
];
}
// Generate session ID. buf must be 33 bytes in size.
static
void
generate_session_id
(
char
*
buf
,
const
char
*
random
,
const
char
*
user
,
const
struct
mg_request_info
*
request_info
)
{
char
remote_ip
[
20
],
remote_port
[
20
];
snprintf
(
remote_ip
,
sizeof
(
remote_ip
),
"%ld"
,
request_info
->
remote_ip
);
snprintf
(
remote_port
,
sizeof
(
remote_port
),
"%d"
,
request_info
->
remote_port
);
mg_md5
(
buf
,
random
,
user
,
remote_port
,
remote_ip
,
NULL
);
}
}
// A handler for the /authorize endpoint.
// A handler for the /authorize endpoint.
// Login page form sends user name and password to this endpoint.
// Login page form sends user name and password to this endpoint.
static
void
authorize
(
struct
mg_connection
*
conn
,
static
void
authorize
(
struct
mg_connection
*
conn
,
const
struct
mg_request_info
*
request_info
)
{
const
struct
mg_request_info
*
request_info
)
{
char
user
[
20
],
password
[
20
],
original_url
[
200
];
char
user
[
MAX_USER_LEN
],
password
[
MAX_USER_LEN
],
original_url
[
200
];
struct
session
*
session
;
// Fetch user name and password.
// Fetch user name and password.
mg_get_qsvar
(
request_info
,
"user"
,
user
,
sizeof
(
user
));
mg_get_qsvar
(
request_info
,
"user"
,
user
,
sizeof
(
user
));
mg_get_qsvar
(
request_info
,
"password"
,
password
,
sizeof
(
password
));
mg_get_qsvar
(
request_info
,
"password"
,
password
,
sizeof
(
password
));
mg_get_cookie
(
conn
,
"original_url"
,
original_url
,
sizeof
(
original_url
));
mg_get_cookie
(
conn
,
"original_url"
,
original_url
,
sizeof
(
original_url
));
if
(
user
[
0
]
&&
password
[
0
]
&&
check_password
(
user
,
password
)
)
{
if
(
check_password
(
user
,
password
)
&&
(
session
=
new_session
())
!=
NULL
)
{
// Authentication success:
// Authentication success:
// 1. create new session
// 1. create new session
// 2. set session ID token in the cookie
// 2. set session ID token in the cookie
// 3. remove original_url from the cookie - not needed anymore
// 3. remove original_url from the cookie - not needed anymore
// 4. redirect client back to the original URL
// 4. redirect client back to the original URL
// TODO(lsm): implement sessions.
//
// The most secure way is to stay HTTPS all the time. However, just to
// show the technique, we redirect to HTTP after the successful
// authentication. The danger of doing this is that session cookie can
// be stolen and an attacker may impersonate the user.
// Secure application must use HTTPS all the time.
strlcpy
(
session
->
user
,
user
,
sizeof
(
session
->
user
));
snprintf
(
session
->
random
,
sizeof
(
session
->
random
),
"%d"
,
rand
());
generate_session_id
(
session
->
session_id
,
session
->
random
,
session
->
user
,
request_info
);
printf
(
"New session, user: %s, id: %s, redirecting to %s
\n
"
,
session
->
user
,
session
->
session_id
,
original_url
);
mg_printf
(
conn
,
"HTTP/1.1 302 Found
\r\n
"
mg_printf
(
conn
,
"HTTP/1.1 302 Found
\r\n
"
"Set-Cookie: sid=1234; max-age=2h; http-only
\r\n
"
// Set session ID
"Set-Cookie: session=%s; max-age=3600; http-only
\r\n
"
// Session ID
"Set-Cookie: original_url=/; max_age=0
\r\n
"
// Delete original_url
"Set-Cookie: user=%s
\r\n
"
// Set user, needed by Javascript code
"Location: %s
\r\n\r\n
"
,
original_url
[
0
]
==
'\0'
?
"/"
:
original_url
);
"Set-Cookie: original_url=/; max-age=0
\r\n
"
// Delete original_url
"Location: %s
\r\n\r\n
"
,
session
->
session_id
,
session
->
user
,
original_url
[
0
]
==
'\0'
?
"/"
:
original_url
);
}
else
{
}
else
{
// Authentication failure, redirect to login
again
.
// Authentication failure, redirect to login.
redirect_to_login
(
conn
,
request_info
);
redirect_to_login
(
conn
,
request_info
);
}
}
}
}
...
@@ -213,8 +259,33 @@ static void authorize(struct mg_connection *conn,
...
@@ -213,8 +259,33 @@ static void authorize(struct mg_connection *conn,
// Return 1 if request is authorized, 0 otherwise.
// Return 1 if request is authorized, 0 otherwise.
static
int
is_authorized
(
const
struct
mg_connection
*
conn
,
static
int
is_authorized
(
const
struct
mg_connection
*
conn
,
const
struct
mg_request_info
*
request_info
)
{
const
struct
mg_request_info
*
request_info
)
{
// TODO(lsm): implement this part: fetch session ID from the cookie.
char
valid_id
[
33
],
received_id
[
33
];
return
0
;
int
i
,
authorized
=
0
;
mg_get_cookie
(
conn
,
"session"
,
received_id
,
sizeof
(
received_id
));
if
(
received_id
[
0
]
!=
'\0'
)
{
pthread_rwlock_rdlock
(
&
rwlock
);
for
(
i
=
0
;
i
<
MAX_SESSIONS
;
i
++
)
{
if
(
sessions
[
i
].
expire
!=
0
&&
sessions
[
i
].
expire
>
time
(
NULL
)
&&
strcmp
(
sessions
[
i
].
session_id
,
received_id
)
==
0
)
{
break
;
}
}
if
(
i
<
MAX_SESSIONS
)
{
generate_session_id
(
valid_id
,
sessions
[
i
].
random
,
sessions
[
i
].
user
,
request_info
);
if
(
strcmp
(
valid_id
,
received_id
)
==
0
)
{
sessions
[
i
].
expire
=
time
(
0
)
+
SESSION_TTL
;
authorized
=
1
;
}
}
pthread_rwlock_unlock
(
&
rwlock
);
}
printf
(
"session: %s, uri: %s, authorized: %s, cookie: %s
\n
"
,
received_id
,
request_info
->
uri
,
authorized
?
"yes"
:
"no"
,
mg_get_header
(
conn
,
"Cookie"
));
return
authorized
;
}
}
// Return 1 if authorization is required for requested URL, 0 otherwise.
// Return 1 if authorization is required for requested URL, 0 otherwise.
...
@@ -246,18 +317,22 @@ static enum mg_error_t process_request(struct mg_connection *conn,
...
@@ -246,18 +317,22 @@ static enum mg_error_t process_request(struct mg_connection *conn,
return
processed
;
return
processed
;
}
}
int
main
(
int
argc
,
char
*
argv
[]
)
{
int
main
(
void
)
{
struct
mg_context
*
ctx
;
struct
mg_context
*
ctx
;
ctx
=
mg_start
();
// Initialize random number generator. It will be used later on for
// the session identifier creation.
srand
((
unsigned
)
time
(
0
));
// Start and setup Mongoose
ctx
=
mg_start
();
mg_set_option
(
ctx
,
"root"
,
web_root
);
mg_set_option
(
ctx
,
"root"
,
web_root
);
mg_set_option
(
ctx
,
"ssl_cert"
,
ssl_certificate
);
// Must be set before ports
mg_set_option
(
ctx
,
"ssl_cert"
,
ssl_certificate
);
// Must be set before ports
mg_set_option
(
ctx
,
"ports"
,
http_ports
);
mg_set_option
(
ctx
,
"ports"
,
http_ports
);
mg_set_option
(
ctx
,
"dir_list"
,
"no"
);
// Disable directory listing
mg_set_option
(
ctx
,
"dir_list"
,
"no"
);
// Disable directory listing
mg_set_callback
(
ctx
,
MG_EVENT_NEW_REQUEST
,
&
process_request
);
mg_set_callback
(
ctx
,
MG_EVENT_NEW_REQUEST
,
&
process_request
);
// Wait until enter is pressed, then exit
printf
(
"Chat server started on ports %s, press enter to quit.
\n
"
,
http_ports
);
printf
(
"Chat server started on ports %s, press enter to quit.
\n
"
,
http_ports
);
getchar
();
getchar
();
mg_stop
(
ctx
);
mg_stop
(
ctx
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment