Started working on support for WebSockets.
Fixed some compiler warnings when compiling with -Wextra Thanks to Jan Jaeger's excellent bug report, made some changes that should make it easier to build ShellInABox for OpenWRT. git-svn-id: https://shellinabox.googlecode.com/svn/trunk@202 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
parent
5a75b2f091
commit
9b0a937e35
29 changed files with 581 additions and 138 deletions
2
COPYING
2
COPYING
|
@ -1,4 +1,4 @@
|
||||||
Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License version 2 as
|
it under the terms of the GNU General Public License version 2 as
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
2010-03-29 Markus Gutschke <markus@shellinabox.com>
|
||||||
|
|
||||||
|
* Started working on support for WebSockets.
|
||||||
|
|
||||||
|
* Fixed some compiler warnings when compiling with -Wextra
|
||||||
|
|
||||||
|
* Thanks to Jan Jaeger's excellent bug report, made some changes
|
||||||
|
that should make it easier to build ShellInABox for OpenWRT.
|
||||||
|
|
||||||
2009-12-10 Markus Gutschke <markus@shellinabox.com>
|
2009-12-10 Markus Gutschke <markus@shellinabox.com>
|
||||||
|
|
||||||
* Add .note.GNU-stack to all object files so that the generated
|
* Add .note.GNU-stack to all object files so that the generated
|
||||||
|
|
38
Makefile.am
38
Makefile.am
|
@ -2,6 +2,8 @@ AM_CPPFLAGS =
|
||||||
AM_CFLAGS = -g -std=gnu99 -Wall
|
AM_CFLAGS = -g -std=gnu99 -Wall
|
||||||
AM_LDFLAGS = -g
|
AM_LDFLAGS = -g
|
||||||
|
|
||||||
|
OBJCOPY ?= objcopy
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libhttp.la \
|
noinst_LTLIBRARIES = libhttp.la \
|
||||||
liblogging.la
|
liblogging.la
|
||||||
noinst_DATA = $(top_srcdir)/demo/demo.js
|
noinst_DATA = $(top_srcdir)/demo/demo.js
|
||||||
|
@ -234,37 +236,37 @@ clean-local:
|
||||||
-rm -rf GNU-stack
|
-rm -rf GNU-stack
|
||||||
|
|
||||||
.css.o:
|
.css.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
.gif.o:
|
.gif.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
|
|
||||||
.html.o:
|
.html.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
|
|
||||||
.ico.o:
|
.ico.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,20 +282,20 @@ shellinabox/shell_in_a_box.o: shellinabox/shell_in_a_box.js config.h
|
||||||
"$<" >"$@"
|
"$<" >"$@"
|
||||||
|
|
||||||
.js.o:
|
.js.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
|
|
||||||
.wav.o:
|
.wav.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
|
|
||||||
|
|
38
Makefile.in
38
Makefile.in
|
@ -1152,6 +1152,8 @@ uninstall-man: uninstall-man1
|
||||||
uninstall-man uninstall-man1
|
uninstall-man uninstall-man1
|
||||||
|
|
||||||
|
|
||||||
|
OBJCOPY ?= objcopy
|
||||||
|
|
||||||
libtool: $(LIBTOOL_DEPS)
|
libtool: $(LIBTOOL_DEPS)
|
||||||
$(SHELL) ./config.status --recheck
|
$(SHELL) ./config.status --recheck
|
||||||
|
|
||||||
|
@ -1247,35 +1249,35 @@ clean-local:
|
||||||
-rm -rf GNU-stack
|
-rm -rf GNU-stack
|
||||||
|
|
||||||
.css.o:
|
.css.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
.gif.o:
|
.gif.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
.html.o:
|
.html.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
.ico.o:
|
.ico.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
shellinabox/shell_in_a_box.o: shellinabox/shell_in_a_box.js config.h
|
shellinabox/shell_in_a_box.o: shellinabox/shell_in_a_box.js config.h
|
||||||
|
@ -1290,19 +1292,19 @@ shellinabox/shell_in_a_box.o: shellinabox/shell_in_a_box.js config.h
|
||||||
"$<" >"$@"
|
"$<" >"$@"
|
||||||
|
|
||||||
.js.o:
|
.js.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
.wav.o:
|
.wav.o:
|
||||||
@echo objcopy "$<" "$@"
|
@echo $(OBJCOPY) "$<" "$@"
|
||||||
@objcopy -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)` \
|
@$(OBJCOPY) -I binary `$(objcopyflags)` `echo "$<" | $(renamesymbols)`\
|
||||||
"$<" "$@"
|
"$<" "$@"
|
||||||
@-printf '\000' >GNU-stack && \
|
@-printf '\000' >GNU-stack && \
|
||||||
objcopy --add-section .note.GNU-stack=GNU-stack "$@"; \
|
$(OBJCOPY) --add-section .note.GNU-stack=GNU-stack "$@"; \
|
||||||
rm -f GNU-stack
|
rm -f GNU-stack
|
||||||
|
|
||||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||||
|
|
5
config.h
5
config.h
|
@ -25,6 +25,9 @@
|
||||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||||
#define HAVE_INTTYPES_H 1
|
#define HAVE_INTTYPES_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have support for isnan */
|
||||||
|
#define HAVE_ISNAN 1
|
||||||
|
|
||||||
/* Define to 1 if you have the <libutil.h> header file. */
|
/* Define to 1 if you have the <libutil.h> header file. */
|
||||||
/* #undef HAVE_LIBUTIL_H */
|
/* #undef HAVE_LIBUTIL_H */
|
||||||
|
|
||||||
|
@ -141,7 +144,7 @@
|
||||||
#define STDC_HEADERS 1
|
#define STDC_HEADERS 1
|
||||||
|
|
||||||
/* Most recent revision number in the version control system */
|
/* Most recent revision number in the version control system */
|
||||||
#define VCS_REVISION "200"
|
#define VCS_REVISION "202"
|
||||||
|
|
||||||
/* Version number of package */
|
/* Version number of package */
|
||||||
#define VERSION "2.10"
|
#define VERSION "2.10"
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||||
#undef HAVE_INTTYPES_H
|
#undef HAVE_INTTYPES_H
|
||||||
|
|
||||||
|
/* Define to 1 if you have support for isnan */
|
||||||
|
#undef HAVE_ISNAN
|
||||||
|
|
||||||
/* Define to 1 if you have the <libutil.h> header file. */
|
/* Define to 1 if you have the <libutil.h> header file. */
|
||||||
#undef HAVE_LIBUTIL_H
|
#undef HAVE_LIBUTIL_H
|
||||||
|
|
||||||
|
|
21
configure
vendored
21
configure
vendored
|
@ -2325,7 +2325,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
|
||||||
ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
||||||
|
|
||||||
|
|
||||||
VCS_REVISION=200
|
VCS_REVISION=202
|
||||||
|
|
||||||
|
|
||||||
cat >>confdefs.h <<_ACEOF
|
cat >>confdefs.h <<_ACEOF
|
||||||
|
@ -10682,6 +10682,25 @@ if ac_fn_c_try_link "$LINENO"; then :
|
||||||
|
|
||||||
$as_echo "#define HAVE_SIGWAIT 1" >>confdefs.h
|
$as_echo "#define HAVE_SIGWAIT 1" >>confdefs.h
|
||||||
|
|
||||||
|
fi
|
||||||
|
rm -f core conftest.err conftest.$ac_objext \
|
||||||
|
conftest$ac_exeext conftest.$ac_ext
|
||||||
|
|
||||||
|
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||||
|
/* end confdefs.h. */
|
||||||
|
#include <math.h>
|
||||||
|
int
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
isnan(0.0);
|
||||||
|
;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_ACEOF
|
||||||
|
if ac_fn_c_try_link "$LINENO"; then :
|
||||||
|
|
||||||
|
$as_echo "#define HAVE_ISNAN 1" >>confdefs.h
|
||||||
|
|
||||||
fi
|
fi
|
||||||
rm -f core conftest.err conftest.$ac_objext \
|
rm -f core conftest.err conftest.$ac_objext \
|
||||||
conftest$ac_exeext conftest.$ac_ext
|
conftest$ac_exeext conftest.$ac_ext
|
||||||
|
|
|
@ -2,7 +2,7 @@ AC_PREREQ(2.57)
|
||||||
|
|
||||||
dnl This is the one location where the authoritative version number is stored
|
dnl This is the one location where the authoritative version number is stored
|
||||||
AC_INIT(shellinabox, 2.10, markus@shellinabox.com)
|
AC_INIT(shellinabox, 2.10, markus@shellinabox.com)
|
||||||
VCS_REVISION=200
|
VCS_REVISION=202
|
||||||
AC_SUBST(VCS_REVISION)
|
AC_SUBST(VCS_REVISION)
|
||||||
AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
|
AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
|
||||||
[Most recent revision number in the version control system])
|
[Most recent revision number in the version control system])
|
||||||
|
@ -55,6 +55,12 @@ AC_TRY_LINK([#include <pthread.h>
|
||||||
[AC_DEFINE(HAVE_SIGWAIT, 1,
|
[AC_DEFINE(HAVE_SIGWAIT, 1,
|
||||||
Define to 1 if you have a working sigwait)])
|
Define to 1 if you have a working sigwait)])
|
||||||
|
|
||||||
|
dnl Not every system has support for isnan()
|
||||||
|
AC_TRY_LINK([#include <math.h>],
|
||||||
|
[isnan(0.0);],
|
||||||
|
[AC_DEFINE(HAVE_ISNAN, 1,
|
||||||
|
Define to 1 if you have support for isnan)])
|
||||||
|
|
||||||
dnl On some systems, calling /bin/login does not work. Disable the LOGIN
|
dnl On some systems, calling /bin/login does not work. Disable the LOGIN
|
||||||
dnl feature, if the user tells us that it does not do the right thing.
|
dnl feature, if the user tells us that it does not do the right thing.
|
||||||
AC_ARG_ENABLE(login,
|
AC_ARG_ENABLE(login,
|
||||||
|
|
2
debian/copyright
vendored
2
debian/copyright
vendored
|
@ -5,7 +5,7 @@ It was downloaded from http://shellinabox.com/
|
||||||
|
|
||||||
Upstream Author: markus@shellinabox.com
|
Upstream Author: markus@shellinabox.com
|
||||||
|
|
||||||
Copyright (c) 2008-2009, Markus Gutschke
|
Copyright (c) 2008-2010, Markus Gutschke
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// VT100.js -- JavaScript based terminal emulator
|
// VT100.js -- JavaScript based terminal emulator
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -1955,8 +1955,8 @@ VT100.prototype.toggleBell = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
VT100.prototype.about = function() {
|
VT100.prototype.about = function() {
|
||||||
alert("VT100 Terminal Emulator " + "2.10 (revision 200)" +
|
alert("VT100 Terminal Emulator " + "2.10 (revision 202)" +
|
||||||
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
"For more information check http://shellinabox.com");
|
"For more information check http://shellinabox.com");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// http.h -- Library for implementing embedded custom HTTP servers
|
// http.h -- Library for implementing embedded custom HTTP servers
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -56,7 +56,13 @@
|
||||||
#define HTTP_SUSPEND 3
|
#define HTTP_SUSPEND 3
|
||||||
#define HTTP_PARTIAL_REPLY 4
|
#define HTTP_PARTIAL_REPLY 4
|
||||||
|
|
||||||
|
#define WS_START_OF_FRAME 0x0100
|
||||||
|
#define WS_END_OF_FRAME 0x0200
|
||||||
|
#define WS_CONNECTION_OPENED 0xFF00
|
||||||
|
#define WS_CONNECTION_CLOSED 0x7F00
|
||||||
|
|
||||||
#define NO_MSG "\001"
|
#define NO_MSG "\001"
|
||||||
|
#define BINARY_MSG "\001%d%p"
|
||||||
|
|
||||||
#define NOINTR(x) ({ int i__; while ((i__ = (x)) < 0 && errno == EINTR); i__;})
|
#define NOINTR(x) ({ int i__; while ((i__ = (x)) < 0 && errno == EINTR); i__;})
|
||||||
|
|
||||||
|
@ -77,6 +83,9 @@ void serverRegisterHttpHandler(Server *server, const char *url,
|
||||||
void serverRegisterStreamingHttpHandler(Server *server, const char *url,
|
void serverRegisterStreamingHttpHandler(Server *server, const char *url,
|
||||||
int (*handler)(HttpConnection *, void *),
|
int (*handler)(HttpConnection *, void *),
|
||||||
void *arg);
|
void *arg);
|
||||||
|
void serverRegisterWebSocketHandler(Server *server, const char *url,
|
||||||
|
int (*handler)(HttpConnection *, void *, int, const char *, int),
|
||||||
|
void *arg);
|
||||||
ServerConnection *serverAddConnection(Server *server, int fd,
|
ServerConnection *serverAddConnection(Server *server, int fd,
|
||||||
int (*handleConnection)(ServerConnection *,
|
int (*handleConnection)(ServerConnection *,
|
||||||
void *arg, short *events,
|
void *arg, short *events,
|
||||||
|
@ -109,6 +118,10 @@ void *httpSetPrivate(HttpConnection *http, void *private);
|
||||||
void httpSendReply(HttpConnection *http, int code,
|
void httpSendReply(HttpConnection *http, int code,
|
||||||
const char *msg, const char *fmt, ...)
|
const char *msg, const char *fmt, ...)
|
||||||
__attribute__((format(printf, 4, 5)));
|
__attribute__((format(printf, 4, 5)));
|
||||||
|
void httpSendWebSocketTextMsg(HttpConnection *http, int type, const char *fmt,
|
||||||
|
...) __attribute__((format(printf, 3, 4)));
|
||||||
|
void httpSendWebSocketBinaryMsg(HttpConnection *http, int type,
|
||||||
|
const void *buf, int len);
|
||||||
void httpExitLoop(HttpConnection *http, int exitAll);
|
void httpExitLoop(HttpConnection *http, int exitAll);
|
||||||
Server *httpGetServer(const HttpConnection *http);
|
Server *httpGetServer(const HttpConnection *http);
|
||||||
ServerConnection *httpGetServerConnection(const HttpConnection *);
|
ServerConnection *httpGetServerConnection(const HttpConnection *);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// httpconnection.c -- Manage state machine for HTTP connections
|
// httpconnection.c -- Manage state machine for HTTP connections
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -66,6 +66,9 @@
|
||||||
#ifdef HAVE_STRLCAT
|
#ifdef HAVE_STRLCAT
|
||||||
#define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
|
#define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; })
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef HAVE_ISNAN
|
||||||
|
#define isnan(x) ({ typeof(x) _x = (x); _x != _x; })
|
||||||
|
#endif
|
||||||
#define max(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); \
|
#define max(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); \
|
||||||
_a > _b ? _a : _b; })
|
_a > _b ? _a : _b; })
|
||||||
|
|
||||||
|
@ -223,8 +226,9 @@ static char *strcasestr(const char *haystack, const char *needle) {
|
||||||
|
|
||||||
static int httpFinishCommand(struct HttpConnection *http) {
|
static int httpFinishCommand(struct HttpConnection *http) {
|
||||||
int rc = HTTP_DONE;
|
int rc = HTTP_DONE;
|
||||||
if (http->callback && !http->done) {
|
if ((http->callback || http->websocketHandler) && !http->done) {
|
||||||
rc = http->callback(http, http->arg, NULL, 0);
|
rc = http->callback ? http->callback(http, http->arg, NULL, 0)
|
||||||
|
: http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED, NULL,0);
|
||||||
check(rc != HTTP_SUSPEND);
|
check(rc != HTTP_SUSPEND);
|
||||||
check(rc != HTTP_PARTIAL_REPLY);
|
check(rc != HTTP_PARTIAL_REPLY);
|
||||||
http->callback = NULL;
|
http->callback = NULL;
|
||||||
|
@ -267,6 +271,7 @@ static int httpFinishCommand(struct HttpConnection *http) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void httpDestroyHeaders(void *arg, char *key, char *value) {
|
static void httpDestroyHeaders(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
free(key);
|
free(key);
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
@ -296,7 +301,7 @@ static char *getPeerName(int fd, int *port, int numericHosts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void httpSetState(struct HttpConnection *http, int state) {
|
static void httpSetState(struct HttpConnection *http, int state) {
|
||||||
if (state == http->state) {
|
if (state == (int)http->state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +377,9 @@ void initHttpConnection(struct HttpConnection *http, struct Server *server,
|
||||||
http->msgOffset = 0;
|
http->msgOffset = 0;
|
||||||
http->totalWritten = 0;
|
http->totalWritten = 0;
|
||||||
http->expecting = 0;
|
http->expecting = 0;
|
||||||
|
http->websocketType = WS_UNDEFINED;
|
||||||
http->callback = NULL;
|
http->callback = NULL;
|
||||||
|
http->websocketHandler = NULL;
|
||||||
http->arg = NULL;
|
http->arg = NULL;
|
||||||
http->private = NULL;
|
http->private = NULL;
|
||||||
http->code = 200;
|
http->code = 200;
|
||||||
|
@ -388,8 +395,12 @@ void initHttpConnection(struct HttpConnection *http, struct Server *server,
|
||||||
void destroyHttpConnection(struct HttpConnection *http) {
|
void destroyHttpConnection(struct HttpConnection *http) {
|
||||||
if (http) {
|
if (http) {
|
||||||
if (http->isSuspended || http->isPartialReply) {
|
if (http->isSuspended || http->isPartialReply) {
|
||||||
if (http->callback && !http->done) {
|
if (!http->done) {
|
||||||
|
if (http->callback) {
|
||||||
http->callback(http, http->arg, NULL, 0);
|
http->callback(http, http->arg, NULL, 0);
|
||||||
|
} else if (http->websocketHandler) {
|
||||||
|
http->websocketHandler(http, http->arg, WS_CONNECTION_CLOSED,NULL,0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
http->callback = NULL;
|
http->callback = NULL;
|
||||||
http->isSuspended = 0;
|
http->isSuspended = 0;
|
||||||
|
@ -823,23 +834,86 @@ static int httpHandleCommand(struct HttpConnection *http,
|
||||||
const char *host = getFromHashMap(&http->header,
|
const char *host = getFromHashMap(&http->header,
|
||||||
"host");
|
"host");
|
||||||
if (host) {
|
if (host) {
|
||||||
for (char ch; (ch = *host) != '\000'; host++) {
|
for (char ch, *ptr = (char *)host; (ch = *ptr) != '\000'; ptr++) {
|
||||||
if (ch == ':') {
|
if (ch == ':') {
|
||||||
*(char *)host = '\000';
|
*ptr = '\000';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ch != '-' && ch != '.' &&
|
if (ch != '-' && ch != '.' &&
|
||||||
(ch < '0' ||(ch > '9' && ch < 'A') ||
|
(ch < '0' ||(ch > '9' && ch < 'A') ||
|
||||||
(ch > 'Z' && ch < 'a')|| ch > 'z')) {
|
(ch > 'Z' && ch < 'a')||(ch > 'z' && ch <= 0x7E))) {
|
||||||
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
httpSendReply(http, 400, "Bad Request", NO_MSG);
|
||||||
return HTTP_DONE;
|
return HTTP_DONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *diff;
|
char *diff;
|
||||||
struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
|
struct HttpHandler *h = (struct HttpHandler *)getFromTrie(handlers,
|
||||||
http->path, &diff);
|
http->path, &diff);
|
||||||
|
|
||||||
if (h) {
|
if (h) {
|
||||||
|
if (h->websocketHandler) {
|
||||||
|
// Check for WebSocket handshake
|
||||||
|
const char *upgrade = getFromHashMap(&http->header,
|
||||||
|
"upgrade");
|
||||||
|
if (upgrade && !strcmp(upgrade, "WebSocket")) {
|
||||||
|
const char *connection = getFromHashMap(&http->header,
|
||||||
|
"connection");
|
||||||
|
if (connection && !strcmp(connection, "Upgrade")) {
|
||||||
|
const char *origin = getFromHashMap(&http->header,
|
||||||
|
"origin");
|
||||||
|
if (origin) {
|
||||||
|
for (const char *ptr = origin; *ptr; ptr++) {
|
||||||
|
if ((unsigned char)*ptr < ' ') {
|
||||||
|
goto bad_ws_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *protocol = getFromHashMap(&http->header,
|
||||||
|
"websocket-protocol");
|
||||||
|
if (protocol) {
|
||||||
|
for (const char *ptr = protocol; *ptr; ptr++) {
|
||||||
|
if ((unsigned char)*ptr < ' ') {
|
||||||
|
goto bad_ws_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *port = NULL;
|
||||||
|
if (http->port != (http->sslHndl ? 443 : 80)) {
|
||||||
|
port = stringPrintf(NULL,
|
||||||
|
":%d", http->port);
|
||||||
|
}
|
||||||
|
char *response = stringPrintf(NULL,
|
||||||
|
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
||||||
|
"Upgrade: WebSocket\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"WebSocket-Origin: %s\r\n"
|
||||||
|
"WebSocket-Location: %s://%s%s%s\r\n"
|
||||||
|
"%s%s%s"
|
||||||
|
"\r\n",
|
||||||
|
origin,
|
||||||
|
http->sslHndl ? "wss" : "ws", host && *host ? host : "localhost",
|
||||||
|
port ? port : "", http->path,
|
||||||
|
protocol ? "WebSocket-Protocol: " : "",
|
||||||
|
protocol ? protocol : "",
|
||||||
|
protocol ? "\r\n" : "");
|
||||||
|
free(port);
|
||||||
|
debug("Switching to WebSockets");
|
||||||
|
httpTransfer(http, response, strlen(response));
|
||||||
|
if (http->expecting < 0) {
|
||||||
|
http->expecting = 0;
|
||||||
|
}
|
||||||
|
http->websocketHandler = h->websocketHandler;
|
||||||
|
httpSetState(http, WEBSOCKET);
|
||||||
|
return HTTP_READ_MORE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bad_ws_upgrade:;
|
||||||
|
|
||||||
|
if (h->handler) {
|
||||||
check(diff);
|
check(diff);
|
||||||
while (diff > http->path && diff[-1] == '/') {
|
while (diff > http->path && diff[-1] == '/') {
|
||||||
diff--;
|
diff--;
|
||||||
|
@ -868,6 +942,7 @@ static int httpHandleCommand(struct HttpConnection *http,
|
||||||
return h->handler(http, h->arg);
|
return h->handler(http, h->arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
httpSendReply(http, 404, "File Not Found", NO_MSG);
|
httpSendReply(http, 404, "File Not Found", NO_MSG);
|
||||||
return HTTP_DONE;
|
return HTTP_DONE;
|
||||||
}
|
}
|
||||||
|
@ -1011,13 +1086,20 @@ static int httpParseHeaders(struct HttpConnection *http,
|
||||||
serverSetTimeout(connection, CONNECTION_TIMEOUT);
|
serverSetTimeout(connection, CONNECTION_TIMEOUT);
|
||||||
}
|
}
|
||||||
check(!http->done);
|
check(!http->done);
|
||||||
if (!http->expecting && http->callback) {
|
if (!http->expecting) {
|
||||||
|
if (http->callback) {
|
||||||
rc = http->callback(http, http->arg, "", 0);
|
rc = http->callback(http, http->arg, "", 0);
|
||||||
if (rc != HTTP_READ_MORE) {
|
if (rc != HTTP_READ_MORE) {
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
|
} else if (http->websocketHandler) {
|
||||||
|
http->websocketHandler(http, http->arg, WS_CONNECTION_OPENED,
|
||||||
|
NULL, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (http->state != WEBSOCKET) {
|
||||||
httpSetState(http, http->expecting ? PAYLOAD : COMMAND);
|
httpSetState(http, http->expecting ? PAYLOAD : COMMAND);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case HTTP_SUSPEND:
|
case HTTP_SUSPEND:
|
||||||
http->isSuspended = 1;
|
http->isSuspended = 1;
|
||||||
|
@ -1187,6 +1269,140 @@ static int httpParsePayload(struct HttpConnection *http, int offset,
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int httpHandleWebSocket(struct HttpConnection *http, int offset,
|
||||||
|
const char *buf, int bytes) {
|
||||||
|
check(http->websocketHandler);
|
||||||
|
int ch = 0x00;
|
||||||
|
while (bytes > offset) {
|
||||||
|
if (http->websocketType & WS_UNDEFINED) {
|
||||||
|
ch = httpGetChar(http, buf, bytes, &offset);
|
||||||
|
check(ch >= 0);
|
||||||
|
if (http->websocketType & 0xFF) {
|
||||||
|
// Reading another byte of length information.
|
||||||
|
if (http->expecting > 0xFFFFFF) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
http->expecting = (128 * http->expecting) + (ch & 0x7F);
|
||||||
|
if ((ch & 0x80) == 0) {
|
||||||
|
// Done reading length information.
|
||||||
|
http->websocketType &= ~WS_UNDEFINED;
|
||||||
|
|
||||||
|
// ch is used to detect when we read the terminating byte in text
|
||||||
|
// mode. In binary mode, it must be set to something other than 0xFF.
|
||||||
|
ch = 0x00;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Reading first byte of frame.
|
||||||
|
http->websocketType = (ch & 0xFF) | WS_START_OF_FRAME;
|
||||||
|
if (ch & 0x80) {
|
||||||
|
// For binary data, we have to read the length before we can start
|
||||||
|
// processing payload.
|
||||||
|
http->websocketType |= WS_UNDEFINED;
|
||||||
|
http->expecting = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (http->websocketType & 0x80) {
|
||||||
|
// Binary data
|
||||||
|
if (http->expecting) {
|
||||||
|
if (offset < 0) {
|
||||||
|
handle_partial:
|
||||||
|
check(-offset <= http->partialLength);
|
||||||
|
int len = -offset;
|
||||||
|
if (len >= http->expecting) {
|
||||||
|
len = http->expecting;
|
||||||
|
http->websocketType |= WS_END_OF_FRAME;
|
||||||
|
}
|
||||||
|
if (len &&
|
||||||
|
http->websocketHandler(http, http->arg, http->websocketType,
|
||||||
|
http->partial + http->partialLength + offset,
|
||||||
|
len) != HTTP_DONE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == 0xFF) {
|
||||||
|
// In text mode, we jump to handle_partial, when we find the
|
||||||
|
// terminating 0xFF byte. If so, we should try to consume it now.
|
||||||
|
if (len < http->partialLength) {
|
||||||
|
len++;
|
||||||
|
http->websocketType = WS_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == http->partialLength) {
|
||||||
|
free(http->partial);
|
||||||
|
http->partial = NULL;
|
||||||
|
http->partialLength = 0;
|
||||||
|
} else {
|
||||||
|
memmove(http->partial, http->partial + len,
|
||||||
|
http->partialLength - len);
|
||||||
|
http->partialLength -= len;
|
||||||
|
}
|
||||||
|
offset += len;
|
||||||
|
http->expecting -= len;
|
||||||
|
} else {
|
||||||
|
handle_buffered:;
|
||||||
|
int len = bytes - offset;
|
||||||
|
if (len >= http->expecting) {
|
||||||
|
len = http->expecting;
|
||||||
|
http->websocketType |= WS_END_OF_FRAME;
|
||||||
|
}
|
||||||
|
if (len &&
|
||||||
|
http->websocketHandler(http, http->arg, http->websocketType,
|
||||||
|
buf + offset, len) != HTTP_DONE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == 0xFF) {
|
||||||
|
// In text mode, we jump to handle_buffered, when we find the
|
||||||
|
// terminating 0xFF byte. If so, we should consume it now.
|
||||||
|
check(offset + len < bytes);
|
||||||
|
len++;
|
||||||
|
http->websocketType = WS_UNDEFINED;
|
||||||
|
}
|
||||||
|
offset += len;
|
||||||
|
http->expecting -= len;
|
||||||
|
}
|
||||||
|
http->websocketType &= ~(WS_START_OF_FRAME | WS_END_OF_FRAME);
|
||||||
|
} else {
|
||||||
|
// Read all data. Go back to looking for a new frame header.
|
||||||
|
http->websocketType = WS_UNDEFINED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Process text data until we find a 0xFF bytes.
|
||||||
|
int i = offset;
|
||||||
|
|
||||||
|
// If we have partial data, process that first.
|
||||||
|
while (i < 0) {
|
||||||
|
ch = httpGetChar(http, buf, bytes, &i);
|
||||||
|
check(ch != -1);
|
||||||
|
|
||||||
|
// Terminate when we either find the 0xFF, or we have reached the end
|
||||||
|
// of partial data.
|
||||||
|
if (ch == 0xFF || !i) {
|
||||||
|
// Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
|
||||||
|
http->expecting = i - offset - (ch == 0xFF);
|
||||||
|
goto handle_partial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all remaining buffered bytes (i.e. positive offset).
|
||||||
|
while (bytes > i) {
|
||||||
|
ch = httpGetChar(http, buf, bytes, &i);
|
||||||
|
check(ch != -1);
|
||||||
|
|
||||||
|
// Terminate when we either find the 0xFF, or we have reached the end
|
||||||
|
// of buffered data.
|
||||||
|
if (ch == 0xFF || bytes == i) {
|
||||||
|
// Set WS_END_OF_FRAME, iff we have found the 0xFF marker.
|
||||||
|
http->expecting = i - offset - (ch == 0xFF);
|
||||||
|
goto handle_buffered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
short *events, short revents) {
|
short *events, short revents) {
|
||||||
struct HttpConnection *http = (struct HttpConnection *)http_;
|
struct HttpConnection *http = (struct HttpConnection *)http_;
|
||||||
|
@ -1219,6 +1435,7 @@ int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes > 0 && http->state == SNIFFING_SSL) {
|
if (bytes > 0 && http->state == SNIFFING_SSL) {
|
||||||
// Assume that all legitimate HTTP commands start with a sequence of
|
// Assume that all legitimate HTTP commands start with a sequence of
|
||||||
// letters followed by a space character. If we don't see this pattern,
|
// letters followed by a space character. If we don't see this pattern,
|
||||||
|
@ -1241,7 +1458,7 @@ int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
strcmp(method, "CONNECT");
|
strcmp(method, "CONNECT");
|
||||||
http->state = COMMAND;
|
http->state = COMMAND;
|
||||||
break;
|
break;
|
||||||
} else if (j >= sizeof(method)-1 ||
|
} else if (j >= (int)sizeof(method)-1 ||
|
||||||
ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
|
ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
|
||||||
isSSL = 1;
|
isSSL = 1;
|
||||||
http->state = COMMAND;
|
http->state = COMMAND;
|
||||||
|
@ -1262,6 +1479,7 @@ int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes > 0 || (eof && http->partial)) {
|
if (bytes > 0 || (eof && http->partial)) {
|
||||||
check(!!http->partial == !!http->partialLength);
|
check(!!http->partial == !!http->partialLength);
|
||||||
int offset = -http->partialLength;
|
int offset = -http->partialLength;
|
||||||
|
@ -1331,7 +1549,16 @@ int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
consumed = len;
|
consumed = len;
|
||||||
pushBack = bytes - offset - len;
|
pushBack = bytes - offset - len;
|
||||||
}
|
}
|
||||||
|
} else if (http->state == WEBSOCKET) {
|
||||||
|
if (!httpHandleWebSocket(http, offset, buf, bytes)) {
|
||||||
|
httpCloseRead(http);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
consumed += bytes - offset;
|
||||||
|
} else {
|
||||||
|
check(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (pushBack) {
|
if (pushBack) {
|
||||||
check(offset + pushBack == bytes);
|
check(offset + pushBack == bytes);
|
||||||
if (offset >= 0) {
|
if (offset >= 0) {
|
||||||
|
@ -1383,6 +1610,7 @@ int httpHandleConnection(struct ServerConnection *connection, void *http_,
|
||||||
break;
|
break;
|
||||||
case PAYLOAD:
|
case PAYLOAD:
|
||||||
case DISCARD_PAYLOAD:
|
case DISCARD_PAYLOAD:
|
||||||
|
case WEBSOCKET:
|
||||||
http->expecting = 0;
|
http->expecting = 0;
|
||||||
httpCloseRead(http);
|
httpCloseRead(http);
|
||||||
httpSetState(http, COMMAND);
|
httpSetState(http, COMMAND);
|
||||||
|
@ -1563,6 +1791,89 @@ void httpSendReply(struct HttpConnection *http, int code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void httpSendWebSocketTextMsg(struct HttpConnection *http, int type,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
check(type >= 0 && type <= 0x7F);
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
char *buf;
|
||||||
|
int len;
|
||||||
|
if (strcmp(fmt, BINARY_MSG)) {
|
||||||
|
// Send a printf() style text message
|
||||||
|
buf = vStringPrintf(NULL, fmt, ap);
|
||||||
|
len = strlen(buf);
|
||||||
|
} else {
|
||||||
|
// Send a binary message
|
||||||
|
len = va_arg(ap, int);
|
||||||
|
buf = va_arg(ap, char *);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
check(len >= 0 && len < 0x60000000);
|
||||||
|
|
||||||
|
// We assume that all input data is directly mapped in the range 0..255
|
||||||
|
// (e.g. ISO-8859-1). In order to transparently send it over a web socket,
|
||||||
|
// we have to encode it in UTF-8.
|
||||||
|
int utf8Len = len + 2;
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
if (buf[i] & 0x80) {
|
||||||
|
++utf8Len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *utf8;
|
||||||
|
check(utf8 = malloc(utf8Len));
|
||||||
|
utf8[0] = type;
|
||||||
|
for (int i = 0, j = 1; i < len; ++i) {
|
||||||
|
unsigned char ch = buf[i];
|
||||||
|
if (ch & 0x80) {
|
||||||
|
utf8[j++] = 0xC0 + (ch >> 6);
|
||||||
|
utf8[j++] = 0x80 + (ch & 0x3F);
|
||||||
|
} else {
|
||||||
|
utf8[j++] = ch;
|
||||||
|
}
|
||||||
|
check(j < utf8Len);
|
||||||
|
}
|
||||||
|
utf8[utf8Len-1] = '\xFF';
|
||||||
|
|
||||||
|
// Free our temporary buffer, if we actually did allocate one.
|
||||||
|
if (strcmp(fmt, BINARY_MSG)) {
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to browser.
|
||||||
|
httpTransfer(http, utf8, utf8Len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpSendWebSocketBinaryMsg(struct HttpConnection *http, int type,
|
||||||
|
const void *buf, int len) {
|
||||||
|
check(type >= 0x80 && type <= 0xFF);
|
||||||
|
check(len > 0 && len < 0x7FFFFFF0);
|
||||||
|
|
||||||
|
// Allocate buffer for header and payload.
|
||||||
|
char *data;
|
||||||
|
check(data = malloc(len + 6));
|
||||||
|
data[0] = type;
|
||||||
|
|
||||||
|
// Convert length to base-128.
|
||||||
|
int i = 0;
|
||||||
|
int l = len;
|
||||||
|
do {
|
||||||
|
data[++i] = 0x80 + (l & 0x7F);
|
||||||
|
l /= 128;
|
||||||
|
} while (l);
|
||||||
|
data[i] &= 0x7F;
|
||||||
|
|
||||||
|
// Reverse digits, so that they are big-endian.
|
||||||
|
for (int j = 0; j < i/2; ++j) {
|
||||||
|
char ch = data[1+j];
|
||||||
|
data[1+j] = data[i-j];
|
||||||
|
data[i-j] = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmit header and payload.
|
||||||
|
memmove(data + i + 1, buf, len);
|
||||||
|
httpTransfer(http, data, len + i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
void httpExitLoop(struct HttpConnection *http, int exitAll) {
|
void httpExitLoop(struct HttpConnection *http, int exitAll) {
|
||||||
serverExitLoop(http->server, exitAll);
|
serverExitLoop(http->server, exitAll);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// httpconnection.h -- Manage state machine for HTTP connections
|
// httpconnection.h -- Manage state machine for HTTP connections
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -60,6 +60,10 @@
|
||||||
#define HTTP_SUSPEND 3
|
#define HTTP_SUSPEND 3
|
||||||
#define HTTP_PARTIAL_REPLY 4
|
#define HTTP_PARTIAL_REPLY 4
|
||||||
|
|
||||||
|
#define WS_UNDEFINED 0x1000
|
||||||
|
#define WS_START_OF_FRAME 0x0100
|
||||||
|
#define WS_END_OF_FRAME 0x0200
|
||||||
|
|
||||||
#define NO_MSG "\001"
|
#define NO_MSG "\001"
|
||||||
|
|
||||||
struct HttpConnection {
|
struct HttpConnection {
|
||||||
|
@ -71,7 +75,8 @@ struct HttpConnection {
|
||||||
int isSuspended;
|
int isSuspended;
|
||||||
int isPartialReply;
|
int isPartialReply;
|
||||||
int done;
|
int done;
|
||||||
enum { SNIFFING_SSL, COMMAND, HEADERS, PAYLOAD, DISCARD_PAYLOAD } state;
|
enum { SNIFFING_SSL, COMMAND, HEADERS, PAYLOAD, DISCARD_PAYLOAD,
|
||||||
|
WEBSOCKET } state;
|
||||||
char *peerName;
|
char *peerName;
|
||||||
int peerPort;
|
int peerPort;
|
||||||
char *url;
|
char *url;
|
||||||
|
@ -91,8 +96,11 @@ struct HttpConnection {
|
||||||
int msgOffset;
|
int msgOffset;
|
||||||
int totalWritten;
|
int totalWritten;
|
||||||
int expecting;
|
int expecting;
|
||||||
|
int websocketType;
|
||||||
int (*callback)(struct HttpConnection *, void *,
|
int (*callback)(struct HttpConnection *, void *,
|
||||||
const char *,int);
|
const char *,int);
|
||||||
|
int (*websocketHandler)(struct HttpConnection *, void *,
|
||||||
|
int, const char *, int);
|
||||||
void *arg;
|
void *arg;
|
||||||
void *private;
|
void *private;
|
||||||
int code;
|
int code;
|
||||||
|
@ -104,6 +112,8 @@ struct HttpConnection {
|
||||||
struct HttpHandler {
|
struct HttpHandler {
|
||||||
int (*handler)(struct HttpConnection *, void *);
|
int (*handler)(struct HttpConnection *, void *);
|
||||||
int (*streamingHandler)(struct HttpConnection *, void *, const char *, int);
|
int (*streamingHandler)(struct HttpConnection *, void *, const char *, int);
|
||||||
|
int (*websocketHandler)(struct HttpConnection *, void *, int,
|
||||||
|
const char *, int);
|
||||||
void *arg, *streamingArg;
|
void *arg, *streamingArg;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -128,6 +138,11 @@ void *httpSetPrivate(struct HttpConnection *http, void *private);
|
||||||
void httpSendReply(struct HttpConnection *http, int code,
|
void httpSendReply(struct HttpConnection *http, int code,
|
||||||
const char *msg, const char *fmt, ...)
|
const char *msg, const char *fmt, ...)
|
||||||
__attribute__((format(printf, 4, 5)));
|
__attribute__((format(printf, 4, 5)));
|
||||||
|
void httpSendWebSocketTextMsg(struct HttpConnection *http, int type,
|
||||||
|
const char *fmt, ...)
|
||||||
|
__attribute__((format(printf, 3, 4)));
|
||||||
|
void httpSendWebSocketBinaryMsg(struct HttpConnection *http, int type,
|
||||||
|
const void *buf, int len);
|
||||||
void httpExitLoop(struct HttpConnection *http, int exitAll);
|
void httpExitLoop(struct HttpConnection *http, int exitAll);
|
||||||
struct Server *httpGetServer(const struct HttpConnection *http);
|
struct Server *httpGetServer(const struct HttpConnection *http);
|
||||||
struct ServerConnection *httpGetServerConnection(const struct HttpConnection*);
|
struct ServerConnection *httpGetServerConnection(const struct HttpConnection*);
|
||||||
|
|
|
@ -5,6 +5,7 @@ serverGetListeningPort
|
||||||
serverGetFd
|
serverGetFd
|
||||||
serverRegisterHttpHandler
|
serverRegisterHttpHandler
|
||||||
serverRegisterStreamingHttpHandler
|
serverRegisterStreamingHttpHandler
|
||||||
|
serverRegisterWebSocketHandler
|
||||||
serverAddConnection
|
serverAddConnection
|
||||||
serverDeleteConnection
|
serverDeleteConnection
|
||||||
serverSetTimeout
|
serverSetTimeout
|
||||||
|
@ -24,6 +25,8 @@ httpSetCallback
|
||||||
httpGetPrivate
|
httpGetPrivate
|
||||||
httpSetPrivate
|
httpSetPrivate
|
||||||
httpSendReply
|
httpSendReply
|
||||||
|
httpSendWebSocketTextMsg
|
||||||
|
httpSendWebSocketBinaryMsg
|
||||||
httpExitLoop
|
httpExitLoop
|
||||||
httpGetServer
|
httpGetServer
|
||||||
httpGetServerConnection
|
httpGetServerConnection
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// server.c -- Generic server that can deal with HTTP connections
|
// server.c -- Generic server that can deal with HTTP connections
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -129,6 +129,7 @@ static int serverCollectHandler(struct HttpConnection *http, void *handler_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void serverDestroyHandlers(void *arg, char *value) {
|
static void serverDestroyHandlers(void *arg, char *value) {
|
||||||
|
(void)arg;
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +144,7 @@ void serverRegisterHttpHandler(struct Server *server, const char *url,
|
||||||
h->handler = serverCollectHandler;
|
h->handler = serverCollectHandler;
|
||||||
h->arg = h;
|
h->arg = h;
|
||||||
h->streamingHandler = handler;
|
h->streamingHandler = handler;
|
||||||
|
h->websocketHandler = NULL;
|
||||||
h->streamingArg = arg;
|
h->streamingArg = arg;
|
||||||
addToTrie(&server->handlers, url, (char *)h);
|
addToTrie(&server->handlers, url, (char *)h);
|
||||||
}
|
}
|
||||||
|
@ -158,13 +160,31 @@ void serverRegisterStreamingHttpHandler(struct Server *server, const char *url,
|
||||||
check(h = malloc(sizeof(struct HttpHandler)));
|
check(h = malloc(sizeof(struct HttpHandler)));
|
||||||
h->handler = handler;
|
h->handler = handler;
|
||||||
h->streamingHandler = NULL;
|
h->streamingHandler = NULL;
|
||||||
|
h->websocketHandler = NULL;
|
||||||
h->streamingArg = NULL;
|
h->streamingArg = NULL;
|
||||||
h->arg = arg;
|
h->arg = arg;
|
||||||
addToTrie(&server->handlers, url, (char *)h);
|
addToTrie(&server->handlers, url, (char *)h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void serverRegisterWebSocketHandler(struct Server *server, const char *url,
|
||||||
|
int (*handler)(struct HttpConnection *, void *, int, const char *, int),
|
||||||
|
void *arg) {
|
||||||
|
if (!handler) {
|
||||||
|
addToTrie(&server->handlers, url, NULL);
|
||||||
|
} else {
|
||||||
|
struct HttpHandler *h;
|
||||||
|
check(h = malloc(sizeof(struct HttpHandler)));
|
||||||
|
h->handler = NULL;
|
||||||
|
h->streamingHandler = NULL;
|
||||||
|
h->websocketHandler = handler;
|
||||||
|
h->arg = arg;
|
||||||
|
addToTrie(&server->handlers, url, (char *)h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int serverQuitHandler(struct HttpConnection *http, void *arg) {
|
static int serverQuitHandler(struct HttpConnection *http, void *arg) {
|
||||||
|
(void)arg;
|
||||||
httpSendReply(http, 200, "Good Bye", NO_MSG);
|
httpSendReply(http, 200, "Good Bye", NO_MSG);
|
||||||
httpExitLoop(http, 1);
|
httpExitLoop(http, 1);
|
||||||
return HTTP_DONE;
|
return HTTP_DONE;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// server.h -- Generic server that can deal with HTTP connections
|
// server.h -- Generic server that can deal with HTTP connections
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -93,6 +93,9 @@ void serverRegisterHttpHandler(struct Server *server, const char *url,
|
||||||
void serverRegisterStreamingHttpHandler(struct Server *server, const char *url,
|
void serverRegisterStreamingHttpHandler(struct Server *server, const char *url,
|
||||||
int (*handler)(struct HttpConnection *, void *),
|
int (*handler)(struct HttpConnection *, void *),
|
||||||
void *arg);
|
void *arg);
|
||||||
|
void serverRegisterWebSocketHandler(struct Server *server, const char *url,
|
||||||
|
int (*handler)(struct HttpConnection *, void *, int, const char *, int),
|
||||||
|
void *arg);
|
||||||
struct ServerConnection *serverAddConnection(struct Server *server, int fd,
|
struct ServerConnection *serverAddConnection(struct Server *server, int fd,
|
||||||
int (*handleConnection)(struct ServerConnection *c,
|
int (*handleConnection)(struct ServerConnection *c,
|
||||||
void *arg, short *events,
|
void *arg, short *events,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// ssl.c -- Support functions that find and load SSL support, if available
|
// ssl.c -- Support functions that find and load SSL support, if available
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -288,11 +288,11 @@ static void loadSSL(void) {
|
||||||
{ { &d2i_X509 }, "d2i_X509" },
|
{ { &d2i_X509 }, "d2i_X509" },
|
||||||
{ { &X509_free }, "X509_free" }
|
{ { &X509_free }, "X509_free" }
|
||||||
};
|
};
|
||||||
for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
|
for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
|
||||||
if (!(*symbols[i].var = loadSymbol("libssl.so", symbols[i].fn))) {
|
if (!(*symbols[i].var = loadSymbol("libssl.so", symbols[i].fn))) {
|
||||||
debug("Failed to load SSL support. Could not find \"%s\"",
|
debug("Failed to load SSL support. Could not find \"%s\"",
|
||||||
symbols[i].fn);
|
symbols[i].fn);
|
||||||
for (int j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
|
for (unsigned j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
|
||||||
*symbols[j].var = NULL;
|
*symbols[j].var = NULL;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -361,7 +361,7 @@ static const unsigned char *sslSecureReadASCIIFileToMem(int fd) {
|
||||||
check((buf = malloc(bufSize)) != NULL);
|
check((buf = malloc(bufSize)) != NULL);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
check(len < bufSize - 1);
|
check(len < bufSize - 1);
|
||||||
size_t readLen = bufSize - len - 1;
|
ssize_t readLen = bufSize - len - 1;
|
||||||
ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
|
ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
|
||||||
if (bytesRead > 0) {
|
if (bytesRead > 0) {
|
||||||
len += bytesRead;
|
len += bytesRead;
|
||||||
|
@ -414,7 +414,7 @@ static const unsigned char *sslPEMtoASN1(const unsigned char *pem,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
unsigned char *ret;
|
unsigned char *ret;
|
||||||
size_t maxSize = (((end - ptr)*6)+7)/8;
|
ssize_t maxSize = (((end - ptr)*6)+7)/8;
|
||||||
check((ret = malloc(maxSize)) != NULL);
|
check((ret = malloc(maxSize)) != NULL);
|
||||||
unsigned char *out = ret;
|
unsigned char *out = ret;
|
||||||
unsigned bits = 0;
|
unsigned bits = 0;
|
||||||
|
@ -542,6 +542,7 @@ static int sslSetCertificateFromFile(SSL_CTX *context,
|
||||||
|
|
||||||
#ifdef HAVE_TLSEXT
|
#ifdef HAVE_TLSEXT
|
||||||
static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
|
static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
|
||||||
|
(void)al;
|
||||||
check(!ERR_peek_error());
|
check(!ERR_peek_error());
|
||||||
const char *name = SSL_get_servername(sslHndl,
|
const char *name = SSL_get_servername(sslHndl,
|
||||||
TLSEXT_NAMETYPE_host_name);
|
TLSEXT_NAMETYPE_host_name);
|
||||||
|
@ -603,7 +604,7 @@ static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
|
||||||
}
|
}
|
||||||
free(serverName);
|
free(serverName);
|
||||||
if (context != ssl->sslContext) {
|
if (context != ssl->sslContext) {
|
||||||
check(SSL_set_SSL_CTX(sslHndl, context) > 0);
|
check(SSL_set_SSL_CTX(sslHndl, context));
|
||||||
}
|
}
|
||||||
check(!ERR_peek_error());
|
check(!ERR_peek_error());
|
||||||
return SSL_TLSEXT_ERR_OK;
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
@ -616,6 +617,8 @@ static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
|
||||||
static int gethostbyname_r(const char *name, struct hostent *ret,
|
static int gethostbyname_r(const char *name, struct hostent *ret,
|
||||||
char *buf, size_t buflen,
|
char *buf, size_t buflen,
|
||||||
struct hostent **result, int *h_errnop) {
|
struct hostent **result, int *h_errnop) {
|
||||||
|
(void)buf;
|
||||||
|
(void)buflen;
|
||||||
if (result) {
|
if (result) {
|
||||||
*result = NULL;
|
*result = NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// url.c -- Object representing uniform resource locators
|
// url.c -- Object representing uniform resource locators
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -87,6 +87,7 @@ static char *urlUnescape(char *s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void urlDestroyHashMapEntry(void *arg, char *key, char *value) {
|
static void urlDestroyHashMapEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
free(key);
|
free(key);
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// launcher.c -- Launch services from a privileged process
|
// launcher.c -- Launch services from a privileged process
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -321,7 +321,7 @@ static void loadPAM(void) {
|
||||||
{ { &pam_start }, "libpam.so", "pam_start" },
|
{ { &pam_start }, "libpam.so", "pam_start" },
|
||||||
{ { &misc_conv }, "libpam_misc.so", "misc_conv" }
|
{ { &misc_conv }, "libpam_misc.so", "misc_conv" }
|
||||||
};
|
};
|
||||||
for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
|
for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
|
||||||
if (!(*symbols[i].var = loadSymbol(symbols[i].lib, symbols[i].fn))) {
|
if (!(*symbols[i].var = loadSymbol(symbols[i].lib, symbols[i].fn))) {
|
||||||
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
|
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
|
||||||
if (!strcmp(symbols[i].fn, "pam_binary_handler_fn")) {
|
if (!strcmp(symbols[i].fn, "pam_binary_handler_fn")) {
|
||||||
|
@ -336,7 +336,7 @@ static void loadPAM(void) {
|
||||||
}
|
}
|
||||||
debug("Failed to load PAM support. Could not find \"%s\"",
|
debug("Failed to load PAM support. Could not find \"%s\"",
|
||||||
symbols[i].fn);
|
symbols[i].fn);
|
||||||
for (int j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
|
for (unsigned j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
|
||||||
*symbols[j].var = NULL;
|
*symbols[j].var = NULL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -419,7 +419,7 @@ int launchChild(int service, struct Session *session, const char *url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LaunchRequest *request;
|
struct LaunchRequest *request;
|
||||||
size_t len = sizeof(struct LaunchRequest) + strlen(u) + 1;
|
ssize_t len = sizeof(struct LaunchRequest) + strlen(u) + 1;
|
||||||
check(request = calloc(len, 1));
|
check(request = calloc(len, 1));
|
||||||
request->service = service;
|
request->service = service;
|
||||||
request->width = session->width;
|
request->width = session->width;
|
||||||
|
@ -538,6 +538,8 @@ void deleteUtmp(struct Utmp *utmp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroyUtmpHashEntry(void *arg, char *key, char *value) {
|
static void destroyUtmpHashEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
|
(void)key;
|
||||||
deleteUtmp((struct Utmp *)value);
|
deleteUtmp((struct Utmp *)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,6 +767,9 @@ static const struct passwd *getPWEnt(uid_t uid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sigAlrmHandler(int sig, siginfo_t *info, void *unused) {
|
static void sigAlrmHandler(int sig, siginfo_t *info, void *unused) {
|
||||||
|
(void)sig;
|
||||||
|
(void)info;
|
||||||
|
(void)unused;
|
||||||
puts("\nLogin timed out after 60 seconds.");
|
puts("\nLogin timed out after 60 seconds.");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
@ -958,7 +963,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restricted &&
|
if (restricted &&
|
||||||
(service->uid != restricted || service->gid != pw->pw_gid)) {
|
(service->uid != (int)restricted || service->gid != (int)pw->pw_gid)) {
|
||||||
puts("\nAccess denied!");
|
puts("\nAccess denied!");
|
||||||
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
||||||
if (service->authUser != 2 /* SSH */) {
|
if (service->authUser != 2 /* SSH */) {
|
||||||
|
@ -1004,7 +1009,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
|
||||||
if (i == ngroups) {
|
if (i == ngroups) {
|
||||||
groups[ngroups++] = service->gid;
|
groups[ngroups++] = service->gid;
|
||||||
break;
|
break;
|
||||||
} else if (groups[i] == service->gid) {
|
} else if ((int)groups[i] == service->gid) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1047,6 +1052,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroyVariableHashEntry(void *arg, char *key, char *value) {
|
static void destroyVariableHashEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
free(key);
|
free(key);
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
@ -1054,6 +1060,9 @@ static void destroyVariableHashEntry(void *arg, char *key, char *value) {
|
||||||
static void execService(int width, int height, struct Service *service,
|
static void execService(int width, int height, struct Service *service,
|
||||||
const char *peerName, char **environment,
|
const char *peerName, char **environment,
|
||||||
const char *url) {
|
const char *url) {
|
||||||
|
(void)width;
|
||||||
|
(void)height;
|
||||||
|
|
||||||
// Create a hash table with all the variables that we can expand. This
|
// Create a hash table with all the variables that we can expand. This
|
||||||
// includes all environment variables being passed to the child.
|
// includes all environment variables being passed to the child.
|
||||||
HashMap *vars;
|
HashMap *vars;
|
||||||
|
@ -1346,7 +1355,10 @@ static void childProcess(struct Service *service, int width, int height,
|
||||||
pam_handle_t *pam = internalLogin(service, utmp, &environment);
|
pam_handle_t *pam = internalLogin(service, utmp, &environment);
|
||||||
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
#if defined(HAVE_SECURITY_PAM_APPL_H)
|
||||||
if (pam && !geteuid()) {
|
if (pam && !geteuid()) {
|
||||||
check(pam_open_session(pam, PAM_SILENT) == PAM_SUCCESS);
|
if (pam_open_session(pam, PAM_SILENT) != PAM_SUCCESS) {
|
||||||
|
fprintf(stderr, "Access denied.\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
switch (pid) {
|
switch (pid) {
|
||||||
case -1:
|
case -1:
|
||||||
|
@ -1409,6 +1421,9 @@ static void childProcess(struct Service *service, int width, int height,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sigChildHandler(int sig, siginfo_t *info, void *unused) {
|
static void sigChildHandler(int sig, siginfo_t *info, void *unused) {
|
||||||
|
(void)sig;
|
||||||
|
(void)info;
|
||||||
|
(void)unused;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void launcherDaemon(int fd) {
|
static void launcherDaemon(int fd) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// privileges.c -- Manage process privileges
|
// privileges.c -- Manage process privileges
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -76,7 +76,7 @@ static void removeGroupPrivileges(int showError) {
|
||||||
getresuid(&ru, &eu, &su);
|
getresuid(&ru, &eu, &su);
|
||||||
|
|
||||||
// Try to switch the user-provided group.
|
// Try to switch the user-provided group.
|
||||||
if ((ru && runAsGroup != rg) ||
|
if ((ru && runAsGroup != (int)rg) ||
|
||||||
setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
setresgid(runAsGroup, runAsGroup, runAsGroup)) {
|
||||||
if (showError) {
|
if (showError) {
|
||||||
fatal("Only privileged users can change their group memberships");
|
fatal("Only privileged users can change their group memberships");
|
||||||
|
@ -110,7 +110,7 @@ void lowerPrivileges(void) {
|
||||||
|
|
||||||
if (runAsUser >= 0) {
|
if (runAsUser >= 0) {
|
||||||
// Try to switch to the user-provided user id.
|
// Try to switch to the user-provided user id.
|
||||||
if (r && runAsUser != r) {
|
if (r && runAsUser != (int)r) {
|
||||||
fatal("Only privileged users can change their user id");
|
fatal("Only privileged users can change their user id");
|
||||||
}
|
}
|
||||||
check(!setresuid(runAsUser, runAsUser, -1));
|
check(!setresuid(runAsUser, runAsUser, -1));
|
||||||
|
@ -136,7 +136,7 @@ void dropPrivileges(void) {
|
||||||
|
|
||||||
if (runAsUser >= 0) {
|
if (runAsUser >= 0) {
|
||||||
// Try to switch to the user-provided user id.
|
// Try to switch to the user-provided user id.
|
||||||
if ((r && runAsUser != r) ||
|
if ((r && runAsUser != (int)r) ||
|
||||||
setresuid(runAsUser, runAsUser, runAsUser)) {
|
setresuid(runAsUser, runAsUser, runAsUser)) {
|
||||||
fatal("Only privileged users can change their user id.");
|
fatal("Only privileged users can change their user id.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// service.c -- Service descriptions
|
// service.c -- Service descriptions
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -243,9 +243,15 @@ void deleteService(struct Service *service) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyServiceHashEntry(void *arg, char *key, char *value) {
|
void destroyServiceHashEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
|
(void)key;
|
||||||
|
(void)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int enumerateServicesHelper(void *arg, const char *key, char **value) {
|
static int enumerateServicesHelper(void *arg, const char *key, char **value) {
|
||||||
|
(void)arg;
|
||||||
|
(void)key;
|
||||||
|
|
||||||
check(services = realloc(services,
|
check(services = realloc(services,
|
||||||
++numServices * sizeof(struct Service *)));
|
++numServices * sizeof(struct Service *)));
|
||||||
services[numServices-1] = *(struct Service **)value;
|
services[numServices-1] = *(struct Service **)value;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// service.h -- Service descriptions
|
// service.h -- Service descriptions
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -55,8 +55,8 @@ struct Service {
|
||||||
int useHomeDir;
|
int useHomeDir;
|
||||||
int authUser;
|
int authUser;
|
||||||
int useDefaultShell;
|
int useDefaultShell;
|
||||||
int uid;
|
int uid; /* Can be -1 */
|
||||||
int gid;
|
int gid; /* Can be -1 */
|
||||||
const char *user;
|
const char *user;
|
||||||
const char *group;
|
const char *group;
|
||||||
const char *cwd;
|
const char *cwd;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// session.c -- Session management for HTTP/HTTPS connections
|
// session.c -- Session management for HTTP/HTTPS connections
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -153,6 +153,9 @@ void finishAllSessions(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroySessionHashEntry(void *arg, char *key, char *value) {
|
static void destroySessionHashEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
|
(void)key;
|
||||||
|
|
||||||
deleteSession((struct Session *)value);
|
deleteSession((struct Session *)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +170,7 @@ char *newSessionKey(void) {
|
||||||
char *ptr = sessionKey;
|
char *ptr = sessionKey;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int bits = 0;
|
int bits = 0;
|
||||||
for (int i = 0;;) {
|
for (unsigned i = 0;;) {
|
||||||
bits = (bits << 8) | buf[i];
|
bits = (bits << 8) | buf[i];
|
||||||
count += 8;
|
count += 8;
|
||||||
drain:
|
drain:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
|
// ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -358,8 +358,8 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ShellInABox.prototype.about = function() {
|
ShellInABox.prototype.about = function() {
|
||||||
alert("Shell In A Box version " + "2.10 (revision 200)" +
|
alert("Shell In A Box version " + "2.10 (revision 202)" +
|
||||||
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
"For more information check http://shellinabox.com" +
|
"For more information check http://shellinabox.com" +
|
||||||
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
|
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
|
||||||
"\n\n" +
|
"\n\n" +
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
|
// ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -359,7 +359,7 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
|
||||||
|
|
||||||
ShellInABox.prototype.about = function() {
|
ShellInABox.prototype.about = function() {
|
||||||
alert("Shell In A Box version " + VERSION +
|
alert("Shell In A Box version " + VERSION +
|
||||||
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
"For more information check http://shellinabox.com" +
|
"For more information check http://shellinabox.com" +
|
||||||
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
|
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
|
||||||
"\n\n" +
|
"\n\n" +
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// shellinaboxd.c -- A custom web server that makes command line applications
|
// shellinaboxd.c -- A custom web server that makes command line applications
|
||||||
// available as AJAX web applications.
|
// available as AJAX web applications.
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -257,6 +257,7 @@ static void sessionDone(void *arg) {
|
||||||
|
|
||||||
static int handleSession(struct ServerConnection *connection, void *arg,
|
static int handleSession(struct ServerConnection *connection, void *arg,
|
||||||
short *events, short revents) {
|
short *events, short revents) {
|
||||||
|
(void)events;
|
||||||
struct Session *session = (struct Session *)arg;
|
struct Session *session = (struct Session *)arg;
|
||||||
session->connection = connection;
|
session->connection = connection;
|
||||||
int len = MAX_RESPONSE - session->len;
|
int len = MAX_RESPONSE - session->len;
|
||||||
|
@ -311,6 +312,7 @@ static int invalidatePendingHttpSession(void *arg, const char *key,
|
||||||
|
|
||||||
static int dataHandler(HttpConnection *http, struct Service *service,
|
static int dataHandler(HttpConnection *http, struct Service *service,
|
||||||
const char *buf, int len, URL *url) {
|
const char *buf, int len, URL *url) {
|
||||||
|
(void)len;
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
// Somebody unexpectedly closed our http connection (e.g. because of a
|
// Somebody unexpectedly closed our http connection (e.g. because of a
|
||||||
// timeout). This is the last notification that we will get.
|
// timeout). This is the last notification that we will get.
|
||||||
|
@ -514,13 +516,13 @@ static void serveStaticFile(HttpConnection *http, const char *contentType,
|
||||||
// Remember the beginning of the "[if ...]" statement
|
// Remember the beginning of the "[if ...]" statement
|
||||||
ifPtr = ptr;
|
ifPtr = ptr;
|
||||||
}
|
}
|
||||||
} else if (ifPtr && !elsePtr && eol - ptr >= strlen(tag) + 7 &&
|
} else if (ifPtr && !elsePtr && eol - ptr >= (ssize_t)strlen(tag) + 7 &&
|
||||||
!memcmp(ptr, "[else ", 6) &&
|
!memcmp(ptr, "[else ", 6) &&
|
||||||
!memcmp(ptr + 6, tag, strlen(tag)) &&
|
!memcmp(ptr + 6, tag, strlen(tag)) &&
|
||||||
ptr[6 + strlen(tag)] == ']') {
|
ptr[6 + strlen(tag)] == ']') {
|
||||||
// Found an "[else ...]" statement. Remember where it started.
|
// Found an "[else ...]" statement. Remember where it started.
|
||||||
elsePtr = ptr;
|
elsePtr = ptr;
|
||||||
} else if (ifPtr && eol - ptr >= strlen(tag) + 8 &&
|
} else if (ifPtr && eol - ptr >= (ssize_t)strlen(tag) + 8 &&
|
||||||
!memcmp(ptr, "[endif ", 7) &&
|
!memcmp(ptr, "[endif ", 7) &&
|
||||||
!memcmp(ptr + 7, tag, strlen(tag)) &&
|
!memcmp(ptr + 7, tag, strlen(tag)) &&
|
||||||
ptr[7 + strlen(tag)] == ']') {
|
ptr[7 + strlen(tag)] == ']') {
|
||||||
|
@ -810,6 +812,7 @@ static void usage(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroyExternalFileHashEntry(void *arg, char *key, char *value) {
|
static void destroyExternalFileHashEntry(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
free(key);
|
free(key);
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
@ -938,7 +941,7 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
st.st_size + 2));
|
st.st_size + 2));
|
||||||
char *newData = strrchr(cssStyleSheet, '\000');
|
char *newData = strrchr(cssStyleSheet, '\000');
|
||||||
*newData++ = '\n';
|
*newData++ = '\n';
|
||||||
if (fread(newData, 1, st.st_size, css) != st.st_size) {
|
if (fread(newData, st.st_size, 1, css) != 1) {
|
||||||
fatal("Failed to read style sheet \"%s\"", optarg);
|
fatal("Failed to read style sheet \"%s\"", optarg);
|
||||||
}
|
}
|
||||||
newData[st.st_size]= '\000';
|
newData[st.st_size]= '\000';
|
||||||
|
@ -1149,7 +1152,7 @@ static void parseArgs(int argc, char * const argv[]) {
|
||||||
|
|
||||||
static void removeLimits() {
|
static void removeLimits() {
|
||||||
static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
|
static int res[] = { RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NPROC };
|
||||||
for (int i = 0; i < sizeof(res)/sizeof(int); i++) {
|
for (unsigned i = 0; i < sizeof(res)/sizeof(int); i++) {
|
||||||
struct rlimit rl;
|
struct rlimit rl;
|
||||||
getrlimit(res[i], &rl);
|
getrlimit(res[i], &rl);
|
||||||
if (rl.rlim_max < RLIM_INFINITY) {
|
if (rl.rlim_max < RLIM_INFINITY) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// usercss.c -- Defines user-selectable CSS options
|
// usercss.c -- Defines user-selectable CSS options
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -60,6 +60,9 @@
|
||||||
static struct HashMap *defines;
|
static struct HashMap *defines;
|
||||||
|
|
||||||
static void definesDestructor(void *arg, char *key, char *value) {
|
static void definesDestructor(void *arg, char *key, char *value) {
|
||||||
|
(void)arg;
|
||||||
|
(void)value;
|
||||||
|
|
||||||
free(key);
|
free(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +76,7 @@ static void readStylesheet(struct UserCSS *userCSS, const char *filename,
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
check(fp = fdopen(fd, "r"));
|
check(fp = fdopen(fd, "r"));
|
||||||
check(*style = malloc(st.st_size + 1));
|
check(*style = malloc(st.st_size + 1));
|
||||||
check(fread(*style, 1, st.st_size, fp) == st.st_size);
|
check(fread(*style, st.st_size, 1, fp) == 1);
|
||||||
(*style)[st.st_size] = '\000';
|
(*style)[st.st_size] = '\000';
|
||||||
*len = st.st_size;
|
*len = st.st_size;
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// VT100.js -- JavaScript based terminal emulator
|
// VT100.js -- JavaScript based terminal emulator
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -1955,8 +1955,8 @@ VT100.prototype.toggleBell = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
VT100.prototype.about = function() {
|
VT100.prototype.about = function() {
|
||||||
alert("VT100 Terminal Emulator " + "2.10 (revision 200)" +
|
alert("VT100 Terminal Emulator " + "2.10 (revision 202)" +
|
||||||
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
"For more information check http://shellinabox.com");
|
"For more information check http://shellinabox.com");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// VT100.js -- JavaScript based terminal emulator
|
// VT100.js -- JavaScript based terminal emulator
|
||||||
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License version 2 as
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -1956,7 +1956,7 @@ VT100.prototype.toggleBell = function() {
|
||||||
|
|
||||||
VT100.prototype.about = function() {
|
VT100.prototype.about = function() {
|
||||||
alert("VT100 Terminal Emulator " + VERSION +
|
alert("VT100 Terminal Emulator " + VERSION +
|
||||||
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
"\nCopyright 2008-2010 by Markus Gutschke\n" +
|
||||||
"For more information check http://shellinabox.com");
|
"For more information check http://shellinabox.com");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue