Initial commit of Mosh for Chrome as a stand-alone project.

This was initially developed with the intent of being a patch for the upstream
Mosh project. But it became apparent that it should be its own project, as it
really is a separate application which uses Mosh as a library.
This commit is contained in:
Richard Woodbury 2014-01-15 19:10:10 -05:00
commit 68661bb9dc
34 changed files with 4404 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build
*.swp

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "deps/chromium_assets"]
path = deps/chromium_assets
url = http://git.chromium.org/chromiumos/platform/assets.git
[submodule "deps/mosh"]
path = deps/mosh
url = https://github.com/keithw/mosh.git

674
COPYING Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

35
README Normal file
View file

@ -0,0 +1,35 @@
Mosh for Chrome: Mosh client port for Native Client.
Main author: Richard Woodbury (rpwoodbu@mybox.org)
To build on Linux, just execute:
$ ./build.sh
This will download and build all dependencies. The directory "src/app" will
contain all the assets necessary to run the client in the browser. Go to
chrome://extensions, enable "Developer mode", and click the "Load unpacked
extension button", directing it to the "app" directory. Then the app will be
launchable from the app screen and the extensions screen.
If there are problems with the build (particularly with building dependencies),
you may need to delete the "build" directory to get a fresh start.
To distribute the app, just upload the .zip file found in the "src/app"
directory.
The initial build will be very slow, as it has to download and build large
dependencies. Subsequent builds are much faster. To go even faster while
developing, including being able to build without updating dependencies (which
requires Internet access), execute:
$ ./build.sh fast
This will not work if you have not had a successful build in the "slow" mode.
Also note that it does not rebuild the .zip file, so DO NOT DISTRUBITE a "fast"
build.
To build on other platforms, you will need to get and setup the NaCl SDK and
naclports yourself, and set NACL_SDK_ROOT and NACL_PORTS appropriately (see
"build.sh" for hints). But all target platforms are cross-compiled from the one
you are running, so there is no reason to build on other platforms, other than
personal preference.

248
build.sh Executable file
View file

@ -0,0 +1,248 @@
#!/bin/bash -e
# Build the Native Client port of the Mosh client for running in the Chrome
# browser. If you have already built once and are doing active development on
# the Native Client port, invoke with the parameter "fast".
# Copyright 2013 Richard Woodbury
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
NACL_SDK_ZIP="nacl_sdk.zip"
NACL_SDK_URL="http://storage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/${NACL_SDK_ZIP}"
NACL_SDK_DIR="nacl_sdk"
NACL_SDK_VERSION="pepper_32"
DEPOT_TOOLS_URL="https://chromium.googlesource.com/chromium/tools/depot_tools.git"
DEPOT_TOOLS_DIR="depot_tools"
NACL_PORTS_URL="https://chromium.googlesource.com/external/naclports.git"
NACL_PORTS_DIR="naclports"
PROTOBUF_DIR="protobuf-2.5.0"
PROTOBUF_TAR="${PROTOBUF_DIR}.tar.bz2"
PROTOBUF_URL="https://protobuf.googlecode.com/files/${PROTOBUF_TAR}"
LIBSSH_DIR="libssh-0.6.0"
LIBSSH_TAR="${LIBSSH_DIR}.tar.xz"
LIBSSH_URL="https://red.libssh.org/attachments/download/71/${LIBSSH_TAR}"
INCLUDE_OVERRIDE="$(pwd)/src/include"
FAST=""
if [[ $# -gt 0 ]]; then
FAST="$1"
shift 1
fi
if [[ ! -d "build" ]]; then
mkdir -p build
fi
# Get NaCl SDK.
if [[ "$(uname)" != "Linux" && "${NACL_SDK_ROOT}" == "" ]]; then
echo "Please set NACL_SDK_ROOT. Auto-setup of this is only on Linux."
exit 1
fi
if [[ "${NACL_SDK_ROOT}" == "" ]]; then
if [[ ! -d "build/${NACL_SDK_DIR}" ]]; then
pushd build > /dev/null
wget "${NACL_SDK_URL}"
unzip "${NACL_SDK_ZIP}"
popd > /dev/null
fi
if [[ "${FAST}" != "fast" ]]; then
pushd "build/${NACL_SDK_DIR}"
./naclsdk update "${NACL_SDK_VERSION}"
popd > /dev/null
fi
export NACL_SDK_ROOT="$(pwd)/build/${NACL_SDK_DIR}/${NACL_SDK_VERSION}"
fi
# Get depot_tools. If not on Linux, skip this and expect ${NACL_PORTS} to be
# set.
if [[ "$(uname)" == "Linux" ]]; then
if [[ ! -d "build/${DEPOT_TOOLS_DIR}" ]]; then
pushd build > /dev/null
git clone "${DEPOT_TOOLS_URL}"
popd > /dev/null
fi
export PATH="${PATH}:$(pwd)/build/${DEPOT_TOOLS_DIR}"
fi
# Get NaCl Ports.
if [[ "$(uname)" != "Linux" && "${NACL_PORTS}" == "" ]]; then
echo "Please set NACL_PORTS. Auto-setup of this is only on Linux."
exit 1
fi
if [[ "${NACL_PORTS}" == "" ]]; then
if [[ ! -d "build/${NACL_PORTS_DIR}" ]]; then
mkdir -p "build/${NACL_PORTS_DIR}"
pushd "build/${NACL_PORTS_DIR}" > /dev/null
gclient config --name=src "${NACL_PORTS_URL}"
popd > /dev/null
fi
if [[ "${FAST}" != "fast" ]]; then
pushd "build/${NACL_PORTS_DIR}" > /dev/null
gclient sync
popd > /dev/null
fi
export NACL_PORTS="$(pwd)/build/${NACL_PORTS_DIR}"
fi
# Get and build protoc to match what's in NaCl Ports.
if [[ ! -d "build/${PROTOBUF_DIR}" ]]; then
mkdir -p "build/${PROTOBUF_DIR}"
pushd "build" > /dev/null
wget "${PROTOBUF_URL}"
tar -xjf "${PROTOBUF_TAR}"
cd "${PROTOBUF_DIR}"
./configure
make
popd > /dev/null
fi
PROTO_PATH="$(pwd)/build/${PROTOBUF_DIR}/src"
export PATH="${PROTO_PATH}:${PATH}"
export LD_LIBRARY_PATH="${PROTO_PATH}/.libs"
# Get and patch (but not build) libssh.
if [[ ! -d "build/${LIBSSH_DIR}" ]]; then
pushd "build" > /dev/null
if [[ ! -f "${LIBSSH_TAR}" ]]; then
wget "${LIBSSH_URL}"
fi
tar -xJf "${LIBSSH_TAR}"
cd "${LIBSSH_DIR}"
patch -p1 < ../../src/libssh.patch
popd > /dev/null
fi
#export NACL_GLIBC="1"
pushd src > /dev/null
make clean
popd > /dev/null
for arch in x86_64 i686; do ( # Do all this in a separate subshell.
export NACL_ARCH="${arch}"
echo "Building packages in NaCl Ports..."
pushd "${NACL_PORTS}/src" > /dev/null
make ncurses zlib openssl protobuf
popd > /dev/null
echo "Updating submodules..."
git submodule init
git submodule update
echo "Making hterm dist..."
pushd deps/chromium_assets/chromeapps/hterm > /dev/null
if [[ ! -d dist ]]; then
bin/mkdist.sh
fi
popd > /dev/null
echo "Loading naclports environment..."
# For some reason I have to build NACLPORTS_LIBDIR myself, and I need vars to
# do this that nacl_env.sh generates, so I end up calling that guy twice.
. ${NACL_PORTS}/src/build_tools/nacl_env.sh
export NACLPORTS_LIBDIR=${NACL_TOOLCHAIN_ROOT}/${NACL_CROSS_PREFIX}/usr/lib
eval $(${NACL_PORTS}/src/build_tools/nacl_env.sh --print)
glibc_compat="${NACL_TOOLCHAIN_ROOT}/${arch}-nacl/usr/include/glibc-compat"
export CFLAGS="${CFLAGS} -I${glibc_compat}"
export CXXFLAGS="${CXXFLAGS} -I${glibc_compat}"
if [[ ${FAST} != "fast" ]]; then
if [[ ! -d "build/${LIBSSH_DIR}/build-${arch}" ]]; then
echo "Building libssh..."
pushd "build/${LIBSSH_DIR}" > /dev/null
rm -Rf "build-${arch}"
mkdir "build-${arch}"
cd "build-${arch}"
cmake -DWITH_ZLIB=OFF -DWITH_STATIC_LIB=ON -DWITH_SHARED_LIB=OFF -DWITH_EXAMPLES=OFF -DHAVE_GETADDRINFO=ON ..
make
popd > /dev/null
fi
#
# Mosh client build.
#
pushd deps/mosh > /dev/null
if [[ ! -f configure ]]; then
echo "Running autogen."
./autogen.sh
fi
popd > /dev/null # ..
# Make a symlink into the usual include location so that the "override"
# assert.h can find it. It changes for each port, and in unexpected ways,
# which complicates things.
include_arch="${arch}"
if [[ "${include_arch}" == "i686" ]]; then
include_arch="x86_64" # Yes, really.
fi
rm -f build/include
ln -s "${NACL_TOOLCHAIN_ROOT}/${include_arch}-nacl/include" build/include
build_dir="build/${NACL_ARCH}"
mkdir -p "${build_dir}"
pushd "${build_dir}" > /dev/null
# Built-in functions cannot be overridden.
export CXXFLAGS="${CXXFLAGS} -fno-builtin"
if [[ "${NACL_GLIBC}" != "1" ]]; then
# Do things specific to newlib.
export CXXFLAGS="${CXXFLAGS} -I${INCLUDE_OVERRIDE} -DHAVE_FORKPTY -DHAVE_SYS_UIO_H"
fi
export LDFLAGS="${LDFLAGS} -Xlinker --unresolved-symbols=ignore-all"
configure_options="--host=${arch} --enable-client=yes --enable-server=no --disable-silent-rules"
if [[ "${arch}" == "i686" ]]; then
# The i686 build doesn't seem to have stack protection, even though
# "configure" finds it, so disabling hardening. :(
configure_options="${configure_options} --disable-hardening"
fi
echo "Configuring..."
../../deps/mosh/configure ${configure_options}
echo "Building Mosh with NaCl compiler..."
make clean
if [[ "${NACL_GLIBC}" == "1" ]]; then
make || echo "*** Ignore error IFF it was the linking step. ***"
else
make
fi
popd > /dev/null # ${build_dir}
fi
pushd src > /dev/null
target="app/mosh_client_${NACL_ARCH}.nexe"
echo "Building ${target}..."
make "${target}"
popd > /dev/null # src
) done
pushd src > /dev/null
# Copy hterm dist files into app directory.
mkdir -p app/hterm
cp -f ../deps/chromium_assets/chromeapps/hterm/dist/js/* app/hterm
if [[ ${FAST} == "fast" ]]; then
make nmf
else
make all
fi
popd > /dev/null # src
echo "Done."

1
deps/chromium_assets vendored Submodule

@ -0,0 +1 @@
Subproject commit f08a102569e3dd80b4eef0da20e31406857a4520

1
deps/mosh vendored Submodule

@ -0,0 +1 @@
Subproject commit c6bf3a2025e86b34512a995ea8b1e45d7586860f

1
src/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.o

129
src/Makefile Normal file
View file

@ -0,0 +1,129 @@
# Copyright 2013 Richard Woodbury
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
OUTDIR:=app
PROJECT:=$(OUTDIR)/mosh_client
CXX_SOURCES:=\
mosh_nacl.cc \
pepper_posix.cc \
pepper_posix_native_udp.cc \
pepper_posix_native_tcp.cc \
pepper_posix_selector.cc \
pepper_posix_tcp.cc \
pepper_posix_udp.cc \
pepper_wrapper.cc \
ssh.cc
CXX_HEADERS:=\
pepper_posix.h \
pepper_posix_selector.h \
pepper_posix_native_udp.h \
pepper_posix_native_tcp.h \
pepper_posix_tcp.h \
pepper_posix_udp.h \
pepper_wrapper.h \
ssh.h
OBJECTS:=\
../build/ARCH/src/frontend/mosh-client-nacl.o \
../build/ARCH/src/frontend/stmclient.o \
../build/ARCH/src/frontend/terminaloverlay.o
LIBDIRS:=\
-L../build/ARCH/src/frontend \
-L../build/ARCH/src/crypto \
-L../build/ARCH/src/network \
-L../build/ARCH/src/statesync \
-L../build/ARCH/src/terminal \
-L../build/ARCH/src/util \
-L../build/ARCH/src/protobufs \
-L../build/libssh-0.6.0/build-ARCH/src
LIBS:=\
-lmoshcrypto \
-lmoshnetwork \
-lmoshstatesync \
-lmoshterminal \
-lmoshutil \
-lmoshprotos \
-lssh
OSNAME:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py)
TC_PATH:=$(abspath $(NACL_SDK_ROOT)/toolchain/$(OSNAME)_x86_newlib)
CXX:=$(TC_PATH)/bin/i686-nacl-g++
OBJCOPY:=$(TC_PATH)/bin/i686-nacl-objcopy
# Project Build flags
override LDFLAGS+=-Xlinker --wrap=dup -Xlinker --wrap=_ssh_log -lppapi_cpp -lppapi -lz -lssl -lncurses -lprotobuf -lcrypto -lglibc-compat
# Add this linker flag to build statically:
#-static -T $(TC_PATH)/x86_64-nacl/lib/ldscripts/elf64_nacl.x.static
#override WARNINGS+=-Wno-long-long -Wall -Wswitch-enum -Werror
#override CXXFLAGS+=-pthread -std=gnu++0x $(WARNINGS) -Iinclude \
-I$(NACL_SDK_ROOT)/include
override CXXFLAGS+=-pthread $(WARNINGS) -I$(NACL_SDK_ROOT)/include -fno-builtin \
-I../build/libssh-0.6.0/include
#override CFLAGS+=-pthread $(WARNINGS) -Iinclude -I$(NACL_SDK_ROOT)/include
#COMPAT_INC:=-I$(PNACL_TC_ROOT)/usr/include/glibc-compat
COMPAT_INC:=-I${TC_PATH}/ARCH-nacl/usr/include/glibc-compat
NEWLIB_CXXFLAGS:=$(CXXFLAGS) -DUSE_NEWLIB $(COMPAT_INC)
THIS_MAKEFILE:=$(abspath $(lastword $(MAKEFILE_LIST)))
# Declare the ALL target first, to make the 'all' target the default build
all: $(PROJECT).zip
# Convenience target for skipping the .zip file.
nmf: $(PROJECT).nmf
$(PROJECT).zip : $(PROJECT).nmf
cd $(OUTDIR) && zip -r ../$(PROJECT).zip .
# Define 32 bit compile and link rules for C++ sources
i686_OBJS:=$(patsubst %.cc,%_32.o,$(CXX_SOURCES))
$(i686_OBJS) : %_32.o : %.cc $(THIS_MAKE) $(CXX_HEADERS)
$(CXX) -o $@ -c $< -m32 $(i686_NEWLIB_CXXFLAGS)
../build/i686/src/frontend/mosh-client-nacl.o : ../build/i686/src/frontend/mosh-client.o
$(OBJCOPY) --redefine-sym main=mosh_main $< $@
i686_MOSHOBJS:=$(subst ARCH,i686,$(OBJECTS))
i686_MOSHLIBDIRS:=$(subst ARCH,i686,$(LIBDIRS))
i686_NEWLIB_CXXFLAGS:=$(subst ARCH,i686,$(NEWLIB_CXXFLAGS))
$(PROJECT)_i686.nexe : $(i686_OBJS) $(i686_MOSHOBJS)
$(CXX) -o $@ $^ -m32 $(LIBS) $(i686_NEWLIB_CXXFLAGS) \
$(LDFLAGS) $(i686_MOSHLIBDIRS)
# Define 64 bit compile and link rules for C++ sources
x86_64_OBJS:=$(patsubst %.cc,%_64.o,$(CXX_SOURCES))
$(x86_64_OBJS) : %_64.o : %.cc $(THIS_MAKE) $(CXX_HEADERS)
$(CXX) -o $@ -c $< -m64 $(x86_64_NEWLIB_CXXFLAGS)
../build/x86_64/src/frontend/mosh-client-nacl.o : ../build/x86_64/src/frontend/mosh-client.o
$(OBJCOPY) --redefine-sym main=mosh_main $< $@
x86_64_MOSHOBJS:=$(subst ARCH,x86_64,$(OBJECTS))
x86_64_MOSHLIBDIRS:=$(subst ARCH,x86_64,$(LIBDIRS))
x86_64_NEWLIB_CXXFLAGS:=$(subst ARCH,x86_64,$(NEWLIB_CXXFLAGS))
$(PROJECT)_x86_64.nexe : $(x86_64_OBJS) $(x86_64_MOSHOBJS)
$(CXX) -o $@ $^ -m64 $(LIBS) $(x86_64_NEWLIB_CXXFLAGS) \
$(LDFLAGS) $(x86_64_MOSHLIBDIRS)
# Create NaCl Manifest
$(PROJECT).nmf : $(PROJECT)_x86_64.nexe $(PROJECT)_i686.nexe
$(NACL_SDK_ROOT)/tools/create_nmf.py $^ -o $@ -s $(OUTDIR)
clean:
rm -rf *.o $(PROJECT).zip $(PROJECT)*.nexe $(PROJECT).pexe \
$(PROJECT).nmf $(OUTDIR)/lib* $(OUTDIR)/hterm

5
src/app/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
lib*
*.nmf
*.nexe
*.zip
hterm

5
src/app/background.js Normal file
View file

@ -0,0 +1,5 @@
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('mosh_client.html', {
'id': 'mosh_client',
});
});

BIN
src/app/laptop_terminal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

27
src/app/manifest.json Normal file
View file

@ -0,0 +1,27 @@
{
"manifest_version": 2,
"minimum_chrome_version": "32",
"name": "Mosh",
"description": "Mosh (mobile shell) for Chrome.",
"offline_enabled": true,
"version": "0.0.0.13",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
{"socket": [
"resolve-host",
"tcp-connect",
"udp-bind",
"udp-send-to"
]},
"clipboardRead",
"clipboardWrite",
"storage"
],
"icons": {
"128": "laptop_terminal.png"
}
}

137
src/app/mosh_client.html Normal file
View file

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html> <!--
Copyright 2013 Richard Woodbury
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<head>
<title>Mosh</title>
<script src="mosh_client.js" type="text/javascript"></script>
<script src="hterm/hterm_deps.js" type="text/javascript"></script>
<script src="hterm/hterm.js" type="text/javascript"></script>
<style>
body {
position: absolute;
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#terminal {
display: block;
position: relative;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="setup">
<form id="args">
<table>
<tr>
<td>Mode:</td>
<td>
<label>
<input id="ssh-mode" name="mode" type="radio" value="ssh" checked>SSH</input>
</label>
<label>
<input id="manual-mode" name="mode" type="radio" value="manual">Manual</input>
</label>
</td>
</tr>
<tr id="username-row" hidden="true">
<td>Username:</td>
<td><input id="user" type="text" autofocus></td>
</tr>
<tr>
<td>Hostname or IP address:</td>
<td><input id="addr" type="text"></td>
</tr>
<td id="credential-label"></td>
<td><input id="key" type="password"></td>
</tr>
<tr>
<td>Port number:</td>
<td><input id="port" type="text"></td>
</tr>
</table>
<input id="connect" type="submit" value="Connect">
</form>
<h3>Instructions</h3>
<p>
<strong>NEW!</strong>
Mosh for Chrome now supports ssh-based connection setup! Just fill
in the fields, and just like the "mosh" command, an ssh connection will be
made to the server to establish trust, start the mosh-server, and get the
encryption key. Right now, only password auth is supported.
<p>
Should you need to setup the connection manually (e.g., cannot use password
auth), choose "manual" mode. Then manually ssh to the remote machine and
run 'mosh-server'. It will print the key and the port number; enter those
into this form, as well as the IP address (not the hostname) of the remote
machine, and click 'Connect'. 'mosh-server' gives you about a minute to do
this before giving up and exiting. You can close your ssh session once
'mosh-server' is running.
<h3>Known issues</h3>
<p>
<ul>
<li>ssh fingerprint goes by too fast, with no option to confirm.
<li>Only ssh password auth is currently supported.
<li>Too easy to close the window by mistake.
<li>x86_32 port is built with --disable-hardening, which reduces
security somewhat.
<li>Unicode input doesn't work (rendering does).
</ul>
<h3>Changelog</h3>
<ul>
<li>0.0.0.13 (2014-01-15) - Add ssh-based connection setup.
<li>0.0.0.12 (2014-01-05) - Fix Unicode rendering (input still lacking).
This also fixes the bad interaction with tmux, as tmux uses Unicode
characters for drawing borders. This was accomplished by building
against newlib instead of glibc (long story). This could be the source
of new and exciting bugs. But besides fixing Unicode support, this
paves the way for ARM support, and ultimately using Portable Native
Client (PNaCl), which allows a single build to run on any architecture.
(PNaCl doesn't yet support C++ exceptions, but when it does, we're
ready.)
<li>0.0.0.11 (2013-12-23) - Improved error reporting. stderr now goes to
the terminal (just like the real mosh-client), and also the Javascript
console. If in doubt, check the Javascript console, as the terminal may
obscure errors. <li>0.0.0.10 (2013-12-16) - Now permits hostnames as
well as IP addresses. Crypto randomness is now actually random. Tested
successfully on Linux and MacOS. <li>0.0.0.9 (2013-12-15) - Fix for
x86_32 failing to start. Tested on Linux; not yet tested on MacOS.
<li>0.0.0.8 (2013-12-02) - Build against pepper_31 and newer naclports.
Eliminate spurious new browser tab on connect.
<li>0.0.0.7 (2013-11-30) - Add support for i686 (needed for 32-bit
Chrome, e.g. MacOS).
<li>0.0.0.6 (2013-10-16) - Fix bug in readfrom() emulation which caused
Mosh to show a crypto error, rendering the session useless.
<li>0.0.0.5 (2013-10-15) - Clean up initial screen and add this info.
<li>0.0.0.4 (2013-10-14) - Big code refactor.
<li>0.0.0.3 (2013-10-08) - Fix bug in packet handling code which caused
perceived sluggishness.
<li>0.0.0.2 (2013-10-08) - Add application icon.
<li>0.0.0.1 (2013-10-07) - Initial working version.
</div>
<div id="terminal"></div>
</body>
</html>

163
src/app/mosh_client.js Normal file
View file

@ -0,0 +1,163 @@
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
'use strict';
window.onload = function() {
var connectButton = document.querySelector('#connect');
connectButton.onclick = onConnectClick;
var sshModeButton = document.querySelector('#ssh-mode');
sshModeButton.onchange = updateMode;
var manualModeButton = document.querySelector('#manual-mode');
manualModeButton.onchange = updateMode;
var form = document.querySelector('#args');
form.onsubmit = function() { return false; };
updateMode();
};
function execMosh() {
var args = {}
var form = document.querySelector('#args');
args['addr'] = form['addr'].value;
args['port'] = form['port'].value;
args['user'] = form['user'].value;
args['key'] = form['key'].value;
for (var i = 0; i < form['mode'].length; ++i) {
if (form['mode'][i].checked) {
args['mode'] = form['mode'][i].value;
break;
}
}
var setup = document.querySelector('#setup');
setup.parentNode.removeChild(setup);
var terminal = new hterm.Terminal('mosh');
terminal.decorate(document.querySelector('#terminal'));
terminal.onTerminalReady = function() {
terminal.setCursorPosition(0, 0);
terminal.setCursorVisible(true);
terminal.runCommandClass(mosh.CommandInstance, args);
};
document.title += ' - ' + args.addr;
// Useful for console debugging.
window.term_ = terminal;
};
function onConnectClick(e) {
lib.init(execMosh, console.log.bind(console));
};
function updateMode(e) {
var sshModeButton = document.querySelector('#ssh-mode');
var portField = document.querySelector('#port');
var usernameRow = document.querySelector('#username-row');
var credentialLabel = document.querySelector('#credential-label');
if (sshModeButton.checked) {
portField.value = 22;
usernameRow.hidden = false;
credentialLabel.innerText = "Password:";
} else {
portField.value = 60001;
usernameRow.hidden = true;
credentialLabel.innerText = "MOSH_KEY:";
}
}
var mosh = {};
mosh.CommandInstance = function(argv) {
// Command arguments.
this.argv_ = argv;
// Command environment.
this.environment_ = argv.environment || {};
// hterm.Terminal.IO instance.
this.io = null;
};
mosh.CommandInstance.run = function(argv) {
return new nassh.CommandInstance(argv);
};
mosh.CommandInstance.prototype.run = function() {
// Useful for console debugging.
window.mosh_client_ = this;
this.io = this.argv_.io.push();
this.io.onVTKeystroke = this.sendString_.bind(this);
this.io.sendString = this.sendString_.bind(this);
this.io.onTerminalResize = this.onTerminalResize_.bind(this);
this.moshNaCl_ = window.document.createElement('embed');
this.moshNaCl_.style.cssText = (
'position: absolute;' +
'top: -99px' +
'width: 0;' +
'height: 0;');
this.moshNaCl_.setAttribute('src', 'mosh_client.nmf');
this.moshNaCl_.setAttribute('type', 'application/x-nacl');
this.moshNaCl_.setAttribute('key', this.argv_.argString['key']);
this.moshNaCl_.setAttribute('addr', this.argv_.argString['addr']);
this.moshNaCl_.setAttribute('port', this.argv_.argString['port']);
this.moshNaCl_.setAttribute('user', this.argv_.argString['user']);
this.moshNaCl_.setAttribute('mode', this.argv_.argString['mode']);
// Delete argv_, as it contains sensitive info.
delete this.argv_;
this.moshNaCl_.addEventListener('load', function(e) {
console.log('Mosh NaCl loaded.');
// Remove sensitive argument attributes.
window.mosh_client_.moshNaCl_.removeAttribute('key');
});
this.moshNaCl_.addEventListener('message', this.onMessage_.bind(this));
this.moshNaCl_.addEventListener('crash', function(e) {
console.log('Mosh NaCl crashed.');
// TODO: Handle the crash better, so the user knows what happened.
});
document.body.insertBefore(this.moshNaCl_, document.body.firstChild);
};
mosh.CommandInstance.prototype.onMessage_ = function(e) {
var data = e.data['data'];
var type = e.data['type'];
if (type == 'display') {
this.io.print(data);
} else if (type == 'log') {
console.log(String(data));
} else if (type == 'error') {
// TODO: Find a way to output errors that doesn't interfere with the
// terminal window.
this.io.print('ERROR: ' + String(data) + '\r');
console.log('ERROR: ' + String(data));
} else {
console.log('Unknown message type: ' + JSON.stringify(e.data));
}
};
mosh.CommandInstance.prototype.sendString_ = function(string) {
this.moshNaCl_.postMessage(string);
};
mosh.CommandInstance.prototype.onTerminalResize_ = function(w, h) {
// Send new size as an int, with the width as the high 16 bits.
this.moshNaCl_.postMessage((w << 16) + h);
};

52
src/include/assert.h Normal file
View file

@ -0,0 +1,52 @@
// Override assert.h to make Mosh build against newlib.
// This includes things not necessarily meant for assert.h, but this is a
// convenient place that is included from most things.
#ifndef __OVERRIDE_ASSERT_H__
#define __OVERRIDE_ASSERT_H__
// First, include the "real" assert.h, through a symlink maintained by the
// build script.
#include "../../build/include/assert.h"
// Include anything that wasn't included properly.
#include <sys/time.h>
extern "C" {
// Newlib headers are missing posix_memalign().
int posix_memalign(void **memptr, size_t alignment, size_t size);
// Define getrlimit() and friends, which we "implement" in the wrapper.
#define RLIMIT_CORE 0
typedef int rlim_t;
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
// Define some things needed for Internet functions.
typedef uint32_t u_int32_t;
// TODO: Make this do something better and just ignore alignment.
#define _ALIGN(n) n
// Nullify cfmakeraw, as we don't need it to function.
#define cfmakeraw(n) ;
// Define pselect.
#include <sys/signal.h>
extern int pselect (int __nfds, fd_set *__restrict __readfds,
fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,
const struct timespec *__restrict __timeout,
const sigset_t *__restrict __sigmask);
// Newlib's getopt() seems to crash. Redirect it to our implementation.
#define getopt(a, b, c) mygetopt(a, b, c)
int mygetopt(int argc, char * const argv[], const char *optstring);
} // extern "C"
#endif // __OVERRIDE_ASSERT_H__

76
src/libssh.patch Normal file
View file

@ -0,0 +1,76 @@
diff -ur ./cmake/Modules/DefineCompilerFlags.cmake ../libssh-0.6.0.fixed-up/cmake/Modules/DefineCompilerFlags.cmake
--- ./cmake/Modules/DefineCompilerFlags.cmake 2013-12-21 12:37:12.000000000 -0500
+++ ../libssh-0.6.0.fixed-up/cmake/Modules/DefineCompilerFlags.cmake 2014-01-12 00:16:15.741775257 -0500
@@ -10,7 +10,7 @@
if (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)")
# add -Wconversion ?
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -pedantic-errors")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -fno-builtin")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wmissing-prototypes -Wdeclaration-after-statement")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunused -Wfloat-equal -Wpointer-arith -Wwrite-strings -Wformat-security")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-format-attribute")
diff -ur ./src/CMakeLists.txt ../libssh-0.6.0.fixed-up/src/CMakeLists.txt
--- ./src/CMakeLists.txt 2013-12-21 12:37:12.000000000 -0500
+++ ../libssh-0.6.0.fixed-up/src/CMakeLists.txt 2014-01-12 00:20:14.306463716 -0500
@@ -93,10 +93,12 @@
CACHE INTERNAL "libssh link libraries"
)
+if (WITH_SHARED_LIB)
set(LIBSSH_SHARED_LIBRARY
ssh_shared
CACHE INTERNAL "libssh shared library"
)
+endif (WITH_SHARED_LIB)
if (WITH_STATIC_LIB)
set(LIBSSH_STATIC_LIBRARY
@@ -217,6 +219,7 @@
${LIBSSH_PRIVATE_INCLUDE_DIRS}
)
+if (WITH_SHARED_LIB)
add_library(${LIBSSH_SHARED_LIBRARY} SHARED ${libssh_SRCS})
target_link_libraries(${LIBSSH_SHARED_LIBRARY} ${LIBSSH_LINK_LIBRARIES})
@@ -247,6 +250,7 @@
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
COMPONENT libraries
)
+endif (WITH_SHARED_LIB)
if (WITH_STATIC_LIB)
add_library(${LIBSSH_STATIC_LIBRARY} STATIC ${libssh_SRCS})
diff -ur ./src/threads/CMakeLists.txt ../libssh-0.6.0.fixed-up/src/threads/CMakeLists.txt
--- ./src/threads/CMakeLists.txt 2013-02-07 13:23:14.000000000 -0500
+++ ../libssh-0.6.0.fixed-up/src/threads/CMakeLists.txt 2014-01-12 00:15:36.461657082 -0500
@@ -11,10 +11,12 @@
${CMAKE_BINARY_DIR}
)
+if (WITH_SHARED_LIB)
set(LIBSSH_THREADS_SHARED_LIBRARY
ssh_threads_shared
CACHE INTERNAL "libssh threads shared library"
)
+endif (WITH_SHARED_LIB)
if (WITH_STATIC_LIB)
set(LIBSSH_THREADS_STATIC_LIBRARY
@@ -53,6 +55,7 @@
${LIBSSH_THREADS_PRIVATE_INCLUDE_DIRS}
)
+if (WITH_SHARED_LIB)
add_library(${LIBSSH_THREADS_SHARED_LIBRARY} SHARED ${libssh_threads_SRCS})
target_link_libraries(${LIBSSH_THREADS_SHARED_LIBRARY} ${LIBSSH_THREADS_LINK_LIBRARIES})
@@ -82,6 +85,7 @@
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
COMPONENT libraries
)
+endif (WITH_SHARED_LIB)
if (WITH_STATIC_LIB)
add_library(${LIBSSH_THREADS_STATIC_LIBRARY} STATIC ${libssh_threads_SRCS})

543
src/mosh_nacl.cc Normal file
View file

@ -0,0 +1,543 @@
// mosh_nacl.cc - Mosh for Native Client (NaCl).
//
// This file contains the Mosh-specific bits of the NaCl port of the
// client.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_wrapper.h"
#include "ssh.h"
#include <deque>
#include <string>
#include <vector>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include "irt.h"
#include "ppapi/cpp/host_resolver.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/instance_handle.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
#include "ppapi/utility/completion_callback_factory.h"
using ::std::string;
using ::std::vector;
// Forward declaration of mosh_main(), as it has no header file.
extern "C" {
int mosh_main(int argc, char *argv[]);
}
// Used by pepper_wrapper.h functions.
static class MoshClientInstance *instance = NULL;
// Implements most of the plumbing to get keystrokes to Mosh. A tiny amount of
// plumbing is in the MoshClientInstance::HandleMessage().
class Keyboard : public PepperPOSIX::Reader {
public:
Keyboard() {
pthread_mutex_init(&keypresses_lock_, NULL);
}
virtual ~Keyboard() {
pthread_mutex_destroy(&keypresses_lock_);
}
virtual ssize_t Read(void *buf, size_t count) {
// TODO: Could submit in batches, but rarely will get in batches.
int result = 0;
pthread_mutex_lock(&keypresses_lock_);
if (keypresses_.size() > 0) {
((char *)buf)[0] = keypresses_.front();
keypresses_.pop_front();
target_->UpdateRead(keypresses_.size() > 0);
result = 1;
} else {
// Cannot use Log() here; circular dependency.
fprintf(stderr, "read(): From STDIN, no data, treat as nonblocking.\n");
}
pthread_mutex_unlock(&keypresses_lock_);
return result;
}
// Handle input from the keyboard.
void HandleInput(string input) {
pthread_mutex_lock(&keypresses_lock_);
for (int i = 0; i < input.size(); ++i) {
keypresses_.push_back(input[i]);
}
pthread_mutex_unlock(&keypresses_lock_);
target_->UpdateRead(true);
}
private:
// Queue of keyboard keypresses.
std::deque<char> keypresses_; // Guard with keypresses_lock_.
pthread_mutex_t keypresses_lock_;
};
// Implements the plumbing to get stdout to the terminal.
class Terminal : public PepperPOSIX::Writer {
public:
Terminal(MoshClientInstance *instance) : instance_(instance) {}
// This has to be defined below MoshClientInstance due to dependence on it.
virtual ssize_t Write(const void *buf, size_t count);
private:
MoshClientInstance *instance_;
};
// Implements the plumbing to get stderr to Javascript.
class ErrorLog : public PepperPOSIX::Writer {
public:
ErrorLog(MoshClientInstance *instance) : instance_(instance) {}
// This has to be defined below MoshClientInstance due to dependence on it.
virtual ssize_t Write(const void *buf, size_t count);
private:
MoshClientInstance *instance_;
};
// Implements the plumbing to get SIGWINCH to Mosh. A tiny amount of plumbing
// is in MoshClientInstance::HandleMessage();
class WindowChange : public PepperPOSIX::Signal {
public:
WindowChange() : sigwinch_handler_(NULL), width_(80), height_(24) {}
// Update geometry and send SIGWINCH.
void Update(int width, int height) {
width_ = width;
height_ = height;
target_->UpdateRead(true);
}
void SetHandler(void (*sigwinch_handler)(int)) {
sigwinch_handler_ = sigwinch_handler;
}
virtual void Handle() {
sigwinch_handler_(SIGWINCH);
target_->UpdateRead(false);
}
int height() { return height_; }
int width() { return width_; }
private:
int width_;
int height_;
void (*sigwinch_handler_)(int);
};
class DevURandom : public PepperPOSIX::Reader {
public:
DevURandom() {
nacl_interface_query(NACL_IRT_RANDOM_v0_1, &random_, sizeof(random_));
}
virtual ssize_t Read(void *buf, size_t count) {
size_t bytes_read = 0;
random_.get_random_bytes(buf, count, &bytes_read);
return bytes_read;
}
private:
struct nacl_irt_random random_;
};
// DevURandom factory for registration with PepperPOSIX::POSIX.
PepperPOSIX::File *DevURandomFactory() {
return new DevURandom();
}
class MoshClientInstance : public pp::Instance {
public:
explicit MoshClientInstance(PP_Instance instance) :
pp::Instance(instance), addr_(NULL), port_(NULL), ssh_mode_(false),
posix_(NULL), keyboard_(NULL), instance_handle_(this),
window_change_(NULL), resolver_(instance_handle_), cc_factory_(this) {
++num_instances_;
assert (num_instances_ == 1);
::instance = this;
}
virtual ~MoshClientInstance() {
// Wait for thread to finish.
if (thread_) {
pthread_join(thread_, NULL);
}
delete[] addr_;
delete[] port_;
delete posix_;
}
virtual void HandleMessage(const pp::Var &var) {
if (var.is_string()) {
string s = var.AsString();
keyboard_->HandleInput(s);
} else if (var.is_number()) {
int32_t num = var.AsInt();
window_change_->Update(num >> 16, num & 0xffff);
} else {
Log("Got a message of an unexpected type.");
}
}
// Type of data to output to Javascript.
enum OutputType {
TYPE_DISPLAY = 0,
TYPE_LOG,
TYPE_ERROR,
};
// Low-level function to output data to Javascript.
void Output(OutputType t, const string &s) {
string type;
switch (t) {
case TYPE_DISPLAY:
type = "display";
break;
case TYPE_LOG:
type = "log";
break;
case TYPE_ERROR:
type = "error";
break;
default:
// Bad type.
return;
}
pp::VarDictionary dict;
dict.Set(pp::Var("type"), pp::Var(type));
dict.Set(pp::Var("data"), pp::Var(s));
PostMessage(pp::Var(dict));
}
// Sends messages to the Javascript console log.
void Logv(OutputType t, const char *format, va_list argp) {
char buf[1024];
int size = vsnprintf(buf, sizeof(buf), format, argp);
Output(t, string((const char *)buf, size));
}
// Sends messages to the Javascript console log.
void Log(const char *format, ...) {
va_list argp;
va_start(argp, format);
Logv(TYPE_LOG, format, argp);
va_end(argp);
}
// Sends error messages to the Javascript console log and terminal.
void Error(const char *format, ...) {
va_list argp;
va_start(argp, format);
Logv(TYPE_ERROR, format, argp);
va_end(argp);
}
virtual bool Init(uint32_t argc, const char *argn[], const char *argv[]) {
bool got_addr = false;
const char *secret;
for (int i = 0; i < argc; ++i) {
string name = argn[i];
int len = strlen(argv[i]) + 1;
if (name == "key") {
secret = argv[i];
} else if (name == "addr" && addr_ == NULL) {
// TODO: Support IPv6 when Mosh does.
const PP_HostResolver_Hint hint = {PP_NETADDRESS_FAMILY_IPV4, 0};
// Mosh will launch via this callback when the resolution completes.
resolver_.Resolve(argv[i], 0, hint,
cc_factory_.NewCallback(&MoshClientInstance::Launch));
got_addr = true;
} else if (name == "port" && port_ == NULL) {
port_ = new char[len];
strncpy(port_, argv[i], len);
} else if (name == "mode") {
if (string(argv[i]) == "ssh") {
ssh_mode_ = true;
}
} else if (name == "user") {
ssh_user_ = argv[i];
}
}
if (got_addr == false || port_ == NULL) {
Log("Must supply addr and port attributes.");
return false;
}
if (ssh_mode_) {
if (ssh_user_.size() == 0) {
Log("Must provide a username for ssh mode.");
return false;
}
strncpy(ssh_password_, secret, sizeof(ssh_password_));
// Ensure it is null-terminated.
ssh_password_[sizeof(ssh_password_)-1] = 0;
} else {
setenv("MOSH_KEY", secret, 1);
}
// Setup communications.
keyboard_ = new Keyboard();
Terminal *terminal = new Terminal(this);
ErrorLog *error_log = new ErrorLog(this);
window_change_ = new WindowChange();
posix_ = new PepperPOSIX::POSIX(
instance_handle_, keyboard_, terminal, error_log, window_change_);
posix_->RegisterFile("/dev/urandom", DevURandomFactory);
// Mosh will launch via the resolution callback (see above).
return true;
}
void Launch(int32_t result) {
if (result != PP_OK) {
Error("Resolution failed: %d", result);
return;
}
if (resolver_.GetNetAddressCount() < 1) {
Error("There were no addresses.");
return;
}
pp::NetAddress address = resolver_.GetNetAddress(0);
string addr_str = address.DescribeAsString(false).AsString();
int addr_len = addr_str.size() + 1;
addr_ = new char[addr_len];
strncpy(addr_, addr_str.c_str(), addr_len);
if (ssh_mode_) {
int thread_err = pthread_create(&thread_, NULL, SSHLogin, this);
if (thread_err != 0) {
Error("Failed to create SSH login thread: %s", strerror(thread_err));
}
} else {
LaunchMosh(0);
}
}
// Mosh launcher that can be a callback.
void LaunchMosh(int32_t result) {
int thread_err = pthread_create(&thread_, NULL, Mosh, this);
if (thread_err != 0) {
Error("Failed to create Mosh thread: %s", strerror(thread_err));
}
}
static void *Mosh(void *data) {
MoshClientInstance *thiz = reinterpret_cast<MoshClientInstance *>(data);
setenv("TERM", "xterm-256color", 1);
char *argv[] = { "mosh-client", thiz->addr_, thiz->port_ };
thiz->Log("Mosh(): Calling mosh_main");
mosh_main(sizeof(argv) / sizeof(argv[0]), argv);
thiz->Log("Mosh(): mosh_main returned");
return 0;
}
// Get MOSH_KEY via SSH.
static void *SSHLogin(void *data) {
MoshClientInstance *thiz = reinterpret_cast<MoshClientInstance *>(data);
setenv("HOME", "dummy", 1); // To satisfy libssh.
ssh::Session s(thiz->addr_, atoi(thiz->port_), thiz->ssh_user_);
if (s.Connect() == false) {
thiz->Error("Could not connect via ssh: %s", s.GetLastError().c_str());
return NULL;
}
// TODO: This _really_ needs to be more secure.
thiz->Output(TYPE_DISPLAY,
"Fingerprint of remote ssh host (MD5): " +
s.GetPublicKey()->MD5() + "\r\n");
// TODO: Should probably prompt the user for a password interactively.
if (s.AuthUsingPassword(thiz->ssh_password_) == false) {
thiz->Error("ssh authentication failed: %s", s.GetLastError().c_str());
// For safety, zero the password.
memset(thiz->ssh_password_, 0, sizeof(thiz->ssh_password_));
return NULL;
}
// For safety, zero the password.
memset(thiz->ssh_password_, 0, sizeof(thiz->ssh_password_));
ssh::Channel *c = s.NewChannel();
if (c->Execute("mosh-server") == false) {
thiz->Error("Failed to execute mosh-server: %s",
s.GetLastError().c_str());
return NULL;
}
string buf;
if (c->Read(&buf, NULL) == false) {
thiz->Error("Error reading from remote ssh server: %s",
s.GetLastError().c_str());
return NULL;
}
char key[23];
thiz->port_ = new char[6];
int result = sscanf(buf.c_str(), "\r\nMOSH CONNECT %5s %22s\r\n",
thiz->port_, key);
if (result != 2) {
thiz->Error("Bad response when running mosh-server: '%s'", buf.c_str());
return NULL;
}
setenv("MOSH_KEY", key, 1);
pp::Module::Get()->core()->CallOnMainThread(
0, thiz->cc_factory_.NewCallback(&MoshClientInstance::LaunchMosh));
return NULL;
}
// Pepper POSIX emulation.
PepperPOSIX::POSIX *posix_;
// Window change "file"; must be visible to sigaction().
WindowChange *window_change_;
private:
static int num_instances_; // This needs to be a singleton.
pthread_t thread_;
// Non-const params for mosh_main().
char *addr_;
char *port_;
bool ssh_mode_;
// Using an old-school char[] to ensure a safe lifecycle.
char ssh_password_[128];
string ssh_user_;
pp::InstanceHandle instance_handle_;
Keyboard *keyboard_;
pp::CompletionCallbackFactory<MoshClientInstance> cc_factory_;
pp::HostResolver resolver_;
};
// Initialize static data for MoshClientInstance.
int MoshClientInstance::num_instances_ = 0;
ssize_t Terminal::Write(const void *buf, size_t count) {
string s((const char *)buf, count);
instance_->Output(MoshClientInstance::TYPE_DISPLAY, s);
return count;
}
ssize_t ErrorLog::Write(const void *buf, size_t count) {
string s((const char *)buf, count);
instance_->Output(MoshClientInstance::TYPE_ERROR, s);
return count;
}
class MoshClientModule : public pp::Module {
public:
MoshClientModule() : pp::Module() {}
virtual ~MoshClientModule() {}
virtual pp::Instance *CreateInstance(PP_Instance instance) {
return new MoshClientInstance(instance);
}
};
namespace pp {
Module *CreateModule() {
return new MoshClientModule();
}
} // namespace pp
//
// Window size wrapper functions that are too specialized to be
// moved to a general wrapper module.
//
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact) {
Log("sigaction(%d, ...)", signum);
assert(oldact == NULL);
switch (signum) {
case SIGWINCH:
instance->window_change_->SetHandler(act->sa_handler);
break;
}
return 0;
}
#ifdef USE_NEWLIB
int ioctl(int d, int request, ...) {
#else
int ioctl(int d, long unsigned int request, ...) {
#endif
if (d != STDIN_FILENO || request != TIOCGWINSZ) {
Log("ioctl(%d, %u, ...): Got unexpected call", d, request);
errno = EPROTO;
return -1;
}
va_list argp;
va_start(argp, request);
struct winsize *ws = va_arg(argp, struct winsize*);
ws->ws_row = instance->window_change_->height();
ws->ws_col = instance->window_change_->width();
va_end(argp);
return 0;
}
//
// Functions for pepper_wrapper.h.
//
PepperPOSIX::POSIX *GetPOSIX() {
return instance->posix_;
}
void Log(const char *format, ...) {
va_list argp;
va_start(argp, format);
if (instance != NULL) {
instance->Logv(MoshClientInstance::TYPE_LOG, format, argp);
}
va_end(argp);
}
//
// Function for SSH logging.
//
extern "C" {
void __wrap__ssh_log(int verbosity, const char *function,
const char *format, ...) {
if (verbosity > SSH_LOG_WARNING) {
return;
}
string f = string("libssh: ") + function + "(): " + format;
va_list argp;
va_start(argp, format);
if (instance != NULL) {
instance->Logv(MoshClientInstance::TYPE_LOG, f.c_str(), argp);
}
va_end(argp);
}
} // extern "C"

346
src/pepper_posix.cc Normal file
View file

@ -0,0 +1,346 @@
// pepper_posix.cc - Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix.h"
#include "pepper_posix_native_udp.h"
#include "pepper_posix_native_tcp.h"
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
namespace PepperPOSIX {
const int SIGNAL_FD = -1;
POSIX::POSIX(const pp::InstanceHandle &instance_handle,
Reader *std_in, Writer *std_out, Writer *std_err, Signal *signal)
: instance_handle_(instance_handle), signal_(signal) {
files_[STDIN_FILENO] = std_in;
if (std_in != NULL) {
std_in->target_ = selector_.NewTarget(STDIN_FILENO);
}
files_[STDOUT_FILENO] = std_out;
if (std_out != NULL) {
std_out->target_ = selector_.NewTarget(STDOUT_FILENO);
}
files_[STDERR_FILENO] = std_err;
if (std_err != NULL) {
std_err->target_ = selector_.NewTarget(STDERR_FILENO);
}
if (signal_ != NULL) {
// "Pseudo" file descriptor in Target needs to be set out of issuance
// range.
signal_->target_ = selector_.NewTarget(SIGNAL_FD);
}
}
POSIX::~POSIX() {
for (::std::map<int, File *>::iterator i = files_.begin();
i != files_.end();
++i) {
Close(i->first);
}
}
int POSIX::Open(const char *pathname, int flags, mode_t mode) {
::std::map<string, File *(*)()>::iterator factories_iter =
factories_.find(string(pathname));
if (factories_iter == factories_.end()) {
errno = EACCES;
return -1;
}
File *file = factories_iter->second();
// TODO: Error out if |file|'s type doesn't match |flags| (i.e., Reader
// cannot be O_WRONLY).
int fd = NextFileDescriptor();
files_[fd] = file;
file->target_ = selector_.NewTarget(fd);
return fd;
}
int POSIX::Close(int fd) {
if (files_.count(fd) == 0) {
errno = EBADF;
return -1;
}
int result = files_[fd]->Close();
delete files_[fd];
files_.erase(fd);
return result;
}
ssize_t POSIX::Read(int fd, void *buf, size_t count) {
if (files_.count(fd) == 0) {
errno = EBADF;
return -1;
}
Reader *reader = dynamic_cast<Reader *>(files_[fd]);
if (reader == NULL) {
errno = EBADF;
return -1;
}
return reader->Read(buf, count);
}
ssize_t POSIX::Write(int fd, const void *buf, size_t count) {
if (files_.count(fd) == 0) {
errno = EBADF;
return -1;
}
Writer *writer = dynamic_cast<Writer *>(files_[fd]);
if (writer == NULL) {
errno = EBADF;
return -1;
}
return writer->Write(buf, count);
}
int POSIX::NextFileDescriptor() {
for (int fd = 0; ; ++fd) {
if (files_.count(fd) == 0) {
return fd;
}
}
}
int POSIX::Socket(int domain, int type, int protocol) {
if (domain != AF_INET) {
errno = EINVAL;
return -1;
}
File *file = NULL;
if (type == SOCK_DGRAM && (protocol == 0 || protocol == IPPROTO_UDP)) {
file = new NativeUDP(instance_handle_);
} else if (type == SOCK_STREAM && (protocol == 0 || protocol == IPPROTO_TCP)) {
file = new NativeTCP(instance_handle_);
}
if (file != NULL) {
int fd = NextFileDescriptor();
file->target_ = selector_.NewTarget(fd);
files_[fd] = file;
return fd;
}
errno = EINVAL;
return -1;
}
int POSIX::Dup(int oldfd) {
if (files_.count(oldfd) == 0) {
errno = EBADF;
return -1;
}
// Currently can only dup UDP sockets.
UDP *udp = dynamic_cast<UDP *>(files_[oldfd]);
if (udp == NULL) {
errno = EBADF;
return -1;
}
return Socket(AF_INET, SOCK_DGRAM, 0);
}
int POSIX::PSelect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
const struct timespec *timeout, const sigset_t *sigmask) {
int result = 0;
fd_set new_readfds, new_writefds;
FD_ZERO(&new_readfds);
FD_ZERO(&new_writefds);
vector<Target *> read_targets, write_targets;
for (int fd = 0; fd < nfds; ++fd) {
if (readfds != NULL && FD_ISSET(fd, readfds)) {
read_targets.push_back(files_[fd]->target_);
}
if (writefds != NULL && FD_ISSET(fd, writefds)) {
write_targets.push_back(files_[fd]->target_);
}
}
// Signal is handled specially.
if (signal_ != NULL) {
read_targets.push_back(signal_->target_);
}
vector<Target *> ready_targets = selector_.Select(
read_targets, write_targets, timeout);
for (vector<Target *>::iterator i = ready_targets.begin();
i != ready_targets.end();
++i) {
int fd = (*i)->id();
// Signal is handled specially.
if (fd == SIGNAL_FD && signal_ != NULL && signal_->target_->has_read_data()) {
signal_->Handle();
continue;
}
if (readfds != NULL && FD_ISSET(fd, readfds) && (*i)->has_read_data()) {
FD_SET(fd, &new_readfds);
++result;
}
if (writefds != NULL && FD_ISSET(fd, writefds) && (*i)->has_write_data()) {
FD_SET(fd, &new_writefds);
++result;
}
}
if (readfds != NULL) {
FD_ZERO(readfds);
}
if (writefds != NULL) {
FD_ZERO(writefds);
}
if (exceptfds != NULL) {
FD_ZERO(exceptfds);
}
for (int fd = 0; fd < nfds; ++fd) {
if (FD_ISSET(fd, &new_readfds)) {
FD_SET(fd, readfds);
}
if (FD_ISSET(fd, &new_writefds)) {
FD_SET(fd, writefds);
}
}
return result;
}
ssize_t POSIX::Recv(int sockfd, void *buf, size_t len, int flags) {
if (files_.count(sockfd) == 0) {
errno = EBADF;
return -1;
}
TCP *tcp = dynamic_cast<TCP *>(files_[sockfd]);
if (tcp == NULL) {
errno = EBADF;
return -1;
}
return tcp->Receive(buf, len, flags);
}
ssize_t POSIX::RecvMsg(int sockfd, struct msghdr *msg, int flags) {
if (files_.count(sockfd) == 0) {
errno = EBADF;
return -1;
}
UDP *udp = dynamic_cast<UDP *>(files_[sockfd]);
if (udp == NULL) {
errno = EBADF;
return -1;
}
return udp->Receive(msg, flags);
}
// Make a PP_NetAddress_IPv4 from sockaddr.
void MakeAddress(const struct sockaddr *addr, socklen_t addrlen,
PP_NetAddress_IPv4 *pp_addr) {
// TODO: Make an IPv6 version, but since mosh doesn't support it now, this
// will do.
assert(addr->sa_family == AF_INET);
assert(addrlen >= 4);
const struct sockaddr_in *in_addr = (struct sockaddr_in*)addr;
uint32_t a = in_addr->sin_addr.s_addr;
for (int i = 0; i < 4; ++i) {
pp_addr->addr[i] = a & 0xff;
a >>= 8;
}
pp_addr->port = in_addr->sin_port;
}
ssize_t POSIX::Send(int sockfd, const void *buf, size_t len, int flags) {
if (files_.count(sockfd) == 0) {
return EBADF;
}
TCP *tcp = dynamic_cast<TCP *>(files_[sockfd]);
if (tcp == NULL) {
errno = EBADF;
return -1;
}
return tcp->Send(buf, len, flags);
}
ssize_t POSIX::SendTo(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen) {
if (files_.count(sockfd) == 0) {
return EBADF;
}
UDP *udp = dynamic_cast<UDP *>(files_[sockfd]);
if (udp == NULL) {
errno = EBADF;
return -1;
}
vector<char> buffer((const char*)buf, (const char*)buf+len);
PP_NetAddress_IPv4 addr;
MakeAddress(dest_addr, addrlen, &addr);
return udp->Send(buffer, flags, addr);
}
int POSIX::FCntl(int fd, int cmd, va_list arg) {
if (cmd == F_SETFL) {
long long_arg = va_arg(arg, long);
if (long_arg & O_NONBLOCK) {
// For now, everything is nonblocking, so this is a no-op.
return 0;
}
Log("POSIX::FCntl(): Got F_SETFL, but unsupported arg: 0%lo", long_arg);
// TODO: Consider this an error?
return 0;
}
// Anything we don't explicitly handle or ignore is considered an error, to
// avoid any potential confusion.
Log("POSIX::FCntl(): Unsupported cmd/arg");
errno = EINVAL;
return -1;
}
int POSIX::Connect(
int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
if (files_.count(sockfd) == 0) {
return EBADF;
}
TCP *tcp = dynamic_cast<TCP *>(files_[sockfd]);
if (tcp == NULL) {
errno = EBADF;
return -1;
}
PP_NetAddress_IPv4 pp_addr;
MakeAddress(addr, addrlen, &pp_addr);
return tcp->Connect(pp_addr);
}
} // namespace PepperPOSIX

162
src/pepper_posix.h Normal file
View file

@ -0,0 +1,162 @@
// pepper_posix.h - Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX
#define PEPPER_POSIX
#include "pepper_posix_selector.h"
#include <map>
#include <string>
#include <stdarg.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "ppapi/c/ppb_net_address.h"
#include "ppapi/cpp/instance_handle.h"
using ::std::map;
using ::std::string;
// Implement this to plumb logging from Pepper functions to your app.
void Log(const char *format, ...);
namespace PepperPOSIX {
// Abstract class representing a POSIX file.
class File {
public:
File() : target_(NULL) {}
virtual ~File() { delete target_; };
virtual int Close() { return 0; }
int fd() {
if (target_ == NULL) {
return -1;
}
return target_->id();
}
Target *target_;
private:
// Disable copy and assignment.
File(const File &);
File &operator=(const File &);
};
// Abstract class defining a file that is read-only.
class Reader : public virtual File {
public:
virtual ssize_t Read(void *buf, size_t count) = 0;
};
// Abstract class defining a file that is write-only.
class Writer : public virtual File {
public:
virtual ssize_t Write(const void *buf, size_t count) = 0;
};
// Abstract class defining a file that is read/write.
class ReadWriter : public Reader, public Writer {
};
// Special File to handle signals. Write a method in your implementation that
// calls target_->UpdateRead(true) when there's an outstanding signal. Handle()
// will get called when there is.
class Signal : public File {
public:
// Implement this to handle a signal. It will be called from PSelect. It is
// the responsibility of the implementer to track what signals are
// outstanding. Call target_->UpdateRead(false) from this method when there
// are no more outstanding signals.
virtual void Handle() = 0;
};
// POSIX implements the top-level POSIX file functionality, allowing easy
// binding to "unistd.h" functions. As such, the methods bear a great
// resemblance to those functions.
class POSIX {
public:
// Provide implementations of Reader and Writer which emulate STDIN, STDOUT,
// and STDERR. Provide an implementation of Signal to handle signals, which
// will be called from PSelect().
//
// Set any of these to NULL if not used. Takes ownership of all.
POSIX(const pp::InstanceHandle &instance_handle,
Reader *std_in, Writer *std_out, Writer *std_err, Signal *signal);
~POSIX();
int Open(const char *pathname, int flags, mode_t mode);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
int Close(int fd);
int Socket(int domain, int type, int protocol);
int Dup(int oldfd);
int PSelect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
const struct timespec *timeout, const sigset_t *sigmask);
ssize_t Recv(int sockfd, void *buf, size_t len, int flags);
ssize_t RecvMsg(int sockfd, struct msghdr *msg, int flags);
ssize_t Send(int sockfd, const void *buf, size_t len, int flags);
ssize_t SendTo(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
int FCntl(int fd, int cmd, va_list args);
int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// Register a filename and File factory to be used when that file is
// opened.
void RegisterFile(string filename, File *(*factory)()) {
factories_[filename] = factory;
}
private:
// Returns the next available file descriptor.
int NextFileDescriptor();
// Map of file descriptors and the File objects they represent.
map<int, File *> files_;
// Map of registered files and their File factories.
map<string, File *(*)()> factories_;
Signal *signal_;
Selector selector_;
const pp::InstanceHandle &instance_handle_;
// Disable copy and assignment.
POSIX(const POSIX &);
POSIX &operator=(const POSIX &);
};
} // namespace PepperPOSIX
#endif // PEPPER_POSIX

View file

@ -0,0 +1,133 @@
// pepper_posix_native_tcp.cc - Native Pepper TCP implementation.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix_native_tcp.h"
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
namespace PepperPOSIX {
NativeTCP::NativeTCP(
const pp::InstanceHandle &instance_handle) :
instance_handle_(instance_handle), factory_(this) {
socket_ = new pp::TCPSocket(instance_handle);
}
NativeTCP::~NativeTCP() {
delete socket_;
}
int NativeTCP::Bind(const PP_NetAddress_IPv4 &address) {
pp::NetAddress net_address(instance_handle_, address);
pp::Var string_address = net_address.DescribeAsString(true);
if (string_address.is_undefined()) {
Log("NativeTCP::Bind() Address is bogus.");
// TODO: Return something appropriate.
return false;
}
return socket_->Bind(net_address, pp::CompletionCallback());
}
int NativeTCP::Connect(const PP_NetAddress_IPv4 &address) {
address_ = pp::NetAddress(instance_handle_, address);
pp::Var string_address = address_.DescribeAsString(true);
if (string_address.is_undefined()) {
Log("NativeTCP::Connect() Address is bogus.");
// TODO: Return something appropriate.
return false;
}
// API calls need to be done on the main thread.
pp::Module::Get()->core()->CallOnMainThread(
0, factory_.NewCallback(&NativeTCP::ConnectOnMainThread));
return EINPROGRESS;
}
// This callback should only be called on the main thread.
void NativeTCP::ConnectOnMainThread(int32_t unusued) {
int32_t result = socket_->Connect(
address_, factory_.NewCallback(&NativeTCP::Connected));
if (result != PP_OK_COMPLETIONPENDING) {
Log("NativeTCP::ConnectOnMainThread(): "
"socket_->Connect() returned %d", result);
// TODO: Perhaps crash here?
}
// TODO: Flesh out error mapping.
}
void NativeTCP::Connected(int32_t result) {
if (result == PP_OK) {
target_->UpdateWrite(true);
StartReceive();
return;
}
// TODO: Handle connection failures more appropraitely.
Log("NativeTCP::Connected(): Connection failed; result: %d", result);
}
ssize_t NativeTCP::Send(const void *buf, size_t count, int flags) {
if (flags != 0) {
Log("NativeTCP::Send(): Unsupported flag: 0x%x", flags);
}
int32_t result = socket_->Write(
(const char *)buf, count, pp::CompletionCallback());
if (result < 0) {
Log("NativeTCP::Send(): Got negative result: %d", result);
}
return result;
}
// StartReceive prepares to receive more data, and returns without blocking.
void NativeTCP::StartReceive() {
int32_t result = socket_->Read(
receive_buffer_, sizeof(receive_buffer_),
factory_.NewCallback(&NativeTCP::Received));
if (result != PP_OK_COMPLETIONPENDING) {
Log("NativeTCP::StartReceive(): RecvFrom unexpectedly returned %d", result);
// TODO: Perhaps crash here?
}
}
// Received is the callback result of StartReceive().
void NativeTCP::Received(int32_t result) {
if (result < 0) {
Log("NativeTCP::Received(%d, ...): Negative result; bailing.", result);
return;
}
AddData(receive_buffer_, result);
// Await another packet.
StartReceive();
}
// Close the socket.
int NativeTCP::Close() {
// Destroying socket_ is the same as closing it.
delete socket_;
socket_ = NULL;
return 0;
}
} // namespace PepperPOSIX

View file

@ -0,0 +1,68 @@
// pepper_posix_native_udp.h - Native Pepper UDP implementation.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX_NATIVE_TCP_HPP
#define PEPPER_POSIX_NATIVE_TCP_HPP
#include "pepper_posix_tcp.h"
#include "ppapi/cpp/instance_handle.h"
#include "ppapi/cpp/tcp_socket.h"
#include "ppapi/utility/completion_callback_factory.h"
const int TCP_RECEIVE_BUFFER_SIZE = 64*1024; // 64 kB, a decent window size.
namespace PepperPOSIX {
// NativeTCP implements TCP using the native Pepper TCPSockets API.
class NativeTCP : public TCP {
public:
NativeTCP(const pp::InstanceHandle &instance_handle);
virtual ~NativeTCP();
// Bind replaces bind().
virtual int Bind(const PP_NetAddress_IPv4 &address);
// Connect replaces connect().
virtual int Connect(const PP_NetAddress_IPv4 &address);
// Send replaces send().
virtual ssize_t Send(const void *buf, size_t count, int flags);
// Close replaces close().
virtual int Close();
private:
void ConnectOnMainThread(int32_t unused);
void Connected(int32_t result);
void StartReceive();
void Received(int32_t result);
pp::TCPSocket *socket_;
const pp::InstanceHandle &instance_handle_;
char receive_buffer_[TCP_RECEIVE_BUFFER_SIZE];
pp::CompletionCallbackFactory<NativeTCP> factory_;
pp::NetAddress address_;
// Disable copy and assignment.
NativeTCP(const NativeTCP&);
NativeTCP &operator=(const NativeTCP&);
};
} // namespace PepperPOSIX
#endif // PEPPER_POSIX_NATIVE_TCP_HPP

View file

@ -0,0 +1,142 @@
// pepper_posix_native_udp.cc - Native Pepper UDP implementation.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix_native_udp.h"
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
namespace PepperPOSIX {
NativeUDP::NativeUDP(
const pp::InstanceHandle &instance_handle) :
instance_handle_(instance_handle), factory_(this) {
socket_ = new pp::UDPSocket(instance_handle);
bound_ = false;
}
NativeUDP::~NativeUDP() {
delete socket_;
}
int NativeUDP::Bind(const PP_NetAddress_IPv4 &address) {
pp::NetAddress net_address(instance_handle_, address);
pp::Var string_address = net_address.DescribeAsString(true);
if (string_address.is_undefined()) {
Log("NativeUDP::Bind() Address is bogus.");
// TODO: Return something appropriate.
return false;
}
int32_t result = socket_->Bind(net_address, pp::CompletionCallback());
if (result == PP_OK) {
bound_ = true;
pp::Module::Get()->core()->CallOnMainThread(
0, factory_.NewCallback(&NativeUDP::StartReceive));
}
// TODO: Flesh out error mapping.
return result;
}
ssize_t NativeUDP::Send(
const vector<char> &buf, int flags,
const PP_NetAddress_IPv4 &address) {
if (!bound_) {
PP_NetAddress_IPv4 any_address;
memset(&any_address, 0, sizeof(any_address));
int result = Bind(any_address);
if (result != 0) {
Log("NativeUDP::Send(): Bind failed with %d", result);
return 0;
}
}
pp::NetAddress net_address(instance_handle_, address);
return socket_->SendTo(
buf.data(), buf.size(), net_address, pp::CompletionCallback());
}
// StartReceive prepares to receive another packet, and returns without
// blocking.
void NativeUDP::StartReceive(int32_t unused) {
int32_t result = socket_->RecvFrom(
receive_buffer_, sizeof(receive_buffer_),
factory_.NewCallbackWithOutput(&NativeUDP::Received));
if (result != PP_OK_COMPLETIONPENDING) {
Log("NativeUDP::StartReceive(): RecvFrom returned %d", result);
// TODO: Perhaps crash here?
}
}
// Received is the callback result of StartReceive().
void NativeUDP::Received(int32_t result, const pp::NetAddress &address) {
if (result < 0) {
Log("NativeUDP::Received(%d, ...): Negative result; bailing.", result);
return;
}
PP_NetAddress_IPv4 ipv4_addr;
if (!address.DescribeAsIPv4Address(&ipv4_addr)) {
// TODO: Implement IPv6 support, once mosh itself supports it.
Log("NativeUDP::Received(): Failed to convert address.");
return;
}
struct msghdr *message = (struct msghdr *)malloc(sizeof(struct msghdr));
memset(message, 0, sizeof(*message));
struct sockaddr_in *addr =
(struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
addr->sin_port = ipv4_addr.port;
uint32_t a = 0;
for (int i = 0; i < 4; ++i) {
a |= ipv4_addr.addr[i] << (8*i);
}
addr->sin_addr.s_addr = a;
message->msg_name = addr;
message->msg_namelen = sizeof(*addr);
message->msg_iov = (struct iovec *)malloc(sizeof(struct iovec));
message->msg_iovlen = 1;
message->msg_iov->iov_base = malloc(result);
message->msg_iov->iov_len = result;
memcpy(
message->msg_iov->iov_base, receive_buffer_, message->msg_iov->iov_len);
AddPacket(message); // Takes ownership.
// Await another packet.
StartReceive(0);
}
// Close the socket.
int NativeUDP::Close() {
// Destroying socket_ is the same as closing it.
delete socket_;
socket_ = NULL;
return 0;
}
} // namespace PepperPOSIX

View file

@ -0,0 +1,65 @@
// pepper_posix_native_udp.h - Native Pepper UDP implementation.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX_NATIVE_UDP_HPP
#define PEPPER_POSIX_NATIVE_UDP_HPP
#include "pepper_posix_udp.h"
#include "ppapi/cpp/instance_handle.h"
#include "ppapi/cpp/udp_socket.h"
#include "ppapi/utility/completion_callback_factory.h"
const int UDP_RECEIVE_BUFFER_SIZE = 1500; // Typical MTU.
namespace PepperPOSIX {
// NativeUDP implements UDP using the native Pepper UDPSockets API.
class NativeUDP : public UDP {
public:
NativeUDP(const pp::InstanceHandle &instance_handle);
virtual ~NativeUDP();
// Bind replaces bind().
virtual int Bind(const PP_NetAddress_IPv4 &address);
// Send replaces sendto. Usage is similar, but tweaked for C++.
virtual ssize_t Send(
const vector<char> &buf, int flags,
const PP_NetAddress_IPv4 &address);
// Close replaces close().
virtual int Close();
private:
void StartReceive(int32_t unused);
void Received(int32_t result, const pp::NetAddress &address);
pp::UDPSocket *socket_;
bool bound_;
const pp::InstanceHandle &instance_handle_;
char receive_buffer_[UDP_RECEIVE_BUFFER_SIZE];
pp::CompletionCallbackFactory<NativeUDP> factory_;
// Disable copy and assignment.
NativeUDP(const NativeUDP&);
NativeUDP &operator=(const NativeUDP&);
};
} // namespace PepperPOSIX
#endif // PEPPER_POSIX_NATIVE_UDP_HPP

View file

@ -0,0 +1,137 @@
// pepper_posix_selector.cc - Selector for Pepper POSIX adapters.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix_selector.h"
#include <algorithm>
#include <assert.h>
namespace PepperPOSIX {
Selector::Selector() {
pthread_mutex_init(&notify_mutex_, NULL);
pthread_cond_init(&notify_cv_, NULL);
}
Selector::~Selector() {
// It is a logical error to delete Selector before all Targets have been
// deleted.
assert(targets_.size() == 0);
pthread_cond_destroy(&notify_cv_);
pthread_mutex_destroy(&notify_mutex_);
}
Target *Selector::NewTarget(int id) {
Target *t = new Target(this, id);
targets_.push_back(t);
return t;
}
void Selector::Deregister(const Target *target) {
vector<Target*>::iterator t = std::find(
targets_.begin(), targets_.end(), target);
// It is a logical error to deregister a target that is not registered.
assert(*t == target);
targets_.erase(t);
}
void Selector::Notify() {
pthread_cond_signal(&notify_cv_);
}
vector<Target*> Selector::Select(
const vector<Target*> &read_targets, const vector<Target*> &write_targets,
const struct timespec *timeout) {
// Calculate absolute time for timeout. This should be done ASAP to reduce
// the chances of this method not returning by the timeout specified. There
// are no guarantees, of course.
struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
if (timeout != NULL) {
abstime.tv_sec += timeout->tv_sec;
abstime.tv_nsec += timeout->tv_nsec;
}
// Check if any data is available.
vector<Target*> result = HasData(read_targets, write_targets);
if (result.size() > 0) {
// Data available now; return immediately.
return result;
}
// Wait for a target to have data.
pthread_mutex_lock(&notify_mutex_);
if (timeout == NULL) {
pthread_cond_wait(&notify_cv_, &notify_mutex_);
} else {
pthread_cond_timedwait(&notify_cv_, &notify_mutex_, &abstime);
}
pthread_mutex_unlock(&notify_mutex_);
// Must check again to see who has data.
return HasData(read_targets, write_targets);
}
const vector<Target*> Selector::HasData(
const vector<Target*> &read_targets, const vector<Target*> &write_targets) {
vector<Target*> result;
for (vector<Target*>::const_iterator t = read_targets.begin();
t != read_targets.end(); ++t) {
if ((*t)->has_read_data()) {
result.push_back(*t);
}
}
for (vector<Target*>::const_iterator t = write_targets.begin();
t != write_targets.end(); ++t) {
if ((*t)->has_write_data()) {
result.push_back(*t);
}
}
return result;
}
Target::~Target() {
selector_->Deregister(this);
}
void Target::UpdateRead(bool has_data) {
if (has_data == has_read_data_) {
// No state change; do nothing.
return;
}
has_read_data_ = has_data;
if (has_data == true) {
// Only notify if we now have data.
selector_->Notify();
}
}
void Target::UpdateWrite(bool has_data) {
if (has_data == has_write_data_) {
// No state change; do nothing.
return;
}
has_write_data_ = has_data;
if (has_data == true) {
// Only notify if we now have data.
selector_->Notify();
}
}
} // namespace PepperPosix

128
src/pepper_posix_selector.h Normal file
View file

@ -0,0 +1,128 @@
// pepper_posix_selector.h - Selector for Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX_SELECTOR_HPP
#define PEPPER_POSIX_SELECTOR_HPP
#include <vector>
#include <pthread.h>
namespace PepperPOSIX {
using std::vector;
class Target; // This is declared fully below.
// Selector implements select()-style functionality for callback-style I/O.
// In your I/O implementation, get and retain a Target instance by calling
// NewTarget(). Call Target's UpdateRead() or UpdateWrite() methods whenever
// availability of data changes. Other threads can then call Selector's
// Select() or SelectAll() to block until data is available.
class Selector {
public:
explicit Selector();
~Selector();
friend class Target; // Allow Target to access private members of Selector.
// NewTarget creates a new Target instance for use by an I/O class. The
// returned Target is owned by the caller, but Selector maintains a pointer
// to it for the life of the Target. It is an error to delete Selector until
// all Targets are deleted. id is an opaque identifier for the user to
// distinguish one Target from another.
Target *NewTarget(int id);
// Select returns a subset of targets for which data is available, or
// waits until the timeout period has passed. It calls
// pthread_cond_timedwait() if there are no targets with data available
// when the method is called.
vector<Target*> Select(
const vector<Target*> &read_targets, const vector<Target*> &write_targets,
const struct timespec *timeout);
// SelectAll is similar to Select, but waits for all registered targets.
vector<Target*> SelectAll(const struct timespec *timeout) {
return Select(targets_, targets_, timeout);
}
private:
// Notify is to be called only from class Target to indicate when
// there is data available. Internally, Notify uses
// pthread_cond_signal() to indicate that there is data. It does not
// need to procure a lock, as it modifies no internal state.
void Notify();
// Deregister is to be called only from the class Target when it is
// being destroyed and must deregister with Selector.
void Deregister(const Target *target);
// HasData returns a vector of Targets that have data ready to be read.
const vector<Target*> HasData(const vector<Target*> &read_targets,
const vector<Target*> &write_targets);
vector<Target*> targets_;
pthread_mutex_t notify_mutex_;
pthread_cond_t notify_cv_;
// Disable copy and assignment.
Selector(const Selector&);
Selector &operator=(const Selector&);
};
// Target is used by an I/O "target" to communicate with a Selector instance
// for emulating POSIX select()-style functionality. The I/O target should call
// UpdateRead() or UpdateWrite() whenever data availability changes.
class Target {
public:
Target(class Selector *s, int id) :
selector_(s), id_(id), has_read_data_(false), has_write_data_(false) {}
~Target();
// UpdateRead updates Target whether there is pending data available in the
// I/O target. If the state has changed, Target notifies Selector of the
// change. You can call UpdateRead even if the state has not changed since
// the last call to UpdateRead, as UpdateRead will detect that and not send
// superfluous notifications to Selector.
void UpdateRead(bool has_data);
// UpdateWrite updates Target whether new data can be accepted by the I/O
// target. If the state has changed, Target notifies Selector of the change.
// You can call UpdateWrite even if the state has not changed since the last
// call to UpdateWrite, as UpdateWrite will detect that and not send
// superfluous notifications to Selector.
void UpdateWrite(bool has_data);
const bool has_read_data() { return has_read_data_; }
const bool has_write_data() { return has_write_data_; }
const int id() { return id_; }
private:
class Selector *selector_;
int id_;
bool has_read_data_;
bool has_write_data_;
// Disable copy and assignment.
Target(const Target&);
Target &operator=(const Target&);
};
} // namespace PepperPosix
#endif // PEPPER_POSIX_SELECTOR_HPP

111
src/pepper_posix_tcp.cc Normal file
View file

@ -0,0 +1,111 @@
// pepper_posix_udp.cc - TCP Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix_tcp.h"
#include <errno.h>
#include <stdio.h>
#include <string.h> // DONOTSUBMIT
namespace PepperPOSIX {
TCP::TCP() {
pthread_mutex_init(&buffer_lock_, NULL);
}
TCP::~TCP() {
pthread_mutex_destroy(&buffer_lock_);
}
ssize_t TCP::Receive(void *buf, size_t count, int flags) {
bool peek = false;
if (flags & MSG_PEEK) {
peek = true;
flags &= ~MSG_PEEK;
}
if (flags != 0) {
Log("TCP::Receive(): Unsupported flag: 0x%x", flags);
}
pthread_mutex_lock(&buffer_lock_);
if (buffer_.size() == 0) {
pthread_mutex_unlock(&buffer_lock_);
Log("TCP::Receive(): EWOULDBLOCK");
errno = EWOULDBLOCK;
return -1;
}
char *cbuf = (char *)buf;
int read_count = 0;
if (peek) {
for (;
read_count < count && read_count < buffer_.size();
++read_count) {
*cbuf = buffer_[read_count];
++cbuf;
}
} else {
for (;
read_count < count && buffer_.size() > 0;
++read_count) {
*cbuf = buffer_.front();
buffer_.pop_front();
++cbuf;
}
target_->UpdateRead(buffer_.size() > 0);
}
pthread_mutex_unlock(&buffer_lock_);
return read_count;
}
ssize_t TCP::Read(void *buf, size_t count) {
return Receive(buf, count, 0);
}
ssize_t TCP::Write(const void *buf, size_t count) {
return Send(buf, count, 0);
}
void TCP::AddData(const void *buf, size_t count) {
pthread_mutex_lock(&buffer_lock_);
const char *cbuf = (const char *)buf;
for (; count > 0; --count) {
buffer_.push_back(*cbuf);
++cbuf;
}
pthread_mutex_unlock(&buffer_lock_);
target_->UpdateRead(true);
}
int StubTCP::Bind(const PP_NetAddress_IPv4 &address) {
Log("StubBind()");
return 0;
}
int StubTCP::Connect(const PP_NetAddress_IPv4 &address) {
Log("StubConnect()");
return 0;
}
ssize_t StubTCP::Send(const void *buf, size_t count, int flags) {
Log("StubSend()");
return 0;
}
} // namespace PepperPOSIX

98
src/pepper_posix_tcp.h Normal file
View file

@ -0,0 +1,98 @@
// pepper_posix_udp.h - TCP Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX_TCP_HPP
#define PEPPER_POSIX_TCP_HPP
#include "pepper_posix.h"
#include "pepper_posix_selector.h"
#include <deque>
#include <vector>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include "ppapi/c/ppb_net_address.h"
namespace PepperPOSIX {
// TCP implements the basic POSIX emulation logic for TCP communication. It is
// not fully implemented. An implementation should fully implement Bind(),
// Connect(), and Send(), and insert received data using AddData(). It is
// expected that AddData() will be called from a different thread than the one
// calling other methods; no other thread safety is provided.
class TCP : public ReadWriter {
public:
TCP();
virtual ~TCP();
// Read replaces read().
virtual ssize_t Read(void *buf, size_t count);
// Recv replaces recv().
virtual ssize_t Receive(void *buf, size_t count, int flags);
// Write replaces write().
virtual ssize_t Write(const void *buf, size_t count);
// Send replaces send().
virtual ssize_t Send(const void *buf, size_t count, int flags) = 0;
// Bind replaces bind().
virtual int Bind(const PP_NetAddress_IPv4 &address) = 0;
// Connect replaces connect().
virtual int Connect(const PP_NetAddress_IPv4 &address) = 0;
protected:
// AddData is used by the subclass to add data to the incoming buffer.
// This method can be called from another thread than the one used to call
// the other methods. Takes ownership of *message and its associated buffers.
void AddData(const void *buf, size_t count);
private:
std::deque<char> buffer_; // Guard with buffer_lock_.
pthread_mutex_t buffer_lock_;
// Disable copy and assignment.
TCP(const TCP &);
TCP &operator=(const TCP &);
};
// StubTCP is an instantiatable stubbed subclass of TCP for debugging.
class StubTCP : public TCP {
public:
StubTCP() {};
virtual ~StubTCP() {};
virtual ssize_t Send(const void *buf, size_t count, int flags);
virtual int Bind(const PP_NetAddress_IPv4 &address);
virtual int Connect(const PP_NetAddress_IPv4 &address);
private:
// Disable copy and assignment.
StubTCP(const StubTCP &);
StubTCP &operator=(const StubTCP &);
};
} // namespace PepperPOSIX
#endif // PEPPER_POSIX_TCP_HPP

113
src/pepper_posix_udp.cc Normal file
View file

@ -0,0 +1,113 @@
// pepper_posix_udp.cc - UDP Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_posix_udp.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
namespace PepperPOSIX {
void DestroyMessage(struct ::msghdr *message) {
free(message->msg_name);
for (int i = 0; i < message->msg_iovlen; ++i) {
free(message->msg_iov[i].iov_base);
free(message->msg_iov + i);
}
assert(message->msg_control == NULL); // This isn't being used.
delete message;
}
UDP::UDP() {
pthread_mutex_init(&packets_lock_, NULL);
}
UDP::~UDP() {
// There really shouldn't be another thread actively involved at destruction
// time, but getting the lock nonetheless.
pthread_mutex_lock(&packets_lock_);
for (std::deque<struct ::msghdr *>::iterator i = packets_.begin();
i != packets_.end();
++i) {
DestroyMessage(*i);
}
pthread_mutex_unlock(&packets_lock_);
pthread_mutex_destroy(&packets_lock_);
}
ssize_t UDP::Receive(struct ::msghdr *message, int flags) {
pthread_mutex_lock(&packets_lock_);
if (packets_.size() == 0) {
pthread_mutex_unlock(&packets_lock_);
errno = EWOULDBLOCK;
return -1;
}
struct ::msghdr *latest = packets_.front();
packets_.pop_front();
target_->UpdateRead(packets_.size() > 0);
pthread_mutex_unlock(&packets_lock_);
if (message->msg_namelen >= latest->msg_namelen) {
memcpy(message->msg_name, latest->msg_name, latest->msg_namelen);
} else {
Log("UDP::Receive(): msg_namelen too short.");
}
assert(latest->msg_iovlen == 1); // For simplicity, as this is internal.
ssize_t size = 0;
size_t input_len = latest->msg_iov->iov_len;
for (int i = 0; i < message->msg_iovlen && size < input_len; ++i) {
size_t output_len = message->msg_iov[i].iov_len;
size_t to_copy = output_len <= input_len ? output_len : input_len;
memcpy(message->msg_iov[i].iov_base, latest->msg_iov->iov_base, to_copy);
size += to_copy;
}
assert(size == input_len); // TODO: Return a real error, or handle better.
// TODO: Ignoring flags, msg_flags, and msg_control for now.
DestroyMessage(latest);
return size;
}
void UDP::AddPacket(struct ::msghdr *message) {
pthread_mutex_lock(&packets_lock_);
packets_.push_back(message);
pthread_mutex_unlock(&packets_lock_);
target_->UpdateRead(true);
}
ssize_t StubUDP::Send(
const vector<char> &buf, int flags, const PP_NetAddress_IPv4 &addr) {
Log("StubUDP::Send(): size=%d", buf.size());
Log("StubUDP::Send(): Pretending we received something.");
AddPacket(NULL);
return buf.size();
}
int StubUDP::Bind(const PP_NetAddress_IPv4 &address) {
Log("StubBind()");
return 0;
}
} // namespace PepperPOSIX

93
src/pepper_posix_udp.h Normal file
View file

@ -0,0 +1,93 @@
// pepper_posix_udp.h - UDP Pepper POSIX adapters.
//
// Pepper POSIX is a set of adapters to enable POSIX-like APIs to work with the
// callback-based APIs of Pepper (and transitively, JavaScript).
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_POSIX_UDP_HPP
#define PEPPER_POSIX_UDP_HPP
#include "pepper_posix.h"
#include "pepper_posix_selector.h"
#include <deque>
#include <vector>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include "ppapi/c/ppb_net_address.h"
namespace PepperPOSIX {
// UDP implements the basic POSIX emulation logic for UDP communication. It is
// not fully implemented. An implementation should fully implement Bind() and
// Send(), and insert received packets using AddPacket(). It is expected that
// AddPacket() will be called from a different thread than the one calling the
// other methods; no other thread safety is provided.
class UDP : public File {
public:
UDP();
virtual ~UDP();
// Receive replaces recvmsg(); see its documentation for usage.
ssize_t Receive(struct ::msghdr *message, int flags);
// Bind replaces bind().
virtual int Bind(const PP_NetAddress_IPv4 &address) = 0;
// Send replaces sendto(). Usage is similar, but tweaked for C++.
virtual ssize_t Send(
const vector<char> &buf, int flags,
const PP_NetAddress_IPv4 &address) = 0;
protected:
// AddPacket is used by the subclass to add a packet to the incoming queue.
// This method can be called from another thread than the one used to call
// the other methods. Takes ownership of *message and its associated buffers.
void AddPacket(struct ::msghdr *message);
private:
std::deque<struct ::msghdr *> packets_; // Guard with packets_lock_.
pthread_mutex_t packets_lock_;
// Disable copy and assignment.
UDP(const UDP &);
UDP &operator=(const UDP &);
};
// StubUDP is an instantiatable stubbed subclass of UDP for debugging.
class StubUDP : public UDP {
public:
// Bind replaces bind().
virtual int Bind(const PP_NetAddress_IPv4 &address);
// Send replaces sendto. Usage is similar, but tweaked for C++.
virtual ssize_t Send(
const vector<char> &buf, int flags,
const PP_NetAddress_IPv4 &address);
private:
// Disable copy and assignment.
StubUDP(const StubUDP &);
StubUDP &operator=(const StubUDP &);
};
} // namespace PepperPOSIX
#endif // PEPPER_POSIX_UDP_HPP

329
src/pepper_wrapper.cc Normal file
View file

@ -0,0 +1,329 @@
// pepper_wrapper.cc - C wrapper functions to interface to PepperPOSIX.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "pepper_wrapper.h"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <langinfo.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
extern "C" {
// These are used to avoid CORE dumps. Should be OK to stub them out. However,
// it seems that on x86_32 with glibc, pthread_create() calls this with
// RLIMIT_STACK. It needs to return an error at least, otherwise the thread
// cannot be created. This does not seem to be an issue on x86_64 nor with
// newlib (which doesn't have RLIMIT_STACK in the headers).
int getrlimit(int resource, struct rlimit *rlim) {
#ifndef USE_NEWLIB
if (resource == RLIMIT_STACK) {
errno = EAGAIN;
return -1;
}
#endif
return 0;
}
int setrlimit(int resource, const struct rlimit *rlim) {
return 0;
}
// sigprocmask() isn't meaningful in NaCl; stubbing out.
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) {
Log("sigprocmask(%d, ...)", how);
return 0;
}
// kill() is used to send a SIGSTOP on Ctrl-Z, which is not useful for NaCl.
// This shouldn't be called, but it is annoying to see a linker warning about
// it not being implemented.
int kill(pid_t pid, int sig) {
Log("kill(%d, %d)", pid, sig);
return 0;
}
// Stubbing out getpid() by just returning a bogus PID.
pid_t getpid(void) {
Log("getpid()");
return 0;
}
// Stub these out. In the NaCl glibc, locale support is terrible (and we don't
// get UTF-8 because of it). In newlib, there seems to be some crashiness with
// nl_langinfo(). This will do for both cases (although no UTF-8 in glibc can
// cause a bit of a mess).
#ifndef USE_NEWLIB
char *setlocale(int category, const char *locale) {
Log("setlocale(%d, \"%s\")", category, locale);
return "NaCl";
}
#endif
char *nl_langinfo(nl_item item) {
switch (item) {
case CODESET:
Log("nl_langinfo(CODESET)");
return "UTF-8";
default:
Log("nl_langinfo(%d)", item);
return "Error";
}
}
// We don't really care about terminal attributes.
int tcgetattr(int fd, struct termios *termios_p) {
Log("tcgetattr(%d, ...)", fd);
return 0;
}
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p) {
Log("tcsetattr(%d, %d, ...)", fd, optional_actions);
return 0;
}
// The getopt() in newlib crashes. We don't really need it anyway. Stub it out.
// getopt() is redirected to mygetopt() using the magic include file.
#ifdef USE_NEWLIB
int mygetopt(int argc, char * const argv[], const char *optstring) {
optind = 1;
return -1;
}
#endif
//
// Wrap fopen() and friends to capture access to stderr and /dev/urandom.
//
FILE *fopen(const char *path, const char *mode) {
int flags = 0;
if (mode[1] == '+') {
flags = O_RDWR;
} else if (mode[0] == 'r') {
flags = O_RDONLY;
} else if (mode[0] == 'w' || mode[0] == 'a') {
flags = O_WRONLY;
} else {
errno = EINVAL;
return NULL;
}
FILE *stream = new FILE;
memset(stream, 0, sizeof(*stream));
// TODO: Consider the mode param of open().
#ifdef USE_NEWLIB
stream->_file = open(path, flags);
#else
stream->_fileno = open(path, flags);
#endif
return stream;
}
int fprintf(FILE *stream, const char *format, ...) {
char buf[1024];
va_list argp;
va_start(argp, format);
int size = vsnprintf(buf, sizeof(buf), format, argp);
va_end(argp);
return write(fileno(stream), buf, size);
}
int fileno(FILE *stream) {
#ifdef USE_NEWLIB
return stream->_file;
#else
return stream->_fileno;
#endif
}
int fclose(FILE *stream) {
int result = close(fileno(stream));
if (result == 0) {
delete stream;
return 0;
}
return result;
}
// Fake getaddrinfo(), as we expect it will always be an IP address and numeric
// port.
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
if (hints->ai_flags & AI_CANONNAME) {
Log("getaddrinfo(): AI_CANONNAME not implemented.");
return EAI_FAIL;
}
// Parse node (aka hostname) as dotted-quad IPv4 address.
int part[4];
sscanf(node, "%3d.%3d.%3d.%3d", &part[0], &part[1], &part[2], &part[3]);
uint32_t ip_addr = 0;
for (int i = 0; i < 4; ++i) {
ip_addr |= part[i] << (8*i);
}
// TODO: Handle IPv6 when Mosh does.
struct sockaddr_in *addr = new sockaddr_in;
addr->sin_family = AF_INET;
addr->sin_port = 0;
addr->sin_addr.s_addr = ip_addr;
addr->sin_port = htons(atoi(service));
struct addrinfo *ai = new struct addrinfo;
memset(ai, 0, sizeof(*ai));
ai->ai_family = AF_INET;
ai->ai_addrlen = sizeof(*addr);
ai->ai_addr = (struct sockaddr *)addr;
if (hints != NULL) {
ai->ai_protocol = hints->ai_protocol;
ai->ai_socktype = hints->ai_socktype;
}
*res = ai;
return 0;
}
void freeaddrinfo(struct addrinfo *res) {
while (res != NULL) {
struct addrinfo *last = res;
delete res->ai_addr;
res = res->ai_next;
delete last;
}
}
char *gai_strerror(int errcode) {
Log("gai_strerror(): Not implemented.");
return "gai_strerror not implemented";
}
//
// Wrap all unistd functions to communicate via the Pepper API.
//
// There is a pseudo-overload that includes a third param |mode_t|.
int open(const char *pathname, int flags, ...) {
// TODO: For now, ignoring |mode_t| param.
return GetPOSIX()->Open(pathname, flags, 0);
}
ssize_t read(int fd, void *buf, size_t count) {
return GetPOSIX()->Read(fd, buf, count);
}
ssize_t write(int fd, const void *buf, size_t count) {
return GetPOSIX()->Write(fd, buf, count);
}
// printf is used rarely (only once at the time of this writing).
int printf(const char *format, ...) {
char buf[1024];
va_list argp;
va_start(argp, format);
int size = vsnprintf(buf, sizeof(buf), format, argp);
va_end(argp);
return write(STDOUT_FILENO, buf, size);
}
int close(int fd) {
return GetPOSIX()->Close(fd);
}
int socket(int domain, int type, int protocol) {
return GetPOSIX()->Socket(domain, type, protocol);
}
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
Log("bind(%d, ...): Not implemented", sockfd);
errno = ENOMEM;
return -1;
}
// Most socket options aren't supported by PPAPI, so just stubbing out.
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen) {
return 0;
}
// This is needed to return TCP connection status.
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen) {
Log("getsockopt(%d, %d, %d, ...", sockfd, level, optname);
// TODO: Wire this up for returning TCP connection status. For now, stub out.
return -1;
}
// For some reason, after linking in ssh.cc, dup() gets brought in from libnacl
// (which I don't even want to link, but seems to anyway). Using linker flag
// "--wrap=dup" allows me to work around this by redirecting all references to
// "dup" to "__wrap_dup" instead.
int __wrap_dup(int oldfd) {
return GetPOSIX()->Dup(oldfd);
}
int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
const struct timespec *timeout, const sigset_t *sigmask) {
return GetPOSIX()->PSelect(
nfds, readfds, writefds, exceptfds, timeout, sigmask);
}
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
const struct timespec *timeout) {
return pselect(nfds, readfds, writefds, exceptfds, timeout, NULL);
}
ssize_t recv(int sockfd, void *buf, size_t len, int flags) {
return GetPOSIX()->Recv(sockfd, buf, len, flags);
}
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {
return GetPOSIX()->RecvMsg(sockfd, msg, flags);
}
ssize_t send(int sockfd, const void *buf, size_t len, int flags){
return GetPOSIX()->Send(sockfd, buf, len, flags);
}
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen) {
return GetPOSIX()->SendTo(sockfd, buf, len, flags, dest_addr, addrlen);
}
int fcntl(int fd, int cmd, ...) {
va_list argp;
va_start(argp, cmd);
int result = GetPOSIX()->FCntl(fd, cmd, argp);
va_end(argp);
return result;
}
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
return GetPOSIX()->Connect(sockfd, addr, addrlen);
}
} // extern "C"

30
src/pepper_wrapper.h Normal file
View file

@ -0,0 +1,30 @@
// pepper_wrapper.h - Main include file for Pepper wrapping.
//
// Implement the functions below to interface with Pepper wrapping.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef PEPPER_WRAPPER
#define PEPPER_WRAPPER
#include "pepper_posix.h"
// Implement this to return an appropriate instance of PepperPOSIX::POSIX.
// You may want to return a particular one based on the calling thread to
// support multiple Pepper Instances.
PepperPOSIX::POSIX *GetPOSIX();
#endif // PEPPER_WRAPPER

173
src/ssh.cc Normal file
View file

@ -0,0 +1,173 @@
// ssh.cc - C++ wrapper around libssh.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "ssh.h"
namespace ssh {
Session::Session(const string &host, int port, const string &user) :
s_(ssh_new()), connected_(false), key_(NULL) {
SetOption(SSH_OPTIONS_HOST, host);
SetOption(SSH_OPTIONS_PORT, port);
SetOption(SSH_OPTIONS_USER, user);
}
Session::~Session() {
Disconnect();
ssh_free(s_);
}
bool Session::Connect() {
int result = ssh_connect(s_);
if (result == SSH_OK) {
connected_ = true;
}
return ParseCode(result);
}
void Session::Disconnect() {
if (connected_) {
connected_ = false;
if (key_ != NULL) {
delete key_;
key_ = NULL;
}
for (::std::vector<Channel *>::iterator it = channels_.begin();
it != channels_.end();
++it) {
delete *it;
}
channels_.clear();
ssh_disconnect(s_);
}
}
Key *Session::GetPublicKey() {
if (connected_ && key_ == NULL) {
ssh_key key = NULL;
ssh_get_publickey(s_, &key);
key_ = new Key(key);
}
return key_;
}
Channel *Session::NewChannel() {
Channel *c = new Channel(ssh_channel_new(s_));
channels_.push_back(c);
return c;
}
Key::Key(ssh_key key) : key_(key) { }
Key::~Key() {
ssh_key_free(key_);
}
string Key::MD5() {
if (key_ == NULL) {
return string();
}
unsigned char *hash_buf = NULL;
size_t hash_len = 0;
int result = ssh_get_publickey_hash(
key_, SSH_PUBLICKEY_HASH_MD5, &hash_buf, &hash_len);
if (result != 0 ) {
return string();
}
char *hash_hex = ssh_get_hexa(hash_buf, hash_len);
string hash(hash_hex);
delete hash_hex;
ssh_clean_pubkey_hash(&hash_buf);
return hash;
}
Channel::Channel(ssh_channel c) :
c_(c), session_open_(false) { }
Channel::~Channel() {
Close();
ssh_channel_free(c_);
}
bool Channel::Close() {
if (session_open_) {
if (ParseCode(ssh_channel_close(c_)) == false) {
return false;
}
session_open_ = false;
}
return true;
}
bool Channel::Execute(const string &command) {
if (OpenSession() == false) {
return false;
}
// TODO: Make PTY optional.
bool result = ParseCode(ssh_channel_request_pty(c_));
if (result == false) {
return false;
}
return ParseCode(ssh_channel_request_exec(c_, command.c_str()));
}
bool Channel::Read(string *out, string *err) {
if (session_open_ == false) {
return false;
}
// Nasty hack to avoid compiler warning due to type mismatch in libssh.
const unsigned int ssh_error = (unsigned int)SSH_ERROR;
char buffer[256];
// First read all of stdout.
unsigned int bytes_read = 1; // Prime the pump.
if (out != NULL) {
while (bytes_read > 0 && bytes_read != ssh_error) {
bytes_read = ssh_channel_read(c_, buffer, sizeof(buffer), 0);
out->append(buffer, bytes_read);
}
if (bytes_read == ssh_error) {
return false;
}
}
// Now read all of stderr.
if (err != NULL) {
bytes_read = 1; // Prime the pump.
while (bytes_read > 0 && bytes_read != ssh_error) {
bytes_read = ssh_channel_read(c_, buffer, sizeof(buffer), 1);
err->append(buffer, bytes_read);
}
if (bytes_read == ssh_error) {
return false;
}
}
return true;
}
bool Channel::OpenSession() {
if (session_open_ == false) {
if (ParseCode(ssh_channel_open_session(c_)) == false) {
return false;
}
session_open_ = true;
}
return true;
}
} // namespace ssh

171
src/ssh.h Normal file
View file

@ -0,0 +1,171 @@
// ssh.h - C++ wrapper around libssh.
//
// There is already a thin wrapper available in "libssh/libsshpp.hpp", but it
// is clunky, and doesn't provide access to ssh_get_pubkey_hash() nor
// ssh_get_publickey() in any way. This wrapper aims to be better, but will be
// a subset of the functionality needed for the immediate purposes.
// Copyright 2013 Richard Woodbury
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <libssh/libssh.h>
#include <string>
#include <vector>
namespace ssh {
using ::std::string;
class Key;
class Channel;
// Base class implementing shared error handling code.
class ResultCode {
public:
ResultCode() : last_code_(SSH_OK) {}
// Get the error code from the last call. This code is what the underlyling
// libssh call returns (e.g., SSH_OK, SSH_ERORR, etc.).
int GetLastErrorCode() { return last_code_; }
protected:
// Converts return codes into simple error handling.
bool ParseCode(int code, int ok=SSH_OK) {
last_code_ = code;
return code == ok;
}
private:
int last_code_;
};
// Represents an ssh session.
class Session : public ResultCode {
public:
Session(const string &host, int port, const string &user);
~Session();
// Gets the human-readable error string from the last call. Analog to
// ssh_get_error().
string GetLastError() { return string(ssh_get_error(s_)); }
// Connect to the host. Analog to ssh_connect().
bool Connect();
// Disconnect from the host. This is not necessary to call unless you want to
// reuse the object, as it is called from the destructor. Analog to
// ssh_disconnect().
void Disconnect();
// Determines if the connected server is known. Analog to
// ssh_is_server_known().
bool ServerKnown() {
return ParseCode(ssh_is_server_known(s_), SSH_SERVER_KNOWN_OK);
}
// Returns the public key as a Key. Ownership is retained, thus is valid only
// for the lifetime of Session. Analog to ssh_get_publickey().
Key *GetPublicKey();
// Authenticate using password auth. Analog to ssh_userauth_password().
bool AuthUsingPassword(const string &password) {
return ParseCode(
ssh_userauth_password(s_, NULL, password.c_str()), SSH_AUTH_SUCCESS);
}
// Gets a new channel. Ownership is retained, thus is valid only for the
// lifetime of Session. Analog to ssh_channel_new().
Channel *NewChannel();
// Analog to ssh_options_set(), but easier to use as it is overloaded to
// handle the various input types.
bool SetOption(enum ssh_options_e type, const string &option) {
return ParseCode(ssh_options_set(s_, type, option.c_str()));
}
bool SetOption(enum ssh_options_e type, const char *option) {
return ParseCode(ssh_options_set(s_, type, option));
}
bool SetOption(enum ssh_options_e type, long int option) {
return ParseCode(ssh_options_set(s_, type, &option));
}
bool SetOption(enum ssh_options_e type, void *option) {
return ParseCode(ssh_options_set(s_, type, option));
}
private:
ssh_session s_;
bool connected_;
Key *key_;
::std::vector<Channel *> channels_;
// Disable copy and assign.
Session(Session &);
Session &operator=(Session &);
};
// Represents a key. Do not instantiate directly; call Session::GetPublicKey().
class Key {
public:
// Do not call this constructor; use Session::GetPublicKey().
explicit Key(ssh_key key);
~Key();
// Get key as MD5 hash. Will return an empty string on error.
string MD5();
private:
ssh_key key_;
// Disable copy and assign.
Key(Key &);
Key &operator=(Key &);
};
// Represents an ssh channel. Do not instantiate this yourself; should be
// obtained via Session::NewChannel().
class Channel : public ResultCode {
public:
// Do not call this constructor; use Session::NewChannel().
explicit Channel(ssh_channel c);
~Channel();
// Execute the command. Analog to ssh_channel_request_exec().
bool Execute(const string &command);
// Read the whole stdout/stderr contents from the remote side. Bring your
// own strings. Set to NULL if you don't care about one or the other.
bool Read(string *out, string *err);
private:
// Opens a session. This is private because it is handled automatically, and
// should never need to be called by the user. Analog to
// ssh_channel_open_session(), but that shouldn't matter to you.
bool OpenSession();
// Close the channel. This is private because it is handled automatically,
// and should never need to be called by the user. Analog to
// ssh_channel_close().
bool Close();
ssh_channel c_;
// Whether a session has been opened.
bool session_open_;
// Disable copy and assign.
Channel(Channel &);
Channel &operator=(Channel &);
};
} // namespace ssh