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
d3577b78
Commit
d3577b78
authored
Oct 01, 2013
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved auth code to separate file
parent
199e0a33
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1865 additions
and
1866 deletions
+1865
-1866
Makefile
build/Makefile
+1
-1
auth.c
build/src/auth.c
+301
-0
crypto.c
build/src/crypto.c
+196
-0
internal.h
build/src/internal.h
+2
-0
mongoose.c
build/src/mongoose.c
+0
-499
mongoose.c
mongoose.c
+1365
-1366
No files found.
build/Makefile
View file @
d3577b78
...
...
@@ -27,7 +27,7 @@ VERSION = $(shell perl -lne \
# The order in which files are listed is important
SOURCES
=
src/internal.h src/string.c src/parse_date.c src/options.c
\
src/mongoose.c
src/
crypto.c src/auth.c src/
mongoose.c
TINY_SOURCES
=
../mongoose.c main.c
LUA_SOURCES
=
$(TINY_SOURCES)
sqlite3.c lsqlite3.c lua_5.2.1.c
...
...
build/src/auth.c
0 → 100644
View file @
d3577b78
#include "internal.h"
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static
void
bin2str
(
char
*
to
,
const
unsigned
char
*
p
,
size_t
len
)
{
static
const
char
*
hex
=
"0123456789abcdef"
;
for
(;
len
--
;
p
++
)
{
*
to
++
=
hex
[
p
[
0
]
>>
4
];
*
to
++
=
hex
[
p
[
0
]
&
0x0f
];
}
*
to
=
'\0'
;
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char
*
mg_md5
(
char
buf
[
33
],
...)
{
unsigned
char
hash
[
16
];
const
char
*
p
;
va_list
ap
;
MD5_CTX
ctx
;
MD5Init
(
&
ctx
);
va_start
(
ap
,
buf
);
while
((
p
=
va_arg
(
ap
,
const
char
*
))
!=
NULL
)
{
MD5Update
(
&
ctx
,
(
const
unsigned
char
*
)
p
,
(
unsigned
)
strlen
(
p
));
}
va_end
(
ap
);
MD5Final
(
hash
,
&
ctx
);
bin2str
(
buf
,
hash
,
sizeof
(
hash
));
return
buf
;
}
// Check the user's password, return 1 if OK
static
int
check_password
(
const
char
*
method
,
const
char
*
ha1
,
const
char
*
uri
,
const
char
*
nonce
,
const
char
*
nc
,
const
char
*
cnonce
,
const
char
*
qop
,
const
char
*
response
)
{
char
ha2
[
32
+
1
],
expected_response
[
32
+
1
];
// Some of the parameters may be NULL
if
(
method
==
NULL
||
nonce
==
NULL
||
nc
==
NULL
||
cnonce
==
NULL
||
qop
==
NULL
||
response
==
NULL
)
{
return
0
;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if
(
// strcmp(dig->uri, c->ouri) != 0 ||
strlen
(
response
)
!=
32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
)
{
return
0
;
}
mg_md5
(
ha2
,
method
,
":"
,
uri
,
NULL
);
mg_md5
(
expected_response
,
ha1
,
":"
,
nonce
,
":"
,
nc
,
":"
,
cnonce
,
":"
,
qop
,
":"
,
ha2
,
NULL
);
return
mg_strcasecmp
(
response
,
expected_response
)
==
0
;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static
FILE
*
open_auth_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
name
[
PATH_MAX
];
const
char
*
p
,
*
e
,
*
gpass
=
conn
->
ctx
->
config
[
GLOBAL_PASSWORDS_FILE
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
FILE
*
fp
=
NULL
;
if
(
gpass
!=
NULL
)
{
// Use global passwords file
fp
=
mg_fopen
(
gpass
,
"r"
);
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
}
else
if
(
mg_stat
(
path
,
&
file
)
&&
file
.
is_directory
)
{
mg_snprintf
(
name
,
sizeof
(
name
),
"%s%c%s"
,
path
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
else
{
// Try to find .htpasswd in requested directory.
for
(
p
=
path
,
e
=
p
+
strlen
(
p
)
-
1
;
e
>
p
;
e
--
)
if
(
e
[
0
]
==
'/'
)
break
;
mg_snprintf
(
name
,
sizeof
(
name
),
"%.*s%c%s"
,
(
int
)
(
e
-
p
),
p
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
return
fp
;
}
// Parsed Authorization header
struct
ah
{
char
*
user
,
*
uri
,
*
cnonce
,
*
response
,
*
qop
,
*
nc
,
*
nonce
;
};
// Return 1 on success. Always initializes the ah structure.
static
int
parse_auth_header
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_size
,
struct
ah
*
ah
)
{
char
*
name
,
*
value
,
*
s
;
const
char
*
auth_header
;
(
void
)
memset
(
ah
,
0
,
sizeof
(
*
ah
));
if
((
auth_header
=
mg_get_header
(
conn
,
"Authorization"
))
==
NULL
||
mg_strncasecmp
(
auth_header
,
"Digest "
,
7
)
!=
0
)
{
return
0
;
}
// Make modifiable copy of the auth header
(
void
)
mg_strlcpy
(
buf
,
auth_header
+
7
,
buf_size
);
s
=
buf
;
// Parse authorization header
for
(;;)
{
// Gobble initial spaces
while
(
isspace
(
*
(
unsigned
char
*
)
s
))
{
s
++
;
}
name
=
skip_quoted
(
&
s
,
"="
,
" "
,
0
);
// Value is either quote-delimited, or ends at first comma or space.
if
(
s
[
0
]
==
'\"'
)
{
s
++
;
value
=
skip_quoted
(
&
s
,
"
\"
"
,
" "
,
'\\'
);
if
(
s
[
0
]
==
','
)
{
s
++
;
}
}
else
{
value
=
skip_quoted
(
&
s
,
", "
,
" "
,
0
);
// IE uses commas, FF uses spaces
}
if
(
*
name
==
'\0'
)
{
break
;
}
if
(
!
strcmp
(
name
,
"username"
))
{
ah
->
user
=
value
;
}
else
if
(
!
strcmp
(
name
,
"cnonce"
))
{
ah
->
cnonce
=
value
;
}
else
if
(
!
strcmp
(
name
,
"response"
))
{
ah
->
response
=
value
;
}
else
if
(
!
strcmp
(
name
,
"uri"
))
{
ah
->
uri
=
value
;
}
else
if
(
!
strcmp
(
name
,
"qop"
))
{
ah
->
qop
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nc"
))
{
ah
->
nc
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nonce"
))
{
ah
->
nonce
=
value
;
}
}
// CGI needs it as REMOTE_USER
if
(
ah
->
user
!=
NULL
)
{
conn
->
request_info
.
remote_user
=
mg_strdup
(
ah
->
user
);
}
else
{
return
0
;
}
return
1
;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static
int
authorize
(
struct
mg_connection
*
conn
,
FILE
*
fp
)
{
struct
ah
ah
;
char
line
[
256
],
f_user
[
256
],
ha1
[
256
],
f_domain
[
256
],
buf
[
MG_BUF_LEN
];
if
(
!
parse_auth_header
(
conn
,
buf
,
sizeof
(
buf
),
&
ah
))
{
return
0
;
}
// Loop over passwords file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%s"
,
f_user
,
f_domain
,
ha1
)
!=
3
)
{
continue
;
}
if
(
!
strcmp
(
ah
.
user
,
f_user
)
&&
!
strcmp
(
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
f_domain
))
return
check_password
(
conn
->
request_info
.
request_method
,
ha1
,
ah
.
uri
,
ah
.
nonce
,
ah
.
nc
,
ah
.
cnonce
,
ah
.
qop
,
ah
.
response
);
}
return
0
;
}
// Return 1 if request is authorised, 0 otherwise.
static
int
check_authorization
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
fname
[
PATH_MAX
];
struct
vec
uri_vec
,
filename_vec
;
const
char
*
list
;
FILE
*
fp
=
NULL
;
int
authorized
=
1
;
list
=
conn
->
ctx
->
config
[
PROTECT_URI
];
while
((
list
=
next_option
(
list
,
&
uri_vec
,
&
filename_vec
))
!=
NULL
)
{
if
(
!
memcmp
(
conn
->
request_info
.
uri
,
uri_vec
.
ptr
,
uri_vec
.
len
))
{
mg_snprintf
(
fname
,
sizeof
(
fname
),
"%.*s"
,
(
int
)
filename_vec
.
len
,
filename_vec
.
ptr
);
fp
=
mg_fopen
(
fname
,
"r"
);
break
;
}
}
if
(
fp
==
NULL
)
{
fp
=
open_auth_file
(
conn
,
path
);
}
if
(
fp
!=
NULL
)
{
authorized
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
authorized
;
}
static
void
send_authorization_request
(
struct
mg_connection
*
conn
)
{
conn
->
status_code
=
401
;
mg_printf
(
conn
,
"HTTP/1.1 401 Unauthorized
\r\n
"
"Content-Length: 0
\r\n
"
"WWW-Authenticate: Digest qop=
\"
auth
\"
, "
"realm=
\"
%s
\"
, nonce=
\"
%lu
\"\r\n\r\n
"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
(
unsigned
long
)
time
(
NULL
));
}
static
int
is_authorized_for_put
(
struct
mg_connection
*
conn
)
{
const
char
*
passfile
=
conn
->
ctx
->
config
[
PUT_DELETE_PASSWORDS_FILE
];
FILE
*
fp
;
int
ret
=
0
;
if
(
passfile
!=
NULL
&&
(
fp
=
mg_fopen
(
passfile
,
"r"
))
!=
NULL
)
{
ret
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
ret
;
}
int
mg_modify_passwords_file
(
const
char
*
fname
,
const
char
*
domain
,
const
char
*
user
,
const
char
*
pass
)
{
int
found
;
char
line
[
512
],
u
[
512
],
d
[
512
],
ha1
[
33
],
tmp
[
PATH_MAX
];
FILE
*
fp
,
*
fp2
;
found
=
0
;
fp
=
fp2
=
NULL
;
// Regard empty password as no password - remove user record.
if
(
pass
!=
NULL
&&
pass
[
0
]
==
'\0'
)
{
pass
=
NULL
;
}
(
void
)
snprintf
(
tmp
,
sizeof
(
tmp
),
"%s.tmp"
,
fname
);
// Create the file if does not exist
if
((
fp
=
fopen
(
fname
,
"a+"
))
!=
NULL
)
{
fclose
(
fp
);
}
// Open the given file and temporary file
if
((
fp
=
fopen
(
fname
,
"r"
))
==
NULL
)
{
return
0
;
}
else
if
((
fp2
=
fopen
(
tmp
,
"w+"
))
==
NULL
)
{
fclose
(
fp
);
return
0
;
}
// Copy the stuff to temporary file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%*s"
,
u
,
d
)
!=
2
)
{
continue
;
}
if
(
!
strcmp
(
u
,
user
)
&&
!
strcmp
(
d
,
domain
))
{
found
++
;
if
(
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
}
else
{
fprintf
(
fp2
,
"%s"
,
line
);
}
}
// If new user, just add it
if
(
!
found
&&
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
// Close files
fclose
(
fp
);
fclose
(
fp2
);
// Put the temp file in place of real file
remove
(
fname
);
rename
(
tmp
,
fname
);
return
1
;
}
build/src/crypto.c
0 → 100644
View file @
d3577b78
static
int
is_big_endian
(
void
)
{
static
const
int
n
=
1
;
return
((
char
*
)
&
n
)[
0
]
==
0
;
}
#ifndef HAVE_MD5
typedef
struct
MD5Context
{
uint32_t
buf
[
4
];
uint32_t
bits
[
2
];
unsigned
char
in
[
64
];
}
MD5_CTX
;
static
void
byteReverse
(
unsigned
char
*
buf
,
unsigned
longs
)
{
uint32_t
t
;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if
(
is_big_endian
())
{
do
{
t
=
(
uint32_t
)
((
unsigned
)
buf
[
3
]
<<
8
|
buf
[
2
])
<<
16
|
((
unsigned
)
buf
[
1
]
<<
8
|
buf
[
0
]);
*
(
uint32_t
*
)
buf
=
t
;
buf
+=
4
;
}
while
(
--
longs
);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static
void
MD5Init
(
MD5_CTX
*
ctx
)
{
ctx
->
buf
[
0
]
=
0x67452301
;
ctx
->
buf
[
1
]
=
0xefcdab89
;
ctx
->
buf
[
2
]
=
0x98badcfe
;
ctx
->
buf
[
3
]
=
0x10325476
;
ctx
->
bits
[
0
]
=
0
;
ctx
->
bits
[
1
]
=
0
;
}
static
void
MD5Transform
(
uint32_t
buf
[
4
],
uint32_t
const
in
[
16
])
{
register
uint32_t
a
,
b
,
c
,
d
;
a
=
buf
[
0
];
b
=
buf
[
1
];
c
=
buf
[
2
];
d
=
buf
[
3
];
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xd76aa478
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
1
]
+
0xe8c7b756
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x242070db
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
3
]
+
0xc1bdceee
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf57c0faf
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
5
]
+
0x4787c62a
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa8304613
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
7
]
+
0xfd469501
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x698098d8
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
9
]
+
0x8b44f7af
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffff5bb1
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
11
]
+
0x895cd7be
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x6b901122
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
13
]
+
0xfd987193
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xa679438e
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
15
]
+
0x49b40821
,
22
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xf61e2562
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
6
]
+
0xc040b340
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x265e5a51
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
0
]
+
0xe9b6c7aa
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xd62f105d
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
10
]
+
0x02441453
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0xd8a1e681
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
4
]
+
0xe7d3fbc8
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0x21e1cde6
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
14
]
+
0xc33707d6
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xf4d50d87
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
8
]
+
0x455a14ed
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0xa9e3e905
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
2
]
+
0xfcefa3f8
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0x676f02d9
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
12
]
+
0x8d2a4c8a
,
20
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xfffa3942
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
8
]
+
0x8771f681
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x6d9d6122
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
14
]
+
0xfde5380c
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xa4beea44
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
4
]
+
0x4bdecfa9
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0xf6bb4b60
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
10
]
+
0xbebfbc70
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0x289b7ec6
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
0
]
+
0xeaa127fa
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xd4ef3085
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
6
]
+
0x04881d05
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0xd9d4d039
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
12
]
+
0xe6db99e5
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0x1fa27cf8
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
2
]
+
0xc4ac5665
,
23
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xf4292244
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
7
]
+
0x432aff97
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xab9423a7
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
5
]
+
0xfc93a039
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x655b59c3
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
3
]
+
0x8f0ccc92
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffeff47d
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
1
]
+
0x85845dd1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x6fa87e4f
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
15
]
+
0xfe2ce6e0
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa3014314
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
13
]
+
0x4e0811a1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf7537e82
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
11
]
+
0xbd3af235
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x2ad7d2bb
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
9
]
+
0xeb86d391
,
21
);
buf
[
0
]
+=
a
;
buf
[
1
]
+=
b
;
buf
[
2
]
+=
c
;
buf
[
3
]
+=
d
;
}
static
void
MD5Update
(
MD5_CTX
*
ctx
,
unsigned
char
const
*
buf
,
unsigned
len
)
{
uint32_t
t
;
t
=
ctx
->
bits
[
0
];
if
((
ctx
->
bits
[
0
]
=
t
+
((
uint32_t
)
len
<<
3
))
<
t
)
ctx
->
bits
[
1
]
++
;
ctx
->
bits
[
1
]
+=
len
>>
29
;
t
=
(
t
>>
3
)
&
0x3f
;
if
(
t
)
{
unsigned
char
*
p
=
(
unsigned
char
*
)
ctx
->
in
+
t
;
t
=
64
-
t
;
if
(
len
<
t
)
{
memcpy
(
p
,
buf
,
len
);
return
;
}
memcpy
(
p
,
buf
,
t
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
t
;
len
-=
t
;
}
while
(
len
>=
64
)
{
memcpy
(
ctx
->
in
,
buf
,
64
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
64
;
len
-=
64
;
}
memcpy
(
ctx
->
in
,
buf
,
len
);
}
static
void
MD5Final
(
unsigned
char
digest
[
16
],
MD5_CTX
*
ctx
)
{
unsigned
count
;
unsigned
char
*
p
;
uint32_t
*
a
;
count
=
(
ctx
->
bits
[
0
]
>>
3
)
&
0x3F
;
p
=
ctx
->
in
+
count
;
*
p
++
=
0x80
;
count
=
64
-
1
-
count
;
if
(
count
<
8
)
{
memset
(
p
,
0
,
count
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
memset
(
ctx
->
in
,
0
,
56
);
}
else
{
memset
(
p
,
0
,
count
-
8
);
}
byteReverse
(
ctx
->
in
,
14
);
a
=
(
uint32_t
*
)
ctx
->
in
;
a
[
14
]
=
ctx
->
bits
[
0
];
a
[
15
]
=
ctx
->
bits
[
1
];
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
byteReverse
((
unsigned
char
*
)
ctx
->
buf
,
4
);
memcpy
(
digest
,
ctx
->
buf
,
16
);
memset
((
char
*
)
ctx
,
0
,
sizeof
(
*
ctx
));
}
#endif // !HAVE_MD5
build/src/internal.h
View file @
d3577b78
...
...
@@ -458,3 +458,5 @@ struct de {
struct
file
file
;
};
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
);
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
);
build/src/mongoose.c
View file @
d3577b78
...
...
@@ -1205,505 +1205,6 @@ static void get_mime_type(struct mg_context *ctx, const char *path,
vec
->
len
=
strlen
(
vec
->
ptr
);
}
static
int
is_big_endian
(
void
)
{
static
const
int
n
=
1
;
return
((
char
*
)
&
n
)[
0
]
==
0
;
}
#ifndef HAVE_MD5
typedef
struct
MD5Context
{
uint32_t
buf
[
4
];
uint32_t
bits
[
2
];
unsigned
char
in
[
64
];
}
MD5_CTX
;
static
void
byteReverse
(
unsigned
char
*
buf
,
unsigned
longs
)
{
uint32_t
t
;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if
(
is_big_endian
())
{
do
{
t
=
(
uint32_t
)
((
unsigned
)
buf
[
3
]
<<
8
|
buf
[
2
])
<<
16
|
((
unsigned
)
buf
[
1
]
<<
8
|
buf
[
0
]);
*
(
uint32_t
*
)
buf
=
t
;
buf
+=
4
;
}
while
(
--
longs
);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static
void
MD5Init
(
MD5_CTX
*
ctx
)
{
ctx
->
buf
[
0
]
=
0x67452301
;
ctx
->
buf
[
1
]
=
0xefcdab89
;
ctx
->
buf
[
2
]
=
0x98badcfe
;
ctx
->
buf
[
3
]
=
0x10325476
;
ctx
->
bits
[
0
]
=
0
;
ctx
->
bits
[
1
]
=
0
;
}
static
void
MD5Transform
(
uint32_t
buf
[
4
],
uint32_t
const
in
[
16
])
{
register
uint32_t
a
,
b
,
c
,
d
;
a
=
buf
[
0
];
b
=
buf
[
1
];
c
=
buf
[
2
];
d
=
buf
[
3
];
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xd76aa478
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
1
]
+
0xe8c7b756
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x242070db
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
3
]
+
0xc1bdceee
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf57c0faf
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
5
]
+
0x4787c62a
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa8304613
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
7
]
+
0xfd469501
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x698098d8
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
9
]
+
0x8b44f7af
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffff5bb1
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
11
]
+
0x895cd7be
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x6b901122
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
13
]
+
0xfd987193
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xa679438e
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
15
]
+
0x49b40821
,
22
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xf61e2562
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
6
]
+
0xc040b340
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x265e5a51
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
0
]
+
0xe9b6c7aa
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xd62f105d
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
10
]
+
0x02441453
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0xd8a1e681
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
4
]
+
0xe7d3fbc8
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0x21e1cde6
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
14
]
+
0xc33707d6
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xf4d50d87
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
8
]
+
0x455a14ed
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0xa9e3e905
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
2
]
+
0xfcefa3f8
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0x676f02d9
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
12
]
+
0x8d2a4c8a
,
20
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xfffa3942
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
8
]
+
0x8771f681
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x6d9d6122
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
14
]
+
0xfde5380c
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xa4beea44
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
4
]
+
0x4bdecfa9
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0xf6bb4b60
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
10
]
+
0xbebfbc70
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0x289b7ec6
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
0
]
+
0xeaa127fa
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xd4ef3085
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
6
]
+
0x04881d05
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0xd9d4d039
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
12
]
+
0xe6db99e5
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0x1fa27cf8
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
2
]
+
0xc4ac5665
,
23
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xf4292244
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
7
]
+
0x432aff97
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xab9423a7
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
5
]
+
0xfc93a039
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x655b59c3
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
3
]
+
0x8f0ccc92
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffeff47d
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
1
]
+
0x85845dd1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x6fa87e4f
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
15
]
+
0xfe2ce6e0
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa3014314
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
13
]
+
0x4e0811a1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf7537e82
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
11
]
+
0xbd3af235
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x2ad7d2bb
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
9
]
+
0xeb86d391
,
21
);
buf
[
0
]
+=
a
;
buf
[
1
]
+=
b
;
buf
[
2
]
+=
c
;
buf
[
3
]
+=
d
;
}
static
void
MD5Update
(
MD5_CTX
*
ctx
,
unsigned
char
const
*
buf
,
unsigned
len
)
{
uint32_t
t
;
t
=
ctx
->
bits
[
0
];
if
((
ctx
->
bits
[
0
]
=
t
+
((
uint32_t
)
len
<<
3
))
<
t
)
ctx
->
bits
[
1
]
++
;
ctx
->
bits
[
1
]
+=
len
>>
29
;
t
=
(
t
>>
3
)
&
0x3f
;
if
(
t
)
{
unsigned
char
*
p
=
(
unsigned
char
*
)
ctx
->
in
+
t
;
t
=
64
-
t
;
if
(
len
<
t
)
{
memcpy
(
p
,
buf
,
len
);
return
;
}
memcpy
(
p
,
buf
,
t
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
t
;
len
-=
t
;
}
while
(
len
>=
64
)
{
memcpy
(
ctx
->
in
,
buf
,
64
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
64
;
len
-=
64
;
}
memcpy
(
ctx
->
in
,
buf
,
len
);
}
static
void
MD5Final
(
unsigned
char
digest
[
16
],
MD5_CTX
*
ctx
)
{
unsigned
count
;
unsigned
char
*
p
;
uint32_t
*
a
;
count
=
(
ctx
->
bits
[
0
]
>>
3
)
&
0x3F
;
p
=
ctx
->
in
+
count
;
*
p
++
=
0x80
;
count
=
64
-
1
-
count
;
if
(
count
<
8
)
{
memset
(
p
,
0
,
count
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
memset
(
ctx
->
in
,
0
,
56
);
}
else
{
memset
(
p
,
0
,
count
-
8
);
}
byteReverse
(
ctx
->
in
,
14
);
a
=
(
uint32_t
*
)
ctx
->
in
;
a
[
14
]
=
ctx
->
bits
[
0
];
a
[
15
]
=
ctx
->
bits
[
1
];
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
byteReverse
((
unsigned
char
*
)
ctx
->
buf
,
4
);
memcpy
(
digest
,
ctx
->
buf
,
16
);
memset
((
char
*
)
ctx
,
0
,
sizeof
(
*
ctx
));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static
void
bin2str
(
char
*
to
,
const
unsigned
char
*
p
,
size_t
len
)
{
static
const
char
*
hex
=
"0123456789abcdef"
;
for
(;
len
--
;
p
++
)
{
*
to
++
=
hex
[
p
[
0
]
>>
4
];
*
to
++
=
hex
[
p
[
0
]
&
0x0f
];
}
*
to
=
'\0'
;
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char
*
mg_md5
(
char
buf
[
33
],
...)
{
unsigned
char
hash
[
16
];
const
char
*
p
;
va_list
ap
;
MD5_CTX
ctx
;
MD5Init
(
&
ctx
);
va_start
(
ap
,
buf
);
while
((
p
=
va_arg
(
ap
,
const
char
*
))
!=
NULL
)
{
MD5Update
(
&
ctx
,
(
const
unsigned
char
*
)
p
,
(
unsigned
)
strlen
(
p
));
}
va_end
(
ap
);
MD5Final
(
hash
,
&
ctx
);
bin2str
(
buf
,
hash
,
sizeof
(
hash
));
return
buf
;
}
// Check the user's password, return 1 if OK
static
int
check_password
(
const
char
*
method
,
const
char
*
ha1
,
const
char
*
uri
,
const
char
*
nonce
,
const
char
*
nc
,
const
char
*
cnonce
,
const
char
*
qop
,
const
char
*
response
)
{
char
ha2
[
32
+
1
],
expected_response
[
32
+
1
];
// Some of the parameters may be NULL
if
(
method
==
NULL
||
nonce
==
NULL
||
nc
==
NULL
||
cnonce
==
NULL
||
qop
==
NULL
||
response
==
NULL
)
{
return
0
;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if
(
// strcmp(dig->uri, c->ouri) != 0 ||
strlen
(
response
)
!=
32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
)
{
return
0
;
}
mg_md5
(
ha2
,
method
,
":"
,
uri
,
NULL
);
mg_md5
(
expected_response
,
ha1
,
":"
,
nonce
,
":"
,
nc
,
":"
,
cnonce
,
":"
,
qop
,
":"
,
ha2
,
NULL
);
return
mg_strcasecmp
(
response
,
expected_response
)
==
0
;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static
FILE
*
open_auth_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
name
[
PATH_MAX
];
const
char
*
p
,
*
e
,
*
gpass
=
conn
->
ctx
->
config
[
GLOBAL_PASSWORDS_FILE
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
FILE
*
fp
=
NULL
;
if
(
gpass
!=
NULL
)
{
// Use global passwords file
if
((
fp
=
mg_fopen
(
gpass
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"fopen(%s): %s"
,
gpass
,
strerror
(
ERRNO
));
}
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
}
else
if
(
mg_stat
(
path
,
&
file
)
&&
file
.
is_directory
)
{
mg_snprintf
(
name
,
sizeof
(
name
),
"%s%c%s"
,
path
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
else
{
// Try to find .htpasswd in requested directory.
for
(
p
=
path
,
e
=
p
+
strlen
(
p
)
-
1
;
e
>
p
;
e
--
)
if
(
e
[
0
]
==
'/'
)
break
;
mg_snprintf
(
name
,
sizeof
(
name
),
"%.*s%c%s"
,
(
int
)
(
e
-
p
),
p
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
return
fp
;
}
// Parsed Authorization header
struct
ah
{
char
*
user
,
*
uri
,
*
cnonce
,
*
response
,
*
qop
,
*
nc
,
*
nonce
;
};
// Return 1 on success. Always initializes the ah structure.
static
int
parse_auth_header
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_size
,
struct
ah
*
ah
)
{
char
*
name
,
*
value
,
*
s
;
const
char
*
auth_header
;
(
void
)
memset
(
ah
,
0
,
sizeof
(
*
ah
));
if
((
auth_header
=
mg_get_header
(
conn
,
"Authorization"
))
==
NULL
||
mg_strncasecmp
(
auth_header
,
"Digest "
,
7
)
!=
0
)
{
return
0
;
}
// Make modifiable copy of the auth header
(
void
)
mg_strlcpy
(
buf
,
auth_header
+
7
,
buf_size
);
s
=
buf
;
// Parse authorization header
for
(;;)
{
// Gobble initial spaces
while
(
isspace
(
*
(
unsigned
char
*
)
s
))
{
s
++
;
}
name
=
skip_quoted
(
&
s
,
"="
,
" "
,
0
);
// Value is either quote-delimited, or ends at first comma or space.
if
(
s
[
0
]
==
'\"'
)
{
s
++
;
value
=
skip_quoted
(
&
s
,
"
\"
"
,
" "
,
'\\'
);
if
(
s
[
0
]
==
','
)
{
s
++
;
}
}
else
{
value
=
skip_quoted
(
&
s
,
", "
,
" "
,
0
);
// IE uses commas, FF uses spaces
}
if
(
*
name
==
'\0'
)
{
break
;
}
if
(
!
strcmp
(
name
,
"username"
))
{
ah
->
user
=
value
;
}
else
if
(
!
strcmp
(
name
,
"cnonce"
))
{
ah
->
cnonce
=
value
;
}
else
if
(
!
strcmp
(
name
,
"response"
))
{
ah
->
response
=
value
;
}
else
if
(
!
strcmp
(
name
,
"uri"
))
{
ah
->
uri
=
value
;
}
else
if
(
!
strcmp
(
name
,
"qop"
))
{
ah
->
qop
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nc"
))
{
ah
->
nc
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nonce"
))
{
ah
->
nonce
=
value
;
}
}
// CGI needs it as REMOTE_USER
if
(
ah
->
user
!=
NULL
)
{
conn
->
request_info
.
remote_user
=
mg_strdup
(
ah
->
user
);
}
else
{
return
0
;
}
return
1
;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static
int
authorize
(
struct
mg_connection
*
conn
,
FILE
*
fp
)
{
struct
ah
ah
;
char
line
[
256
],
f_user
[
256
],
ha1
[
256
],
f_domain
[
256
],
buf
[
MG_BUF_LEN
];
if
(
!
parse_auth_header
(
conn
,
buf
,
sizeof
(
buf
),
&
ah
))
{
return
0
;
}
// Loop over passwords file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%s"
,
f_user
,
f_domain
,
ha1
)
!=
3
)
{
continue
;
}
if
(
!
strcmp
(
ah
.
user
,
f_user
)
&&
!
strcmp
(
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
f_domain
))
return
check_password
(
conn
->
request_info
.
request_method
,
ha1
,
ah
.
uri
,
ah
.
nonce
,
ah
.
nc
,
ah
.
cnonce
,
ah
.
qop
,
ah
.
response
);
}
return
0
;
}
// Return 1 if request is authorised, 0 otherwise.
static
int
check_authorization
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
fname
[
PATH_MAX
];
struct
vec
uri_vec
,
filename_vec
;
const
char
*
list
;
FILE
*
fp
=
NULL
;
int
authorized
=
1
;
list
=
conn
->
ctx
->
config
[
PROTECT_URI
];
while
((
list
=
next_option
(
list
,
&
uri_vec
,
&
filename_vec
))
!=
NULL
)
{
if
(
!
memcmp
(
conn
->
request_info
.
uri
,
uri_vec
.
ptr
,
uri_vec
.
len
))
{
mg_snprintf
(
fname
,
sizeof
(
fname
),
"%.*s"
,
(
int
)
filename_vec
.
len
,
filename_vec
.
ptr
);
if
((
fp
=
mg_fopen
(
fname
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"%s: cannot open %s: %s"
,
__func__
,
fname
,
strerror
(
errno
));
}
break
;
}
}
if
(
fp
==
NULL
)
{
fp
=
open_auth_file
(
conn
,
path
);
}
if
(
fp
!=
NULL
)
{
authorized
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
authorized
;
}
static
void
send_authorization_request
(
struct
mg_connection
*
conn
)
{
conn
->
status_code
=
401
;
mg_printf
(
conn
,
"HTTP/1.1 401 Unauthorized
\r\n
"
"Content-Length: 0
\r\n
"
"WWW-Authenticate: Digest qop=
\"
auth
\"
, "
"realm=
\"
%s
\"
, nonce=
\"
%lu
\"\r\n\r\n
"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
(
unsigned
long
)
time
(
NULL
));
}
static
int
is_authorized_for_put
(
struct
mg_connection
*
conn
)
{
const
char
*
passfile
=
conn
->
ctx
->
config
[
PUT_DELETE_PASSWORDS_FILE
];
FILE
*
fp
;
int
ret
=
0
;
if
(
passfile
!=
NULL
&&
(
fp
=
mg_fopen
(
passfile
,
"r"
))
!=
NULL
)
{
ret
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
ret
;
}
int
mg_modify_passwords_file
(
const
char
*
fname
,
const
char
*
domain
,
const
char
*
user
,
const
char
*
pass
)
{
int
found
;
char
line
[
512
],
u
[
512
],
d
[
512
],
ha1
[
33
],
tmp
[
PATH_MAX
];
FILE
*
fp
,
*
fp2
;
found
=
0
;
fp
=
fp2
=
NULL
;
// Regard empty password as no password - remove user record.
if
(
pass
!=
NULL
&&
pass
[
0
]
==
'\0'
)
{
pass
=
NULL
;
}
(
void
)
snprintf
(
tmp
,
sizeof
(
tmp
),
"%s.tmp"
,
fname
);
// Create the file if does not exist
if
((
fp
=
fopen
(
fname
,
"a+"
))
!=
NULL
)
{
fclose
(
fp
);
}
// Open the given file and temporary file
if
((
fp
=
fopen
(
fname
,
"r"
))
==
NULL
)
{
return
0
;
}
else
if
((
fp2
=
fopen
(
tmp
,
"w+"
))
==
NULL
)
{
fclose
(
fp
);
return
0
;
}
// Copy the stuff to temporary file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%*s"
,
u
,
d
)
!=
2
)
{
continue
;
}
if
(
!
strcmp
(
u
,
user
)
&&
!
strcmp
(
d
,
domain
))
{
found
++
;
if
(
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
}
else
{
fprintf
(
fp2
,
"%s"
,
line
);
}
}
// If new user, just add it
if
(
!
found
&&
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
// Close files
fclose
(
fp
);
fclose
(
fp2
);
// Put the temp file in place of real file
remove
(
fname
);
rename
(
tmp
,
fname
);
return
1
;
}
static
SOCKET
conn2
(
const
char
*
host
,
int
port
,
int
use_ssl
,
char
*
ebuf
,
size_t
ebuf_len
)
{
struct
sockaddr_in
sin
;
...
...
mongoose.c
View file @
d3577b78
...
...
@@ -458,6 +458,8 @@ struct de {
struct
file
file
;
};
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
);
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
);
static
void
mg_strlcpy
(
register
char
*
dst
,
register
const
char
*
src
,
size_t
n
)
{
for
(;
*
src
!=
'\0'
&&
n
>
1
;
n
--
)
{
...
...
@@ -806,6 +808,502 @@ const char *mg_get_option(const struct mg_context *ctx, const char *name) {
return
ctx
->
config
[
i
];
}
}
static
int
is_big_endian
(
void
)
{
static
const
int
n
=
1
;
return
((
char
*
)
&
n
)[
0
]
==
0
;
}
#ifndef HAVE_MD5
typedef
struct
MD5Context
{
uint32_t
buf
[
4
];
uint32_t
bits
[
2
];
unsigned
char
in
[
64
];
}
MD5_CTX
;
static
void
byteReverse
(
unsigned
char
*
buf
,
unsigned
longs
)
{
uint32_t
t
;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if
(
is_big_endian
())
{
do
{
t
=
(
uint32_t
)
((
unsigned
)
buf
[
3
]
<<
8
|
buf
[
2
])
<<
16
|
((
unsigned
)
buf
[
1
]
<<
8
|
buf
[
0
]);
*
(
uint32_t
*
)
buf
=
t
;
buf
+=
4
;
}
while
(
--
longs
);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static
void
MD5Init
(
MD5_CTX
*
ctx
)
{
ctx
->
buf
[
0
]
=
0x67452301
;
ctx
->
buf
[
1
]
=
0xefcdab89
;
ctx
->
buf
[
2
]
=
0x98badcfe
;
ctx
->
buf
[
3
]
=
0x10325476
;
ctx
->
bits
[
0
]
=
0
;
ctx
->
bits
[
1
]
=
0
;
}
static
void
MD5Transform
(
uint32_t
buf
[
4
],
uint32_t
const
in
[
16
])
{
register
uint32_t
a
,
b
,
c
,
d
;
a
=
buf
[
0
];
b
=
buf
[
1
];
c
=
buf
[
2
];
d
=
buf
[
3
];
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xd76aa478
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
1
]
+
0xe8c7b756
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x242070db
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
3
]
+
0xc1bdceee
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf57c0faf
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
5
]
+
0x4787c62a
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa8304613
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
7
]
+
0xfd469501
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x698098d8
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
9
]
+
0x8b44f7af
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffff5bb1
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
11
]
+
0x895cd7be
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x6b901122
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
13
]
+
0xfd987193
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xa679438e
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
15
]
+
0x49b40821
,
22
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xf61e2562
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
6
]
+
0xc040b340
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x265e5a51
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
0
]
+
0xe9b6c7aa
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xd62f105d
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
10
]
+
0x02441453
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0xd8a1e681
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
4
]
+
0xe7d3fbc8
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0x21e1cde6
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
14
]
+
0xc33707d6
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xf4d50d87
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
8
]
+
0x455a14ed
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0xa9e3e905
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
2
]
+
0xfcefa3f8
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0x676f02d9
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
12
]
+
0x8d2a4c8a
,
20
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xfffa3942
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
8
]
+
0x8771f681
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x6d9d6122
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
14
]
+
0xfde5380c
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xa4beea44
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
4
]
+
0x4bdecfa9
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0xf6bb4b60
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
10
]
+
0xbebfbc70
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0x289b7ec6
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
0
]
+
0xeaa127fa
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xd4ef3085
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
6
]
+
0x04881d05
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0xd9d4d039
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
12
]
+
0xe6db99e5
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0x1fa27cf8
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
2
]
+
0xc4ac5665
,
23
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xf4292244
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
7
]
+
0x432aff97
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xab9423a7
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
5
]
+
0xfc93a039
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x655b59c3
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
3
]
+
0x8f0ccc92
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffeff47d
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
1
]
+
0x85845dd1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x6fa87e4f
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
15
]
+
0xfe2ce6e0
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa3014314
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
13
]
+
0x4e0811a1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf7537e82
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
11
]
+
0xbd3af235
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x2ad7d2bb
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
9
]
+
0xeb86d391
,
21
);
buf
[
0
]
+=
a
;
buf
[
1
]
+=
b
;
buf
[
2
]
+=
c
;
buf
[
3
]
+=
d
;
}
static
void
MD5Update
(
MD5_CTX
*
ctx
,
unsigned
char
const
*
buf
,
unsigned
len
)
{
uint32_t
t
;
t
=
ctx
->
bits
[
0
];
if
((
ctx
->
bits
[
0
]
=
t
+
((
uint32_t
)
len
<<
3
))
<
t
)
ctx
->
bits
[
1
]
++
;
ctx
->
bits
[
1
]
+=
len
>>
29
;
t
=
(
t
>>
3
)
&
0x3f
;
if
(
t
)
{
unsigned
char
*
p
=
(
unsigned
char
*
)
ctx
->
in
+
t
;
t
=
64
-
t
;
if
(
len
<
t
)
{
memcpy
(
p
,
buf
,
len
);
return
;
}
memcpy
(
p
,
buf
,
t
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
t
;
len
-=
t
;
}
while
(
len
>=
64
)
{
memcpy
(
ctx
->
in
,
buf
,
64
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
64
;
len
-=
64
;
}
memcpy
(
ctx
->
in
,
buf
,
len
);
}
static
void
MD5Final
(
unsigned
char
digest
[
16
],
MD5_CTX
*
ctx
)
{
unsigned
count
;
unsigned
char
*
p
;
uint32_t
*
a
;
count
=
(
ctx
->
bits
[
0
]
>>
3
)
&
0x3F
;
p
=
ctx
->
in
+
count
;
*
p
++
=
0x80
;
count
=
64
-
1
-
count
;
if
(
count
<
8
)
{
memset
(
p
,
0
,
count
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
memset
(
ctx
->
in
,
0
,
56
);
}
else
{
memset
(
p
,
0
,
count
-
8
);
}
byteReverse
(
ctx
->
in
,
14
);
a
=
(
uint32_t
*
)
ctx
->
in
;
a
[
14
]
=
ctx
->
bits
[
0
];
a
[
15
]
=
ctx
->
bits
[
1
];
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
byteReverse
((
unsigned
char
*
)
ctx
->
buf
,
4
);
memcpy
(
digest
,
ctx
->
buf
,
16
);
memset
((
char
*
)
ctx
,
0
,
sizeof
(
*
ctx
));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static
void
bin2str
(
char
*
to
,
const
unsigned
char
*
p
,
size_t
len
)
{
static
const
char
*
hex
=
"0123456789abcdef"
;
for
(;
len
--
;
p
++
)
{
*
to
++
=
hex
[
p
[
0
]
>>
4
];
*
to
++
=
hex
[
p
[
0
]
&
0x0f
];
}
*
to
=
'\0'
;
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char
*
mg_md5
(
char
buf
[
33
],
...)
{
unsigned
char
hash
[
16
];
const
char
*
p
;
va_list
ap
;
MD5_CTX
ctx
;
MD5Init
(
&
ctx
);
va_start
(
ap
,
buf
);
while
((
p
=
va_arg
(
ap
,
const
char
*
))
!=
NULL
)
{
MD5Update
(
&
ctx
,
(
const
unsigned
char
*
)
p
,
(
unsigned
)
strlen
(
p
));
}
va_end
(
ap
);
MD5Final
(
hash
,
&
ctx
);
bin2str
(
buf
,
hash
,
sizeof
(
hash
));
return
buf
;
}
// Check the user's password, return 1 if OK
static
int
check_password
(
const
char
*
method
,
const
char
*
ha1
,
const
char
*
uri
,
const
char
*
nonce
,
const
char
*
nc
,
const
char
*
cnonce
,
const
char
*
qop
,
const
char
*
response
)
{
char
ha2
[
32
+
1
],
expected_response
[
32
+
1
];
// Some of the parameters may be NULL
if
(
method
==
NULL
||
nonce
==
NULL
||
nc
==
NULL
||
cnonce
==
NULL
||
qop
==
NULL
||
response
==
NULL
)
{
return
0
;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if
(
// strcmp(dig->uri, c->ouri) != 0 ||
strlen
(
response
)
!=
32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
)
{
return
0
;
}
mg_md5
(
ha2
,
method
,
":"
,
uri
,
NULL
);
mg_md5
(
expected_response
,
ha1
,
":"
,
nonce
,
":"
,
nc
,
":"
,
cnonce
,
":"
,
qop
,
":"
,
ha2
,
NULL
);
return
mg_strcasecmp
(
response
,
expected_response
)
==
0
;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static
FILE
*
open_auth_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
name
[
PATH_MAX
];
const
char
*
p
,
*
e
,
*
gpass
=
conn
->
ctx
->
config
[
GLOBAL_PASSWORDS_FILE
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
FILE
*
fp
=
NULL
;
if
(
gpass
!=
NULL
)
{
// Use global passwords file
fp
=
mg_fopen
(
gpass
,
"r"
);
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
}
else
if
(
mg_stat
(
path
,
&
file
)
&&
file
.
is_directory
)
{
mg_snprintf
(
name
,
sizeof
(
name
),
"%s%c%s"
,
path
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
else
{
// Try to find .htpasswd in requested directory.
for
(
p
=
path
,
e
=
p
+
strlen
(
p
)
-
1
;
e
>
p
;
e
--
)
if
(
e
[
0
]
==
'/'
)
break
;
mg_snprintf
(
name
,
sizeof
(
name
),
"%.*s%c%s"
,
(
int
)
(
e
-
p
),
p
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
return
fp
;
}
// Parsed Authorization header
struct
ah
{
char
*
user
,
*
uri
,
*
cnonce
,
*
response
,
*
qop
,
*
nc
,
*
nonce
;
};
// Return 1 on success. Always initializes the ah structure.
static
int
parse_auth_header
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_size
,
struct
ah
*
ah
)
{
char
*
name
,
*
value
,
*
s
;
const
char
*
auth_header
;
(
void
)
memset
(
ah
,
0
,
sizeof
(
*
ah
));
if
((
auth_header
=
mg_get_header
(
conn
,
"Authorization"
))
==
NULL
||
mg_strncasecmp
(
auth_header
,
"Digest "
,
7
)
!=
0
)
{
return
0
;
}
// Make modifiable copy of the auth header
(
void
)
mg_strlcpy
(
buf
,
auth_header
+
7
,
buf_size
);
s
=
buf
;
// Parse authorization header
for
(;;)
{
// Gobble initial spaces
while
(
isspace
(
*
(
unsigned
char
*
)
s
))
{
s
++
;
}
name
=
skip_quoted
(
&
s
,
"="
,
" "
,
0
);
// Value is either quote-delimited, or ends at first comma or space.
if
(
s
[
0
]
==
'\"'
)
{
s
++
;
value
=
skip_quoted
(
&
s
,
"
\"
"
,
" "
,
'\\'
);
if
(
s
[
0
]
==
','
)
{
s
++
;
}
}
else
{
value
=
skip_quoted
(
&
s
,
", "
,
" "
,
0
);
// IE uses commas, FF uses spaces
}
if
(
*
name
==
'\0'
)
{
break
;
}
if
(
!
strcmp
(
name
,
"username"
))
{
ah
->
user
=
value
;
}
else
if
(
!
strcmp
(
name
,
"cnonce"
))
{
ah
->
cnonce
=
value
;
}
else
if
(
!
strcmp
(
name
,
"response"
))
{
ah
->
response
=
value
;
}
else
if
(
!
strcmp
(
name
,
"uri"
))
{
ah
->
uri
=
value
;
}
else
if
(
!
strcmp
(
name
,
"qop"
))
{
ah
->
qop
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nc"
))
{
ah
->
nc
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nonce"
))
{
ah
->
nonce
=
value
;
}
}
// CGI needs it as REMOTE_USER
if
(
ah
->
user
!=
NULL
)
{
conn
->
request_info
.
remote_user
=
mg_strdup
(
ah
->
user
);
}
else
{
return
0
;
}
return
1
;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static
int
authorize
(
struct
mg_connection
*
conn
,
FILE
*
fp
)
{
struct
ah
ah
;
char
line
[
256
],
f_user
[
256
],
ha1
[
256
],
f_domain
[
256
],
buf
[
MG_BUF_LEN
];
if
(
!
parse_auth_header
(
conn
,
buf
,
sizeof
(
buf
),
&
ah
))
{
return
0
;
}
// Loop over passwords file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%s"
,
f_user
,
f_domain
,
ha1
)
!=
3
)
{
continue
;
}
if
(
!
strcmp
(
ah
.
user
,
f_user
)
&&
!
strcmp
(
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
f_domain
))
return
check_password
(
conn
->
request_info
.
request_method
,
ha1
,
ah
.
uri
,
ah
.
nonce
,
ah
.
nc
,
ah
.
cnonce
,
ah
.
qop
,
ah
.
response
);
}
return
0
;
}
// Return 1 if request is authorised, 0 otherwise.
static
int
check_authorization
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
fname
[
PATH_MAX
];
struct
vec
uri_vec
,
filename_vec
;
const
char
*
list
;
FILE
*
fp
=
NULL
;
int
authorized
=
1
;
list
=
conn
->
ctx
->
config
[
PROTECT_URI
];
while
((
list
=
next_option
(
list
,
&
uri_vec
,
&
filename_vec
))
!=
NULL
)
{
if
(
!
memcmp
(
conn
->
request_info
.
uri
,
uri_vec
.
ptr
,
uri_vec
.
len
))
{
mg_snprintf
(
fname
,
sizeof
(
fname
),
"%.*s"
,
(
int
)
filename_vec
.
len
,
filename_vec
.
ptr
);
fp
=
mg_fopen
(
fname
,
"r"
);
break
;
}
}
if
(
fp
==
NULL
)
{
fp
=
open_auth_file
(
conn
,
path
);
}
if
(
fp
!=
NULL
)
{
authorized
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
authorized
;
}
static
void
send_authorization_request
(
struct
mg_connection
*
conn
)
{
conn
->
status_code
=
401
;
mg_printf
(
conn
,
"HTTP/1.1 401 Unauthorized
\r\n
"
"Content-Length: 0
\r\n
"
"WWW-Authenticate: Digest qop=
\"
auth
\"
, "
"realm=
\"
%s
\"
, nonce=
\"
%lu
\"\r\n\r\n
"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
(
unsigned
long
)
time
(
NULL
));
}
static
int
is_authorized_for_put
(
struct
mg_connection
*
conn
)
{
const
char
*
passfile
=
conn
->
ctx
->
config
[
PUT_DELETE_PASSWORDS_FILE
];
FILE
*
fp
;
int
ret
=
0
;
if
(
passfile
!=
NULL
&&
(
fp
=
mg_fopen
(
passfile
,
"r"
))
!=
NULL
)
{
ret
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
ret
;
}
int
mg_modify_passwords_file
(
const
char
*
fname
,
const
char
*
domain
,
const
char
*
user
,
const
char
*
pass
)
{
int
found
;
char
line
[
512
],
u
[
512
],
d
[
512
],
ha1
[
33
],
tmp
[
PATH_MAX
];
FILE
*
fp
,
*
fp2
;
found
=
0
;
fp
=
fp2
=
NULL
;
// Regard empty password as no password - remove user record.
if
(
pass
!=
NULL
&&
pass
[
0
]
==
'\0'
)
{
pass
=
NULL
;
}
(
void
)
snprintf
(
tmp
,
sizeof
(
tmp
),
"%s.tmp"
,
fname
);
// Create the file if does not exist
if
((
fp
=
fopen
(
fname
,
"a+"
))
!=
NULL
)
{
fclose
(
fp
);
}
// Open the given file and temporary file
if
((
fp
=
fopen
(
fname
,
"r"
))
==
NULL
)
{
return
0
;
}
else
if
((
fp2
=
fopen
(
tmp
,
"w+"
))
==
NULL
)
{
fclose
(
fp
);
return
0
;
}
// Copy the stuff to temporary file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%*s"
,
u
,
d
)
!=
2
)
{
continue
;
}
if
(
!
strcmp
(
u
,
user
)
&&
!
strcmp
(
d
,
domain
))
{
found
++
;
if
(
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
}
else
{
fprintf
(
fp2
,
"%s"
,
line
);
}
}
// If new user, just add it
if
(
!
found
&&
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
// Close files
fclose
(
fp
);
fclose
(
fp2
);
// Put the temp file in place of real file
remove
(
fname
);
rename
(
tmp
,
fname
);
return
1
;
}
// Return number of bytes left to read for this connection
static
int64_t
left_to_read
(
const
struct
mg_connection
*
conn
)
{
...
...
@@ -995,1520 +1493,1021 @@ static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
return
WaitForSingleObject
(
*
mutex
,
INFINITE
)
==
WAIT_OBJECT_0
?
0
:
-
1
;
}
static
int
pthread_cond_signal
(
pthread_cond_t
*
cv
)
{
return
SetEvent
(
cv
->
signal
)
==
0
?
-
1
:
0
;
}
static
int
pthread_cond_broadcast
(
pthread_cond_t
*
cv
)
{
// Implementation with PulseEvent() has race condition, see
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
return
PulseEvent
(
cv
->
broadcast
)
==
0
?
-
1
:
0
;
}
static
int
pthread_cond_destroy
(
pthread_cond_t
*
cv
)
{
return
CloseHandle
(
cv
->
signal
)
&&
CloseHandle
(
cv
->
broadcast
)
?
0
:
-
1
;
}
// For Windows, change all slashes to backslashes in path names.
static
void
change_slashes_to_backslashes
(
char
*
path
)
{
int
i
;
for
(
i
=
0
;
path
[
i
]
!=
'\0'
;
i
++
)
{
if
(
path
[
i
]
==
'/'
)
path
[
i
]
=
'\\'
;
// i > 0 check is to preserve UNC paths, like \\server\file.txt
if
(
path
[
i
]
==
'\\'
&&
i
>
0
)
while
(
path
[
i
+
1
]
==
'\\'
||
path
[
i
+
1
]
==
'/'
)
(
void
)
memmove
(
path
+
i
+
1
,
path
+
i
+
2
,
strlen
(
path
+
i
+
1
));
}
}
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static
void
to_unicode
(
const
char
*
path
,
wchar_t
*
wbuf
,
size_t
wbuf_len
)
{
char
buf
[
PATH_MAX
*
2
],
buf2
[
PATH_MAX
*
2
];
mg_strlcpy
(
buf
,
path
,
sizeof
(
buf
));
change_slashes_to_backslashes
(
buf
);
// Convert to Unicode and back. If doubly-converted string does not
// match the original, something is fishy, reject.
memset
(
wbuf
,
0
,
wbuf_len
*
sizeof
(
wchar_t
));
MultiByteToWideChar
(
CP_UTF8
,
0
,
buf
,
-
1
,
wbuf
,
(
int
)
wbuf_len
);
WideCharToMultiByte
(
CP_UTF8
,
0
,
wbuf
,
(
int
)
wbuf_len
,
buf2
,
sizeof
(
buf2
),
NULL
,
NULL
);
if
(
strcmp
(
buf
,
buf2
)
!=
0
)
{
wbuf
[
0
]
=
L'\0'
;
}
}
#if defined(_WIN32_WCE)
static
time_t
time
(
time_t
*
ptime
)
{
time_t
t
;
SYSTEMTIME
st
;
FILETIME
ft
;
GetSystemTime
(
&
st
);
SystemTimeToFileTime
(
&
st
,
&
ft
);
t
=
SYS2UNIX_TIME
(
ft
.
dwLowDateTime
,
ft
.
dwHighDateTime
);
if
(
ptime
!=
NULL
)
{
*
ptime
=
t
;
}
return
t
;
}
static
struct
tm
*
localtime
(
const
time_t
*
ptime
,
struct
tm
*
ptm
)
{
int64_t
t
=
((
int64_t
)
*
ptime
)
*
RATE_DIFF
+
EPOCH_DIFF
;
FILETIME
ft
,
lft
;
SYSTEMTIME
st
;
TIME_ZONE_INFORMATION
tzinfo
;
if
(
ptm
==
NULL
)
{
return
NULL
;
}
*
(
int64_t
*
)
&
ft
=
t
;
FileTimeToLocalFileTime
(
&
ft
,
&
lft
);
FileTimeToSystemTime
(
&
lft
,
&
st
);
ptm
->
tm_year
=
st
.
wYear
-
1900
;
ptm
->
tm_mon
=
st
.
wMonth
-
1
;
ptm
->
tm_wday
=
st
.
wDayOfWeek
;
ptm
->
tm_mday
=
st
.
wDay
;
ptm
->
tm_hour
=
st
.
wHour
;
ptm
->
tm_min
=
st
.
wMinute
;
ptm
->
tm_sec
=
st
.
wSecond
;
ptm
->
tm_yday
=
0
;
// hope nobody uses this
ptm
->
tm_isdst
=
GetTimeZoneInformation
(
&
tzinfo
)
==
TIME_ZONE_ID_DAYLIGHT
?
1
:
0
;
return
ptm
;
}
static
struct
tm
*
gmtime
(
const
time_t
*
ptime
,
struct
tm
*
ptm
)
{
// FIXME(lsm): fix this.
return
localtime
(
ptime
,
ptm
);
}
static
size_t
strftime
(
char
*
dst
,
size_t
dst_size
,
const
char
*
fmt
,
const
struct
tm
*
tm
)
{
(
void
)
snprintf
(
dst
,
dst_size
,
"implement strftime() for WinCE"
);
return
0
;
}
#endif
// Windows happily opens files with some garbage at the end of file name.
// For example, fopen("a.cgi ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
// This function returns non-0 if path ends with some garbage.
static
int
path_cannot_disclose_cgi
(
const
char
*
path
)
{
static
const
char
*
allowed_last_characters
=
"_-"
;
int
last
=
path
[
strlen
(
path
)
-
1
];
return
isalnum
(
last
)
||
strchr
(
allowed_last_characters
,
last
)
!=
NULL
;
}
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
)
{
wchar_t
wbuf
[
PATH_MAX
]
=
L"
\\\\
?
\\
"
;
WIN32_FILE_ATTRIBUTE_DATA
info
;
filep
->
modification_time
=
0
;
to_unicode
(
path
,
wbuf
+
4
,
ARRAY_SIZE
(
wbuf
)
-
4
);
if
(
GetFileAttributesExW
(
wbuf
,
GetFileExInfoStandard
,
&
info
)
!=
0
)
{
filep
->
size
=
MAKEUQUAD
(
info
.
nFileSizeLow
,
info
.
nFileSizeHigh
);
filep
->
modification_time
=
SYS2UNIX_TIME
(
info
.
ftLastWriteTime
.
dwLowDateTime
,
info
.
ftLastWriteTime
.
dwHighDateTime
);
filep
->
is_directory
=
info
.
dwFileAttributes
&
FILE_ATTRIBUTE_DIRECTORY
;
// If file name is fishy, reset the file structure and return error.
// Note it is important to reset, not just return the error, cause
// functions like is_file_opened() check the struct.
if
(
!
filep
->
is_directory
&&
!
path_cannot_disclose_cgi
(
path
))
{
memset
(
filep
,
0
,
sizeof
(
*
filep
));
}
}
return
filep
->
modification_time
!=
0
;
}
static
int
mg_remove
(
const
char
*
path
)
{
wchar_t
wbuf
[
PATH_MAX
];
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
DeleteFileW
(
wbuf
)
?
0
:
-
1
;
}
static
int
mg_mkdir
(
const
char
*
path
,
int
mode
)
{
char
buf
[
PATH_MAX
];
wchar_t
wbuf
[
PATH_MAX
];
(
void
)
mode
;
mg_strlcpy
(
buf
,
path
,
sizeof
(
buf
));
change_slashes_to_backslashes
(
buf
);
(
void
)
MultiByteToWideChar
(
CP_UTF8
,
0
,
buf
,
-
1
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
CreateDirectoryW
(
wbuf
,
NULL
)
?
0
:
-
1
;
}
// Implementation of POSIX opendir/closedir/readdir for Windows.
static
DIR
*
opendir
(
const
char
*
name
)
{
DIR
*
dir
=
NULL
;
wchar_t
wpath
[
PATH_MAX
];
DWORD
attrs
;
if
(
name
==
NULL
)
{
SetLastError
(
ERROR_BAD_ARGUMENTS
);
}
else
if
((
dir
=
(
DIR
*
)
malloc
(
sizeof
(
*
dir
)))
==
NULL
)
{
SetLastError
(
ERROR_NOT_ENOUGH_MEMORY
);
}
else
{
to_unicode
(
name
,
wpath
,
ARRAY_SIZE
(
wpath
));
attrs
=
GetFileAttributesW
(
wpath
);
if
(
attrs
!=
0xFFFFFFFF
&&
((
attrs
&
FILE_ATTRIBUTE_DIRECTORY
)
==
FILE_ATTRIBUTE_DIRECTORY
))
{
(
void
)
wcscat
(
wpath
,
L"
\\
*"
);
dir
->
handle
=
FindFirstFileW
(
wpath
,
&
dir
->
info
);
dir
->
result
.
d_name
[
0
]
=
'\0'
;
}
else
{
free
(
dir
);
dir
=
NULL
;
}
}
return
dir
;
}
static
int
closedir
(
DIR
*
dir
)
{
int
result
=
0
;
if
(
dir
!=
NULL
)
{
if
(
dir
->
handle
!=
INVALID_HANDLE_VALUE
)
result
=
FindClose
(
dir
->
handle
)
?
0
:
-
1
;
free
(
dir
);
}
else
{
result
=
-
1
;
SetLastError
(
ERROR_BAD_ARGUMENTS
);
}
return
result
;
}
static
struct
dirent
*
readdir
(
DIR
*
dir
)
{
struct
dirent
*
result
=
0
;
if
(
dir
)
{
if
(
dir
->
handle
!=
INVALID_HANDLE_VALUE
)
{
result
=
&
dir
->
result
;
(
void
)
WideCharToMultiByte
(
CP_UTF8
,
0
,
dir
->
info
.
cFileName
,
-
1
,
result
->
d_name
,
sizeof
(
result
->
d_name
),
NULL
,
NULL
);
if
(
!
FindNextFileW
(
dir
->
handle
,
&
dir
->
info
))
{
(
void
)
FindClose
(
dir
->
handle
);
dir
->
handle
=
INVALID_HANDLE_VALUE
;
}
}
else
{
SetLastError
(
ERROR_FILE_NOT_FOUND
);
}
}
else
{
SetLastError
(
ERROR_BAD_ARGUMENTS
);
}
return
result
;
}
#ifndef HAVE_POLL
static
int
poll
(
struct
pollfd
*
pfd
,
int
n
,
int
milliseconds
)
{
struct
timeval
tv
;
fd_set
set
;
int
i
,
result
;
SOCKET
maxfd
=
0
;
tv
.
tv_sec
=
milliseconds
/
1000
;
tv
.
tv_usec
=
(
milliseconds
%
1000
)
*
1000
;
FD_ZERO
(
&
set
);
for
(
i
=
0
;
i
<
n
;
i
++
)
{
FD_SET
((
SOCKET
)
pfd
[
i
].
fd
,
&
set
);
pfd
[
i
].
revents
=
0
;
if
(
pfd
[
i
].
fd
>
maxfd
)
{
maxfd
=
pfd
[
i
].
fd
;
}
}
if
((
result
=
select
(
maxfd
+
1
,
&
set
,
NULL
,
NULL
,
&
tv
))
>
0
)
{
for
(
i
=
0
;
i
<
n
;
i
++
)
{
if
(
FD_ISSET
(
pfd
[
i
].
fd
,
&
set
))
{
pfd
[
i
].
revents
=
POLLIN
;
}
}
}
return
result
;
}
#endif // HAVE_POLL
static
void
set_close_on_exec
(
SOCKET
sock
)
{
(
void
)
SetHandleInformation
((
HANDLE
)
sock
,
HANDLE_FLAG_INHERIT
,
0
);
static
int
pthread_cond_signal
(
pthread_cond_t
*
cv
)
{
return
SetEvent
(
cv
->
signal
)
==
0
?
-
1
:
0
;
}
int
mg_start_thread
(
mg_thread_func_t
f
,
void
*
p
)
{
return
(
long
)
_beginthread
((
void
(
__cdecl
*
)(
void
*
))
f
,
0
,
p
)
==
-
1L
?
-
1
:
0
;
static
int
pthread_cond_broadcast
(
pthread_cond_t
*
cv
)
{
// Implementation with PulseEvent() has race condition, see
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
return
PulseEvent
(
cv
->
broadcast
)
==
0
?
-
1
:
0
;
}
static
HANDLE
dlopen
(
const
char
*
dll_name
,
int
flags
)
{
wchar_t
wbuf
[
PATH_MAX
];
(
void
)
flags
;
to_unicode
(
dll_name
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
LoadLibraryW
(
wbuf
);
static
int
pthread_cond_destroy
(
pthread_cond_t
*
cv
)
{
return
CloseHandle
(
cv
->
signal
)
&&
CloseHandle
(
cv
->
broadcast
)
?
0
:
-
1
;
}
#if !defined(NO_CGI)
#define SIGKILL 0
static
int
kill
(
pid_t
pid
,
int
sig_num
)
{
(
void
)
TerminateProcess
(
pid
,
sig_num
);
(
void
)
CloseHandle
(
pid
);
return
0
;
}
// For Windows, change all slashes to backslashes in path names.
static
void
change_slashes_to_backslashes
(
char
*
path
)
{
int
i
;
static
void
trim_trailing_whitespaces
(
char
*
s
)
{
char
*
e
=
s
+
strlen
(
s
)
-
1
;
while
(
e
>
s
&&
isspace
(
*
(
unsigned
char
*
)
e
))
{
*
e
--
=
'\0'
;
for
(
i
=
0
;
path
[
i
]
!=
'\0'
;
i
++
)
{
if
(
path
[
i
]
==
'/'
)
path
[
i
]
=
'\\'
;
// i > 0 check is to preserve UNC paths, like \\server\file.txt
if
(
path
[
i
]
==
'\\'
&&
i
>
0
)
while
(
path
[
i
+
1
]
==
'\\'
||
path
[
i
+
1
]
==
'/'
)
(
void
)
memmove
(
path
+
i
+
1
,
path
+
i
+
2
,
strlen
(
path
+
i
+
1
));
}
}
static
pid_t
spawn_process
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
char
*
envblk
,
char
*
envp
[],
int
fdin
,
int
fdout
,
const
char
*
dir
)
{
HANDLE
me
;
char
*
interp
,
full_interp
[
PATH_MAX
],
full_dir
[
PATH_MAX
],
cmdline
[
PATH_MAX
],
buf
[
PATH_MAX
];
FILE
*
fp
;
STARTUPINFOA
si
;
PROCESS_INFORMATION
pi
=
{
0
};
(
void
)
envp
;
memset
(
&
si
,
0
,
sizeof
(
si
));
si
.
cb
=
sizeof
(
si
);
// TODO(lsm): redirect CGI errors to the error log file
si
.
dwFlags
=
STARTF_USESTDHANDLES
|
STARTF_USESHOWWINDOW
;
si
.
wShowWindow
=
SW_HIDE
;
me
=
GetCurrentProcess
();
DuplicateHandle
(
me
,
(
HANDLE
)
_get_osfhandle
(
fdin
),
me
,
&
si
.
hStdInput
,
0
,
TRUE
,
DUPLICATE_SAME_ACCESS
);
DuplicateHandle
(
me
,
(
HANDLE
)
_get_osfhandle
(
fdout
),
me
,
&
si
.
hStdOutput
,
0
,
TRUE
,
DUPLICATE_SAME_ACCESS
);
// If CGI file is a script, try to read the interpreter line
interp
=
conn
->
ctx
->
config
[
CGI_INTERPRETER
];
if
(
interp
==
NULL
)
{
buf
[
0
]
=
buf
[
1
]
=
'\0'
;
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static
void
to_unicode
(
const
char
*
path
,
wchar_t
*
wbuf
,
size_t
wbuf_len
)
{
char
buf
[
PATH_MAX
*
2
],
buf2
[
PATH_MAX
*
2
];
// Read the first line of the script into the buffer
snprintf
(
cmdline
,
sizeof
(
cmdline
),
"%s%c%s"
,
dir
,
'/'
,
prog
);
if
((
fp
=
mg_fopen
(
cmdline
,
"r"
))
!=
NULL
)
{
fgets
(
buf
,
sizeof
(
buf
),
fp
);
fclose
(
fp
);
buf
[
sizeof
(
buf
)
-
1
]
=
'\0'
;
}
mg_strlcpy
(
buf
,
path
,
sizeof
(
buf
));
change_slashes_to_backslashes
(
buf
);
if
(
buf
[
0
]
==
'#'
&&
buf
[
1
]
==
'!'
)
{
trim_trailing_whitespaces
(
buf
+
2
);
}
else
{
buf
[
2
]
=
'\0'
;
}
interp
=
buf
+
2
;
// Convert to Unicode and back. If doubly-converted string does not
// match the original, something is fishy, reject.
memset
(
wbuf
,
0
,
wbuf_len
*
sizeof
(
wchar_t
));
MultiByteToWideChar
(
CP_UTF8
,
0
,
buf
,
-
1
,
wbuf
,
(
int
)
wbuf_len
);
WideCharToMultiByte
(
CP_UTF8
,
0
,
wbuf
,
(
int
)
wbuf_len
,
buf2
,
sizeof
(
buf2
),
NULL
,
NULL
);
if
(
strcmp
(
buf
,
buf2
)
!=
0
)
{
wbuf
[
0
]
=
L'\0'
;
}
}
if
(
interp
[
0
]
!=
'\0'
)
{
GetFullPathNameA
(
interp
,
sizeof
(
full_interp
),
full_interp
,
NULL
);
interp
=
full_interp
;
}
GetFullPathNameA
(
dir
,
sizeof
(
full_dir
),
full_dir
,
NULL
)
;
#if defined(_WIN32_WCE)
static
time_t
time
(
time_t
*
ptime
)
{
time_t
t
;
SYSTEMTIME
st
;
FILETIME
ft
;
mg_snprintf
(
cmdline
,
sizeof
(
cmdline
),
"%s%s
\"
%s
\\
%s
\"
"
,
interp
,
interp
[
0
]
==
'\0'
?
""
:
" "
,
full_dir
,
prog
);
GetSystemTime
(
&
st
);
SystemTimeToFileTime
(
&
st
,
&
ft
);
t
=
SYS2UNIX_TIME
(
ft
.
dwLowDateTime
,
ft
.
dwHighDateTime
);
DEBUG_TRACE
((
"Running [%s]"
,
cmdline
));
if
(
CreateProcessA
(
NULL
,
cmdline
,
NULL
,
NULL
,
TRUE
,
CREATE_NEW_PROCESS_GROUP
,
envblk
,
NULL
,
&
si
,
&
pi
)
==
0
)
{
cry
(
conn
,
"%s: CreateProcess(%s): %ld"
,
__func__
,
cmdline
,
ERRNO
);
pi
.
hProcess
=
(
pid_t
)
-
1
;
if
(
ptime
!=
NULL
)
{
*
ptime
=
t
;
}
(
void
)
CloseHandle
(
si
.
hStdOutput
);
(
void
)
CloseHandle
(
si
.
hStdInput
);
(
void
)
CloseHandle
(
pi
.
hThread
);
return
(
pid_t
)
pi
.
hProcess
;
}
#endif // !NO_CGI
static
int
set_non_blocking_mode
(
SOCKET
sock
)
{
unsigned
long
on
=
1
;
return
ioctlsocket
(
sock
,
FIONBIO
,
&
on
);
return
t
;
}
#else
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
)
{
struct
stat
st
;
filep
->
modification_time
=
(
time_t
)
0
;
if
(
stat
(
path
,
&
st
)
==
0
)
{
filep
->
size
=
st
.
st_size
;
filep
->
modification_time
=
st
.
st_mtime
;
filep
->
is_directory
=
S_ISDIR
(
st
.
st_mode
);
static
struct
tm
*
localtime
(
const
time_t
*
ptime
,
struct
tm
*
ptm
)
{
int64_t
t
=
((
int64_t
)
*
ptime
)
*
RATE_DIFF
+
EPOCH_DIFF
;
FILETIME
ft
,
lft
;
SYSTEMTIME
st
;
TIME_ZONE_INFORMATION
tzinfo
;
// See https://github.com/cesanta/mongoose/issues/109
// Some filesystems report modification time as 0. Artificially
// bump it up to mark mg_stat() success.
if
(
filep
->
modification_time
==
(
time_t
)
0
)
{
filep
->
modification_time
=
(
time_t
)
1
;
}
if
(
ptm
==
NULL
)
{
return
NULL
;
}
return
filep
->
modification_time
!=
(
time_t
)
0
;
}
*
(
int64_t
*
)
&
ft
=
t
;
FileTimeToLocalFileTime
(
&
ft
,
&
lft
);
FileTimeToSystemTime
(
&
lft
,
&
st
);
ptm
->
tm_year
=
st
.
wYear
-
1900
;
ptm
->
tm_mon
=
st
.
wMonth
-
1
;
ptm
->
tm_wday
=
st
.
wDayOfWeek
;
ptm
->
tm_mday
=
st
.
wDay
;
ptm
->
tm_hour
=
st
.
wHour
;
ptm
->
tm_min
=
st
.
wMinute
;
ptm
->
tm_sec
=
st
.
wSecond
;
ptm
->
tm_yday
=
0
;
// hope nobody uses this
ptm
->
tm_isdst
=
GetTimeZoneInformation
(
&
tzinfo
)
==
TIME_ZONE_ID_DAYLIGHT
?
1
:
0
;
static
void
set_close_on_exec
(
int
fd
)
{
fcntl
(
fd
,
F_SETFD
,
FD_CLOEXEC
);
return
ptm
;
}
int
mg_start_thread
(
mg_thread_func_t
func
,
void
*
param
)
{
pthread_t
thread_id
;
pthread_attr_t
attr
;
int
result
;
(
void
)
pthread_attr_init
(
&
attr
);
(
void
)
pthread_attr_setdetachstate
(
&
attr
,
PTHREAD_CREATE_DETACHED
);
static
struct
tm
*
gmtime
(
const
time_t
*
ptime
,
struct
tm
*
ptm
)
{
// FIXME(lsm): fix this.
return
localtime
(
ptime
,
ptm
);
}
#if USE_STACK_SIZE > 1
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
(
void
)
pthread_attr_setstacksize
(
&
attr
,
USE_STACK_SIZE
);
static
size_t
strftime
(
char
*
dst
,
size_t
dst_size
,
const
char
*
fmt
,
const
struct
tm
*
tm
)
{
(
void
)
snprintf
(
dst
,
dst_size
,
"implement strftime() for WinCE"
);
return
0
;
}
#endif
result
=
pthread_create
(
&
thread_id
,
&
attr
,
func
,
param
);
pthread_attr_destroy
(
&
attr
);
return
result
;
// Windows happily opens files with some garbage at the end of file name.
// For example, fopen("a.cgi ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
// This function returns non-0 if path ends with some garbage.
static
int
path_cannot_disclose_cgi
(
const
char
*
path
)
{
static
const
char
*
allowed_last_characters
=
"_-"
;
int
last
=
path
[
strlen
(
path
)
-
1
];
return
isalnum
(
last
)
||
strchr
(
allowed_last_characters
,
last
)
!=
NULL
;
}
#ifndef NO_CGI
static
pid_t
spawn_process
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
char
*
envblk
,
char
*
envp
[],
int
fdin
,
int
fdout
,
const
char
*
dir
)
{
pid_t
pid
;
const
char
*
interp
;
(
void
)
envblk
;
if
((
pid
=
fork
())
==
-
1
)
{
// Parent
send_http_error
(
conn
,
500
,
http_500_error
,
"fork(): %s"
,
strerror
(
ERRNO
));
}
else
if
(
pid
==
0
)
{
// Child
if
(
chdir
(
dir
)
!=
0
)
{
cry
(
conn
,
"%s: chdir(%s): %s"
,
__func__
,
dir
,
strerror
(
ERRNO
));
}
else
if
(
dup2
(
fdin
,
0
)
==
-
1
)
{
cry
(
conn
,
"%s: dup2(%d, 0): %s"
,
__func__
,
fdin
,
strerror
(
ERRNO
));
}
else
if
(
dup2
(
fdout
,
1
)
==
-
1
)
{
cry
(
conn
,
"%s: dup2(%d, 1): %s"
,
__func__
,
fdout
,
strerror
(
ERRNO
));
}
else
{
// Not redirecting stderr to stdout, to avoid output being littered
// with the error messages.
(
void
)
close
(
fdin
);
(
void
)
close
(
fdout
);
// After exec, all signal handlers are restored to their default values,
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
// implementation, SIGCHLD's handler will leave unchanged after exec
// if it was set to be ignored. Restore it to default action.
signal
(
SIGCHLD
,
SIG_DFL
);
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
)
{
wchar_t
wbuf
[
PATH_MAX
]
=
L"
\\\\
?
\\
"
;
WIN32_FILE_ATTRIBUTE_DATA
info
;
interp
=
conn
->
ctx
->
config
[
CGI_INTERPRETER
];
if
(
interp
==
NULL
)
{
(
void
)
execle
(
prog
,
prog
,
NULL
,
envp
);
cry
(
conn
,
"%s: execle(%s): %s"
,
__func__
,
prog
,
strerror
(
ERRNO
));
}
else
{
(
void
)
execle
(
interp
,
interp
,
prog
,
NULL
,
envp
);
cry
(
conn
,
"%s: execle(%s %s): %s"
,
__func__
,
interp
,
prog
,
strerror
(
ERRNO
));
}
filep
->
modification_time
=
0
;
to_unicode
(
path
,
wbuf
+
4
,
ARRAY_SIZE
(
wbuf
)
-
4
);
if
(
GetFileAttributesExW
(
wbuf
,
GetFileExInfoStandard
,
&
info
)
!=
0
)
{
filep
->
size
=
MAKEUQUAD
(
info
.
nFileSizeLow
,
info
.
nFileSizeHigh
);
filep
->
modification_time
=
SYS2UNIX_TIME
(
info
.
ftLastWriteTime
.
dwLowDateTime
,
info
.
ftLastWriteTime
.
dwHighDateTime
);
filep
->
is_directory
=
info
.
dwFileAttributes
&
FILE_ATTRIBUTE_DIRECTORY
;
// If file name is fishy, reset the file structure and return error.
// Note it is important to reset, not just return the error, cause
// functions like is_file_opened() check the struct.
if
(
!
filep
->
is_directory
&&
!
path_cannot_disclose_cgi
(
path
))
{
memset
(
filep
,
0
,
sizeof
(
*
filep
));
}
exit
(
EXIT_FAILURE
);
}
return
pid
;
return
filep
->
modification_time
!=
0
;
}
#endif // !NO_CGI
static
int
set_non_blocking_mode
(
SOCKET
sock
)
{
int
flags
;
flags
=
fcntl
(
sock
,
F_GETFL
,
0
);
(
void
)
fcntl
(
sock
,
F_SETFL
,
flags
|
O_NONBLOCK
);
return
0
;
static
int
mg_remove
(
const
char
*
path
)
{
wchar_t
wbuf
[
PATH_MAX
];
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
DeleteFileW
(
wbuf
)
?
0
:
-
1
;
}
#endif // _WIN32
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static
int64_t
push
(
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
,
const
char
*
buf
,
int64_t
len
)
{
int64_t
sent
;
int
n
,
k
;
(
void
)
ssl
;
// Get rid of warning
sent
=
0
;
while
(
sent
<
len
)
{
// How many bytes we send in this iteration
k
=
len
-
sent
>
INT_MAX
?
INT_MAX
:
(
int
)
(
len
-
sent
);
if
(
ssl
!=
NULL
)
{
n
=
SSL_write
(
ssl
,
buf
+
sent
,
k
);
}
else
if
(
fp
!=
NULL
)
{
n
=
(
int
)
fwrite
(
buf
+
sent
,
1
,
(
size_t
)
k
,
fp
);
if
(
ferror
(
fp
))
n
=
-
1
;
}
else
{
n
=
send
(
sock
,
buf
+
sent
,
(
size_t
)
k
,
MSG_NOSIGNAL
);
}
static
int
mg_mkdir
(
const
char
*
path
,
int
mode
)
{
char
buf
[
PATH_MAX
];
wchar_t
wbuf
[
PATH_MAX
];
if
(
n
<=
0
)
break
;
(
void
)
mode
;
mg_strlcpy
(
buf
,
path
,
sizeof
(
buf
));
change_slashes_to_backslashes
(
buf
);
sent
+=
n
;
}
(
void
)
MultiByteToWideChar
(
CP_UTF8
,
0
,
buf
,
-
1
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
sent
;
return
CreateDirectoryW
(
wbuf
,
NULL
)
?
0
:
-
1
;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success.
static
int
pull
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
nread
;
// Implementation of POSIX opendir/closedir/readdir for Windows.
static
DIR
*
opendir
(
const
char
*
name
)
{
DIR
*
dir
=
NULL
;
wchar_t
wpath
[
PATH_MAX
];
DWORD
attrs
;
if
(
len
<=
0
)
return
0
;
if
(
fp
!=
NULL
)
{
// Use read() instead of fread(), because if we're reading from the CGI
// pipe, fread() may block until IO buffer is filled up. We cannot afford
// to block and must pass all read bytes immediately to the client.
nread
=
read
(
fileno
(
fp
),
buf
,
(
size_t
)
len
);
#ifndef NO_SSL
}
else
if
(
conn
->
ssl
!=
NULL
)
{
nread
=
SSL_read
(
conn
->
ssl
,
buf
,
len
);
#endif
if
(
name
==
NULL
)
{
SetLastError
(
ERROR_BAD_ARGUMENTS
);
}
else
if
((
dir
=
(
DIR
*
)
malloc
(
sizeof
(
*
dir
)))
==
NULL
)
{
SetLastError
(
ERROR_NOT_ENOUGH_MEMORY
);
}
else
{
nread
=
recv
(
conn
->
client
.
sock
,
buf
,
(
size_t
)
len
,
0
);
to_unicode
(
name
,
wpath
,
ARRAY_SIZE
(
wpath
));
attrs
=
GetFileAttributesW
(
wpath
);
if
(
attrs
!=
0xFFFFFFFF
&&
((
attrs
&
FILE_ATTRIBUTE_DIRECTORY
)
==
FILE_ATTRIBUTE_DIRECTORY
))
{
(
void
)
wcscat
(
wpath
,
L"
\\
*"
);
dir
->
handle
=
FindFirstFileW
(
wpath
,
&
dir
->
info
);
dir
->
result
.
d_name
[
0
]
=
'\0'
;
}
else
{
free
(
dir
);
dir
=
NULL
;
}
if
(
nread
>
0
)
{
conn
->
num_bytes_read
+=
nread
;
}
return
conn
->
ctx
->
stop_flag
?
-
1
:
nread
;
return
dir
;
}
static
int
pull_all
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
n
,
nread
=
0
;
static
int
closedir
(
DIR
*
dir
)
{
int
result
=
0
;
while
(
len
>
0
&&
conn
->
ctx
->
stop_flag
==
0
)
{
n
=
pull
(
fp
,
conn
,
buf
+
nread
,
len
);
if
(
n
<
0
)
{
nread
=
n
;
// Propagate the error
break
;
}
else
if
(
n
==
0
)
{
break
;
// No more data to read
if
(
dir
!=
NULL
)
{
if
(
dir
->
handle
!=
INVALID_HANDLE_VALUE
)
result
=
FindClose
(
dir
->
handle
)
?
0
:
-
1
;
free
(
dir
);
}
else
{
nread
+=
n
;
len
-=
n
;
}
result
=
-
1
;
SetLastError
(
ERROR_BAD_ARGUMENTS
);
}
return
nread
;
return
result
;
}
int
mg_read
(
struct
mg_connection
*
conn
,
void
*
buf
,
int
len
)
{
int
n
,
buffered_len
,
nread
=
0
;
int64_t
left
;
// If Content-Length is not set, read until socket is closed
if
(
conn
->
content_len
<=
0
)
{
conn
->
content_len
=
INT64_MAX
;
conn
->
must_close
=
1
;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
static
struct
dirent
*
readdir
(
DIR
*
dir
)
{
struct
dirent
*
result
=
0
;
// First, check for data buffered in conn->buf by read_request().
if
(
len
>
0
&&
(
buffered_len
=
conn
->
data_len
-
conn
->
request_len
)
>
0
)
{
char
*
body
=
conn
->
buf
+
conn
->
request_len
;
if
(
buffered_len
>
len
)
buffered_len
=
len
;
if
(
buffered_len
>
conn
->
content_len
)
buffered_len
=
(
int
)
conn
->
content_len
;
if
(
dir
)
{
if
(
dir
->
handle
!=
INVALID_HANDLE_VALUE
)
{
result
=
&
dir
->
result
;
(
void
)
WideCharToMultiByte
(
CP_UTF8
,
0
,
dir
->
info
.
cFileName
,
-
1
,
result
->
d_name
,
sizeof
(
result
->
d_name
),
NULL
,
NULL
);
memcpy
(
buf
,
body
,
(
size_t
)
buffered_len
);
memmove
(
body
,
body
+
buffered_len
,
&
conn
->
buf
[
conn
->
data_len
]
-
&
body
[
buffered_len
]);
len
-=
buffered_len
;
conn
->
data_len
-=
buffered_len
;
nread
+=
buffered_len
;
if
(
!
FindNextFileW
(
dir
->
handle
,
&
dir
->
info
))
{
(
void
)
FindClose
(
dir
->
handle
);
dir
->
handle
=
INVALID_HANDLE_VALUE
;
}
// Read data from the socket.
if
(
len
>
0
&&
(
left
=
left_to_read
(
conn
))
>
0
)
{
if
(
left
<
len
)
{
len
=
(
int
)
left
;
}
else
{
SetLastError
(
ERROR_FILE_NOT_FOUND
);
}
n
=
pull_all
(
NULL
,
conn
,
(
char
*
)
buf
+
nread
,
(
int
)
len
);
nread
=
n
>=
0
?
nread
+
n
:
n
;
}
else
{
SetLastError
(
ERROR_BAD_ARGUMENTS
)
;
}
return
nread
;
return
result
;
}
int
mg_write
(
struct
mg_connection
*
conn
,
const
void
*
buf
,
int
len
)
{
time_t
now
;
int64_t
n
,
total
,
allowed
;
#ifndef HAVE_POLL
static
int
poll
(
struct
pollfd
*
pfd
,
int
n
,
int
milliseconds
)
{
struct
timeval
tv
;
fd_set
set
;
int
i
,
result
;
SOCKET
maxfd
=
0
;
if
(
conn
->
throttle
>
0
)
{
if
((
now
=
time
(
NULL
))
!=
conn
->
last_throttle_time
)
{
conn
->
last_throttle_time
=
now
;
conn
->
last_throttle_bytes
=
0
;
}
allowed
=
conn
->
throttle
-
conn
->
last_throttle_bytes
;
if
(
allowed
>
(
int64_t
)
len
)
{
allowed
=
len
;
tv
.
tv_sec
=
milliseconds
/
1000
;
tv
.
tv_usec
=
(
milliseconds
%
1000
)
*
1000
;
FD_ZERO
(
&
set
);
for
(
i
=
0
;
i
<
n
;
i
++
)
{
FD_SET
((
SOCKET
)
pfd
[
i
].
fd
,
&
set
);
pfd
[
i
].
revents
=
0
;
if
(
pfd
[
i
].
fd
>
maxfd
)
{
maxfd
=
pfd
[
i
].
fd
;
}
if
((
total
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
allowed
))
==
allowed
)
{
buf
=
(
char
*
)
buf
+
total
;
conn
->
last_throttle_bytes
+=
total
;
while
(
total
<
(
int64_t
)
len
&&
conn
->
ctx
->
stop_flag
==
0
)
{
allowed
=
conn
->
throttle
>
(
int64_t
)
len
-
total
?
(
int64_t
)
len
-
total
:
conn
->
throttle
;
if
((
n
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
allowed
))
!=
allowed
)
{
break
;
}
sleep
(
1
);
conn
->
last_throttle_bytes
=
allowed
;
conn
->
last_throttle_time
=
time
(
NULL
);
buf
=
(
char
*
)
buf
+
n
;
total
+=
n
;
if
((
result
=
select
(
maxfd
+
1
,
&
set
,
NULL
,
NULL
,
&
tv
))
>
0
)
{
for
(
i
=
0
;
i
<
n
;
i
++
)
{
if
(
FD_ISSET
(
pfd
[
i
].
fd
,
&
set
))
{
pfd
[
i
].
revents
=
POLLIN
;
}
}
}
else
{
total
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
len
);
}
return
(
int
)
total
;
return
result
;
}
#endif // HAVE_POLL
// Print message to buffer. If buffer is large enough to hold the message,
// return buffer. If buffer is to small, allocate large enough buffer on heap,
// and return allocated buffer.
static
int
alloc_vprintf
(
char
**
buf
,
size_t
size
,
const
char
*
fmt
,
va_list
ap
)
{
va_list
ap_copy
;
int
len
;
static
void
set_close_on_exec
(
SOCKET
sock
)
{
(
void
)
SetHandleInformation
((
HANDLE
)
sock
,
HANDLE_FLAG_INHERIT
,
0
);
}
// Windows is not standard-compliant, and vsnprintf() returns -1 if
// buffer is too small. Also, older versions of msvcrt.dll do not have
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
// Therefore, we make two passes: on first pass, get required message length.
// On second pass, actually print the message.
va_copy
(
ap_copy
,
ap
);
len
=
vsnprintf
(
NULL
,
0
,
fmt
,
ap_copy
);
int
mg_start_thread
(
mg_thread_func_t
f
,
void
*
p
)
{
return
(
long
)
_beginthread
((
void
(
__cdecl
*
)(
void
*
))
f
,
0
,
p
)
==
-
1L
?
-
1
:
0
;
}
if
(
len
>
(
int
)
size
&&
(
size
=
len
+
1
)
>
0
&&
(
*
buf
=
(
char
*
)
malloc
(
size
))
==
NULL
)
{
len
=
-
1
;
// Allocation failed, mark failure
}
else
{
va_copy
(
ap_copy
,
ap
);
vsnprintf
(
*
buf
,
size
,
fmt
,
ap_copy
);
}
static
HANDLE
dlopen
(
const
char
*
dll_name
,
int
flags
)
{
wchar_t
wbuf
[
PATH_MAX
];
(
void
)
flags
;
to_unicode
(
dll_name
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
return
LoadLibraryW
(
wbuf
);
}
return
len
;
#if !defined(NO_CGI)
#define SIGKILL 0
static
int
kill
(
pid_t
pid
,
int
sig_num
)
{
(
void
)
TerminateProcess
(
pid
,
sig_num
);
(
void
)
CloseHandle
(
pid
);
return
0
;
}
int
mg_vprintf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
va_list
ap
)
{
char
mem
[
MG_BUF_LEN
],
*
buf
=
mem
;
int
len
;
if
((
len
=
alloc_vprintf
(
&
buf
,
sizeof
(
mem
),
fmt
,
ap
))
>
0
)
{
len
=
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
if
(
buf
!=
mem
&&
buf
!=
NULL
)
{
free
(
buf
);
static
void
trim_trailing_whitespaces
(
char
*
s
)
{
char
*
e
=
s
+
strlen
(
s
)
-
1
;
while
(
e
>
s
&&
isspace
(
*
(
unsigned
char
*
)
e
))
{
*
e
--
=
'\0'
;
}
return
len
;
}
int
mg_printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
va_list
ap
;
va_start
(
ap
,
fmt
);
return
mg_vprintf
(
conn
,
fmt
,
ap
);
}
static
pid_t
spawn_process
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
char
*
envblk
,
char
*
envp
[],
int
fdin
,
int
fdout
,
const
char
*
dir
)
{
HANDLE
me
;
char
*
interp
,
full_interp
[
PATH_MAX
],
full_dir
[
PATH_MAX
],
cmdline
[
PATH_MAX
],
buf
[
PATH_MAX
];
FILE
*
fp
;
STARTUPINFOA
si
;
PROCESS_INFORMATION
pi
=
{
0
};
static
int
mg_chunked_printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
char
mem
[
MG_BUF_LEN
],
*
buf
=
mem
;
int
len
;
(
void
)
envp
;
va_list
ap
;
va_start
(
ap
,
fmt
);
if
((
len
=
alloc_vprintf
(
&
buf
,
sizeof
(
mem
),
fmt
,
ap
))
>
0
)
{
len
=
mg_printf
(
conn
,
"%X
\r\n
%s
\r\n
"
,
len
,
buf
);
}
memset
(
&
si
,
0
,
sizeof
(
si
));
si
.
cb
=
sizeof
(
si
);
if
(
buf
!=
mem
&&
buf
!=
NULL
)
{
free
(
buf
)
;
}
// TODO(lsm): redirect CGI errors to the error log file
si
.
dwFlags
=
STARTF_USESTDHANDLES
|
STARTF_USESHOWWINDOW
;
si
.
wShowWindow
=
SW_HIDE
;
return
len
;
}
me
=
GetCurrentProcess
();
DuplicateHandle
(
me
,
(
HANDLE
)
_get_osfhandle
(
fdin
),
me
,
&
si
.
hStdInput
,
0
,
TRUE
,
DUPLICATE_SAME_ACCESS
);
DuplicateHandle
(
me
,
(
HANDLE
)
_get_osfhandle
(
fdout
),
me
,
&
si
.
hStdOutput
,
0
,
TRUE
,
DUPLICATE_SAME_ACCESS
);
int
mg_url_decode
(
const
char
*
src
,
int
src_len
,
char
*
dst
,
int
dst_len
,
int
is_form_url_encoded
)
{
i
nt
i
,
j
,
a
,
b
;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
// If CGI file is a script, try to read the interpreter line
interp
=
conn
->
ctx
->
config
[
CGI_INTERPRETER
];
i
f
(
interp
==
NULL
)
{
buf
[
0
]
=
buf
[
1
]
=
'\0'
;
for
(
i
=
j
=
0
;
i
<
src_len
&&
j
<
dst_len
-
1
;
i
++
,
j
++
)
{
if
(
src
[
i
]
==
'%'
&&
i
<
src_len
-
2
&&
isxdigit
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
1
))
&&
isxdigit
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
2
)))
{
a
=
tolower
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
1
)
);
b
=
tolower
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
2
))
;
dst
[
j
]
=
(
char
)
((
HEXTOI
(
a
)
<<
4
)
|
HEXTOI
(
b
));
i
+=
2
;
}
else
if
(
is_form_url_encoded
&&
src
[
i
]
==
'+
'
)
{
dst
[
j
]
=
' '
;
// Read the first line of the script into the buffer
snprintf
(
cmdline
,
sizeof
(
cmdline
),
"%s%c%s"
,
dir
,
'/'
,
prog
);
if
((
fp
=
mg_fopen
(
cmdline
,
"r"
))
!=
NULL
)
{
fgets
(
buf
,
sizeof
(
buf
),
fp
);
fclose
(
fp
);
b
uf
[
sizeof
(
buf
)
-
1
]
=
'\0'
;
}
if
(
buf
[
0
]
==
'#'
&&
buf
[
1
]
==
'!
'
)
{
trim_trailing_whitespaces
(
buf
+
2
)
;
}
else
{
dst
[
j
]
=
src
[
i
]
;
buf
[
2
]
=
'\0'
;
}
interp
=
buf
+
2
;
}
dst
[
j
]
=
'\0'
;
// Null-terminate the destination
if
(
interp
[
0
]
!=
'\0'
)
{
GetFullPathNameA
(
interp
,
sizeof
(
full_interp
),
full_interp
,
NULL
);
interp
=
full_interp
;
}
GetFullPathNameA
(
dir
,
sizeof
(
full_dir
),
full_dir
,
NULL
);
return
i
>=
src_len
?
j
:
-
1
;
}
mg_snprintf
(
cmdline
,
sizeof
(
cmdline
),
"%s%s
\"
%s
\\
%s
\"
"
,
interp
,
interp
[
0
]
==
'\0'
?
""
:
" "
,
full_dir
,
prog
);
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
char
*
dst
,
size_t
dst_len
)
{
const
char
*
p
,
*
e
,
*
s
;
size_t
name_len
;
int
len
;
DEBUG_TRACE
((
"Running [%s]"
,
cmdline
));
if
(
CreateProcessA
(
NULL
,
cmdline
,
NULL
,
NULL
,
TRUE
,
CREATE_NEW_PROCESS_GROUP
,
envblk
,
NULL
,
&
si
,
&
pi
)
==
0
)
{
cry
(
conn
,
"%s: CreateProcess(%s): %ld"
,
__func__
,
cmdline
,
ERRNO
);
pi
.
hProcess
=
(
pid_t
)
-
1
;
}
if
(
dst
==
NULL
||
dst_len
==
0
)
{
len
=
-
2
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
(
void
)
CloseHandle
(
si
.
hStdOutput
);
(
void
)
CloseHandle
(
si
.
hStdInput
);
(
void
)
CloseHandle
(
pi
.
hThread
);
// data is "var1=val1&var2=val2...". Find variable first
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
return
(
pid_t
)
pi
.
hProcess
;
}
#endif // !NO_CGI
// Point p to variable value
p
+=
name_len
+
1
;
static
int
set_non_blocking_mode
(
SOCKET
sock
)
{
unsigned
long
on
=
1
;
return
ioctlsocket
(
sock
,
FIONBIO
,
&
on
);
}
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
assert
(
s
>=
p
);
#else
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
)
{
struct
stat
st
;
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
filep
->
modification_time
=
(
time_t
)
0
;
if
(
stat
(
path
,
&
st
)
==
0
)
{
filep
->
size
=
st
.
st_size
;
filep
->
modification_time
=
st
.
st_mtime
;
filep
->
is_directory
=
S_ISDIR
(
st
.
st_mode
);
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
// See https://github.com/cesanta/mongoose/issues/109
// Some filesystems report modification time as 0. Artificially
// bump it up to mark mg_stat() success.
if
(
filep
->
modification_time
==
(
time_t
)
0
)
{
filep
->
modification_time
=
(
time_t
)
1
;
}
}
return
len
;
return
filep
->
modification_time
!=
(
time_t
)
0
;
}
int
mg_get_cookie
(
const
char
*
cookie_header
,
const
char
*
var_name
,
char
*
dst
,
size_t
dst_size
)
{
const
char
*
s
,
*
p
,
*
end
;
int
name_len
,
len
=
-
1
;
static
void
set_close_on_exec
(
int
fd
)
{
fcntl
(
fd
,
F_SETFD
,
FD_CLOEXEC
);
}
if
(
dst
==
NULL
||
dst_size
==
0
)
{
len
=
-
2
;
}
else
if
(
var_name
==
NULL
||
(
s
=
cookie_header
)
==
NULL
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
int
mg_start_thread
(
mg_thread_func_t
func
,
void
*
param
)
{
pthread_t
thread_id
;
pthread_attr_t
attr
;
int
result
;
(
void
)
pthread_attr_init
(
&
attr
);
(
void
)
pthread_attr_setdetachstate
(
&
attr
,
PTHREAD_CREATE_DETACHED
);
#if USE_STACK_SIZE > 1
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
(
void
)
pthread_attr_setstacksize
(
&
attr
,
USE_STACK_SIZE
);
#endif
result
=
pthread_create
(
&
thread_id
,
&
attr
,
func
,
param
);
pthread_attr_destroy
(
&
attr
);
return
result
;
}
#ifndef NO_CGI
static
pid_t
spawn_process
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
char
*
envblk
,
char
*
envp
[],
int
fdin
,
int
fdout
,
const
char
*
dir
)
{
pid_t
pid
;
const
char
*
interp
;
(
void
)
envblk
;
if
((
pid
=
fork
())
==
-
1
)
{
// Parent
send_http_error
(
conn
,
500
,
http_500_error
,
"fork(): %s"
,
strerror
(
ERRNO
));
}
else
if
(
pid
==
0
)
{
// Child
if
(
chdir
(
dir
)
!=
0
)
{
cry
(
conn
,
"%s: chdir(%s): %s"
,
__func__
,
dir
,
strerror
(
ERRNO
));
}
else
if
(
dup2
(
fdin
,
0
)
==
-
1
)
{
cry
(
conn
,
"%s: dup2(%d, 0): %s"
,
__func__
,
fdin
,
strerror
(
ERRNO
));
}
else
if
(
dup2
(
fdout
,
1
)
==
-
1
)
{
cry
(
conn
,
"%s: dup2(%d, 1): %s"
,
__func__
,
fdout
,
strerror
(
ERRNO
));
}
else
{
name_len
=
(
int
)
strlen
(
var_name
);
end
=
s
+
strlen
(
s
);
dst
[
0
]
=
'\0'
;
// Not redirecting stderr to stdout, to avoid output being littered
// with the error messages.
(
void
)
close
(
fdin
);
(
void
)
close
(
fdout
);
for
(;
(
s
=
mg_strcasestr
(
s
,
var_name
))
!=
NULL
;
s
+=
name_len
)
{
if
(
s
[
name_len
]
==
'='
)
{
s
+=
name_len
+
1
;
if
((
p
=
strchr
(
s
,
' '
))
==
NULL
)
p
=
end
;
if
(
p
[
-
1
]
==
';'
)
p
--
;
if
(
*
s
==
'"'
&&
p
[
-
1
]
==
'"'
&&
p
>
s
+
1
)
{
s
++
;
p
--
;
}
if
((
size_t
)
(
p
-
s
)
<
dst_size
)
{
len
=
p
-
s
;
mg_strlcpy
(
dst
,
s
,
(
size_t
)
len
+
1
);
// After exec, all signal handlers are restored to their default values,
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
// implementation, SIGCHLD's handler will leave unchanged after exec
// if it was set to be ignored. Restore it to default action.
signal
(
SIGCHLD
,
SIG_DFL
);
interp
=
conn
->
ctx
->
config
[
CGI_INTERPRETER
];
if
(
interp
==
NULL
)
{
(
void
)
execle
(
prog
,
prog
,
NULL
,
envp
);
cry
(
conn
,
"%s: execle(%s): %s"
,
__func__
,
prog
,
strerror
(
ERRNO
));
}
else
{
len
=
-
3
;
}
break
;
(
void
)
execle
(
interp
,
interp
,
prog
,
NULL
,
envp
)
;
cry
(
conn
,
"%s: execle(%s %s): %s"
,
__func__
,
interp
,
prog
,
strerror
(
ERRNO
))
;
}
}
exit
(
EXIT_FAILURE
);
}
return
len
;
return
pid
;
}
#endif // !NO_CGI
// Return 1 if real file has been found, 0 otherwise
static
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_len
,
struct
file
*
filep
)
{
struct
vec
a
,
b
;
const
char
*
rewrite
,
*
uri
=
conn
->
request_info
.
uri
,
*
root
=
conn
->
ctx
->
config
[
DOCUMENT_ROOT
];
char
*
p
;
int
match_len
;
char
gz_path
[
PATH_MAX
];
char
const
*
accept_encoding
;
static
int
set_non_blocking_mode
(
SOCKET
sock
)
{
int
flags
;
// No filesystem access
if
(
root
==
NULL
)
{
return
0
;
}
flags
=
fcntl
(
sock
,
F_GETFL
,
0
);
(
void
)
fcntl
(
sock
,
F_SETFL
,
flags
|
O_NONBLOCK
);
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf
(
buf
,
buf_len
-
1
,
"%s%s"
,
root
,
uri
);
return
0
;
}
#endif // _WIN32
rewrite
=
conn
->
ctx
->
config
[
REWRITE
];
while
((
rewrite
=
next_option
(
rewrite
,
&
a
,
&
b
))
!=
NULL
)
{
if
((
match_len
=
match_prefix
(
a
.
ptr
,
a
.
len
,
uri
))
>
0
)
{
mg_snprintf
(
buf
,
buf_len
-
1
,
"%.*s%s"
,
(
int
)
b
.
len
,
b
.
ptr
,
uri
+
match_len
);
break
;
}
}
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static
int64_t
push
(
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
,
const
char
*
buf
,
int64_t
len
)
{
int64_t
sent
;
int
n
,
k
;
if
(
mg_stat
(
buf
,
filep
))
{
return
1
;
}
(
void
)
ssl
;
// Get rid of warning
sent
=
0
;
while
(
sent
<
len
)
{
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if
((
accept_encoding
=
mg_get_header
(
conn
,
"Accept-Encoding"
))
!=
NULL
)
{
if
(
strstr
(
accept_encoding
,
"gzip"
)
!=
NULL
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
buf
);
if
(
mg_stat
(
gz_path
,
filep
))
{
filep
->
gzipped
=
1
;
return
1
;
}
}
}
// How many bytes we send in this iteration
k
=
len
-
sent
>
INT_MAX
?
INT_MAX
:
(
int
)
(
len
-
sent
);
// Support PATH_INFO for CGI scripts.
for
(
p
=
buf
+
strlen
(
root
==
NULL
?
""
:
root
);
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'/'
)
{
*
p
=
'\0'
;
if
(
match_prefix
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
]),
buf
)
>
0
&&
mg_stat
(
buf
,
filep
))
{
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn
->
path_info
=
p
+
1
;
memmove
(
p
+
2
,
p
+
1
,
strlen
(
p
+
1
)
+
1
);
// +1 is for trailing \0
p
[
1
]
=
'/'
;
return
1
;
if
(
ssl
!=
NULL
)
{
n
=
SSL_write
(
ssl
,
buf
+
sent
,
k
);
}
else
if
(
fp
!=
NULL
)
{
n
=
(
int
)
fwrite
(
buf
+
sent
,
1
,
(
size_t
)
k
,
fp
);
if
(
ferror
(
fp
))
n
=
-
1
;
}
else
{
*
p
=
'/'
;
}
n
=
send
(
sock
,
buf
+
sent
,
(
size_t
)
k
,
MSG_NOSIGNAL
);
}
if
(
n
<=
0
)
break
;
sent
+=
n
;
}
return
0
;
return
sent
;
}
// Check whether full request is buffered. Return:
// -1 if request is malformed
// 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n
static
int
get_request_len
(
const
char
*
buf
,
int
buf_len
)
{
int
i
;
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success.
static
int
pull
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
nread
;
for
(
i
=
0
;
i
<
buf_len
;
i
++
)
{
// Control characters are not allowed but >=128 is.
//
Abort scan as soon as one malformed character is found;
//
don't let subsequent \r\n\r\n win us over anyhow
if
(
!
isprint
(
*
(
const
unsigned
char
*
)
&
buf
[
i
])
&&
buf
[
i
]
!=
'\r'
&&
buf
[
i
]
!=
'\n'
&&
*
(
const
unsigned
char
*
)
&
buf
[
i
]
<
128
)
{
return
-
1
;
}
else
if
(
buf
[
i
]
==
'\n'
&&
i
+
1
<
buf_len
&&
buf
[
i
+
1
]
==
'\n'
)
{
return
i
+
2
;
}
else
if
(
buf
[
i
]
==
'\n'
&&
i
+
2
<
buf_len
&&
buf
[
i
+
1
]
==
'\r'
&&
buf
[
i
+
2
]
==
'\n'
)
{
return
i
+
3
;
if
(
len
<=
0
)
return
0
;
if
(
fp
!=
NULL
)
{
//
Use read() instead of fread(), because if we're reading from the CGI
//
pipe, fread() may block until IO buffer is filled up. We cannot afford
// to block and must pass all read bytes immediately to the client.
nread
=
read
(
fileno
(
fp
),
buf
,
(
size_t
)
len
);
#ifndef NO_SSL
}
else
if
(
conn
->
ssl
!=
NULL
)
{
nread
=
SSL_read
(
conn
->
ssl
,
buf
,
len
)
;
#endif
}
else
{
nread
=
recv
(
conn
->
client
.
sock
,
buf
,
(
size_t
)
len
,
0
)
;
}
if
(
nread
>
0
)
{
conn
->
num_bytes_read
+=
nread
;
}
return
0
;
return
conn
->
ctx
->
stop_flag
?
-
1
:
nread
;
}
// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static
void
remove_double_dots_and_double_slashes
(
char
*
s
)
{
char
*
p
=
s
;
static
int
pull_all
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
n
,
nread
=
0
;
while
(
*
s
!=
'\0'
)
{
*
p
++
=
*
s
++
;
if
(
s
[
-
1
]
==
'/'
||
s
[
-
1
]
==
'\\'
)
{
// Skip all following slashes, backslashes and double-dots
while
(
s
[
0
]
!=
'\0'
)
{
if
(
s
[
0
]
==
'/'
||
s
[
0
]
==
'\\'
)
{
s
++
;
}
else
if
(
s
[
0
]
==
'.'
&&
s
[
1
]
==
'.'
)
{
s
+=
2
;
}
else
{
while
(
len
>
0
&&
conn
->
ctx
->
stop_flag
==
0
)
{
n
=
pull
(
fp
,
conn
,
buf
+
nread
,
len
);
if
(
n
<
0
)
{
nread
=
n
;
// Propagate the error
break
;
}
else
if
(
n
==
0
)
{
break
;
// No more data to read
}
else
{
nread
+=
n
;
len
-=
n
;
}
}
}
}
*
p
=
'\0'
;
}
static
const
struct
{
const
char
*
extension
;
size_t
ext_len
;
const
char
*
mime_type
;
}
builtin_mime_types
[]
=
{
{
".html"
,
5
,
"text/html"
},
{
".htm"
,
4
,
"text/html"
},
{
".shtm"
,
5
,
"text/html"
},
{
".shtml"
,
6
,
"text/html"
},
{
".css"
,
4
,
"text/css"
},
{
".js"
,
3
,
"application/x-javascript"
},
{
".ico"
,
4
,
"image/x-icon"
},
{
".gif"
,
4
,
"image/gif"
},
{
".jpg"
,
4
,
"image/jpeg"
},
{
".jpeg"
,
5
,
"image/jpeg"
},
{
".png"
,
4
,
"image/png"
},
{
".svg"
,
4
,
"image/svg+xml"
},
{
".txt"
,
4
,
"text/plain"
},
{
".torrent"
,
8
,
"application/x-bittorrent"
},
{
".wav"
,
4
,
"audio/x-wav"
},
{
".mp3"
,
4
,
"audio/x-mp3"
},
{
".mid"
,
4
,
"audio/mid"
},
{
".m3u"
,
4
,
"audio/x-mpegurl"
},
{
".ogg"
,
4
,
"application/ogg"
},
{
".ram"
,
4
,
"audio/x-pn-realaudio"
},
{
".xml"
,
4
,
"text/xml"
},
{
".json"
,
5
,
"text/json"
},
{
".xslt"
,
5
,
"application/xml"
},
{
".xsl"
,
4
,
"application/xml"
},
{
".ra"
,
3
,
"audio/x-pn-realaudio"
},
{
".doc"
,
4
,
"application/msword"
},
{
".exe"
,
4
,
"application/octet-stream"
},
{
".zip"
,
4
,
"application/x-zip-compressed"
},
{
".xls"
,
4
,
"application/excel"
},
{
".tgz"
,
4
,
"application/x-tar-gz"
},
{
".tar"
,
4
,
"application/x-tar"
},
{
".gz"
,
3
,
"application/x-gunzip"
},
{
".arj"
,
4
,
"application/x-arj-compressed"
},
{
".rar"
,
4
,
"application/x-arj-compressed"
},
{
".rtf"
,
4
,
"application/rtf"
},
{
".pdf"
,
4
,
"application/pdf"
},
{
".swf"
,
4
,
"application/x-shockwave-flash"
},
{
".mpg"
,
4
,
"video/mpeg"
},
{
".webm"
,
5
,
"video/webm"
},
{
".mpeg"
,
5
,
"video/mpeg"
},
{
".mov"
,
4
,
"video/quicktime"
},
{
".mp4"
,
4
,
"video/mp4"
},
{
".m4v"
,
4
,
"video/x-m4v"
},
{
".asf"
,
4
,
"video/x-ms-asf"
},
{
".avi"
,
4
,
"video/x-msvideo"
},
{
".bmp"
,
4
,
"image/bmp"
},
{
".ttf"
,
4
,
"application/x-font-ttf"
},
{
NULL
,
0
,
NULL
}
};
const
char
*
mg_get_builtin_mime_type
(
const
char
*
path
)
{
const
char
*
ext
;
size_t
i
,
path_len
;
return
nread
;
}
path_len
=
strlen
(
path
);
int
mg_read
(
struct
mg_connection
*
conn
,
void
*
buf
,
int
len
)
{
int
n
,
buffered_len
,
nread
=
0
;
int64_t
left
;
for
(
i
=
0
;
builtin_mime_types
[
i
].
extension
!=
NULL
;
i
++
)
{
ext
=
path
+
(
path_len
-
builtin_mime_types
[
i
].
ext_len
);
if
(
path_len
>
builtin_mime_types
[
i
].
ext_len
&&
mg_strcasecmp
(
ext
,
builtin_mime_types
[
i
].
extension
)
==
0
)
{
return
builtin_mime_types
[
i
].
mime_type
;
}
// If Content-Length is not set, read until socket is closed
if
(
conn
->
content_len
<=
0
)
{
conn
->
content_len
=
INT64_MAX
;
conn
->
must_close
=
1
;
}
return
"text/plain"
;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static
void
get_mime_type
(
struct
mg_context
*
ctx
,
const
char
*
path
,
struct
vec
*
vec
)
{
struct
vec
ext_vec
,
mime_vec
;
const
char
*
list
,
*
ext
;
size_t
path_len
;
// First, check for data buffered in conn->buf by read_request().
if
(
len
>
0
&&
(
buffered_len
=
conn
->
data_len
-
conn
->
request_len
)
>
0
)
{
char
*
body
=
conn
->
buf
+
conn
->
request_len
;
if
(
buffered_len
>
len
)
buffered_len
=
len
;
if
(
buffered_len
>
conn
->
content_len
)
buffered_len
=
(
int
)
conn
->
content_len
;
path_len
=
strlen
(
path
);
memcpy
(
buf
,
body
,
(
size_t
)
buffered_len
);
memmove
(
body
,
body
+
buffered_len
,
&
conn
->
buf
[
conn
->
data_len
]
-
&
body
[
buffered_len
]);
len
-=
buffered_len
;
conn
->
data_len
-=
buffered_len
;
nread
+=
buffered_len
;
}
// Scan user-defined mime types first, in case user wants to
// override default mime types.
list
=
ctx
->
config
[
EXTRA_MIME_TYPES
];
while
((
list
=
next_option
(
list
,
&
ext_vec
,
&
mime_vec
))
!=
NULL
)
{
// ext now points to the path suffix
ext
=
path
+
path_len
-
ext_vec
.
len
;
if
(
mg_strncasecmp
(
ext
,
ext_vec
.
ptr
,
ext_vec
.
len
)
==
0
)
{
*
vec
=
mime_vec
;
return
;
// Read data from the socket.
if
(
len
>
0
&&
(
left
=
left_to_read
(
conn
))
>
0
)
{
if
(
left
<
len
)
{
len
=
(
int
)
left
;
}
n
=
pull_all
(
NULL
,
conn
,
(
char
*
)
buf
+
nread
,
(
int
)
len
);
nread
=
n
>=
0
?
nread
+
n
:
n
;
}
vec
->
ptr
=
mg_get_builtin_mime_type
(
path
);
vec
->
len
=
strlen
(
vec
->
ptr
);
}
static
int
is_big_endian
(
void
)
{
static
const
int
n
=
1
;
return
((
char
*
)
&
n
)[
0
]
==
0
;
return
nread
;
}
#ifndef HAVE_MD5
typedef
struct
MD5Context
{
uint32_t
buf
[
4
];
uint32_t
bits
[
2
];
unsigned
char
in
[
64
];
}
MD5_CTX
;
static
void
byteReverse
(
unsigned
char
*
buf
,
unsigned
longs
)
{
uint32_t
t
;
int
mg_write
(
struct
mg_connection
*
conn
,
const
void
*
buf
,
int
len
)
{
time_t
now
;
int64_t
n
,
total
,
allowed
;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if
(
is_big_endian
())
{
do
{
t
=
(
uint32_t
)
((
unsigned
)
buf
[
3
]
<<
8
|
buf
[
2
])
<<
16
|
((
unsigned
)
buf
[
1
]
<<
8
|
buf
[
0
]);
*
(
uint32_t
*
)
buf
=
t
;
buf
+=
4
;
}
while
(
--
longs
);
if
(
conn
->
throttle
>
0
)
{
if
((
now
=
time
(
NULL
))
!=
conn
->
last_throttle_time
)
{
conn
->
last_throttle_time
=
now
;
conn
->
last_throttle_bytes
=
0
;
}
allowed
=
conn
->
throttle
-
conn
->
last_throttle_bytes
;
if
(
allowed
>
(
int64_t
)
len
)
{
allowed
=
len
;
}
if
((
total
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
allowed
))
==
allowed
)
{
buf
=
(
char
*
)
buf
+
total
;
conn
->
last_throttle_bytes
+=
total
;
while
(
total
<
(
int64_t
)
len
&&
conn
->
ctx
->
stop_flag
==
0
)
{
allowed
=
conn
->
throttle
>
(
int64_t
)
len
-
total
?
(
int64_t
)
len
-
total
:
conn
->
throttle
;
if
((
n
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
allowed
))
!=
allowed
)
{
break
;
}
sleep
(
1
);
conn
->
last_throttle_bytes
=
allowed
;
conn
->
last_throttle_time
=
time
(
NULL
);
buf
=
(
char
*
)
buf
+
n
;
total
+=
n
;
}
}
}
else
{
total
=
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
len
);
}
return
(
int
)
total
;
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
// Print message to buffer. If buffer is large enough to hold the message,
// return buffer. If buffer is to small, allocate large enough buffer on heap,
// and return allocated buffer.
static
int
alloc_vprintf
(
char
**
buf
,
size_t
size
,
const
char
*
fmt
,
va_list
ap
)
{
va_list
ap_copy
;
int
len
;
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Windows is not standard-compliant, and vsnprintf() returns -1 if
// buffer is too small. Also, older versions of msvcrt.dll do not have
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
// Therefore, we make two passes: on first pass, get required message length.
// On second pass, actually print the message.
va_copy
(
ap_copy
,
ap
);
len
=
vsnprintf
(
NULL
,
0
,
fmt
,
ap_copy
);
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static
void
MD5Init
(
MD5_CTX
*
ctx
)
{
ctx
->
buf
[
0
]
=
0x67452301
;
ctx
->
buf
[
1
]
=
0xefcdab89
;
ctx
->
buf
[
2
]
=
0x98badcfe
;
ctx
->
buf
[
3
]
=
0x10325476
;
if
(
len
>
(
int
)
size
&&
(
size
=
len
+
1
)
>
0
&&
(
*
buf
=
(
char
*
)
malloc
(
size
))
==
NULL
)
{
len
=
-
1
;
// Allocation failed, mark failure
}
else
{
va_copy
(
ap_copy
,
ap
);
vsnprintf
(
*
buf
,
size
,
fmt
,
ap_copy
);
}
ctx
->
bits
[
0
]
=
0
;
ctx
->
bits
[
1
]
=
0
;
return
len
;
}
static
void
MD5Transform
(
uint32_t
buf
[
4
],
uint32_t
const
in
[
16
])
{
register
uint32_t
a
,
b
,
c
,
d
;
a
=
buf
[
0
];
b
=
buf
[
1
];
c
=
buf
[
2
];
d
=
buf
[
3
];
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xd76aa478
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
1
]
+
0xe8c7b756
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x242070db
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
3
]
+
0xc1bdceee
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf57c0faf
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
5
]
+
0x4787c62a
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa8304613
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
7
]
+
0xfd469501
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x698098d8
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
9
]
+
0x8b44f7af
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffff5bb1
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
11
]
+
0x895cd7be
,
22
);
MD5STEP
(
F1
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x6b901122
,
7
);
MD5STEP
(
F1
,
d
,
a
,
b
,
c
,
in
[
13
]
+
0xfd987193
,
12
);
MD5STEP
(
F1
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xa679438e
,
17
);
MD5STEP
(
F1
,
b
,
c
,
d
,
a
,
in
[
15
]
+
0x49b40821
,
22
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xf61e2562
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
6
]
+
0xc040b340
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x265e5a51
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
0
]
+
0xe9b6c7aa
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xd62f105d
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
10
]
+
0x02441453
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0xd8a1e681
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
4
]
+
0xe7d3fbc8
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0x21e1cde6
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
14
]
+
0xc33707d6
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xf4d50d87
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
8
]
+
0x455a14ed
,
20
);
MD5STEP
(
F2
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0xa9e3e905
,
5
);
MD5STEP
(
F2
,
d
,
a
,
b
,
c
,
in
[
2
]
+
0xfcefa3f8
,
9
);
MD5STEP
(
F2
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0x676f02d9
,
14
);
MD5STEP
(
F2
,
b
,
c
,
d
,
a
,
in
[
12
]
+
0x8d2a4c8a
,
20
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
5
]
+
0xfffa3942
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
8
]
+
0x8771f681
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
11
]
+
0x6d9d6122
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
14
]
+
0xfde5380c
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
1
]
+
0xa4beea44
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
4
]
+
0x4bdecfa9
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
7
]
+
0xf6bb4b60
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
10
]
+
0xbebfbc70
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
13
]
+
0x289b7ec6
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
0
]
+
0xeaa127fa
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
3
]
+
0xd4ef3085
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
6
]
+
0x04881d05
,
23
);
MD5STEP
(
F3
,
a
,
b
,
c
,
d
,
in
[
9
]
+
0xd9d4d039
,
4
);
MD5STEP
(
F3
,
d
,
a
,
b
,
c
,
in
[
12
]
+
0xe6db99e5
,
11
);
MD5STEP
(
F3
,
c
,
d
,
a
,
b
,
in
[
15
]
+
0x1fa27cf8
,
16
);
MD5STEP
(
F3
,
b
,
c
,
d
,
a
,
in
[
2
]
+
0xc4ac5665
,
23
);
int
mg_vprintf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
va_list
ap
)
{
char
mem
[
MG_BUF_LEN
],
*
buf
=
mem
;
int
len
;
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
0
]
+
0xf4292244
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
7
]
+
0x432aff97
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
14
]
+
0xab9423a7
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
5
]
+
0xfc93a039
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
12
]
+
0x655b59c3
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
3
]
+
0x8f0ccc92
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
10
]
+
0xffeff47d
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
1
]
+
0x85845dd1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
8
]
+
0x6fa87e4f
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
15
]
+
0xfe2ce6e0
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
6
]
+
0xa3014314
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
13
]
+
0x4e0811a1
,
21
);
MD5STEP
(
F4
,
a
,
b
,
c
,
d
,
in
[
4
]
+
0xf7537e82
,
6
);
MD5STEP
(
F4
,
d
,
a
,
b
,
c
,
in
[
11
]
+
0xbd3af235
,
10
);
MD5STEP
(
F4
,
c
,
d
,
a
,
b
,
in
[
2
]
+
0x2ad7d2bb
,
15
);
MD5STEP
(
F4
,
b
,
c
,
d
,
a
,
in
[
9
]
+
0xeb86d391
,
21
);
if
((
len
=
alloc_vprintf
(
&
buf
,
sizeof
(
mem
),
fmt
,
ap
))
>
0
)
{
len
=
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
if
(
buf
!=
mem
&&
buf
!=
NULL
)
{
free
(
buf
);
}
buf
[
0
]
+=
a
;
buf
[
1
]
+=
b
;
buf
[
2
]
+=
c
;
buf
[
3
]
+=
d
;
return
len
;
}
static
void
MD5Update
(
MD5_CTX
*
ctx
,
unsigned
char
const
*
buf
,
unsigned
len
)
{
uint32_t
t
;
t
=
ctx
->
bits
[
0
];
if
((
ctx
->
bits
[
0
]
=
t
+
((
uint32_t
)
len
<<
3
))
<
t
)
ctx
->
bits
[
1
]
++
;
ctx
->
bits
[
1
]
+=
len
>>
29
;
t
=
(
t
>>
3
)
&
0x3f
;
int
mg_printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
va_list
ap
;
va_start
(
ap
,
fmt
);
return
mg_vprintf
(
conn
,
fmt
,
ap
);
}
if
(
t
)
{
unsigned
char
*
p
=
(
unsigned
char
*
)
ctx
->
in
+
t
;
static
int
mg_chunked_printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
char
mem
[
MG_BUF_LEN
],
*
buf
=
mem
;
int
len
;
t
=
64
-
t
;
if
(
len
<
t
)
{
memcpy
(
p
,
buf
,
len
);
return
;
}
memcpy
(
p
,
buf
,
t
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
t
;
len
-=
t
;
va_list
ap
;
va_start
(
ap
,
fmt
);
if
((
len
=
alloc_vprintf
(
&
buf
,
sizeof
(
mem
),
fmt
,
ap
))
>
0
)
{
len
=
mg_printf
(
conn
,
"%X
\r\n
%s
\r\n
"
,
len
,
buf
);
}
while
(
len
>=
64
)
{
memcpy
(
ctx
->
in
,
buf
,
64
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
buf
+=
64
;
len
-=
64
;
if
(
buf
!=
mem
&&
buf
!=
NULL
)
{
free
(
buf
);
}
memcpy
(
ctx
->
in
,
buf
,
len
)
;
return
len
;
}
static
void
MD5Final
(
unsigned
char
digest
[
16
],
MD5_CTX
*
ctx
)
{
unsigned
count
;
unsigned
char
*
p
;
uint32_t
*
a
;
count
=
(
ctx
->
bits
[
0
]
>>
3
)
&
0x3F
;
int
mg_url_decode
(
const
char
*
src
,
int
src_len
,
char
*
dst
,
int
dst_len
,
int
is_form_url_encoded
)
{
int
i
,
j
,
a
,
b
;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
p
=
ctx
->
in
+
count
;
*
p
++
=
0x80
;
count
=
64
-
1
-
count
;
if
(
count
<
8
)
{
memset
(
p
,
0
,
count
);
byteReverse
(
ctx
->
in
,
16
);
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
memset
(
ctx
->
in
,
0
,
56
);
for
(
i
=
j
=
0
;
i
<
src_len
&&
j
<
dst_len
-
1
;
i
++
,
j
++
)
{
if
(
src
[
i
]
==
'%'
&&
i
<
src_len
-
2
&&
isxdigit
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
1
))
&&
isxdigit
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
2
)))
{
a
=
tolower
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
1
));
b
=
tolower
(
*
(
const
unsigned
char
*
)
(
src
+
i
+
2
));
dst
[
j
]
=
(
char
)
((
HEXTOI
(
a
)
<<
4
)
|
HEXTOI
(
b
));
i
+=
2
;
}
else
if
(
is_form_url_encoded
&&
src
[
i
]
==
'+'
)
{
dst
[
j
]
=
' '
;
}
else
{
memset
(
p
,
0
,
count
-
8
);
dst
[
j
]
=
src
[
i
];
}
}
byteReverse
(
ctx
->
in
,
14
);
a
=
(
uint32_t
*
)
ctx
->
in
;
a
[
14
]
=
ctx
->
bits
[
0
];
a
[
15
]
=
ctx
->
bits
[
1
];
dst
[
j
]
=
'\0'
;
// Null-terminate the destination
MD5Transform
(
ctx
->
buf
,
(
uint32_t
*
)
ctx
->
in
);
byteReverse
((
unsigned
char
*
)
ctx
->
buf
,
4
);
memcpy
(
digest
,
ctx
->
buf
,
16
);
memset
((
char
*
)
ctx
,
0
,
sizeof
(
*
ctx
));
return
i
>=
src_len
?
j
:
-
1
;
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static
void
bin2str
(
char
*
to
,
const
unsigned
char
*
p
,
size_t
len
)
{
static
const
char
*
hex
=
"0123456789abcdef"
;
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
char
*
dst
,
size_t
dst_len
)
{
const
char
*
p
,
*
e
,
*
s
;
size_t
name_len
;
int
len
;
for
(;
len
--
;
p
++
)
{
*
to
++
=
hex
[
p
[
0
]
>>
4
];
*
to
++
=
hex
[
p
[
0
]
&
0x0f
];
}
*
to
=
'\0'
;
}
if
(
dst
==
NULL
||
dst_len
==
0
)
{
len
=
-
2
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char
*
mg_md5
(
char
buf
[
33
],
...)
{
unsigned
char
hash
[
16
];
const
char
*
p
;
va_list
ap
;
MD5_CTX
ctx
;
// data is "var1=val1&var2=val2...". Find variable first
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
MD5Init
(
&
ctx
);
// Point p to variable value
p
+=
name_len
+
1
;
va_start
(
ap
,
buf
);
while
((
p
=
va_arg
(
ap
,
const
char
*
))
!=
NULL
)
{
MD5Update
(
&
ctx
,
(
const
unsigned
char
*
)
p
,
(
unsigned
)
strlen
(
p
));
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
va_end
(
ap
);
MD5Final
(
hash
,
&
ctx
);
bin2str
(
buf
,
hash
,
sizeof
(
hash
));
return
buf
;
}
assert
(
s
>=
p
);
// Check the user's password, return 1 if OK
static
int
check_password
(
const
char
*
method
,
const
char
*
ha1
,
const
char
*
uri
,
const
char
*
nonce
,
const
char
*
nc
,
const
char
*
cnonce
,
const
char
*
qop
,
const
char
*
response
)
{
char
ha2
[
32
+
1
],
expected_response
[
32
+
1
];
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
// Some of the parameters may be NULL
if
(
method
==
NULL
||
nonce
==
NULL
||
nc
==
NULL
||
cnonce
==
NULL
||
qop
==
NULL
||
response
==
NULL
)
{
return
0
;
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if
(
// strcmp(dig->uri, c->ouri) != 0 ||
strlen
(
response
)
!=
32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
)
{
return
0
;
}
mg_md5
(
ha2
,
method
,
":"
,
uri
,
NULL
);
mg_md5
(
expected_response
,
ha1
,
":"
,
nonce
,
":"
,
nc
,
":"
,
cnonce
,
":"
,
qop
,
":"
,
ha2
,
NULL
);
return
mg_strcasecmp
(
response
,
expected_response
)
==
0
;
return
len
;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static
FILE
*
open_auth_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
name
[
PATH_MAX
];
const
char
*
p
,
*
e
,
*
gpass
=
conn
->
ctx
->
config
[
GLOBAL_PASSWORDS_FILE
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
FILE
*
fp
=
NULL
;
int
mg_get_cookie
(
const
char
*
cookie_header
,
const
char
*
var_name
,
char
*
dst
,
size_t
dst_size
)
{
const
char
*
s
,
*
p
,
*
end
;
int
name_len
,
len
=
-
1
;
if
(
gpass
!=
NULL
)
{
// Use global passwords file
if
((
fp
=
mg_fopen
(
gpass
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"fopen(%s): %s"
,
gpass
,
strerror
(
ERRNO
));
if
(
dst
==
NULL
||
dst_size
==
0
)
{
len
=
-
2
;
}
else
if
(
var_name
==
NULL
||
(
s
=
cookie_header
)
==
NULL
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
(
int
)
strlen
(
var_name
);
end
=
s
+
strlen
(
s
);
dst
[
0
]
=
'\0'
;
for
(;
(
s
=
mg_strcasestr
(
s
,
var_name
))
!=
NULL
;
s
+=
name_len
)
{
if
(
s
[
name_len
]
==
'='
)
{
s
+=
name_len
+
1
;
if
((
p
=
strchr
(
s
,
' '
))
==
NULL
)
p
=
end
;
if
(
p
[
-
1
]
==
';'
)
p
--
;
if
(
*
s
==
'"'
&&
p
[
-
1
]
==
'"'
&&
p
>
s
+
1
)
{
s
++
;
p
--
;
}
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
}
else
if
(
mg_stat
(
path
,
&
file
)
&&
file
.
is_directory
)
{
mg_snprintf
(
name
,
sizeof
(
name
),
"%s%c%s"
,
path
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
if
((
size_t
)
(
p
-
s
)
<
dst_size
)
{
len
=
p
-
s
;
mg_strlcpy
(
dst
,
s
,
(
size_t
)
len
+
1
);
}
else
{
// Try to find .htpasswd in requested directory.
for
(
p
=
path
,
e
=
p
+
strlen
(
p
)
-
1
;
e
>
p
;
e
--
)
if
(
e
[
0
]
==
'/'
)
len
=
-
3
;
}
break
;
mg_snprintf
(
name
,
sizeof
(
name
),
"%.*s%c%s"
,
(
int
)
(
e
-
p
),
p
,
'/'
,
PASSWORDS_FILE_NAME
);
fp
=
mg_fopen
(
name
,
"r"
);
}
return
fp
;
}
}
return
len
;
}
//
Parsed Authorization header
st
ruct
ah
{
char
*
user
,
*
uri
,
*
cnonce
,
*
response
,
*
qop
,
*
nc
,
*
nonce
;
}
;
// Return 1 on success. Always initializes the ah structure.
static
int
parse_auth_header
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_size
,
struct
ah
*
ah
)
{
char
*
name
,
*
value
,
*
s
;
c
onst
char
*
auth_header
;
//
Return 1 if real file has been found, 0 otherwise
st
atic
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_len
,
struct
file
*
filep
)
{
struct
vec
a
,
b
;
const
char
*
rewrite
,
*
uri
=
conn
->
request_info
.
uri
,
*
root
=
conn
->
ctx
->
config
[
DOCUMENT_ROOT
];
char
*
p
;
int
match_len
;
char
gz_path
[
PATH_MAX
]
;
c
har
const
*
accept_encoding
;
(
void
)
memset
(
ah
,
0
,
sizeof
(
*
ah
));
if
((
auth_header
=
mg_get_header
(
conn
,
"Authorization"
))
==
NULL
||
mg_strncasecmp
(
auth_header
,
"Digest "
,
7
)
!=
0
)
{
// No filesystem access
if
(
root
==
NULL
)
{
return
0
;
}
// Make modifiable copy of the auth header
(
void
)
mg_strlcpy
(
buf
,
auth_header
+
7
,
buf_size
);
s
=
buf
;
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf
(
buf
,
buf_len
-
1
,
"%s%s"
,
root
,
uri
);
// Parse authorization header
for
(;;)
{
// Gobble initial spaces
while
(
isspace
(
*
(
unsigned
char
*
)
s
))
{
s
++
;
}
name
=
skip_quoted
(
&
s
,
"="
,
" "
,
0
);
// Value is either quote-delimited, or ends at first comma or space.
if
(
s
[
0
]
==
'\"'
)
{
s
++
;
value
=
skip_quoted
(
&
s
,
"
\"
"
,
" "
,
'\\'
);
if
(
s
[
0
]
==
','
)
{
s
++
;
rewrite
=
conn
->
ctx
->
config
[
REWRITE
];
while
((
rewrite
=
next_option
(
rewrite
,
&
a
,
&
b
))
!=
NULL
)
{
if
((
match_len
=
match_prefix
(
a
.
ptr
,
a
.
len
,
uri
))
>
0
)
{
mg_snprintf
(
buf
,
buf_len
-
1
,
"%.*s%s"
,
(
int
)
b
.
len
,
b
.
ptr
,
uri
+
match_len
);
break
;
}
}
else
{
value
=
skip_quoted
(
&
s
,
", "
,
" "
,
0
);
// IE uses commas, FF uses spaces
}
if
(
*
name
==
'\0'
)
{
break
;
if
(
mg_stat
(
buf
,
filep
))
{
return
1
;
}
if
(
!
strcmp
(
name
,
"username"
))
{
ah
->
user
=
value
;
}
else
if
(
!
strcmp
(
name
,
"cnonce"
))
{
ah
->
cnonce
=
value
;
}
else
if
(
!
strcmp
(
name
,
"response"
))
{
ah
->
response
=
value
;
}
else
if
(
!
strcmp
(
name
,
"uri"
))
{
ah
->
uri
=
value
;
}
else
if
(
!
strcmp
(
name
,
"qop"
))
{
ah
->
qop
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nc"
))
{
ah
->
nc
=
value
;
}
else
if
(
!
strcmp
(
name
,
"nonce"
))
{
ah
->
nonce
=
value
;
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if
((
accept_encoding
=
mg_get_header
(
conn
,
"Accept-Encoding"
))
!=
NULL
)
{
if
(
strstr
(
accept_encoding
,
"gzip"
)
!=
NULL
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
buf
);
if
(
mg_stat
(
gz_path
,
filep
))
{
filep
->
gzipped
=
1
;
return
1
;
}
}
}
// CGI needs it as REMOTE_USER
if
(
ah
->
user
!=
NULL
)
{
conn
->
request_info
.
remote_user
=
mg_strdup
(
ah
->
user
);
// Support PATH_INFO for CGI scripts.
for
(
p
=
buf
+
strlen
(
root
==
NULL
?
""
:
root
);
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'/'
)
{
*
p
=
'\0'
;
if
(
match_prefix
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
]),
buf
)
>
0
&&
mg_stat
(
buf
,
filep
))
{
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn
->
path_info
=
p
+
1
;
memmove
(
p
+
2
,
p
+
1
,
strlen
(
p
+
1
)
+
1
);
// +1 is for trailing \0
p
[
1
]
=
'/'
;
return
1
;
}
else
{
return
0
;
*
p
=
'/'
;
}
}
}
return
1
;
return
0
;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static
int
authorize
(
struct
mg_connection
*
conn
,
FILE
*
fp
)
{
struct
ah
ah
;
char
line
[
256
],
f_user
[
256
],
ha1
[
256
],
f_domain
[
256
],
buf
[
MG_BUF_LEN
];
if
(
!
parse_auth_header
(
conn
,
buf
,
sizeof
(
buf
),
&
ah
))
{
return
0
;
}
// Check whether full request is buffered. Return:
// -1 if request is malformed
// 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n
static
int
get_request_len
(
const
char
*
buf
,
int
buf_len
)
{
int
i
;
// Loop over passwords file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%s"
,
f_user
,
f_domain
,
ha1
)
!=
3
)
{
continue
;
for
(
i
=
0
;
i
<
buf_len
;
i
++
)
{
// Control characters are not allowed but >=128 is.
// Abort scan as soon as one malformed character is found;
// don't let subsequent \r\n\r\n win us over anyhow
if
(
!
isprint
(
*
(
const
unsigned
char
*
)
&
buf
[
i
])
&&
buf
[
i
]
!=
'\r'
&&
buf
[
i
]
!=
'\n'
&&
*
(
const
unsigned
char
*
)
&
buf
[
i
]
<
128
)
{
return
-
1
;
}
else
if
(
buf
[
i
]
==
'\n'
&&
i
+
1
<
buf_len
&&
buf
[
i
+
1
]
==
'\n'
)
{
return
i
+
2
;
}
else
if
(
buf
[
i
]
==
'\n'
&&
i
+
2
<
buf_len
&&
buf
[
i
+
1
]
==
'\r'
&&
buf
[
i
+
2
]
==
'\n'
)
{
return
i
+
3
;
}
if
(
!
strcmp
(
ah
.
user
,
f_user
)
&&
!
strcmp
(
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
f_domain
))
return
check_password
(
conn
->
request_info
.
request_method
,
ha1
,
ah
.
uri
,
ah
.
nonce
,
ah
.
nc
,
ah
.
cnonce
,
ah
.
qop
,
ah
.
response
);
}
return
0
;
}
// Return 1 if request is authorised, 0 otherwise.
static
int
check_authorization
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
char
fname
[
PATH_MAX
];
struct
vec
uri_vec
,
filename_vec
;
const
char
*
list
;
FILE
*
fp
=
NULL
;
int
authorized
=
1
;
// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static
void
remove_double_dots_and_double_slashes
(
char
*
s
)
{
char
*
p
=
s
;
list
=
conn
->
ctx
->
config
[
PROTECT_URI
];
while
((
list
=
next_option
(
list
,
&
uri_vec
,
&
filename_vec
))
!=
NULL
)
{
if
(
!
memcmp
(
conn
->
request_info
.
uri
,
uri_vec
.
ptr
,
uri_vec
.
len
))
{
mg_snprintf
(
fname
,
sizeof
(
fname
),
"%.*s"
,
(
int
)
filename_vec
.
len
,
filename_vec
.
ptr
);
if
((
fp
=
mg_fopen
(
fname
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"%s: cannot open %s: %s"
,
__func__
,
fname
,
strerror
(
errno
));
}
while
(
*
s
!=
'\0'
)
{
*
p
++
=
*
s
++
;
if
(
s
[
-
1
]
==
'/'
||
s
[
-
1
]
==
'\\'
)
{
// Skip all following slashes, backslashes and double-dots
while
(
s
[
0
]
!=
'\0'
)
{
if
(
s
[
0
]
==
'/'
||
s
[
0
]
==
'\\'
)
{
s
++
;
}
else
if
(
s
[
0
]
==
'.'
&&
s
[
1
]
==
'.'
)
{
s
+=
2
;
}
else
{
break
;
}
}
if
(
fp
==
NULL
)
{
fp
=
open_auth_file
(
conn
,
path
);
}
if
(
fp
!=
NULL
)
{
authorized
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
authorized
;
}
static
void
send_authorization_request
(
struct
mg_connection
*
conn
)
{
conn
->
status_code
=
401
;
mg_printf
(
conn
,
"HTTP/1.1 401 Unauthorized
\r\n
"
"Content-Length: 0
\r\n
"
"WWW-Authenticate: Digest qop=
\"
auth
\"
, "
"realm=
\"
%s
\"
, nonce=
\"
%lu
\"\r\n\r\n
"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
],
(
unsigned
long
)
time
(
NULL
));
*
p
=
'\0'
;
}
static
int
is_authorized_for_put
(
struct
mg_connection
*
conn
)
{
const
char
*
passfile
=
conn
->
ctx
->
config
[
PUT_DELETE_PASSWORDS_FILE
];
FILE
*
fp
;
int
ret
=
0
;
if
(
passfile
!=
NULL
&&
(
fp
=
mg_fopen
(
passfile
,
"r"
))
!=
NULL
)
{
ret
=
authorize
(
conn
,
fp
);
fclose
(
fp
);
}
return
ret
;
}
static
const
struct
{
const
char
*
extension
;
size_t
ext_len
;
const
char
*
mime_type
;
}
builtin_mime_types
[]
=
{
{
".html"
,
5
,
"text/html"
},
{
".htm"
,
4
,
"text/html"
},
{
".shtm"
,
5
,
"text/html"
},
{
".shtml"
,
6
,
"text/html"
},
{
".css"
,
4
,
"text/css"
},
{
".js"
,
3
,
"application/x-javascript"
},
{
".ico"
,
4
,
"image/x-icon"
},
{
".gif"
,
4
,
"image/gif"
},
{
".jpg"
,
4
,
"image/jpeg"
},
{
".jpeg"
,
5
,
"image/jpeg"
},
{
".png"
,
4
,
"image/png"
},
{
".svg"
,
4
,
"image/svg+xml"
},
{
".txt"
,
4
,
"text/plain"
},
{
".torrent"
,
8
,
"application/x-bittorrent"
},
{
".wav"
,
4
,
"audio/x-wav"
},
{
".mp3"
,
4
,
"audio/x-mp3"
},
{
".mid"
,
4
,
"audio/mid"
},
{
".m3u"
,
4
,
"audio/x-mpegurl"
},
{
".ogg"
,
4
,
"application/ogg"
},
{
".ram"
,
4
,
"audio/x-pn-realaudio"
},
{
".xml"
,
4
,
"text/xml"
},
{
".json"
,
5
,
"text/json"
},
{
".xslt"
,
5
,
"application/xml"
},
{
".xsl"
,
4
,
"application/xml"
},
{
".ra"
,
3
,
"audio/x-pn-realaudio"
},
{
".doc"
,
4
,
"application/msword"
},
{
".exe"
,
4
,
"application/octet-stream"
},
{
".zip"
,
4
,
"application/x-zip-compressed"
},
{
".xls"
,
4
,
"application/excel"
},
{
".tgz"
,
4
,
"application/x-tar-gz"
},
{
".tar"
,
4
,
"application/x-tar"
},
{
".gz"
,
3
,
"application/x-gunzip"
},
{
".arj"
,
4
,
"application/x-arj-compressed"
},
{
".rar"
,
4
,
"application/x-arj-compressed"
},
{
".rtf"
,
4
,
"application/rtf"
},
{
".pdf"
,
4
,
"application/pdf"
},
{
".swf"
,
4
,
"application/x-shockwave-flash"
},
{
".mpg"
,
4
,
"video/mpeg"
},
{
".webm"
,
5
,
"video/webm"
},
{
".mpeg"
,
5
,
"video/mpeg"
},
{
".mov"
,
4
,
"video/quicktime"
},
{
".mp4"
,
4
,
"video/mp4"
},
{
".m4v"
,
4
,
"video/x-m4v"
},
{
".asf"
,
4
,
"video/x-ms-asf"
},
{
".avi"
,
4
,
"video/x-msvideo"
},
{
".bmp"
,
4
,
"image/bmp"
},
{
".ttf"
,
4
,
"application/x-font-ttf"
},
{
NULL
,
0
,
NULL
}
};
int
mg_modify_passwords_file
(
const
char
*
fname
,
const
char
*
domain
,
const
char
*
user
,
const
char
*
pass
)
{
int
found
;
char
line
[
512
],
u
[
512
],
d
[
512
],
ha1
[
33
],
tmp
[
PATH_MAX
];
FILE
*
fp
,
*
fp2
;
const
char
*
mg_get_builtin_mime_type
(
const
char
*
path
)
{
const
char
*
ext
;
size_t
i
,
path_len
;
found
=
0
;
fp
=
fp2
=
NULL
;
path_len
=
strlen
(
path
);
// Regard empty password as no password - remove user record.
if
(
pass
!=
NULL
&&
pass
[
0
]
==
'\0'
)
{
pass
=
NULL
;
for
(
i
=
0
;
builtin_mime_types
[
i
].
extension
!=
NULL
;
i
++
)
{
ext
=
path
+
(
path_len
-
builtin_mime_types
[
i
].
ext_len
);
if
(
path_len
>
builtin_mime_types
[
i
].
ext_len
&&
mg_strcasecmp
(
ext
,
builtin_mime_types
[
i
].
extension
)
==
0
)
{
return
builtin_mime_types
[
i
].
mime_type
;
}
(
void
)
snprintf
(
tmp
,
sizeof
(
tmp
),
"%s.tmp"
,
fname
);
// Create the file if does not exist
if
((
fp
=
fopen
(
fname
,
"a+"
))
!=
NULL
)
{
fclose
(
fp
);
}
// Open the given file and temporary file
if
((
fp
=
fopen
(
fname
,
"r"
))
==
NULL
)
{
return
0
;
}
else
if
((
fp2
=
fopen
(
tmp
,
"w+"
))
==
NULL
)
{
fclose
(
fp
);
return
0
;
}
return
"text/plain"
;
}
// Copy the stuff to temporary file
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
if
(
sscanf
(
line
,
"%[^:]:%[^:]:%*s"
,
u
,
d
)
!=
2
)
{
continue
;
}
// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static
void
get_mime_type
(
struct
mg_context
*
ctx
,
const
char
*
path
,
struct
vec
*
vec
)
{
struct
vec
ext_vec
,
mime_vec
;
const
char
*
list
,
*
ext
;
size_t
path_len
;
if
(
!
strcmp
(
u
,
user
)
&&
!
strcmp
(
d
,
domain
))
{
found
++
;
if
(
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
}
}
else
{
fprintf
(
fp2
,
"%s"
,
line
);
}
}
path_len
=
strlen
(
path
);
// If new user, just add it
if
(
!
found
&&
pass
!=
NULL
)
{
mg_md5
(
ha1
,
user
,
":"
,
domain
,
":"
,
pass
,
NULL
);
fprintf
(
fp2
,
"%s:%s:%s
\n
"
,
user
,
domain
,
ha1
);
// Scan user-defined mime types first, in case user wants to
// override default mime types.
list
=
ctx
->
config
[
EXTRA_MIME_TYPES
];
while
((
list
=
next_option
(
list
,
&
ext_vec
,
&
mime_vec
))
!=
NULL
)
{
// ext now points to the path suffix
ext
=
path
+
path_len
-
ext_vec
.
len
;
if
(
mg_strncasecmp
(
ext
,
ext_vec
.
ptr
,
ext_vec
.
len
)
==
0
)
{
*
vec
=
mime_vec
;
return
;
}
}
// Close files
fclose
(
fp
);
fclose
(
fp2
);
// Put the temp file in place of real file
remove
(
fname
);
rename
(
tmp
,
fname
);
return
1
;
vec
->
ptr
=
mg_get_builtin_mime_type
(
path
);
vec
->
len
=
strlen
(
vec
->
ptr
);
}
static
SOCKET
conn2
(
const
char
*
host
,
int
port
,
int
use_ssl
,
...
...
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