Commit Graph

92 Commits (577d982b51670f7f68d6dad9d0bf08f8a01879ba)

Author SHA1 Message Date
Selva Nair 577d982b51 PLAP: try to start automatic service if not running
Connection profiles shown on the login screen using PLAP
requires automatic service that starts openvpn.exe
processes for these profiles.

This commit adds an attempt to start the service from
PLAP dll. The service is started only if any PLAP enabled
profiles are found.

As starting the service can spawn up OpenVPN.exe processes and
the GUI may attach to them, auto-connect in the GUI is
suspended during session lock to leave the connections free to
be controlled from PLAP screen.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-10-19 09:49:15 -04:00
Selva Nair b828e763ad Some refactoring to help code reuse
- Move MsgToEventLog from main.c to misc.c
- Move dpi_initialize from main.c to misc.c

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-10-17 11:05:44 -04:00
Selva Nair 6932c5e710 Change the logic of releasing persistent connections in OnHold
(i)
State is changed to detached before auto-starting
so that OnHold() will see state = resuming and keep the hold.

State is set to disconnected instead of detached on detach
so that manual starts will release the hold automatically.

End result: While connecting automatically, do not release if
management-hold is on. But while started manually, release
from hold so that connection can complete without further
user action.

In normal use of automatic service, one would not add management
hold into the config. However, if the user disconnects the connection
the GUI puts it on hold, and we do not want to auto-start it after a
lock-unlock or some other automatic action.

(ii)
Also, currently, for persistent connections, the status
window is not shown automatically which feels unnatural in
real use. Instead, popup the status window when connection
is manually initiated. Its not popped up when automatically
attached to or if silent_connection is on.

Only persistent connections are affected by the change.

fixup: config file list is not recreated from scratch when
enable_persistent == 2 (auto attach mode) to avoid losing info
such as auto_connect = false on detached connections.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair 44990cd83d Detach persistent connections on switch user
This allows a new user to attach to the mgmt i/f of
persistent connections which would be otherwise blocked
by the previously logged in user.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair 7f794eec3d Add a button for detaching from the management interface
Useful for releasing the management interface if the user wants to
connect to it by other means.

Detached connections are set to state = detached (no disconnected)
and auto_connect disabled, so that they could be handled properly
during a re-attach.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair 477c61ff83 Parse config-auto directory for persistent connections
- Parse the config-auto folder used by automatic service
  and mark these profiles as persistent.

- These connections are marked as auto_connect to try
attaching to them at start up with periodic retry in
case the daemon or service are restarted.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair 8f0dbbc8a3 Always check status of automatic service
- Remove service-only mode (start/stop service) which has not been in
  use since we moved to running the GUI as limited user.
  Also its not very useful as it does not allow any control of
  service-started daemons

- Keep CheckServiceStatus and always check the status of
automatic service.

The status of the service will be used to toggle supporting
control of persistent connections started by the service.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair 428ee29246 Add support for marking connections as persistent
Persistent connections have openvpn.exe daemon started
external to the GUI (e.g., by the automatic service).
This patch adds support for attaching to the management
i/f of such daemons from the GUI and control the connection.

The GUI never stops or starts the openvpn.exe process in this
case. Instead, connect and disconnect buttons signal the
management interface of a running openvpn.exe process to start
the tunnel by attaching to mgmt i/f and sending hold-release if
needed  or stop it and wait in management-hold state
(see DisconnectDaemon()).

When the GUI process exits, persistent connections are left in their
current state using DetachOpenVPN().

No connections are marked as persistent as yet. That is done
in a following commit.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-08-08 10:11:25 -04:00
Selva Nair b44e685ff8 Retry on management timeout instead of aborting
In  some cases the service may take a while to startup openvpn.exe,
causing connection to the management interface to timeout. This could
leave  behind the OpenVPN process if/when it eventually starts up.
(Trac 905, 1050).

As errors in starting up the OpenVPN daemon are independently
handled, its better to keep retrying the management interface connection
until aborted due to errors or by the user.

- On timeout, log a message on the status window and retry the
  management interface connection

- Eliminate the timed-out state that is no longer used

- Call StopOpenVPN() before abort so that OpenVPN daemon
  is not left running in case it starts up later.

- In the unlikely event that OpenManagement() fails, show an error

- User can abort by pressing disconnect

A "retrying.." message is logged on to the status window every
15 seconds.

See Trac: #905, #1050

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-06-20 13:15:45 -04:00
Selva Nair 24b9d06957 Update OpenSSL initialization
- Set env variables such as OPENSSL_CONF and OPENSSL_MODULES
- Replace deprecated initialization (since OpenSSL 1.1.0)
  by OpenSSL_init_crypto()

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2022-02-10 09:30:05 -05:00
Selva Nair 9c82e666d8 Show a prompt during profile import using --import
The user is prompted with a message showing the config
name that will be imported. The user can accept or cancel
the operation.

If the user was already prompted for over-write permission
because a config with the same name exists, no further dialog
is shown.

Import using the menu (Import File...) is not affected.

Rationale:
We want to set "Import" as the default verb for the context
menu of .ovpn files. This will allow import of configs by
double-click. Also when .ovpn file is downloaded using a browser,
setting the default bowser action to "open" will result in an import.
In such cases a silent import action could be surprising, and a
prompt showing what is being imported could provide a better UX.

On the flip-side, the prompt/dialog will also be shown when import
is done from the context menu of .ovpn by "right click and
choose import" or when "openvpn-gui.exe --import foo"
or "openvpn-gui.exe --command import foo" is executed. As import
is an action that does not result in an immediately visible result
(unlike, say, edit or print), a prompt requiring user action is of
some value even in these cases. At worst it's a minor annoyance.

See also: https://github.com/OpenVPN/openvpn-build/pull/227
and discussions there-in

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-11-18 14:33:09 -05:00
Selva Nair 56ee704501 Use C standrad compliant printf specifications
%S --> %hs in wide format strings, %ls otherwise
%s --> %ls in wide format strings, unchanged otherwise
%c --> %lc in wide format strings

Resource files together have about 970 lines affected and
were edited by looping through all with
sed -i 's/%S/%hs/g' $file
sed -i 's/%s/%ls/g' $file
All other files were manually changed (about 85 lines).

Recent versions of mingw-w64 implicitly turns on __USE_MINGW_ANSI_STDIO
if _GNU_SOURCE, _XOPEN_SOURCE etc are defined (which we do usei).

This breaks non-standard spec such as %S. Anyway, we have been
gradually getting rid of those.

MSVC builds should not be affected.

v2: multiple occurrences in same line was missed in v1 (/g missing in
sed expression). Fixed.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-11-03 16:20:17 -04:00
Selva Nair e80a39c825 Implement importing profile from a generic URL
ParseUrl extended to parse generic URLs and parse
the path. DownloadProfile() function re-factored
for reuse with generic URL.

Also:
- INTERNET_FLAG_RELOAD added to the request
  call to force reloading the data from server instead
  of using possibly cached data.
- Input box for URL extended in length to about
   50 characters wide.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-08-31 21:55:59 -04:00
Selva Nair 8e4183f9a9 Add '--command import' command line option
Import a config file from command line as

`openvpn-gui.exe --command import <file-path>`

The command is send to a running instance if any.
Otherwise the GUI extecutable is started and
the import processed.

`openvpn-gui --import <file-path>`

is interpreted as the same command.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-08-31 14:48:20 -04:00
Lev Stipakov d6a622a023 URL profile import: allow specifying owner window of message box
This will be used later when parent window
needs to be disabled when message box is displayed.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-08-23 12:07:18 -04:00
Lev Stipakov 78ee9b981d URL profile import: refactor ImportConfigFile
Factor out importing part (everything except file open dialog)
into separate function, which can be used when importing
profile from URL.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-08-23 12:07:18 -04:00
Lev Stipakov 9ded7996ab URL profile import: add profile import dialog
This is the first patch from series which implemets
importing profile from URL, currently implemented
by OpenVPN Access Server.

Move "Import from file" menu item under new "Import"
item. Add "Import from AS..." item under "Import", which
opens new profile import dialog.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-08-23 12:07:18 -04:00
Lev Stipakov bb00d95f86 Web-based extra authentication
This adds support for web-based extra authentication, which may be
used by OpenVPN Cloud. When enabled and client sends IV_SSO=openurl,
server pushes Info command OPEN_URL:<url>. The client opens that URL and
user authenticates.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-06-02 00:49:08 -04:00
Selva Nair 5d209f44cb Construct version string in About dialog at runtime
Required to match the previous commit that removed macro
substitution in resource text strings.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-05-19 20:58:06 -04:00
Lev Stipakov af72adf2ee fix log() confusion
By some reasons Release build ignores _INC_MATH and includes math.h,
which conflicts with our own log definition. Rename it to log_.

While on it, also rename other enum names for consistency.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-05-14 17:20:00 -04:00
Lev Stipakov a68341f021 main.c: trim whitespaces
Signed-off-by: Lev Stipakov <lev@openvpn.net>
2021-05-14 17:20:00 -04:00
Selva Nair c47c3bf81a Remove MAX_CONFIGS limit using WM_MENUCOMMAND for menu messages
We currently use WM_COMMAND message which is delivered with the
ID of the menu item requiring a unique ID for every command
(connect, disconnect etc..) for each connection profile. Instead,
use WM_MENUCOMMAND so that the message delivers a handle to the
menu and the position index of the menu item.

Connection menu array is now dynamically allocated. Yet, there
is still a limitation on the number of configs as the config
index + mgmt_port_offset must be < 65536 to be usable as a port
number. The error message shown for "too many configs" is reworded.
(English language file only).

Note: The current way of selecting the management port based on the
index of the config file increases chances of port conflicts
when the number of configs is large. It could be useful to change
this logic but that is beyond the cope of this PR.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-03-05 22:16:08 -05:00
Selva Nair e4fba0c003 Update echo-msg window from the thread that owns the window
Change the way echo-msg window is update (thread safety).
When new echo-msg content is available for display, update the window
from the thread owning it by sending a message to it.
A blocking SendMessage (with a timeout) is used, as the window
needs access to the config's echo-msg buffer which is cleared
on return from this this call.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-01-07 23:29:02 -05:00
Selva Nair 3f896b002b Implement display of echo message window
- Add a message box that support appending messages with
  a title formatted at a larger font and a text
  displayed in the default font.
- A global instance of the message box is used to
  display messages from all profiles.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-01-07 23:29:02 -05:00
Selva Nair 00732e1d9e Parse and display messages received by echo msg commands
Process four new echo commands to construct messages to be
displayed to the user:

      echo msg message-text
      echo msg-n message-text
      echo msg-window message-title
      echo msg-notify message-title

Note: All rules of push and echo processing apply and determine
what is received as echo commands by the GUI. In addition,
'url-encoded' characters (% followed by two hex digits) are
decoded and displayed.

The message is constructed in the GUI by concatenating the text
specified in one or more "echo msg text" or "echo msg-n text"
commands. In case of "echo msg text" text is appended with a new
line. An empty text in this case will
just add a new line.

The message ends and gets displayed when one of the following
are receieved:

    echo msg-window title
    echo msg-notify title

where "title" becomes the title of the message window. In case of
msg-window, a modeless window shows the message, in the latter case
a notification balloon is shown.

Example: when pushed from the server:

    push "echo msg I say let the world go to hell%2C"
    push "echo msg I must have my cup of tea."
    push "echo msg-window Notes from the underground"

will display a modeless window with title
"Notes from the underground" and a two line body
    --
    I say let the world go to hell,
    I must have my cup of tea.
    --

Note that the message itself is not quoted in the above examples
and so it relies on the server's option-parser combining
individual words into a space separated string. Number of words
on a line is limited by the maximum number of parameters allowed
in openvpn commands (16). This limitation may be avoided by quoting
the text that follows so that the option parser sees it as one
parameter.

The comma character is not allowed in pushed strings, so
it has to be sent encoded as %2C as shown above.
Such encoding of arbitrary bytes is suppored. For example,
newlines may be embedded as %0A, though discouraged. Instead
use multiple "echo msg" commands to separate lines by new line.

An example with embedded spaces and multiple lines concatenated
without a new line in between (note use of single quotes):

    push "echo msg-n I swear to you gentlemen%2C that to be"
    push "echo msg-n ' overly conscious is a sickness%2C ' "
    push "echo msg-n a real%2C thorough sickness."
    push "echo msg-notify Quote of the Day"

    will show up as a notification that displays for an
    OS-dependent interval as:

    --
    Quote of the Day

    I swear to you gentlemen, that to be overly conscious
    is a sickness, a real, thorough sickness.
    --

where the location of the line break is automatically determined
by the notification API and is OS version-dependent.

Commands like "echo msg ..." in the config file are also
processed the same way. It gets displayed when the GUI connects
to the management interface and receives all pending echo.
Pushed message(s) get displayed when the client daemon
processes push-reply and passes on echo directives to the
GUI.

TODO: The actual window that displays the messages is
implemented in the next commit.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2021-01-07 23:29:02 -05:00
Lev Stipakov 1715746477 Introduce "Always use interactive service" option
We didn't use interactive service when gui was running
under admin because of some privilege escalation vulnerability in Vista.

Apparently this issue doesn't exist on Win7 and newer versions so
it is safe to use iservice on those systems.

Introduce "Always use interactive service" option,
which is "on" by default. This should enable users,
who by various reasons run gui as admin, use Wintun.

When gui is running as admin and interactive service
cannot be started or not installed, warn that wintun will not work.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2020-09-18 00:03:03 -04:00
Wouter eef34b3b4d Support per-monitor DPI scaling 2020-09-11 11:15:14 -04:00
Selva Nair b696a7c16d Optionally allow overwrite when importing a config
- Prompt the user for permission if import may overwrite
  an existing config.

- Also raise an error if the import file source matches the
  global or local config directory. Reimporting a config on to
  itself is not supported. This also  avoids ERROR_SHARING_VIOLATION
  in CopyFile() when source and destination are the same.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2020-02-27 12:36:17 -05:00
Selva Nair 9ad57eb415 Add '--command rescan' to rescan config folders
Add an new command 'rescan' that may be sent to a running
instance of the GUI to force it rescan the config folders.

Use case: with an instance of the is GUI running, one can
manually copy a config file to the config folder and start
it using "openvpn-gui --command rescan" followed by
"openvpn-gui --command connect foo"

v2: The calls to rebuild config file list and recreate
menus is refactored into a function.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2020-01-12 16:32:51 -05:00
Lev Stipakov 4ca344819f main.c: remove unneeded header
We don't use any of PBT_* macros.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2019-11-26 10:04:45 -05:00
Selva Nair 6ad90b92a1 Add missing va_end() in MsgToEventLog()
Found by cppcheck

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2019-03-18 10:58:59 -04:00
Ilya Shipitsin bfcf3b4679 main.c: resolve possible null pointer dereference
found by cppcheck

[main.c:457] -> [main.c:457]: (warning) Either the condition 'copy_data->dwData==(WM_APP+15)&&c->hwndStatus&&c' is redundant or there is possible null pointer dereference: c.
2019-03-17 13:54:36 -04:00
Selva Nair f55eeb1da8 Eliminate MAX_CONFIGS limit using a dynamic array
The number of configs is now unlimited. But there is a limit
of 2^16 menu items which permits only about 2^12 configs to be
displayed in the menu. A warning is shown if the number of
configs exceeds this value.

For a responsive menu keep the number of configs under ~1000.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2019-02-11 11:51:23 -05:00
Selva Nair 398a771840 Group configs based on the directory structure to support a nested view
- Group all configs in a subdirectory with directory name
  as the label.

- If any connection is active, newly found configs are
  added to the root group to keep the logic simple.

- Directory hierarchy is scanned up to a depth of 4: i.e.,
  config_dir and global_config_dir and its subdirectories
  up to 3 levels down.

Only support for scanning configs and attaching group labels
is added here. Rendering the nested menu is the subject of
a later commit.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2019-02-11 11:51:23 -05:00
Selva Nair 343643657f Log --command option errors to the event log
This supplements the non-zero exit-code.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-04-24 09:03:16 +03:00
Selva Nair 1cb07f92f3 Fix detection of running instance
When openvpn is run with --help option it pops up a help
message and exits when that window is closed. Such instances
cannot accept any commands and should not be treated as a
running instance.

Fix by
(i) When run with --help, promptly release the semaphore used
to restrict to a single running instance.

(ii) Wait for a short interval (200 msec) before timing out of
locking the semaphore. This helps avoid race conditions.

While at it also make sure the semaphore is released and closed
on exit.

Fixes issue: #237

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-04-24 09:03:16 +03:00
Selva Nair 105e022f7b Subscribe to bytecount message from management interface
Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-01-30 10:20:58 +02:00
Selva Nair 10f34584ea In '--connect profile-name' make the extension (.ovpn) optional
Treat '--connect foo.ovpn' and '--connect foo' as the same. This matches
with the behaviour of '--command connect foo'.

Note: the argument is first compared against config file names and then
against config names.

Based on comment by larson0815 here:
https://github.com/selvanair/openvpn-gui/issues/5

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-01-20 11:42:24 -05:00
Selva Nair 0f21030774 Support sending commands to running instance
- New option --command <action> <params> to send commands to
  a running instance of openvpn-gui.exe
  Supported actions are
      connect, disconnect, reconnect
  each of which takes the name of the config (with or without the
  extension .ovpn) as a parameter;
      disconnect_all, exit
  which take no parameter and
      silent_connection
  which takes an optional parameter = 0 or 1 (1 is the default)

  Examples: with the gui running, start a new instance as

  openvpn-gui.exe --command disconnect myvpn : ask running instance
                        to disconnect myvpn if connected
  openvpn-gui.exe --command status myvpn     : ask running instance
                        to show the status window for myvpn if available
  openvpn-gui.exe --command disconnect_all   : ask running instance
                        to disconnect all active connections

- The second instance exits after issuing a SendMessage to the
  already running instance. If no action is specified, the running
  instance is notified to show a balloon to alert the user

- These messages may also be sent from scripts as COPYDATA messages
  with the wData element specifying the action to execute and lpData
  a pointer to the parameter. The dwData param must be one of
  WM_OVPN_xxx with xxx = START, STOP, RESTART, STOPALL, EXIT or
  SILENT. See main.h for their values.

v2: Bug fixes based on test reports from larson0815
here: https://github.com/selvanair/openvpn-gui/issues/5
and cron410 here: https://github.com/OpenVPN/openvpn-gui/issues/104

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-01-20 11:42:23 -05:00
Selva Nair 31896ce33b Add restart button to connection menus
- This works the same way as restart button in the status window
  but is more conveniently accessible from the tray menu.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2018-01-20 11:41:19 -05:00
Selva Nair d7b0fcbe5b Merge pull request #137 from selvanair/echo
Parse ECHO directives from openvpn

Acked-by: Gert Doering <gert@greenie.muc.de>
2017-03-16 10:22:05 -04:00
Selva Nair 25be842a96 Merge pull request #117 from selvanair/nay-to-savepass
Add a system-wide option to disable the password save feature
2017-03-08 21:37:31 -05:00
Selva Nair 778cc3d225 Add a system-wide option to disable the password save feature
- A new registry HKLM\Software\OpenVPN\disable_save_passwords
  (32 bit DWORD value) may be set to a non-zero value to
  disable password saving by users. Applies to both auth and
  private key passwords. Usernames are always saved.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2017-03-07 11:49:03 -05:00
Selva Nair d4090a8842 Parse ECHO directives from openvpn
Support the following echo commands

- "echo forget-passwords": delete passwords internally saved by the GUI
  but do not disable the password save feature. Useful when pushed
  from the server so that it gets processed after authentication. Also see
  management-notes.txt in openvpn docs.

- "echo save-passwords": enables private-key and auth-user-pass passwords
  to be saved. Will be effective at startup only if present in the config
  file. If pushed from the server, will get used for subsequent
  password prompts. Essentially this has the effect of presenting the password
  dialogs to the user with save-password checkbox selected. The user may still
  uncheck it during the dialog.

Note: echo commands are processed as and when they are received and in the order
received.

TODO: support for "echo setenv name var", "echo disable-save-passwords" etc..

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2017-02-27 13:47:59 -05:00
Chocobo1 d2a8c8d1ff Enable ASLR. Closes #119.
Workaround provided by Selva Nair.
2017-01-22 13:19:34 +08:00
Selva Nair 9cea37ad80 Suppress warning popups if silent_connection is set
Warnings dsabled if silent_connection is true
- interactive service is not installed or not running
- duplicate configs found and ignored

v2: Update description of --silent_connection in README
and English resource file (translations not updated).

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2017-01-03 11:28:45 -05:00
Samuli Seppänen 8020ee1071 Merge pull request #91 from selvanair/pkcs11-pin-v3
pkcs11 pin prompt
2016-11-30 22:18:34 +02:00
Selva Nair 5fe0d5225e Make the program DPI aware
- Set dpi-awareness to true in the manifest (i.e., "system-dpi aware")
- Check system dpi and scale and/or position widgets and windows
  that depend on the system dpi (only components within the status
  window are affected).

Note: Declaring dpi awareness eliminates automatic rescaling of
windows that causes blurred text on high dpi monitors.
Windows 8.1 and later allow per monitor dpi setting which is
not handled here.
2016-11-19 21:57:39 -05:00
Selva Nair be417bb38f Support pkcs11 token insertion request and pin input
Note: IDS_NFO_TOKEN_PASSWORD_CAPTION and IDS_NFO_TOKEN_PASSWORD_REQUEST
strings need translation.

TODO: support for selecting pkcs11-id from the GUI

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2016-11-15 19:29:23 -05:00
Selva Nair 81ece9690e Check for interactive service only if OpenVPN version is >= 2.4
This makes it less confusing to run GUI v11 with OpenVPN 2.3.x

Signed-off-by: Selva Nair <selva.nair@gmail.com>
2016-11-01 23:06:39 -04:00